寄存器:CPU内部的存储单元,用于存放从内存读取而来的数据(包括指令)和CPU运算的中间结果。
使用寄存器来临时存放数据而不直接操作内存原因如下:
-
CPU的工作原理决定了有些操作只能在CPU内部进行。
-
CPU读写寄存器的速度比读写内存的速度要快很多。
为便于交流和使用汇编语言编程,CPU厂商为每个寄存器都取了名字,如ADM64 CPU的rax、rbx、rcx等,这样就很方便的在汇编代码中直接使用寄存器的名称来进行编程,来看段Go编写的一行代码来直观的感受下寄存器的使用:
a := b + c
ADM64 Linux平台下用Go编译器编译上述代码可得如下AT&T格式的汇编代码:
mov (%rsp),%rdx //把变量b的值从内存中读取到寄存器rdx
mov 0x8(%rsp),%rax //把变量c的值从内存中读取到寄存器rax
add %rdx,%rax //把寄存器rdx和rax里面的值相加,之后将结果放回寄存器rax
mov %rax,0x10(%rsp) //把寄存器rax中的值写回变量a所在的寄存器rsp
由上述汇编代码可以看到,一行Go代码被编译成了四行汇编指令,上述汇编代码里面的rsp、rap、rdp都是寄存器的名称(说明:AT&T格式的汇编代码中所有的寄存器名字前面都有一个%符号)。
可以看到,虽然只有四条指令,但也从侧面说明汇编代码比较简单,因为它所做的工作仅仅只是把数据在内存和寄存器中来回倒腾或是做一些基础的数学以及逻辑运算。
不同体系结构的CPU在其内部存在的寄存器的数量、类型以及名称可能会不同,本文只看ADM64 CPU这一种体系结构,这种CPU在其内部存在二十多个可直接被汇编语言中使用的调度器,还有几种仅在操作系统代码中才会出现,应用层的话,通常只会用到如下三个分类共19个调度器:
-
通用寄存器。共有rax、rbx、rcx、rdx、rsi、rdi、rbp、rsp、r8、r9、r10、r11、r12、r13、r14、r15这16个寄存器,CPU对它们的用途没有做特殊规定,可以自定义其用途(其中rsp、rbp这两个寄存器有特殊用途)。
-
程序计数寄存器(rip寄存器,也叫PC寄存器、IP寄存器)。用来存放下一条即将用来执行的指令的地址,它决定程序执行的流程。
-
段寄存器(fs、gs寄存器)。用来实现线程本地存储(TLS),比如ADM64 Linux下Go语言和pthread线程库都用fs存储器来实现线程的TLS(本地存储)。
上述存储器除了段寄存器是16位的,其它都是64位的,也就是八个字节,但是通用寄存器还可以当做32/16/8位存储器来使用。
不过使用的时候就需要换个名字了,比如可用eax来表示一个32位的寄存器,它使用的是rax寄存器的低32位。为方便各位查阅,下表为64位通用寄存器对应32/16/8位存储器的名称,如下:
64位 | 32位 | 16位 | 8位 |
---|---|---|---|
rax | eax | ax | al/ah |
rbx | ebx | bx | bl/bh |
rcx | ecx | cx | cl/ch |
rdx | edx | dx | dl/dh |
rsi | esi | si | - |
rdi | edi | di | - |
rbp | ebp | bp | - |
rsp | esp | sp | - |
r8~r15 | r8d~r15d | r8w~r15w | r8b~r15b |
接下来看下rip、rsp、rbp这三个特殊的寄存器。
1、rip寄存器:存放CPU即将执行的下一条指令在内存中的地址。
来看段汇编语言:
0x0000000000400770: add %rdx,%rax
0x0000000000400773: mov $0x0,%ecx
假设当前CPU正在执行第一条指令,我们可以看到,这条指令在内存中的地址就是0x0000000000400770,很明显,紧随其后的下一条将执行的指令在内存中的地址就是0x0000000000400773,此时,rip寄存器中所存储的值就是0x0000000000400773。
只需要记住,rip寄存器中存储的值不是CPU正在执行的指令在内存中的地址,而是紧随其后那一条将要被执行的指令在内存中的地址。
这里有人可能会问了,上述代码片段中并没有指令来让CPU去修改rip寄存器中的值呀,那rip寄存器是怎么实现上述功能的呢?
修改rip寄存器中的值这个操作是CPU自动控制的,不需要通过指令去修改,当然,CPU对外也提供了几条间接修改rip寄存器的指令,这个后面再详聊。
2、rsp栈顶寄存器以及rbp栈基寄存器。
上述两个寄存器都和函数调用栈相关,其中rsp寄存器一般用来存放函数调用栈的栈顶在内存中的地址,rbp寄存器通常用来存放函数的栈帧在内存中的起始地址。
编译器一般使用上述寄存器加一定偏移来访问函数局部变量或是函数参数,如下:
mov 0x8(%rsp),%rdx
上述汇编语言代码的指令把地址为0x8(%rsp)的内存中的值拷贝到寄存器rdx,此操作就是使用rsp寄存器加一定偏移来读取内存中的值。
到此基础的CPU寄存器相关内容聊得差不多了,这并不是需要了解的全部内容,后续文章中会有体现,喜欢本文的话,欢迎来个三连击。
扫码关注公众号,获取更多优质内容。