指令:计算机的语言
指令:计算机语言的word
指令系统:计算机语言的alphabet
硬件设计三条基本原则之一:简单源于规整
汇编语言需要大量代码
java解释器采用类RV汇编方式进行解释编译,所以代码会稍多
C的代码会较少
C>JAVA>汇编语言
计算机硬件操作数
寄存器是计算机的基本组成单位,计算机做成之后,程序员也可见到寄存器
算术逻辑运算必须依赖于寄存器,寄存器数量有限,RV架构通常32个,64位;
为什么寄存器个数限制为32个,可以从硬件设计原则之二找到:
存储器操作数
计算机数据结构保存在内存之中
算术运算只作用于寄存器
所以,为了处理大量运算,RV必须有控制寄存器和内存之间通信的指令
叫做数据传输指令
内存👉寄存器: load指令;格式:操作名 + 数据待取的寄存器 + 寄存器 + 访存常量
访存常量就是offset;而第二个寄存器的内容就是base_reg
有符号数与无符号数
总结
----------------------------
2.6 逻辑操作
原则1:在一个字内,对一段bit来检测很有用
移位操作:逻辑左移右移可以替代乘除法,补的是零
算数移位:补的是符号位
用异或NOR,一方为11111....可以替代 取非 NOT 来进行处理
2.7用于决策的指令
汇编里面有Label概念
位模式的比较必须处理有符号和无符号数之间的差别
一般的小于和大于等于是有符号数的二进制补码
bltu这些属于无符号数比较
大小比较,相等不相等都属于分支跳转指令
另一种提供额外分支指令的方式是根据 比较结果设置寄存器 然后使用beq/bne
ARM指令系统使用的另一种方法是,保留额外的位来记录指令执行期间发生的情况,叫条件代码/标志位
条件分支利用这些代码组合成期望判断
缺点:如果总是被调用,会造成流水线困难的依赖关系
2.7.2 边界检查的简便方法
将有符号数当作无符号数处理
无符号数比较也检测了某个数是否为负数
例如:bgeu x20 ,x11,xxxxx
用bgeu来检测两个数的相对大小
2.7.3 case/switch 语句
switch 一般由一大堆if组成
更有效的方式是使用编码形成指令序列的地址表,叫做分支表
执行 过程 的六个步骤
1.参数放在过程可以访问到的位置
2.将控制转交给过程
3.获取过程所需的存储资源
4.执行所需的任务
5.将结果值放在调用程序可以访问到的位置
对于过程的跳转分支,有专门的指令--jal jump and link
link的是x1,叫返回地址寄存器
2.8.1 使用更多寄存器
背景
在某个过程中,需要很多很多参数寄存器,但是没那么多,
然后任务完成后你需要将原来的寄存器的值归还原位,这时候需要将寄存器换出到存储器,于是需要用栈(stack)---一种先进后出的队列
减小指针就是压栈,增大指针就是弹栈
2.8.2嵌套过程
不调用其它过程的叫 叶子过程
调用的话就是嵌套过程
嵌套过程可能会出现返回地址寄存器被写覆盖的情况
方法就是把子过程的所有寄存器压栈
递归程序到指令阶段就是不断的压栈弹栈
动态变量就是压栈弹栈的参数等等
静态变量就是在保存寄存器里面的数
只要是static声明或者过程之外声明的C变量都是静态变量
一下是 过程调用 期间 需要保存和不需要保存的东西
有时候有些方案也会采取保存全栈,以确保数据相同
栈中为新设立的数据来分配空间:比如局部变量等等
过程帧/活动记录:存在于栈内,是一个数据段,用于保存 一个调用/过程 中 所保存的寄存器和局部变量。
帧指针拿来寻址比较好
刚开始帧指针没有,如果用的话就以SP的初始地址作为FP的地址,而且FP不会动,全依靠SP动来压栈弹栈,而且,FP的地址就是SP的初始地址,对于一个数据段来说,一个栈可能会有多个数据段,FP的存在可以防止这个数据段的SP越界访问别的数据段
总之,静态 + 安全 就是FP 的主要职能
用不用fp其实对性能来说不怎么影响
我们也可以通过维护稳定的sp 来减少对FP的使用
比如:仅在进入/退出的时候才调整栈
--------
堆是用来存放动态数据 动态数据结构的
存放于内存之中,栈和堆相向生长,可以此消彼长,栈也是存放于内存之中
注意看:栈指针初始化是自定义的,栈是向下增长的
程序代码开始处由PC决定
静态数据起始处是由程序代码的末端决定
动态数据起始处由静态数据起始处决定,它的空间叫堆,和栈相向增长
对于字符串的处理:
字符串表示: 1.头部给出长度 java
2.身体附加带有字符串长度的变量
3.尾部用助记符结尾 C
加载字/半字/Byte/双字, 无符号都是填充0;有符号填充符号位;
用大立即数的寻址方式
32位长固然方便,但是用常量 + 基地址的方式更广泛
我们没有立即数拼接指令,只有高位拼接 + 低位相加 这样的方式;
以往的jal指令,有关立即数的地址偏移就20位,但是现在的内存都太大了,20位不够用,所以有了偏移量 + 寄存器内容 的 取指方式
问题在于:如何设置一个基地址寄存器?
我们知道,条件分支跳转指令一般情况下是:你PC指令知道哪里了,然后基于你PC来进行一定范围的跳转和返回
所以,以PC指令作为你的基地址是一种不错的选择
这种寻址方式叫做PC相对寻址
问题来了:对于过程调用可能会出现超距离跳转,如何处理?:双指令序列
RV指令长度为32bit 4字节,分支指令可以改换单位,也就是直接让offset的单位为字数而非字节数
这样单位一变大,范围自然就大了,
我们知道,存储器的单位是以Byte来计的,而指令是32位的,所以一次就是4Byte,换了单位:换成字就是一次变1
但是,这样的话可能会出现不对齐的奇数地址,于是干脆使用半字,一次变2,这样的话就可以长久保持偶数字地址
寻址方式总结
指令与并行性
同步机制由软件实现,底层依赖于同步指令
lock & unlock
简单来说,基本能力就是对内存进行划分,哪一块只有处理器1可以处理,哪一块只有处理器2可以处理。
这些区域称之为“互斥区”
指令对操作代替原子操作
上边是一次原子交换内容
翻译并启动程序
编译器
汇编器
伪指令应该是方便一些汇编程序编写而设立的类似“函数”的东西
伪指令可以想象成汇编里的函数,可以拿来简化汇编程序编写
汇编器是分体系结构的
链接器/编辑器
简单来说就是能够单独编译每一个过程,好让你单独debug一行的工具
不用重新编译和汇编了
重定位信息 + 符号表 = 定义一个未知标签,链接器的第一步是找到程序指令里的跳转,用新地址替代它们
链接器解析完位置模块,替换完了地址之后,剩下的就是计算内存
汇编器不知道 模块的指令和数据 相对于其它模块的位置
所以链接器将模块放入内存的时候,必须重定位所有的绝对引用,以反映其真实地址
链接器可以生成可执行文件,它长得和目标文件格式相同,但是把一些引用给解析了
链接器的理解
链接器就是把一段代码程序的模块给底层化,然后根据符号表和重定位信息算出代码大小和内存调用大小,并定义一个未知Label,然后整理综合大小,分发给内存的程序段和数据段。
如果说指令是死的一个电路架构,那么参数就是活的,它需要不断的修改和调整,其中,地址跳转和分支指令的参数会需要大量调整,这里就需要汇编器去计算调整参数,
上边三个情况就是针对不同的跳转指令,采取的参数调整策略,其中的参数计算的源数据应该全部来自于链接器消化理解后的 指令地址 和 数据大小
总而言之,链接器先消化理解未知引用,形成可执行文件,得出程序大小和数据大小,然后将其存入内存,随后开始处理各个引用接口处的跳转指令的参数,算出 相对于亘古不变的 指令电路来说,活生生的跳转参数大小。
加载器
我们已经通过链接器,生成了可执行文件,并且将其送到了内存条里;操作系统会读取并执行它们
加载器的作用就是将可执行文件内包含的程序放在主存中准备执行。
说白了加载器就是复现链接器消化后的东西,然后控制系统开始跑代码
动态链接库
动态链接库相当于替代了链接器,在程序执行的过程中,同步执行动态链接库来查找相应的例程,使用相关文件中的额外信息来更新所有的例程引用。
以C排序程序为例的汇总整理
swap代码例子
sort例子
数组与指针
数组实现clear
指针实现clear
版本比较:指针更好
16-17为扩展介绍环节,暂时不细看
2.18 RISC-V指令系统的剩余部分
其实就是介绍介绍指令集,细看可以参考
《RISC-V:硬件架构设计之道》
2.19 谬误与陷阱
2.20本章小结
2.22练习
慢慢刷吧,不着急.....