cpu-register

寄存器的分类

寄存器是CPU中的数据临时存储单元, 不同的CPU体系结构中的寄存器的数量是不一致的
对于x86_64体系下的CPU就提供了16个64为的通用寄存器.

一般情况下64为字长的CPU提供的寄存器的容量是64个bit位, 而32位字长的cpu提供的寄存器的容量是32位bit位.

对于x86_64系的CPU来说, 如果寄存器以r开头则表明是一个64位的寄存器, 如果以e开头则表明是一个32位的寄存器, 同时系统还提供了16位寄存器以及8位的寄存器. 32位的寄存器是64位寄存器的低32位部分并不是独立存在的, 16位寄存器则是23位寄存器的低16位部分并不是独立存在的, 8位寄存器则是16位寄存器的低8位并不是独立存在的.

不管寄存器的长度如何, 它们有些用来存放将要执行的指令地址, 有些用来存储要运算的数据, 有些用来存储计算的结果状态, 有些用来保存内存的基地址信息, 有些用来保存要运算的浮点数. 因此cpu中的寄存器可以按照作用进行如下分类:

数据地址寄存器

数据地址寄存器通常用来做数据计算的临时存储\做累加\计数\地址保存等功能. 定义这些寄存器的作用主要用于在cpu指令中保存操作数, 在cpu中当做一些常规变量来使用. 在cpu中当做一些常规变量来使用. 所以我们的代码里面看到的以及用到的最多寄存器就是这些寄存器:

体系结构 长度 名称
x86_64 64 RAX,RBX,RCX,RDX,RDI,RSI,R8-R15
X86_64 32 EAX,EBX,ECX,EDX,EDI,ESI,R8D-R15D
X86_64 16 AX, BX, CX, DX, DI, SI, R8W-R15W
X86_64 8 AL, BL, CL, DL, DIL, SIL, R8L-R15L
  • 在x86_64体系中RAX一般都用来保存函数的返回值
  • 在函数调用时的非浮点参数传递在x86_64体系中分别保存在RDI, RSI, RDX, RCX, R8, R9

Intel架构CPU的段寄存器

早期的16位实模式程序中的内存访问都是基于物理地址的, 而且还把整个程序拆分位数据段, 代码端, 栈段, 扩展段四个区域, 每个内存区段内的地址编码都是相对于这个段的偏移来设置的, 因此位了定位和区分这些内存区段, CPU分别设置了CS, DS, SS, ES四个寄存器来把保存这些段的基地址.

后来随着CPU和操作系统的发展, 应用程序不再直接访问物理内存地址了, 而是访问由操作系统提供的虚拟内存地址, 同时也不再把整个内存空间划分位数据段和代码段了, 而是提供一个从0开始的平坦连续的内存空间了, 同时将程序所能访问的内存区域和操作系统内核所能访问的内存区域进行了隔离, 我们称这样的程序位保护模式下运行的程序. 因此这时候里面的CS, DS, SS, ES寄存器的作用变为了保护操作系统进入用户态还是核心态以及进行用户态和核心态之间进行切换上下文数据的功能了. 也就是再保护宝石下运行的程序我们将不需要也没有权利去访问这些段寄存器了.

体系结构 长度 名称
x86_64 16 CS,DE,SS,ES,FS,GS


段寄存器: 在8086系统中, 访问存储器的地址码由段地址和段内偏移地址两部分组成. 段寄存器用来存放各分段的逻辑基值, 并指示当前正在使用的4个逻辑段, 包括代码段寄存器CS, 堆栈段寄存器CS, 数据段寄存器DS和附加段数据寄存器ES.

平坦内存模式和分段内存模式下的应用结构

这里面需要澄清的是我们的程序内存区域虽然从物理上不再划分位代码段, 数据段, 栈段几个独立的内存空间. 但是在平坦内存模式下我们依然保留了代码段, 数据段, 栈段的划分, 每个段的基地址都是从0开始, 只是各种类型的数据存放到了不同的内存空间中去了, 也就是说程序分段的机制由硬件划分转化为了软件划分.

栈寄存器

栈空间和操作

从上面可以看出要维护一个栈区域就必须要提供两个寄存器, 一个寄存器用来保存栈的基地址也就是栈的底部, 而一个寄存器则用来保存栈的偏移也就是栈的顶部. 在一般的系统中, 我们都将栈顶地址进行递减, 而当有出栈操作时则将栈顶地址递增. 栈的这种特性, 使得塔非常适合用于保存函数中定义的局部变量, 以及函数内调用函数的情况.

在x86_64体系的CPU中, 提供了一个专门的RBP寄存用来保存栈的基地址, 同时提供一个专门的RSP寄存器来保存栈的栈顶地址

体系结构 长度 名称
x86_64 64 RBP为栈基址寄存器, RSP为栈顶寄存器
x86_64 32 EBP为栈基址寄存器, ESP为栈顶寄存器
x86_64 16 BP为栈基址寄存器, SP为栈顶寄存器

浮点和向量寄存器

因为浮点数的存储以及其运算的特殊性, 所以CPU中专门提供FPU以及相应的浮点数寄存器来处理浮点数, 除了一些浮点数状态和控制寄存器(比如四舍五入的处理方式等)外主要就是一些保存浮点数的寄存器:

体系结构 长度 名称
x86_64 256 YMM0-YMM15
x86_64 128 XMM0-XMM15
x86_64 80 STMM0-STMM15

现在的CPU除了支持标量运算外, 还支持向量运算. 向量运算在图像处理相关的领域用的非常的多

状态寄存器

状态寄存器用来保存指令运行结果的一些信息, 比如相加的结果是否溢出, 结果是否为0, 以及是否是负数等. CPU的某些指令会根据状态寄存器里面的特殊位置来进行跳转的

在x64体系的CPU中提供了一个64位的RFLAGS寄存器来作为状态寄存器

状态寄存器的内容由CPU内部进行置位, 我们的程序中不能将某个数值赋值给状态寄存器.

体系结构 长度 名称
x64 64 RFLAGS

指令寄存器(程序计数器)

我们知道程序代码是保存在内存中的, 那CPU又是如何知道要执行哪一条保存在内存中的指令呢? 这就是通过指令寄存器来完成的. 因为内存中的指令总是按线性序列保存的, CPU只是按照编制的程序来执行指令. 因此CPU内提供一个指令寄存器来记录CPU下一条将要执行的指令的内存地址, 这样每次执行完毕一条指令后, CPU就根据指令寄存器中 所记录的地址搭配内存中去读取指令并执行, 同时又将下一条指令的内存地址保存到指令存储器中, 就这样就重复不断的处理来完成整个程序的执行.

但是这里有两问题:

  1. 前面不是说CPU内有高速缓存吗? 怎么又说每次都去访问内存呢? 而且保存还是内存的地址呢. 这是没有问题的, 指令寄存器中保存的确定是下一条指令在内存中的地址, 但是操作系统除了将部分内恶臭区域中的指令保存到高速缓存外还会建立一个内存地址到高速缓存地址之间的映射关系数据结构. 因此即使是指令寄存器中保存的是内存地址, 但是在指令真实执行时CPU就会根据指令寄存器中的内存地址以及内部建立的内存和高速缓存的映射关系来转化位指令在高速缓存中的地址来读取指令并执行. 当然如果发现指令并不在高速缓冲中时, CPU就会触发一个中断并告诉操作系统, 操作系统再根据特定的策略从内存中再次读取一块新的内存数据到高速缓存中, 并覆盖掉原先保存再高速缓存中的内容, 然后CPU再次读取高速缓存中的指令后继续执行.

  2. 如果说指令寄存器每次都是保存再顺序执行指令的话那么怎么去实现跳转逻辑呢? 答案是跳转指令和函数调用指令的存在. 我们的用户态中的代码不能人为的改变指令寄存器的值, 也就是不嫩那个对指令寄存器进行赋值, 因此默认情况下指令寄存器总是由CPU内部设置位一下条指令的地址, 但是跳转指令和函数调用指令例外.这这条指令的主要作用就是用来改变指令寄存器的内容, 正是因为跳转功能才使得我们的程序可以不只按顺序去执行而是具有条件执行和循环执行代码的能力

在x64体系的CPU中提供了一个64位的指令寄存器RIP

指令寄存器保存的是下一条将要执行的指令的内存地址, 而不是当前正在执行的指令的内存地址

体系结构 长度 名称
x64 64 RIP
x64 64 EIP