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


这是一段进行相加的操作 , 程序启动 , 在经过编译解析后会由操作系统把硬盘中的程序复制到内存中 , 示例中的程序是将 123 和 456 执行相加操作 , 并将结果输出到显示器上 。 由于使用机器语言难以描述 , 所以这是经过翻译后的结果 , 实际上每个指令和数据都可能分布在不同的地址上 , 但为了方便说明 , 把组成一条指令的内存和数据放在了一个内存地址上 。
地址 0100 是程序运行的起始位置 。 Windows 等操作系统把程序从硬盘复制到内存后 , 会将程序计数器作为设定为起始位置 0100 , 然后执行程序 , 每执行一条指令后 , 程序计数器的数值会增加1(或者直接指向下一条指令的地址) , 然后 , CPU 就会根据程序计数器的数值 , 从内存中读取命令并执行 , 也就是说 , 程序计数器控制着程序的流程 。
条件分支和循环机制我们都学过高级语言 , 高级语言中的条件控制流程主要分为三种:顺序执行、条件分支、循环判断三种 , 顺序执行是按照地址的内容顺序的执行指令 。 条件分支是根据条件执行任意地址的指令 。 循环是重复执行同一地址的指令 。

  • 顺序执行的情况比较简单 , 每执行一条指令程序计数器的值就是 + 1 。
  • 条件和循环分支会使程序计数器的值指向任意的地址 , 这样一来 , 程序便可以返回到上一个地址来重复执行同一个指令 , 或者跳转到任意指令 。
下面以条件分支为例来说明程序的执行过程(循环也很相似)
程序员需要了解的硬核知识之CPU文章插图
程序的开始过程和顺序流程是一样的 , CPU 从0100处开始执行命令 , 在0100和0101都是顺序执行 , PC 的值顺序+1 , 执行到0102地址的指令时 , 判断0106寄存器的数值大于0 , 跳转(jump)到0104地址的指令 , 将数值输出到显示器中 , 然后结束程序 , 0103 的指令被跳过了 , 这就和我们程序中的 if() 判断是一样的 , 在不满足条件的情况下 , 指令会直接跳过 。 所以 PC 的执行过程也就没有直接+1 , 而是下一条指令的地址 。
标志寄存器条件和循环分支会使用到 jump(跳转指令) , 会根据当前的指令来判断是否跳转 , 上面我们提到了标志寄存器 , 无论当前累加寄存器的运算结果是正数、负数还是零 , 标志寄存器都会将其保存(也负责溢出和奇偶校验)
溢出(overflow):是指运算的结果超过了寄存器的长度范围
奇偶校验(parity check):是指检查运算结果的值是偶数还是奇数
CPU 在进行运算时 , 标志寄存器的数值会根据当前运算的结果自动设定 , 运算结果的正、负和零三种状态由标志寄存器的三个位表示 。 标志寄存器的第一个字节位、第二个字节位、第三个字节位各自的结果都为1时 , 分别代表着正数、零和负数 。
程序员需要了解的硬核知识之CPU文章插图
CPU 的执行机制比较有意思 , 假设累加寄存器中存储的 XXX 和通用寄存器中存储的 YYY 做比较 , 执行比较的背后 , CPU 的运算机制就会做减法运算 。 而无论减法运算的结果是正数、零还是负数 , 都会保存到标志寄存器中 。 结果为正表示 XXX 比 YYY 大 , 结果为零表示 XXX 和 YYY 相等 , 结果为负表示 XXX 比 YYY 小 。 程序比较的指令 , 实际上是在 CPU 内部做减法运算 。
函数调用机制接下来 , 我们继续介绍函数调用机制 , 哪怕是高级语言编写的程序 , 函数调用处理也是通过把程序计数器的值设定成函数的存储地址来实现的 。 函数执行跳转指令后 , 必须进行返回处理 , 单纯的指令跳转没有意义 , 下面是一个实现函数跳转的例子
程序员需要了解的硬核知识之CPU文章插图


推荐阅读