CPU 执行程序的秘密,藏在了这 15 张图里( 四 )

  • 第一步 , CPU 读取「程序计数器」的值 , 这个值是指令的内存地址 , 然后 CPU 的「控制单元」操作「地址总线」指定需要访问的内存地址 , 接着通知内存设备准备数据 , 数据准备好后通过「数据总线」将指令数据传给 CPU , CPU 收到内存传来的数据后 , 将这个指令数据存入到「指令寄存器」 。
  • 第二步 , CPU 分析「指令寄存器」中的指令 , 确定指令的类型和参数 , 如果是计算类型的指令 , 就把指令交给「逻辑运算单元」运算;如果是存储类型的指令 , 则交由「控制单元」执行;
  • 第三步 , CPU 执行完指令后 , 「程序计数器」的值自增 , 表示指向下一条指令 。 这个自增的大小 , 由 CPU 的位宽决定 , 比如 32 位的 CPU , 指令是 4 个字节 , 需要 4 个内存地址存放 , 因此「程序计数器」的值会自增 4;
  • 简单总结一下就是 , 一个程序执行的时候 , CPU 会根据程序计数器里的内存地址 , 从内存里面把需要执行的指令读取到指令寄存器里面执行 , 然后根据指令长度自增 , 开始顺序读取下一条指令 。
    CPU 从程序计数器读取指令、到执行、再到下一条指令 , 这个过程会不断循环 , 直到程序执行结束 , 这个不断循环的过程被称为 CPU 的指令周期 。
    CPU 执行程序的秘密,藏在了这 15 张图里文章插图
    a = 1 + 2 执行具体过程知道了基本的程序执行过程后 , 接下来用 a = 1 + 2 的作为例子 , 进一步分析该程序在冯诺伊曼模型的执行过程 。
    CPU 是不认识 a = 1 + 2 这个字符串 , 这些字符串只是方便我们程序员认识 , 要想这段程序能跑起来 , 还需要把整个程序翻译成汇编语言的程序 , 这个过程称为编译成汇编代码 。
    针对汇编代码 , 我们还需要用汇编器翻译成机器码 , 这些机器码由 0 和 1 组成的机器语言 , 这一条条机器码 , 就是一条条的计算机指令 , 这个才是 CPU 能够真正认识的东西 。
    下面来看看 a = 1 + 2 在 32 位 CPU 的执行过程 。
    程序编译过程中 , 编译器通过分析代码 , 发现 1 和 2 是数据 , 于是程序运行时 , 内存会有个专门的区域来存放这些数据 , 这个区域就是「数据段」 。 如下图 , 数据 1 和 2 的区域位置:
    • 数据 1 被存放到 0x100 位置;
    • 数据 2 被存放到 0x104 位置;
    注意 , 数据和指令是分开区域存放的 , 存放指令区域的地方称为「正文段」 。
    CPU 执行程序的秘密,藏在了这 15 张图里文章插图
    编译器会把 a = 1 + 2 翻译成 4 条指令 , 存放到正文段中 。 如图 , 这 4 条指令被存放到了 0x200 ~ 0x20c 的区域中:
    • 0x200 的内容是 load 指令将 0x100 地址中的数据 1 装入到寄存器 R0;
    • 0x204 的内容是 load 指令将 0x104 地址中的数据 2 装入到寄存器 R1;
    • 0x208 的内容是 add 指令将寄存器 R0 和 R1 的数据相加 , 并把结果存放到寄存器 R2;
    • 【CPU 执行程序的秘密,藏在了这 15 张图里】0x20c 的内容是 store 指令将寄存器 R2 中的数据存回数据段中的 0x108 地址中 , 这个地址也就是变量 a 内存中的地址;
    编译完成后 , 具体执行程序的时候 , 程序计数器会被设置为 0x200 地址 , 然后依次执行这 4 条指令 。
    上面的例子中 , 由于是在 32 位 CPU 执行的 , 因此一条指令是占 32 位大小 , 所以你会发现每条指令间隔 4 个字节 。
    而数据的大小是根据你在程序中指定的变量类型 , 比如 int 类型的数据则占 4 个字节 , char 类型的数据则占 1 个字节 。
    指令上面的例子中 , 图中指令的内容我写的是简易的汇编代码 , 目的是为了方便理解指令的具体内容 , 事实上指令的内容是一串二进制数字的机器码 , 每条指令都有对应的机器码 , CPU 通过解析机器码来知道指令的内容 。


    推荐阅读