作者 | 轩辕之风O

来源 | 编程技术宇宙(ID:xuanyuancoding)

头图 |  CSDN 下载自东方IC

序言

前段时间,我连续写了十来篇CPU底层系列技术故事文章,有不少读者私信我让我写一下 CPU 的寄存器。

寄存器这个太多太复杂,不适合写故事,拖了很久,总算是写完了,这篇文章就来详细聊聊x86/x64架构的 CPU 中那些纷繁复杂的寄存器们。

长文预警,时速较快,请系好安全带~起飞~

自1946年冯·诺伊曼领导下诞生的世界上第一台通用电子计算机 ENIAC 至今,计算机技术已经发展了七十多载。

从当初专用于数学计算的庞然大物,到后来大型机服务器时代,从个人微机技术蓬勃发展,到互联网浪潮席卷全球,再到移动互联网、云计算日新月异的当下,计算机变的形态各异,无处不在。

这七十多年中,出现了数不清的编程语言,通过这些编程语言,又开发了无数的应用程序。

可无论什么样的应用程序,什么样的编程语言,最终的程序逻辑都是要交付给 CPU 去执行实现的(当然这里有些不严谨,除了 CPU,还有协处理器、GPU 等等)。所以了解和学习 CPU 的原理都是对计算机基础知识的夯实大有裨益。

在七十多年的漫长历程中,也涌现了不少架构的 CPU。

  • MIPS

  • PowerPC

  • x86/x64

  • IA64

  • ARM

  • ······

这篇文章就以市场应用最为广泛的x86-x64架构为目标,通过学习了解它内部的100个寄存器功能作用,来串联阐述CPU底层工作原理。

通过这篇文章,你将了解到:

  • CPU指令执行原理

  • 内存寻址技术

  • 软件调试技术原理

  • 中断与异常处理

  • 系统调用

  • CPU多任务技术

什么是寄存器?

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

x86 架构 CPU 走的是复杂指令集(CISC)路线,提供了丰富的指令来实现强大的功能,与此同时也提供了大量寄存器来辅助功能实现。这篇文章将覆盖下面这些寄存器:

  • 通用寄存器

  • 标志寄存器

  • 指令寄存器

  • 段寄存器

  • 控制寄存器

  • 调试寄存器

  • 描述符寄存器

  • 任务寄存器

  • MSR寄存器

通用寄存器

首当其冲的是通用寄存器,这些的寄存器是程序执行代码最最常用,也最最基础的寄存器,程序执行过程中,绝大部分时间都是在操作这些寄存器来实现指令功能。

所谓通用,即这些寄存器 CPU 没有特殊的用途,交给应用程序“随意”使用。注意,这个随意,我打了引号,对于有些寄存器,CPU 有一些潜规则,用的时候要注意。

  • eax: 通常用来执行加法,函数调用的返回值一般也放在这里面

  • ebx: 数据存取

  • ecx: 通常用来作为计数器,比如for循环

  • edx: 读写I/O端口时,edx用来存放端口号

  • esp: 栈顶指针,指向栈的顶部

  • ebp: 栈底指针,指向栈的底部,通常用ebp+偏移量的形式来定位函数存放在栈中的局部变量

  • esi: 字符串操作时,用于存放数据源的地址

  • edi: 字符串操作时,用于存放目的地址的,和esi两个经常搭配一起使用,执行字符串的复制等操作

在 x64架构中,上面的通用寄存器都扩展成为64位版本,名字也进行了升级。当然,为了兼容32位模式程序,使用上面的名字仍然是可以访问的,相当于访问64位寄存器的低32位。

rax rbx rcx rdx rsp rbp rsi rdi

除了扩展原来存在的通用寄存器,x64架构还引入了8个新的通用寄存器:

r8-r15

在原来32位时代,函数调用时,那个时候通用寄存器少,参数绝大多数时候是通过线程的栈来进行传递(当然也有使用寄存器传递的,比如著名的C++ this指针使用 ecx 寄存器传递,不过能用的寄存器毕竟不多)。

进入x64时代,寄存器资源富裕了,参数传递绝大多数都是用寄存器来传了。寄存器传参的好处是速度快,减少了对内存的读写次数。

当然,具体使用栈还是用寄存器传参数,这个不是编程语言决定的,而是编译器在编译生成 CPU 指令时决定的,如果编译器非要在 x64 架构 CPU 上使用线程栈来传参那也不是不行,这个对高级语言是无感知的。

标志寄存器

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

  • CF 进位标志

  • PF 奇偶标志

  • ZF 零标志

  • SF 符号标志

  • OF 补码溢出标志

  • TF 跟踪标志

  • IF 中断标志

  • ······

在 x64 架构下,原来的 eflags 寄存器升级为64位的 rflags,不过其高32位并没有新增什么功能,保留为将来使用。

指令寄存器

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

而在漏洞攻击中,黑客想尽办法费尽心机都想要修改指令寄存器的地址,从而能够执行恶意代码。

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

段寄存器

段寄存器与 CPU 的内存寻址技术紧密相关。

早在16位的8086 CPU 时代,内存资源宝贵,CPU 使用分段式内存寻址技术:

16位的寄存器能寻址的范围是64KB,通过引入段的概念,将内存空间划分为不同的区域:分段,通过段基址+段内偏移段方式来寻址。

这样一来,段的基地址保存在哪里呢?8086 CPU 专门设置了几个段寄存器用来保存段的基地址,这就是段寄存器段的由来。

段寄存器也是16位的。

段寄存器有下面6个,前面4个是早期16位模式就引入了,到了32位时代,又新增了 fs 和 gs 两个段寄存器。

  • cs: 代码段

  • ds: 数据段

  • ss: 栈段

  • es: 扩展段

  • fs: 数据段

  • gs: 数据段

段寄存器里面存储的内容与 CPU 当前工作的内存寻址模式紧密相关。

当CPU处于16位实地址模式下时,段寄存器存储段的基地址,寻址时,将段寄存器内容左移4位(乘以16)得到段基地址+段内偏移得到最终的地址。

当 CPU 工作于保护模式下,段寄存器存储的内容不再是段基址了,此时的段寄存器中存放的是段选择子,用来指示当前这个段寄存器“指向”的是哪个分段。

注意我这里的指向打了引号,段寄存器中存储的并不是内存段的直接地址,而是段选择子,它的结构如下:

16个 bit 长度的段寄存器内容划分了三个字段:

  • PRL: 特权请求级,就是我们常说的ring0-ring3四个特权级。

  • TI: 0表示用的是全局描述符表GDT,1表示使用的是局部描述符表LDT。

  • Index: 这是一个表格中表项的索引值,这个表格叫内存描述符表,它的每一个表项都描述了一个内存分段。

这里提到了两个表,全局描述符表 GDT 和局部描述符表 LDT,关于这两个表的介绍,下面介绍描述符寄存器时再详述,这里只需要知道,这是 CPU 支持分段式内存管理需要的表格,放在内存中,表格中的每一项都是一个描述符,记录了一个内存分段的信息。

保护模式下的段寄存器和段描述符到最后的内存分段,通过下图的方式联系在一起:

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

控制寄存器

控制寄存器是 CPU 中一组相当重要的寄存器,我们知道 eflags 寄存器记录了当前运行线程的一系列关键信息。

那 CPU 运行过程中自身的一些关键信息保存在哪里呢?答案是控制寄存器!

32位 CPU 总共有cr0-cr4共5个控制寄存器,64位增加了 cr8。他们各自有不同的功能,但都存储了 CPU 工作时的重要信息:

  • cr0: 存储了CPU控制标记和工作状态

  • cr1: 保留未使用

  • cr2: 页错误出现时保存导致出错的地址

  • cr3: 存储了当前进程的虚拟地址空间的重要信息——页目录地址

  • cr4: 也存储了CPU工作相关以及当前人任务的一些信息

  • cr8: 64位新增扩展使用

其中,CR0 尤其重要,它包含了太多重要的 CPU 信息,值得单独关注一下:

一些重要的标记位含义如下:

PG: 是否启用内存分页

AM: 是否启用内存对齐自动检查

WP: 是否开启内存写保护,若开启,对只读页面尝试写入时将触发异常,这一机制常常被用来实现写时复制功能

PE: 是否开启保护模式

除了 CR0,另一个值得关注的寄存器是 CR3,它保存了当前进程所使用的虚拟地址空间的页目录地址,可以说是整个虚拟地址翻译中的顶级指挥棒,在进程空间切换的时候,CR3 也将同步切换。

调试寄存器

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

调试,对于我们程序员是家常便饭,必备技能。但你想过你的程序能够被调试背后的原理吗?

程序能够被调试,关键在于能够被中断执行和恢复执行,被中断的地方就是我们设置的断点。那程序是如何能在遇到断点的时候停下来呢?

对于一些解释执行(PHP、Python、JavaScript)或虚拟机执行(Java)的高级语言,这很容易办到,因为它们的执行都在解释器/虚拟机的掌控之中。

而对于像C、C++这样的“底层”编程语言,程序代码是直接编译成CPU的机器指令来执行的,这就需要 CPU 来提供对于调试的支持了。

对于通常的断点,也就是程序执行到某个位置下就停下来,这种断点实现的方式,在x86/x64上,是利用了一条软中断指令:int 3来进行实现的。

注意,这里的 int 不是指高级语言里面的整数,而是表示 interrupt 中断的意思,是一条汇编指令,int 3则表示中断向量号为3的中断。

在我们使用调试器下断点时,调试器将会把对应位置的原来的指令替换为一个 int 3 指令,机器码为 0xCC。这个动作对我们是透明的,我们在调试器中看到的依然是原来的指令,但实际上内存中已经不是原来的指令了。

顺便提一句,两个 0xCC 是汉字【烫】的编码,在一些编译器里,会给线程的栈中填充大量的 0xCC,如果程序出错的时候,我们经常会看到很多烫烫烫出现,就是这个原因。

言归正传,CPU在执行这条int 3指令时,将自动触发中断处理流程(虽然这实际上不是一个真正的中断),CPU将取出IDTR寄存器指向的中断描述符表IDT的第3项,执行里面的中断处理函数。

而这个中断描述符表,早在操作系统启动之初,就已经提前安排好了,所以执行这条指令后,操作系统的中断处理函数将介入,来处理这一事件。

后面的过程就多了,简单来说,操作系统会把触发这一事件的进程冻结起来,随后将这一事件发送到调试器,调试器拿到之后就知道目标进程触发断点了。这个时候,咱们程序员就能通过调试器的UI交互界面或者命令行调试接口来调试目标进程,查看堆栈、查看内存、变量都随你。

如果我们要继续运行,调试器将会把之前修改的int 3指令给恢复回去,然后告知操作系统:我处理完了,把目标进程解冻吧!

上面简单描述了一下普通断点的实现原理。现在思考一个场景:我们发现一个bug,某个全局整数型变量的值老是莫名其妙被修改,但你发现有很多线程,很多函数都有可能会去修改这个变量,你想找出到底谁干的,怎么办?

这个时候上面的普通断点就没办法了,你需要一种新的断点:硬件断点

这时候就该本小节的主人公调试寄存器登场表演了。

在x86架构CPU内部,提供了8个调试寄存器DR0~DR7。

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个字节)以及作用范围等信息

通过调试器的接口设置硬件断点后,CPU在执行代码的过程中,如果满足条件,将自动中断下来。

回答前面提出的问题,想要找出是谁偷偷修改了全局整形变量,只需要通过调试器设置一个硬件写入断点即可。

描述符寄存器

所谓描述符,其实就是一个数据结构,用来记录一些信息,‘描述’一个东西。把很多个描述符排列在一起,组成一个表,就成了描述符表。再使用一个寄存器来指向这个表,这个寄存器就是描述符寄存器

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

gdtr: 全局描述符表寄存器,前面提到,CPU现在使用的是段+分页结合的内存管理方式,那系统总共有那些分段呢?这就存储在一个叫全局描述符表(GDT)的表格中,并用gdtr寄存器指向这个表。这个表中的每一项都描述了一个内存段的信息。

ldtr: 局部描述符表寄存器,这个寄存器和上面的gdtr一样,同样指向的是一个段描述符表(LDT)。不同的是,GDT是全局唯一,LDT是局部使用的,可以创建多个,随着任务段切换而切换(下文介绍任务寄存器会提到)。

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

一个表项占据8个字节(32位CPU),里面存储了一个内存分段的诸多信息:基地址、大小、权限、类型等信息。

除了这两个段描述符寄存器,还有一个非常重要的描述符寄存器:

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

IDT中的表项称为Gate,中文意思为,因为这是应用程序进入内核的主要入口。虽然表的名字叫中断描述符表,但表中存储的不全是中断描述符,IDT中的表项存在三种类型,对应三种类型的门:

  • 任务门

  • 陷阱门

  • 中断门

三种描述符中都存储了处理这个中断/异常/任务时该去哪里处理的地址。三种门用途不一,其中中断门是真正意义上的中断,而像前面提到的调试指令int 3以及老式的系统调用指令int 2e/int 80都属于陷阱门。任务门则用的较少,要了解任务门,先了解下任务寄存器。

任务寄存器

现代操作系统,都是支持多任务并发运行的,x86架构CPU为了顺应时代潮流,在硬件层面上提供了专门的机制用来支持多任务的切换,这体现在两个方面:

  • CPU 内部设置了一个专用的寄存器——任务寄存器 TR,它指向当前运行的任务。

  • 定义了描述任务的数据结构 TSS,里面存储了一个任务的上下文(一系列寄存器的值),下图是一个32位 CPU 的 TSS 结构图:

x86CPU 的构想是每一个任务对应一个 TSS,然后由 TR 寄存器指向当前的任务,执行任务切换时,修改 TR 寄存器的指向即可,这是硬件层面的多任务切换机制。

这个构想其实还是很不错的,然而现实却打了脸,包括 Linux 和 Windows 在内的主流操作系统都没有使用这个机制来进行线程切换,而是自己使用软件来实现多线程切换。

所以,绝大多数情况下,TR 寄存器都是指向固定的,即便线程切换了,TR 寄存器仍然不会变化。

注意,我这里说的的是绝大多数情况,而没有说死。虽然操作系统不依靠 TSS 来实现多任务切换,但这并不意味着 CPU 提供的 TSS 操作系统一点也没有使用。还是存在一些特殊情况,如一些异常处理会使用到 TSS 来执行处理。

下面这张图,展示了控制寄存器、描述符寄存器、任务寄存器构成的全貌:

模型特定寄存器

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

随着x86 CPU 不断更新换代,MSR 寄存器变的越来越多,但与此同时,有一部分 MSR 寄存器随着版本迭代,慢慢固化下来,成为了变化中那部分不变的,这部分 MSR 寄存器,Intel 将其称为 Architected MSR,这部分 MSR 寄存器,在命名上,统一加上了 IA32 的前缀。

这里选取三个代表性的 MSR 简单介绍一下:

  • IA32_SYSENTER_CS

  • IA32_SYSENTER_ESP

  • IA32_SYSENTER_EIP

这三个MSR寄存器是用来实现快速系统调用

在早期的x86架构 CPU 上,系统调用依赖于软中断实现,类似于前面调试用到的int 3指令,在 Windows 上,系统调用用到的是 int 2e,在 Linux 上,用的是 int 80

软中断毕竟还是比较慢的,因为执行软中断就需要内存查表,通过 IDTR 定位到 IDT,再取出函数进行执行。

系统调用是一个频繁触发的动作,如此这般势必对性能有所影响。在进入奔腾时代后,就加上了上面的三个 MSR 寄存器,分别存储了执行系统调用后,内核系统调用入口函数所需要的段寄存器、堆栈栈顶、函数地址,不再需要内存查表。快速系统调用还提供了专门的 CPU 指令 sysenter/sysexit 用来发起系统调用和退出系统调用。

在64位上,这一对指令升级为 syscall/sysret

总结

以上就是全部要介绍的寄存器了,需要说明一下的是,这并不是 x86 CPU 全部所有的寄存器,除了这些,还存在 XMM、MMX、FPU 浮点数运算等其他寄存器。

这篇文章以 x86/x64 架构 CPU 为目标,通过对 CPU 内部寄存器的阐述,串讲了 CPU 执行代码机制、内存寻址技术、中断与异常处理、多任务管理、系统调用、调试原理等多种计算机底层知识。

更多精彩推荐
☞@程序员,什么才是“2020-1024”的正确打开姿势?
☞“复制粘贴”发明人,竟是物理学博士转行做程序媛!
☞机器人也开始"怕疼"了?科学家开发无需人工干预即可"自愈"的机器人
☞最新!百度首发 OCR 自训练平台 EasyDL OCR
☞SQL分页查询方案的性能对比
☞2021年,很可能是以太坊的“高光之年”
点分享点点赞点在看

一口气看完 45 个寄存器,CPU 核心技术大揭秘相关推荐

  1. 一口气看完45个寄存器,CPU核心技术大揭秘

    作者 | 轩辕之风O 来源 | 编程技术宇宙 头图 | CSDN下载自视觉中国 自1946年冯·诺伊曼领导下诞生的世界上第一台通用电子计算机ENIAC至今,计算机技术已经发展了七十多载. 从当初专用于 ...

  2. 一口气看完MySQL--上篇

    MySql MySql中的数据类型 注释 数据库操作 表的操作 给表关联约束 主键约束 primary key 创建 删除主键约束 联合主键 创建 删除联合主键 唯一约束 unique 创建 删除唯一 ...

  3. B站“一口气看完系列”也叫带货系列?

    "xx分钟一口气看完!" B站影视区已经流行两年的"一口气看完系列"在今年有了新变化,在视频下方大多数都出现了"UP主推荐广告"的跳转入口. ...

  4. 你知道狂飙到底讲的个什么,看完狂飙的后劲有多大

    你知道狂飙到底讲的个什么,#看完狂飙的后劲有多大,#狂飙 ,#疯驴子! 狂飙看爽了,问你一个问题,这部剧到底在讲什么?答不上来,说明你没看懂啊.是讲安心高启强,还是江湖恩怨.政治斗争?都不是.别想遣了 ...

  5. 大模型时代下智能文档处理核心技术大揭秘

    ​强烈推荐一个大神的人工智能的教程:http://www.captainai.net/zhanghan 大模型时代下智能文档处理核心技术大揭秘 前言 一张图全览 文档图像分析与预处理 图像预处理的整体 ...

  6. 手机CPU知识大揭秘

    CPU是CentralProcessingUnit的英文缩写,一般由逻辑运算单元.控制单元和存储单元组成. 手机CPU简单来说就是中央处理器,所谓中央处理器,就是它的核心部分(属于逻辑部分),手机开机 ...

  7. 一口气看完——PythonGUI界面编程入门到实战项目

    1.概念 GUI是图形用户界面的缩写.用户不仅可以输入文返回文字,用户还可以看到窗口.按钮.文字框等图形,还可以用鼠标点击,也可以用键盘输入.到目前为止,我们的程序都是命令行或文字模式程序.GUI是一 ...

  8. Redis6通信协议升级至RESP3,一口气看完13种新数据类型

    我们已经知道RESP V2版本协议的规范,RESP的全程是Redis Serialization Protocol,基于这个实现简单且解析性能优秀的通信协议,Redis的服务端与客户端可以通过底层命令 ...

  9. 一口气看完大唐安史之乱

    开元盛世,安史之乱. 历史上,可能再没有那个帝王,像唐玄宗一样励精图治的将一个王朝带入封建王朝的顶峰,为中华民族缔造了一个昂扬浩大.如真似幻的盛世.又将帝国亲手至于战乱,自此黄粱梦醒,大厦将倾,如日中 ...

最新文章

  1. FPGA笔试题解析(三)
  2. sql server 2005 T-SQL @@LANGUAGE (Transact-SQL)
  3. 4服务器内存和普通内存_关于服务器内存的选择
  4. TypeScript 书写 .d.ts 文件的一些注意事项
  5. quartz 任务调试 建表 sql 语句、create table语句
  6. android滑屏设计规范,安卓设计如何实现滑动屏幕出现如图的效果
  7. Amoeba实现读写分离
  8. 【英语学习】【English L06】U03 House L6 Sharing an apartment
  9. sql server数据表转换成xml
  10. 高级教师和一级教师最大的差异是什么?
  11. chromium thirt_party skia编译shared_liberary
  12. Unity真机调试工具:LogViewer在手机上查看Unity3D的Console Log
  13. Python爬虫——爬取知网论文数据(一)
  14. excel锁定计算机,excel表格根据时间锁定如何操作
  15. Appium连接夜神模拟器
  16. 最新!7月份火爆Github的热门Python项目
  17. 多级表头 el-table-column的使用
  18. 更新提示!Chrome新的零日漏洞正被利用
  19. android源码编译1
  20. 2022年9月改名字这件事的流程亲历和体会

热门文章

  1. 北京大学信息科学技术学院招收2022级博士生
  2. 直博清华的小姐姐!本科就发表了SCI,享受朝九晚五的学习生活,做自己的小太阳!...
  3. 哈佛、MIT等顶级名校全套CS课程资源!
  4. SAP WMSD集成之Copy WM Quantity – Not Copy WM qty as delivery qty into delivery But PGI
  5. SAP WM高阶之下架策略M(Small Large Quantity)
  6. 放心,GPT-3 不会“杀死”编程
  7. SAP PM 入门系列18 - IP25为维护计划 Set Deletion Flag
  8. 强弱AI的辩论:关于人工智能意识的奇妙理论
  9. 无人驾驶还有多久才能全面推开?
  10. AI技术诠释全新智能,多方位优化样样贴心