本文是《go 调度器源代码情景分析》系列 第一章 预备知识的第 1 小节。

    寄存器是 CPU 内部的存储单元,用于存放从内存读取而来的数据(包括指令)和 CPU 运算的中间结果,之所以要使用寄存器来临时存放数据而不是直接操作内存,一是因为 CPU 的工作原理决定了有些操作运算只能在 CPU 内部进行,二是因为 CPU 读写寄存器的速度比读写内存的速度快得多。

    为了便于交流和使用汇编语言进行编程,CPU 厂商为每个寄存器都取了一个名字,比如 AMD64 CPU 中的 rax, rbx, rcx, rdx 等等,这样程序员就可以很方便的在汇编代码中使用寄存器的名字来进行编程,为了对寄存器的使用有个直观的感受,我们用个例子来简单的说明一下。

    假设有如下 go 语言编写的一行代码:

    在 AMD64 Linux 平台下,使用 go 编译器编译它可得到如下 AT&T 格式的汇编代码(如果对汇编代码不熟悉的话可以直接看每一条指令后面的注释,不影响我们理解):

    mov (%rsp),%rdx #把变量 a 的值从内存中读取到寄存器 rdx 中 mov 0x8(%rsp),%rax #把变量 b 的值从内存中读取到寄存器 rax 中 add %rdx,%rax #把寄存器 rdx 和 rax 中的值相加,并把结果放回 rax 寄存器中 mov %rax,0x10(%rsp) #把寄存器 rax 中的值写回变量 c 所在的内存

    可以看到,上面的一行 go 语言代码被编译成了 4 条汇编指令,指令中出现的 rax,rdx 和 rsp 都是寄存器的名字(AT&T 格式的汇编代码中所有寄存器名字前面都有一个 % 符号),虽然这里只有 4 条指令,但也从一个侧面说明汇编代码其实比较简单,它所做的工作不外乎就是把数据在内存和寄存器中搬来搬去或做一些基础的数学和逻辑运算。

    不同体系结构的 CPU,其内部寄存器的数量、种类以及名称可能大不相同,这里我们只介绍目前使用最为广泛的 AMD64 这种体系结构的 CPU,这种 CPU 共有 20 多个可以直接在汇编代码中使用的寄存器,其中有几个寄存器在操作系统代码中才会见到,而应用层代码一般只会用到如下分为三类的 19 个寄存器。

    • 通用寄存器: rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, r8, r9, r10, r11, r12, r13, r14, r15 寄存器。CPU 对这 16 个通用寄存器的用途没有做特殊规定,程序员和编译器可以自定义其用途(下面会介绍,rsp/rbp 寄存器其实是有特殊用途的);
    • 程序计数寄存器(PC 寄存器,有时也叫 IP 寄存器):rip 寄存器。它用来存放下一条即将执行的指令的地址,这个寄存器决定了程序的执行流程;
    • 段寄存器: fs 和 gs 寄存器。一般用它来实现线程本地存储(TLS),比如 AMD64 linux 平台下 go 语言和 pthread 都使用 fs 寄存器来实现系统线程的 TLS,在本章线程本地存储一节和第二章详细分析 goroutine 调度器的时候我们可以分别看到 Linux 平台下 Pthread 线程库和 go 是如何使用 fs 寄存器的。

    上述这些寄存器除了 fs 和 gs 段寄存器是 16 位的,其它都是 64 位的,也就是 8 个字节,其中的 16 个通用寄存器还可以作为 32/16/8 位寄存器使用,只是使用时需要换一个名字,比如可以用 eax 这个名字来表示一个 32 位的寄存器,它使用的是 rax 寄存器的低 32 位。

    为了便于查阅,下表列出这些 64 通用寄存器对应的 32/16/8 位寄存器的名字:

    go语言调度器源代码情景分析之二:CPU寄存器 - 爱写程序的阿波张 - 博客园 - 图1

    通用寄存器的用法如前面我们所见,主要用于临时存放数据,后面的章节我们还会见到大量使用通用寄存器的例子,所以这里就不对其进行详细介绍了,但有三个比较特殊的寄存器值得在这里单独提出来做一下说明:

    • rip 寄存器

    rip 寄存器里面存放的是 CPU 即将执行的下一条指令在内存中的地址。看如下汇编语言代码片段:

    0x0000000000400770: add %rdx,%rax
    0x0000000000400773: mov $0x0,%ecx

    假设当前 CPU 正在执行第一条指令,这条指令在内存中的地址是 0x0000000000400770,紧接它后面的下一条指令的地址是 0x0000000000400773,所以此时 rip 寄存器里面存放的值是 0x0000000000400773。

    这里需要牢记的就是 rip 寄存器的值不是正在被 CPU 执行的指令在内存中的地址,而是紧挨这条正在被执行的指令后面那一条指令的地址。

    读者可能会有疑问,在前面的两个汇编指令片段中并没有指令修改 rip 寄存器的值,是怎么做到让它一直指向下一条即将执行的指令的呢?其实修改 rip 寄存器的值是 CPU 自动控制的,不需要我们用指令去修改,当然 CPU 也提供了几条可以间接修改 rip 寄存器的指令,在汇编语言一节中我们会详细介绍 CPU 自动修改以及用指令修改 rip 寄存器值的两种方式。

    • rsp 栈顶寄存器和 rbp 栈基址寄存器

    这两个寄存器都跟函数调用栈有关,其中 rsp 寄存器一般用来存放函数调用栈的栈顶地址,而 rbp 寄存器通常用来存放函数的栈帧起始地址,编译器一般使用这两个寄存器加一定偏移的方式来访问函数局部变量或函数参数,比如:

    这条指令把地址为 0x8(%rsp) 的内存中的值拷贝到 rdx 寄存器,这里的 0x8(%rsp) 就利用了 rsp 寄存器加偏移 8 的方式来读取内存中的值。

    寄存器的内容我们就先简单的介绍到这里,但这些并不是我们需要了解的有关寄存器的全部内容,有些内容需要等我们学习了汇编指令和函数调用栈之后才能更加深刻的理解,到时候我们再回头来继续介绍相关的知识。
    https://www.cnblogs.com/abozhang/p/10766689.html