CPU 与寄存器

CPU 与寄存器

Jan 2, 2021
操作系统, 编译原理

看 Go 语言的调度器相关内容的时候里面很多内存堆栈、寄存器的概念,一开始没怎么看懂,需要先回来复习下相关的基础知识,早知道大学就好好学了。

前提须知 #

  1. 下面的内容都以 16 位的 8086CPU 的情况为准。
  2. 通常写一条汇编指令或一个寄存器的名称,不区分大小写。
  3. 为了区分不同的进制,在十六进制表示的数据的后面加 H,在二进制表示的数据后面加 B
  4. 现在很多实际的源码中一般都是 32 位或者 64 位的寄存器表示。比如表示 SP 的 32 位寄存器是 ESP,对应 64 位寄存器是 RSP,其他通用寄存器也是同理。

CPU #

CPU 的内部由寄存器、控制器、运算器和时钟四个部分构成,各部分之间由电流信号相互连通。寄存器可用来暂存指令、数据等处理对象。根据种类的不同,一个 CPU 内部会有 20~100 个寄存器。控制器负责把内存上的指令、数据等读入寄存器,并根据指令的执行结果来控制整个计算机。运算器负责运算从内存读入寄存器的数据。时钟负责发出CPU开始计时的时钟信号。

内部总线 #

内部总线即 CPU 内部的总线。它用来连接 CPU 内部的各个组件,内部计算完成后,统一通过控制器向外通信。

外部总线 #

CPU 通过外部总线,对内存进行读写,读取数据和指令和执行,外部总线分为 3 类:地址总线、数据总线和控制总线。

地址总线 #

CPU 是通过地址总线来指定存储器单元的,地址总线通常带有宽度限制,如果一个 CPU 有 10 根地址总线,那么它一次通信可以表示的数据范围就是 2 的 10 次方,即 0 ~ 1023 的内存单元。8086 CPU有 20 位地址总线,可以传送 20 位地址,达到 1M 寻址能力。

数据总线 #

CPU 通过数据总线与主存进行数据通信,数据总线也有宽度限制,8 根总线可以一次性传输 8 位二进制数据(即一字节),8086CPU 为 16 根总线,即一次性可以传输两个字节。

控制总线 #

CPU 对外部器件的控制是通过控制总线进行的,控制总线是一个统称,一般的外部组件比如网卡、显卡、声卡都是,可见控制总线的宽度决定了 CPU 对外部器件的控制能力。

机器语言 #

机器语言是 CPU 可以直接识别并使用的语言,由 0 和 1 组成,它不同于汇编语言,汇编语言是由程序员编写的,现在汇编语言写的人少了,也可以是由高级语言经过编译器前端的编译以后的产物。

内存地址空间 #

这里再简单说一下内存(RAM),内存通常指的是计算机的主存储器,主存通过控制芯片与 CPU 相连,主要负责存储指令和数据。在内存上,指令和数据没有任何区别,都是二进制信息。内存的基本单位是字节(byte),最小单位是位(bit),换算关系为:1 字节 = 8 位

还有一种存储器比较常见,就是只读存储器(ROM),一般用来装 BIOS。

CPU 在操控外部器件的时候,把它们都当作都内存来对待,所有器件的储存单元组成了一个逻辑存储器,对应了一个共同的虚拟的内存地址空间,CPU 所有的操作和汇编指令里的地址,针对的都是这个内存地址空间。

寄存器 #

程序的本质是通过改变各种寄存器中的内容,来实现对CPU的控制,下面开始详细说说各种寄存器都是什么。

通用寄存器 #

8086CPU 的所有寄存器都是 16 位的,可以存放两个字节。其中 AX、BX、CX、DX 这四个寄存器通常用来存放一般性的数据被称为通用寄存器,相应的 32 位寄存器是 EAX,EBX,ECX,EDX。

为了保持向前兼容,这四个通用寄存器都可以分为两个独立的 8 位寄存器来使用:

  • AX 可分为 AH 和 AL
  • BX 可分为 BH 和 BL;
  • CX 可分为 CH 和 CL;
  • DX 可分为 DH 和 DL;

AX 累加器寄存器 #

累加器,运算时较多使用这个寄存器,有些指令规定必须使用它。

BX 基址寄存器 #

基址寄存器,除了存放数据,它经常用来存放一段内存的起始偏移地址,段地址在 DS 中。

mov al, [1]

比如这样的代码,编译器会理解成 mov al, 1,此时需要先将偏移地址送入 bx 寄存器中,用 [bx] 的方式访问内存:

mov bx, 1
mov al, [bx]

或者下面这样在 [] 前面显示给出段地址所在的段寄存器也是可以的:

mov ax, 2000h
mov ds, ax
mov al, ds:[0]

这种方式叫做段前缀

将 dl 中的数据送入 es:bx 中:

mov es:[bx], dl

CX 计数寄存器 #

计数寄存器,除了存放数据,它经常用来存放重复操作的次数,经常配合 loop 命令一起使用。

DX 数据寄存器 #

数据寄存器,除了存放数据,它有时存放32位数据的高 16 位。

段寄存器 #

要理解段寄存器,需要先理解段地址偏移地址的概念。我们知道,一个 8 位的寄存器只能指定内存中 0 ~ 1023 字节的数据,一个 16 位的寄存器也就能表示到 2 的 16 次方即 64 KB。如果想要表示更大范围的内存地址,如何解决?

8086CPU 在内部采用地址加法器将两个 16 位的地址合成一个 20 位的物理地址,这两个地址,一个称为段地址,一个称为偏移地址,对于 8086CPU 来说段地址左移 4 位就可以表示到 20 位地址范围,计算公式为:

$$ 物理地址 = 段地址 \times 16 + 偏移地址 $$

这样合成的地址用 段地址:偏移地址 表示,比如 1000:0 就是 10000H,对于 20 位的物理地址,表示的数据大小就是 1 M。8086CPU 有四个段寄存器:CS、DS、SS、ES。 当 CPU 要访问内存时,由这四个段寄存器提供内存单元的段地址。

CS 和 IP(代码) #

CS 和 IP 是 8086CPU 中两个最关键的寄存器,他们指示了 CPU 当前要读取指令的地址。CS 为代码段寄存器,IP 为指令指针寄存器。任意时刻,CPU 将 CS:IP 要访问的内容当作指令执行。能够改变 CS、IP 的内容的指令被统称为转移指令,比如 jmp

DS 和 [address](数据) #

DS 寄存器通常用来存放要访问数据的段地址,下面的三条指令将 10000H(1000:0)中的数据读到 AL 中。

mov bx, 1000H
mov ds, bx
mov al, [0]

其中 [...] 表示一个内存单元,里面的数字表示段地址为 DS 的偏移地址

SS 和 SP(栈机制) #

在 8086CPU 中,提供了出栈和入栈指令,最基础的是 pushpop,也为栈机制提供了两个寄存器,SS 和 SP,栈顶的段地址存放在 SS 中,偏移地址存放在 SP 中。任意时刻,SS:SP 指向栈顶元素。执行 push 和 pop 指令时,CPU 从 SS 和 SP 寄存器中得到栈顶的地址。pushpop 执行的时候只会修改 SP,所以 8086CPU 中一个栈段的容量最大为 64 KB。

注意在编写汇编语言的时候,需要自己操心栈顶越界的问题,要根据可能用到的最大栈空间来安排栈的大小,防止 push 入栈的数据太多而导致的越界执行,出栈操作的时候也要注意预防栈空的时候继续 pop 进而导致的越界。

地址寄存器 #

16 位的 8086 处理器有 4 个 16 位的通用地址寄存器,它们的主要作用是存放数据的所在偏移地址,也可以存放数据,这 4 个寄存器不能再拆分使用。

SP 堆栈指针 #

这是一个专用的寄存器,存放堆栈栈顶的偏移地址。

BP 基址指针 #

BP 为基址寄存器,一般在函数中用来保存进入函数时的 SP 的栈顶基址,并在函数结束时恢复 SP 的值。

SI 和 DI #

SI 为源变址寄存器,经常用来存放内存中源数据区的偏移地址,所谓变址寄存器,是指在某些指令作用下它可以自动地递增或递减其中的值。

DI 为目的变址寄存器,DI 经常用来存放内存中目的数据区的偏移地址,并在某些指令作用下可以自动地递增或递减其中的值。

SI、DI 和 BX 的功能相近:

mov bx, 0
mov ax, [bx]
;等价于:
mov si, 0
mov ax, [si]
;也等价于:
mov di, 0
mov ax, [di]

指令指针寄存器 #

PC 程序计数器 #

指令指针寄存器是程序计数器的一种实现,CPU 每执行一个指令,程序计数器的值就会自动加 1。

IP #

CS 可以和 IP 一起用表示要执行的指令地址。

标志寄存器 #

标志寄存器(Flag Register)又称程序状态字(Program Status Word,PSW),是一种比较特殊的寄存器,与其他都不太一样,用来存放 CPU 的两类标志:状态标志控制标志。状态标志反映处理器当前的状态,如有无溢出、有无进位等,有 6 个:CF、PF、AF、ZF、SF 和 OF;控制标志用来控制 CPU 的工作方式,如是否响应可屏蔽中断等,有 3 个:TF、IF 和 DF。

图中空白的位,在8086CPU中没有被使用,因此不具有任何的含义。

CF 进位/借位标志 #

CF=1 表示两个无符号数的加法运算有进位,或者是减法运算有借位,需要对它们的高位进行补充处理;CF=0 表示没有产生进位或借位。同样,进行有符号数运算时也会产生新的 CF 标志,此时程序员可以不关心 CF 标志。

PF 奇偶标志 #

PF=1 表示运算结果的低 8 位中有偶数个 “1”;PF=0 表示有奇数个 “1”。它可以用来进行奇偶校验。

AF 辅助进位标志 #

在进行字操作时,低字节向高字节进位时,AF=1,否则为 0。一般用于两个 BCD 数运算后调整结果用,对其他数的运算没有意义。

ZF 零标志 #

ZF=1 表示运算结果为零,减法运算后结果为零意味着两个参加运算的数大小相等;ZF=0,表示运算结果非零。

SF 符号标志 #

SF=1 表示运算结果的最高位为 “1”。对于有符号数,在溢出标志 OF=0 时,SF=1 表示运算结果为负,SF=0 表示运算结果非负(正或零)。OF=1 时,由于结果是错误的,所以符号位也和正确值相反。例如,两个负数相加产生溢出,此时 SF=0。对于无符号数运算,SF 无意义(但是可以看出结果的大小规模)。

OF 溢出标志 #

OF=1 表示两个有符号数的运算结果超出了可以表示的范围,结果是错误的;OF=0 表示没有溢出,结果正确。进行无符号数运算时也会产生新的 OF 标志(CPU 不知道处理对象是否为有符号数),此时程序员可以不关心 OF 标志。

TF 跟踪标志位 #

跟踪标志位用于标识 CPU 是否允许单步中断,以进行程序调试。TF=0 时,8086CPU 处于正常状态;TF=1 时,8086CPU 处于单步状态,每执行一条指令就自动产生一次单步中断。

IF 中断允许标志 #

IF=1 表示允许处理器响应可屏蔽中断请求信号,称为开中断,IF=0 表示不允许处理器响应可屏蔽中断请求信号,称为关中断。

DF 方向标志 #

DF=0 时,每次执行字符串指令后,源或目的地址指针用加法自动修改地址;DF=1 时用减法来修改地址,它用来控制地址的变化方向。

总结 #

本文从零开始重新认识了 CPU,进而展开了 CPU 和寄存器的关系,以及寄存器如何使用内存数据,寄存器的种类太多,一时半会也学不完,后面会慢慢在这篇文章里补充。现在觉得,又得再去复习下汇编语言了,寄存器和汇编语言的关系非常紧密,这么简单的东西,大学怎么就没学会呢,现在会了还不算晚吧?

参考 #

本文共 3944 字,上次修改于 Jan 28, 2024,以 CC 署名-非商业性使用-禁止演绎 4.0 国际 协议进行许可。