Hardware Hierarcy

image.png
image.png

可以看到

  • CPU 的一个核心是 PC(program counter),它是一个仅有 word-sized 大小的 storage device。
    • 凡是 word-sized 大小的存储设备都可以叫 register,所以 PC 是一个特殊的 register,它的名称叫做 PC。
    • register file 中也包含了多个 register,为了区分彼此,每一个 register 都有一个特定的名称。
    • 一般使用 register 时,泛指 register file 中的 register;指定 PC 这个 register 时通常不用 register 来简称,而是直接用 PC 称呼。
  • 这个 register 仅能存放 word-sized 大小的数据,即:memory address。
  • 从计算机开机到关机,每时每刻 CPU 都在执行 PC 所指向的、存放于 memory 的 instruction:
    • 从 PC 所指向的 memory address 读取到相应的 instruction 。
    • 按照这个 instruction 在 ALU 中做相应的计算。
    • 更新 PC 的值为下一条 instruction 的 memory address(不一定同刚才那条 instruction 连续)。
  • register file(上图中 PC 旁边的、多个小长方形组成的矩形):由多个 word-sized 大小的 register 组成的 small storage device。
    • 每一个小的长方形,即是一个 register,且它的大小为 word-sized。
    • 显然,register file 中的「每个小长方形」跟 PC 是一样大小的。且它们都被称之为 register。由此可见,register 可被当做是一个 word-sized 的、在 CPU 中的存储单位。
    • register file 中的每个小长方形,各自都有一个 unique name。
  • ALU 可计算出 new data 或者 new address value。
  • CPU 支持的 instruction:
    • Load:将 memory 中的一个 word-sized 大小的内容,复写到某个 register。
    • Store:将 register 中的 word-sized 大小的内容,复写到 memory 中的某个位置。
    • Operate:将两个 register 中的内容拷贝到 ALU,对这两个 word-sized 的值做算数运算,将结果复写到一个 register 中。
    • Jump:从 instruction 中提取出一个 word-sized 大小的内容,将其复写到 PC。

CPU Register

register file 中的 register 有多个,它们分别有不同的作用,如:

  • general purpose register:用于 data movement、arithmetic instructions。
  • index register:充当 pointer
  • base pointer:data pointer
  • stack pointer
  • segment register:用于存储 program 的 不同部分,如 code segment、data segment、stack segment、extra segment。

每一条 machine instruction,其实就是一个 number:

  • 例如 03 C3 表示:将 EAX 和 EBX 这两个 register 中的内容相加,并将结果放到 EAX 中。
  • 但这样的 machine instruction 太难记忆,于是引入 assemble language:
    • 每一个条 assemble statement 都对应一条 machine instruction;
    • 例如上面的 03 C3 machine instruction 对应的 assemble statement 为 add eax, ebx

可尝试分析的一段代码是

  1. int simple(int *xp, int y)
  2. {
  3. int t = *xp + y;
  4. *xp = t;
  5. return t;
  6. }

使用反汇编之后的结果是
image.png

注意:凡是以 . 开头的语句,都是用于 guide the assembler and linker,可暂时忽略。可得到简化版本:
image.png

Remarks

  • 在使用 %ebp 之前,需要保留它的值到 kernel stack,以便 “结束运行后” 恢复。这个「保存」和「恢复」的过程分别对应为:
    • 第 2 行: push1 %ebp ,将 %ebp 的值保存到 kernel stack。
    • 第 8 行: popl %ebp ,将 kernel stack 的值弹出,并恢复至 %ebp
  • 加载 simple 函数的参数列表:
    • 第 3 行:找到 simple 函数参数列表的 memory 起始位置,将这个起始位置从 %esp 复制到 %ebp
    • 第 4 行:从 %ebp 做 8 位的 offset,将其值复制到 %edx .
    • 第 5 行:从 %ebp 做 12 位(由于前一个变量在 %ebp+8 ,且 int 变量占据 4 byte,所以当前变量的起始位置当然就是 %ebp+8+4 ,即 %ebp+12 )的 offset,将其值复制到 %eax
    • 第 6 行:将上述两步获取的值相加,并将结果复制到 %eax
    • 第 7 行:将刚才获取到的求和结果 %eax 的值,复制到 %edx ,即 *xp 所在的位置。

User/Kernel Mode

image.png
https://en.wikipedia.org/wiki/Linux_kernel

image.png

image.png

Remarks

  • 这里或许最容易造成误会的是,中间的「Hardware」,所指的并非是 external drive(外部设备),而是 CPU 本身。
    • 两侧的 OS 和 Program,其实所指的都是「程序」,而中间才是执行程序的硬件,即:worker。
    • 这里之所以容易造成误会,是因为很容易混淆 program、running program、program executor。

下面以 foo.c 编译出来的可执行文件 foo 的运行为例。运行开始的命令是 ./foo

  • 首先,需要为 foo 这个执行文件做一些初始化工作,如:process 分配、memory 分配。但这些工作都无法在 user mode 下执行,需要进入 kernel mode。
  • 于是,进入 kernel mode 前,需要将当前的 reg/PC 信息保存到 kernel stack 中(对应到第①步)。
  • 进行初始化工作:分配 process、分配 process 中的 memory,并将 ./foo 的指令加载进被分配的 memory 中。
  • 完成这些初始化工作后,通过 return-from-trap 返回到 user mode。此时,CPU 从 kernel stack 中取出之前保存的 reg/PC,开始这个 process 的执行,即:从 process 的 memory 中的 main() 开始执行(对应到第②步)。
  • 执行 main() 函数时,遇到了需要调用 system call 的指令。此时,执行 trap instruction(对应到第③步)。
    • trap instruction 会保存当前的 reg/PC 信息到 kernel stack;
    • 通过 trap table 找到相应的 trap handler(对应到第④步)。
  • trap handler(kernel mode) 接收这个 trap(exception),并执行对应的 system call 的逻辑(对应到第⑤步)。
  • 后续步骤相对显然。

Memory Management

Heap 和 Stack 在 process memory 中的 layout

可以看到,一根 process 的粗略 memory layout 通常如下图所示:
image.png
一个值得思考的问题便是,为什么要如此摆放 stack 和 heap 的位置?可以不可以让 stack 从 low-bit 向 high-bit 增长?

假设 stack 是向下增长、且放于「上图」往上一点的位置,那就意味着当 stack 所用 memory 较少时,其下方会出现一块闲置的 memory。而如果此时 heap 的资源特别紧张,虽然 process 中存在 stack 那块未被使用的闲置 memory,但却无法被 heap 所使用。

由此可知,之所以如此设计和摆放 heap 和 stack 在 memory 中的位置,是为了让 heap 的 free memory 和 stack 的 free memory 变成一块可以「共同使用」的区域,此时,「任何一方」的剩余资源都可以被「另一方」拿来使用,从而降低了 process 中 memory 的浪费。