程序员需要了解的硬核知识之CPU( 三 )


标志寄存器条件和循环分支会使用到 jump(跳转指令) , 会根据当前的指令来判断是否跳转 , 上面我们提到了标志寄存器 , 无论当前累加寄存器的运算结果是正数、负数还是零 , 标志寄存器都会将其保存(也负责溢出和奇偶校验)
溢出(overflow):是指运算的结果超过了寄存器的长度范围
奇偶校验(parity check):是指检查运算结果的值是偶数还是奇数
CPU 在进行运算时 , 标志寄存器的数值会根据当前运算的结果自动设定 , 运算结果的正、负和零三种状态由标志寄存器的三个位表示 。 标志寄存器的第一个字节位、第二个字节位、第三个字节位各自的结果都为1时 , 分别代表着正数、零和负数 。
程序员需要了解的硬核知识之CPU文章插图
CPU 的执行机制比较有意思 , 假设累加寄存器中存储的 XXX 和通用寄存器中存储的 YYY 做比较 , 执行比较的背后 , CPU 的运算机制就会做减法运算 。 而无论减法运算的结果是正数、零还是负数 , 都会保存到标志寄存器中 。 结果为正表示 XXX 比 YYY 大 , 结果为零表示 XXX 和 YYY 相等 , 结果为负表示 XXX 比 YYY 小 。 程序比较的指令 , 实际上是在 CPU 内部做减法运算 。
函数调用机制接下来 , 我们继续介绍函数调用机制 , 哪怕是高级语言编写的程序 , 函数调用处理也是通过把程序计数器的值设定成函数的存储地址来实现的 。 函数执行跳转指令后 , 必须进行返回处理 , 单纯的指令跳转没有意义 , 下面是一个实现函数跳转的例子
程序员需要了解的硬核知识之CPU文章插图
图中将变量 a 和 b 分别赋值为 123 和 456, 调用 MyFun(a,b) 方法 , 进行指令跳转 。 图中的地址是将 C 语言编译成机器语言后运行时的地址 , 由于1行 C 程序在编译后通常会变为多行机器语言 , 所以图中的地址是分散的 。 在执行完 MyFun(a,b)指令后 , 程序会返回到 MyFun(a,b) 的下一条指令 , CPU 继续执行下面的指令 。
函数的调用和返回很重要的两个指令是 call 和 return 指令 , 再将函数的入口地址设定到程序计数器之前 , call 指令会把调用函数后要执行的指令地址存储在名为栈的主存内 。 函数处理完毕后 , 再通过函数的出口来执行 return 指令 。 return 指令的功能是把保存在栈中的地址设定到程序计数器 。 MyFun 函数在被调用之前 , 0154 地址保存在栈中 , MyFun 函数处理完成后 , 会把0154的地址保存在程序计数器中 。 这个调用过程如下
程序员需要了解的硬核知识之CPU文章插图
在一些高级语言的条件或者循环语句中 , 函数调用的处理会转换成 call 指令 , 函数结束后的处理则会转换成 return 指令 。
通过地址和索引实现数组接下来我们看一下基址寄存器和变址寄存器 , 通过这两个寄存器 , 我们可以对主存上的特定区域进行划分 , 来实现类似数组的操作 , 首先 , 我们用十六进制数将计算机内存上的 00000000 - FFFFFFFF 的地址划分出来 。 那么 , 凡是该范围的内存地址 , 只要有一个 32 位的寄存器 , 便可查看全部地址 。 但如果想要想数组那样分割特定的内存区域以达到连续查看的目的的话 , 使用两个寄存器会更加方便 。
例如 , 我们用两个寄存器(基址寄存器和变址寄存器)来表示内存的值
程序员需要了解的硬核知识之CPU文章插图
这种表示方式很类似数组的构造 , 数组是指同样长度的数据在内存中进行连续排列的数据构造 。 用数组名表示数组全部的值 , 通过索引来区分数组的各个数据元素 , 例如: a[0] - a[4] , []内的 0 - 4 就是数组的下标 。
CPU 指令执行过程那么 CPU 是如何执行一条条的指令的呢?
几乎所有的冯·诺伊曼型计算机的CPU , 其工作都可以分为5个阶段:取指令、指令译码、执行指令、访存取数、结果写回 。