程序员应该知道的操作系统知识

上一篇文章我们介绍了冯诺依曼模型,这一篇我们趁热打铁介绍一下程序的执行过程

程序的执行过程

我们还是先来看一张图

image-20201221162809658

程序计数器(PC)和指令寄存器类似一个映射关系,PC中存放的是指令的内存地址,指令寄存器中存放的是内存地址对应的真实指令;

  • 首先,CPU 读取 PC 指针指向的指令,将它导入指令寄存器。具体来说,完成读取指令这件事情有 3 个步骤:
    • CPU 的控制单元操作地址总线指定需要访问的内存地址(简单理解,就是把 PC 指针中的值拷贝到地址总线中)。
    • CPU 通知内存设备准备数据(内存设备准备好了,就通过数据总线将数据传送给 CPU)。
    • CPU 收到内存传来的数据后,将这个数据存入指令寄存器。

完成以上 3 步,CPU 成功读取了 PC 指针指向指令,存入了指令寄存器。

  • 然后,CPU 分析指令寄存器中的指令,确定指令的类型和参数。
  • 如果是计算类型的指令,那么就交给逻辑运算单元计算;如果是存储类型的指令,那么由控制单元执行。
  • PC 指针自增,并准备获取下一条指令。

上面整个过程从 PC 指针读取指令、到执行、再到下一条指令,构成了一个循环,这个不断循环的过程叫作CPU 的指令周期

上面文字介绍可能还不够直观,下面我们举一个例子

详解a = 11 + 15 的执行过程

程序员写的程序a=11+15是字符串,CPU 不能执行字符串,只能执行指令。所以这里需要用到一种特殊的程序——编译器

1. 编译器通过分析,发现 11 和 15 是数据,因此编译好的程序启动时,会在内存中开辟出一个专门的区域存这样的常数,这个专门用来存储常数的区域,就是数据段,如下图所示:

  • 11 被存储到了地址 0x100;

  • 15 被存储到了地址 0x104;

image-20201221142057174

2. 编译器将a=11+15转换成了 4 条指令,程序启动后,这些指令被导入了一个专门用来存储指令的区域,也就是正文段。如上图所示,这 4 条指令被存储到了 0x200-0x20c 的区域中:

0x200 位置的 load 指令将地址 0x100 中的数据 11 导入寄存器 R0;

0x204 位置的 load 指令将地址 0x104 中的数据 15 导入寄存器 R1;

0x208 位置的 add 指令将寄存器 R0 和 R1 中的值相加,存入寄存器 R2;

0x20c 位置的 store 指令将寄存器 R2 中的值存回数据区域中的 0x1108 位置。

3. 具体执行的时候,PC 指针先指向 0x200 位置,然后依次执行这 4 条指令。

这里还有几个问题要说明一下:

变量 a 实际上是内存中的一个地址,a 是给程序员的助记符。

为什么 0x200 中代表加载数据到寄存器的指令是 0x8c000100,我们会在下面详细讨论。

不知道细心的同学是否发现,在上面的例子中,我们每次操作 4 个地址,也就是 32 位,这是因为我们在用 32 位宽的 CPU 举例。在 32 位宽的 CPU 中,指令也是 32 位的。但是数据可以小于 32 位,比如可以加和两个 8 位的字节。

指令

接下来具体分析指令的执行过程。

在上面的例子中,load 指令将内存中的数据导入寄存器,我们写成了 16 进制:0x8c000100,拆分成二进制就是:

image-20201221143757369
  • 最左边的 6 位,叫作操作码,英文是 OpCode,100011 代表 load 指令;

  • 中间的 4 位 0000是寄存器的编号,这里代表寄存器 R0;

  • 后面的 22 位代表要读取的地址,也就是 0x100。

所以我们是把操作码、寄存器的编号、要读取的地址合并到了一个 32 位的指令中。

构造指令的过程,叫作指令的编码,通常由编译器完成;解析指令的过程,叫作指令的解码,由 CPU 完成。由此可见 CPU 内部有一个循环:

  • 首先 CPU 通过 PC 指针读取对应内存地址的指令,我们将这个步骤叫作 Fetch,就是获取的意思。

  • CPU 对指令进行解码,我们将这个部分叫作 Decode。

  • CPU 执行指令,我们将这个部分叫作 Execution。

  • CPU 将结果存回寄存器或者将寄存器存入内存,我们将这个步骤叫作 Store。

image-20201221162212173

上面 4 个步骤,我们叫作 CPU 的指令周期。CPU 的工作就是一个周期接着一个周期,周而复始。

到这里,相信你已经对程序的执行过程有了一定的了解!