认识CPU寄存器


认识CPU寄存器


引言

组件化的硬件体系

上面的计算机系统结构图中我们可以看出 硬件系统的这种组件化的设计思路总是贯彻到各个环节。 在这套设计思想(冯.诺依曼体系架构)里面,总是有一部分负责控制、一部分负责执行、一部分则负责存储, 它之间进行交互以及接口通信则总是通过总线来完成。这种设计思路一样的可以应用在我们的软件设计体系里面: 组件和组件之间通信通过事件的方式来进行解耦处理,而一个组件内部同样也需要明确好各个部分的职责( 一部分负责调度控制、一部分负责执行实现、一部分负责数据存储)。

缓存

一个完整的CPU系统里面有控制部件、运算部件还有寄存器部件。其中寄存器部件的作用就是进行数据的临时存储。 既然有内存作为数据存储的场所,那么为什么还要有寄存器呢?答案就是速度和成本。我们知道CPU的运算速度是非常快的, 如果把运算的数据都放到内存里面的话那将大大降低整个系统的性能。解决的办法是在CPU内部开辟一小块临时存储区域, 并在进行运算时先将数据从内存复制到这一小块临时存储区域中,运算时就在这一小块临时存储区域内进行。 我们称这一小块临时存储区域为寄存器。因为寄存器和运算器以及控制器是非常紧密的联系在一起的,它们的频率一致, 所以运算时就不会因为数据的来回传输以及各设备之间的频率差异导致系统性能的整体下降。 你可能又会问为什么不把整个内存都集成进CPU中去呢?答案其实还是成本问题!

因为CPU速度很快,相应的寄存器也需要存取很快,二者速度上要匹配,所以这些寄存器的制作难度大,选材精, 而且是集成到芯片内部,所以价格高。而内存的成本则相对低廉,而且从工艺上来说,我们不可能在CPU内部集成大量的存储单元。

运算的问题通过寄存器解决了,但是还存在一个问题:我们知道程序在运行时是要将所有可执行的二进制指令代码都装载到内存里面去, CPU每执行一条指令前都需要从内存中将指令读取到CPU内并执行。如果按这样每次都从内存读取一条指令来依次执行的话, 那还是存在着CPU和内存之间的处理瓶颈问题,从而造成整体性能的下降。这个问题怎么解决呢?答案就是高速缓存。 其实在CPU内部不仅有为解决运算问题而设计的寄存器,还集成了一个部分高速缓存存储区域。 高速缓存的制造成本要比寄存器低,但是比内存的制造成本高,容量要比寄存器大,但是比内存的容量小很多。 虽然没有寄存器和运算器之间的距离那么紧密,但是要比内存到运算器之间的距离要近很多。 一般情况下CPU内的高速缓存可能只有几KB或者几十KB那么大。正是通过高速缓存的引入, 当程序在运行时,就可以预先将部分在内存中要执行的指令代码以及数据复制到高速缓存中去, 而CPU则不再每次都从内存中读取指令而是直接从高速缓存依次读取指令来执行,从而加快了整体的速度。 当然要预读取哪块内存区域的指令和数据到缓存上以及怎么去读取这些工作都交给操作系统去调度完成, 这里面的算法和逻辑也非常的复杂,大家可以通过学习操作系统相关的课程去了解,这里就不再展开了。 可以看出高速缓存的作用解决了不同速度设备之间的数据传递问题。在实际中CPU内部可能不止设有一级高速缓存, 有可能会配备两级到三级的高速缓存,越高级的高速缓存速度越快,容量越低,而越低级的高度缓存则速度越慢, 但是容量越大。比如iPhoneX上的搭载的arm处理器A11里面除了固有的37个通用寄存器外,L1级缓存的容量是64KB, L2级缓存的容量达到了8M(这么大的二级缓存,都有可能在你的程序代码少时可以一次性将代码读到缓存中去运行),没有配备三级缓存。

存储的层次结构

我们知道在软件设计上有一个所谓的空间换时间的概念,就是当两个对象之间进行交互时因为二者处理速度并不一致时, 我们就需要引入缓存来解决读写不一致的问题。比如文件读写或者socket通信时,因为IO设备的处理速度很慢, 所以在进行文件读写以及socket通信时总是要将读出或者写入的部分数据先保存到一个缓存中,然后再统一的执行读出和写入操作。

可以看出无论是在硬件层面上还是在软件层面上,当两个组件之间因为速度问题不能进行同步交互时, 就可以借助缓存技术来弥补这种不平衡的状况

指令中的寄存器

CPU执行的每条指令都由操作码和操作数组成,简单理解就是要对谁(操作数)做什么(操作码)。 在CPU内部要运算的数据总是放在寄存器中,而实际的数据则有可能是放在内存或者是IO端口中。 因此我们的程序其实大部分时间就是做了如下三件事情:

把内存或者I/O端口的数据读取到寄存器中

将寄存器中的数据进行运算(运算只能在寄存器中进行)

将寄存器的内容回写到内存或者I/O端口中

这三件事情都是跟寄存器有关,寄存器就是数据存储的中转站,非常的关键,因此在CPU所提供的指令中, 如果操作数有两个时至少要有一个是寄存器。

;下面部分是arm64指令示例:
mov x0, #0x100     ;将常数0x100赋值给寄存器x0
mov x1, x0     ;将寄存器x0的值赋值给寄存器x1
ldr x3, [sp, #0x8]     ;将栈顶加0x8处的内存值赋值给x3寄存器
add x0, x1, x2     ;x0 = x1 + x2 可以看出运算的指令必须放在寄存器中
sub x0, x1, x2     ;x0 = x1 - x2
str x1, [sp, #0x08]     ;将寄存器x1中的值保存到栈顶加0x8处的内存处。

;下面部分是x64指令示例(AT&T汇编):
mov $0x100, %rax     ;将常数0x100赋值给寄存器rax
mov %rax, %rbx     ;将寄存器rax的值赋值给rbx寄存器
movq 8(%rax), %rbx     ;将寄存器rax中的值+8并将所指向内存中的数据赋值给rbx寄存器

所以不要将机器语言或者汇编语言当成是很复杂或者难以理解的语言,如果你仔细观察一段汇编语言代码时, 你就会发现几乎大部分代码都是做的上面的三件事情。我们在高级语言里面看到的只是变量, 但是在低级语言里面看到的就是内存地址和寄存器,你可以将内存地址和寄存器也理解为定义的变量, 带着这样的思路去阅读汇编代码时你就会发现其实汇编语言也不是那么的困难。 在高级语言中我们可以根据自身的需要定义出很多有特殊意义的变量,但是低级语言中因为寄存器就那么几个, 它必须要被复用和重复使用,因此汇编语言中就会出现大量的将寄存器的内容保存到内存中的指令代码, 以及从内存中读取到寄存器中的指令代码。

正文

寄存器是 CPU 内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果以及一些 CPU 运行需要的信息。

一 通用寄存器

最常用的,也是最基础的有8个通用寄存器(注意一般看到的EAX、ECX也是指的这类寄存器在32位CPU上的拓展, 另外AL、AH之类是指的这类寄存器的低位、高位):

寄存器    原文    解释    说明
AX    accumulator    累加寄存器    通常用来执行加法,函数调用的返回值一般也放在这里面
CX    counter        计数寄存器    通常用来作为计数器,比如for循环
DX    data        数据寄存器    数据存取
BX    base        基址寄存器    读写I/O端口时,ebx用来存放端口号
SP    stack pointer    栈指针寄存器    栈顶指针,指向栈的顶部
BP    base pointer    基址指针寄存器    栈底指针,指向栈的底部,通常用ebp+偏移量的形式来定位函数存放在栈中的局部变量
SI    source index    源变址寄存器    字符串操作时,用于存放数据源的地址
DI    destination index    目标变址寄存器    字符串操作时,用于存放目的地址的,和esi两个经常搭配一起使用,执行字符串的复制等操作

二 标志寄存器

标志寄存器,里面有众多标记位,记录了 CPU 执行指令过程中的一系列状态,这些标志大都由 CPU 自动设置和修改:

寄存器    解释
CF    进位标志
PF    奇偶标志
ZF    零标志
SF    符号标志
OF    补码溢出标志
TF    跟踪标志
IF    中断标志

三 指令寄存器

eip: 指令寄存器可以说是CPU中最最重要的寄存器了,它指向了下一条要执行的指令所存放的地址, CPU的工作其实就是不断取出它指向的指令,然后执行这条指令,同时指令寄存器继续指向下面一条指令, 如此不断重复,这就是CPU工作的基本日常。

在 x64 架构下,32位的 eip 升级为64位的 rip寄存器。

四 段寄存器

通过引入分段的概念,将内存空间划分为不同的区域,并通过段基址+段内偏移来寻址。

段寄存器就是用来保存段基地址的。

寄存器    原文    解释
ES    extra segment    附加段寄存器
CS    code segment    代码段寄存器
SS    stack segment    栈段寄存器
DS    data segment    数据段寄存器
FS    segment part 2    无名寄存器
GS    segment part 3    无名寄存器

通用寄存器、段寄存器、标志寄存器、指令寄存器,这四组寄存器共同构成了一个基本的指令执行环境, 一个线程的上下文也基本上就是这些寄存器,在执行线程切换的时候,就是修改它们的内容。

五 控制寄存器

控制寄存器CR0,CR1(保留),CR2,CR3和CR4(64位新增CR8)决定处理器的工作模式和当前执行的任务的特性。

寄存器    解释
CR0    存储了CPU控制标记和工作状态,(最重要)
CR1    保留未使用
CR2    页错误出现时保存导致出错的地址
CR3    存储了当前进程的虚拟地址空间的重要信息——页目录地址
CR4    也存储了CPU工作相关以及当前人任务的一些信息
CR8    64位新增扩展使用

这边重点关注一下CR0寄存器:

其中:

【1】PG(Paging (bit 31 of CR0)) 设置时启用分页;清除时禁用分页.当分页被禁用时,所有的线性地址都被当作物理地址处理. 如果PE标志(寄存器CR0的第0位)没有被设置,PG标志就没有作用;当PE标志被清除时,设置PG标志会导致一个一般保护异常(#GP).

【2】CD(Cache Disable (bit 30 of CR0)) 当CD和NW标志清零时,处理器内部(和外部)缓存中的整个物理内存的内存位置的缓存被启用. 当CD标志被设置时,缓存被限制.为了防止处理器访问和更新其缓存,CD标志必须被设置,并且缓存必须被无效化,这样就不会发生缓存命中.

【3】NW(Not Write-through (bit 29 of CR0)) 当NW和CD标志被清零时, 回写(对于奔腾4,英特尔至强,P6系列和奔腾处理器)或直写(对于Intel486处理器)被启用以用于命中缓存和失效周期的写入.

【4】AM(Alignment Mask (bit 18 of CR0)) 设置时启用自动对齐检查;清除时禁用对齐检查. 只有当AM标志被设置,EFLAGS寄存器中的AC标志被设置,CPL为3,并且处理器工作在受保护或虚拟8086模式下时, 才会进行对齐检查.(32位下必须是4字节对齐 / 64位下必须是8字节对齐)

【5】WP(Write Protect (bit 16 of CR0)) 当设置时,禁止超级用户程序(例如特权级0的程序)写入只读页; 当清除时,允许超级用户程序(例如特权级0的程序)写入只读页(无论U/S位设置如何). 这个标志有利于实现UNIX等操作系统所使用的创建新进程(forking)的写时拷贝法. 这个标志必须在软件设置CR4.CET之前设置,只要CR4.CET=1,就不能清除它.

【6】TS(Task Switched (bit 3 of CR0)) 任务切换标志位

【7】PE(Protection Enable (bit 0 of CR0)) 设置时启用保护模式;清除时启用实地址模式. 这个标志并不直接启用分页.它只启用段级保护.要启用分页,必须同时设置PE和PG标志.

PG = 0 且 PE = 0 处理器工作在实地址模式下;

PG = 0 且 PE = 1 处理器工作在没有开启分页机制的保护模式下;

PG = 1 且 PE = 0 在PE没有开启的情况下 无法开启PG;

PG = 1 且 PE = 1 处理器工作在开启了分页机制的保护模式下;

另外,其他的控制寄存器的具体说明可以参考帖子:控制寄存器说明

六 调试寄存器

在x86/x64 CPU 内部,还有一组用于支持软件调试的寄存器。

寄存器    解释
DR0-DR3    这是四个用于存储地址的寄存器。
DR4-DR5    这两个有点特殊,受前面提到的CR4寄存器中的标志位DE位控制,如果CR4的DE位是1,则DR4、DR5是不可访问的,访问将触发异常。如果CR4的DE位是0,则DR4和DR5将会变成DR6和DR7的别名,相当于做了一个软链接。这样做是为了将DR4、DR5保留,以便将来扩展调试功能时使用。
DR6    这个寄存器中存储了硬件断点触发后的一些状态信息。
DR7    调试控制寄存器,这里面记录了对DR0-DR3这四个寄存器中存储地址的中断方式(是对地址的读,还是写,还是执行)、数据长度(1/2/4个字节)以及作用范围等信息。

七 描述符寄存器

所谓描述符,其实就是一个数据结构,用来记录一些信息,“描述”’一个东西。把很多个描述符排列在一起,组成一个表,就成了描述符表。

描述符寄存器指向描述符表。在x86/x64系列CPU中,有三个非常重要的描述符寄存器,它们分别存储了三个地址,指向了三个非常重要的描述符表。

寄存器    解释    指向
GDTR    全局描述符表寄存器    全局分段描述符表GDT
LDTR    局部描述符表寄存器    局部分段描述符表LDT
IDTR    中断描述符表寄存器    中断描述符表IDT

GDTR/LDTR:指向GDT和LDT中的表项,就是段描述符,描述了一个内存分段的信息,其结构如下:

IDTR:中断描述符表寄存器,指向了中断描述符表IDT,这个表的每一项都是一个中断处理描述符, 当CPU执行过程中发生了硬中断、异常、软中断时,将自动从这个表中定位对应的表项, 里面记录了发生中断、异常时该去哪里执行处理函数。

八 任务寄存器

任务寄存器TR,指向当前运行的任务,这是在硬件层面上,对多任务切换的支持。

九 MSR寄存器

从80486之后的x86架构 CPU,内部增加了一组新的寄存器,统称为 MSR 寄存器,中文直译是模型特定寄存器, 意思是这些寄存器不像上面列出的寄存器是固定的,这些寄存器可能随着不同的版本有所变化。 这些寄存器主要用来支持一些新的功能。

总结,以上就是全部要介绍的寄存器了,但这并不是 x86 CPU 全部所有的寄存器, 除了这些,还存在 XMM、MMX、FPU 浮点数运算等其他寄存器,这边也不再赘述了。






参考资料

CPU寄存器详解 https://blog.csdn.net/sinat_33408502/article/details/124177734

控制寄存器 https://blog.csdn.net/m0_46125480/article/details/120556447

一文让你彻底了解操作系统内存管理(思维导图详解) https://zhuanlan.zhihu.com/p/421563842

CPU寄存器详解 https://www.elecfans.com/emb/20180213635724.html


返回