1.什么是寄存器

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

x86架构CPU走的是复杂指令集(CISC) 路线,提供了丰富的指令来实现强大的功能,与此同时也提供了大量寄存器来辅助功能实现。寄存器分为两类,一类对程序员不可见,这一类寄存器用于支撑CPU内部运行,程序员无法操作。一类对程序员可见,在进行汇编编写程序时,能够直接操作。

  • 通用寄存器:EAX、EBX、ECX、EDX、ESI、EDI、ESP、EBP
  • 标志寄存器:EFLAGS
  • 指令寄存器:EIP
  • 段寄存器:CS、DS、ES、FS、GS、SS
  • 控制寄存器:CR0、CR1、CR2、CR3、CR4
  • 调试寄存器:DR0、DR1、DR2、DR3、DR4、DR5、DR6、DR7
  • 描述符寄存器:GDTR、IDTR、LDTR、TR

2.实模式下寄存器(16bit)

在x86架构下,实模式可以使用的通用寄存器有 AX、BX、CX、DX、SI、DI、BP 和 SP。这些寄存器都是16位的,可以分为两个8位的寄存器来使用。

此外,还有一些特殊用途的寄存器,包括:

  • IP(指令指针寄存器):保存当前执行的指令地址。
  • CS(代码段寄存器):保存代码段的起始地址。
  • DS(数据段寄存器):保存数据段的起始地址。
  • ES(附加段寄存器):附加数据段的起始地址。
  • GS(附加段寄存器):附加数据段的起始地址。
  • FS(附加段寄存器):附加数据段的起始地址。
  • SS(堆栈段寄存器):保存堆栈段的起始地址。
  • FLAGS(标志寄存器):包含各种标志位,如零标志、进位标志、符号标志等

image-20230611122704382

其中FS,GS附加段寄存器是在32位CPU中增加的,但是在32位CPU中在实模式下同样可以使用,因为32位CPU兼容16位CPU的特性。

16位寄存器 功能 高8位 低8位
AX 累加寄存器,常用于算术运算,保存与外设输入输出的数据 AH AL
CX 计数寄存器,常用于循环指令中的循环次数 CH CL
DX 数据寄存器,通常情况下只用于保存外设控制器的端口号地址 DH DL
BX 基址寄存器,来存储内存地址,段基址为DS BH BL
SP 栈指针寄存器,段基址为SS,用来指向栈顶
BP 栈帧的基址寄存器,段基址为SS
SI 源变址寄存器,存储数据源地址,段基址为DS
DI 目的变址寄存器,存储数据目的地址,段基址为DS

BP指向栈底,SP指向栈顶,两者共同维护了栈空间。push 和 pop 可更改SP的值,sp指针的值会自动更新

2.1 寄存器用法举例

  • SI、DI

        mov ecx, 10  ; 设置循环计数为10
    mov esi, 0 ; 设置SI寄存器为0作为初始值
    mov edi, 100 ; 设置DI寄存器为100作为初始值

    loop_start:
    mov eax, [esi] ; 从源地址(SI)读取数据到EAX寄存器
    mov [edi], eax ; 将数据存储到目的地址(DI)
    add esi, 4 ; 增加SI的值,以便读取下一个双字
    add edi, 4 ; 增加DI的值,以便存储到下一个地址
    loop loop_start ; 循环,减少ECX计数,直到为零

    在这个例子中,SI和DI寄存器用作源地址和目的地址。循环从源地址读取数据,然后将其存储到目的地址,然后递增SI和DI以访问下一个元素。通过loop指令和ECX计数器,循环执行直到计数为零。

  • BP、SP

    push ebp         ; 保存当前函数的旧的基址到堆栈中
    mov ebp, esp ; 将当前堆栈指针存储到基址指针寄存器BP中

    sub esp, 16 ; 分配16字节的局部变量空间

    mov dword ptr [ebp-4], 10 ; 将值10存储到基址指针寄存器BP-4指向的位置(局部变量)

    mov eax, dword ptr [ebp-4] ; 从基址指针寄存器BP-4指向的位置读取值到EAX寄存器

    add esp, 16 ; 释放局部变量空间

    pop ebp ; 恢复旧的基址到基址指针寄存器BP中

3.保护模式下寄存器(32bit)

3.1 保护模式寄存器介绍

在32位保护模式下,x86架构提供了更多的通用寄存器以及扩展功能。以下是32位保护模式下可以使用的寄存器:

  1. 通用寄存器(General Purpose Registers):
    • EAX:累加器寄存器(Accumulator Register)。
    • EBX:基址寄存器(Base Register)。
    • ECX:计数寄存器(Counter Register)。
    • EDX:数据寄存器(Data Register)。
    • ESI:源索引寄存器(Source Index Register)。
    • EDI:目的索引寄存器(Destination Index Register)。
    • EBP:基址指针寄存器(Base Pointer Register)。
    • ESP:堆栈指针寄存器(Stack Pointer Register)。
  2. 扩展通用寄存器:
    • EIP:指令指针寄存器(Instruction Pointer Register)。
    • EFLAGS:标志寄存器(Flags Register),用于存储各种标志位,如零标志、进位标志、符号标志等。
  3. 段寄存器(Segment Registers):
    • CS:代码段寄存器(Code Segment Register)。
    • DS:数据段寄存器(Data Segment Register)。
    • ES:附加段寄存器(Extra Segment Register)。
    • FS、GS、SS:附加段寄存器,用于访问额外的数据段。
  4. 控制寄存器(Control Registers):
    • CR0、CR2、CR3、CR4:用于控制和管理保护模式的特性,如分页机制、特权级等。
  5. 段描述符寄存器(Descriptor Registers):
    • GDTR:全局描述符表寄存器(Global Descriptor Table Register)。
    • IDTR:中断描述符表寄存器(Interrupt Descriptor Table Register)。
    • LDTR:局部描述符表寄存器(Local Descriptor Table Register)。
    • TR:任务寄存器(Task Register)。
image-20230128173803324

3.2 控制寄存器

3.2.1 CR0寄存器

image-20230609214128132

  • PE: Protection Enble

    当此位为0,代表在CPU处在实模式,此位为1,表示CPU处在保护模式;从实模式切换到保护模式时需要将此位置为1.

    #进入保护模式
    mov eax, cr0
    or eax, 0x00000001
    mov cr0, eax
  • TS:Task Switched

  • WP:Write Protect

    对于Intel 80486或以上的CPU,CR0的位16是写保护(Write Proctect)标志。当设置该标志时,处理器会禁止超级用户程序(例如特权级0的程序)向用户级只读页面执行写操作;当该位复位时则反之。该标志有利于UNIX类操作系统在创建进程时实现写时复制(Copy on Write)技术。

  • AM:Alignment Mask

  • NW:Not Writethrough

  • CD:Cache Disable

  • PG:Paging

    是否启动分页机制的位,只有在保护模式以上才能开启分页机制。PG位为1开启分页机制,PG位为0关闭分页机制

在CPU刚上电时,处理器被复位成PE=0,PG=0。

3.2.2 CR2寄存器

3.2.3 CR3寄存器

3.2.4 CR4寄存器

3.3 EFLAGS寄存器

image-20230611124419683

  • CF:进位标志
  • PF:奇偶位标志
  • AF:辅助进位标志
  • ZF:零标志位
  • SF:符号标注位
  • TF:陷阱标志位
  • IF:中断标志位。若IF为1,表示中断开启;若为0,表示中断关闭
  • DF:方向标志位。
  • OF:溢出标志位。
  • IOPL:特权级标志位,占2个bit,标志了4个特权级
  • NT:任务嵌套标志位
  • RF:恢复标志位
  • VM:虚拟8086模式
  • AC:对齐检查
  • VIF:虚拟中断标志位
  • VIP:虚拟中断挂起标志位
  • ID:识别标志位
  • 22~31:没有实际用途,占位用,为了将来拓展

3.4 段描述符寄存器

3.4.1 GDTR寄存器

GDTR是个48位的寄存器,专门用来储存GDT的内存地址和大小

image-20230626124256061

GDT:Global Descriptor Table,全局段描述符,在保护模式下,GDT在内存中有且只有一个,GDT的数据结构如下,每个描述符8个字节,64个bit,可以存放在内存当中任意位置,addr相当于GDT的内存起始地址,GDT的总长度就就是GDT界限

在这里插入图片描述

image-20230626124507958

段描述符的主要属性都在高32位:

  • 0~7位:段基址的16-23

  • 24~31位:段基址的24-31

  • 8~11位:type字段,共四位,用来指定本描述符的类型

    image-20230626132808111

  • 12位:S字段,用于指示系统是否是系统段。S为0表示系统段,S为1表示数据段,type字段要和S字段配合在一起才能确定段描述符的确切类型。

  • 15位:Present,即段是否存在。如果段存在于内存中,P为1,否则为0

  • 16~19位:段界限的16-19位

  • 20位:AVL,随便用,操作系统可以随便用这一位

  • 21位:L字段,用来设置是否是64位代码段。L为1表示64位代码段,否则表示32位代码段。

  • 22位:D/B字段

  • 23位:G字段,用来设置段界限的单位大小,若G为0,表示段界限的单位是4KB,若界为1,表示段界限的单位是4KB

  • 24~31位:段基址的最后8位

在实模式下,段寄存器中存储的是段基地址,即内存段的起始地址,而在保护模式下,由于段基址已经存入了段描述符中,所以段寄存器不再存放段基址,而是存放一个叫选择子的东西,选择子用来在段描述符表中索引相应的段描述符,数据结构如下:

image-20230626133735851

  • 0~1位:RPL,存储请求特权级,总共有0、1、2、3四个特权级
  • 2位:TI,用来表示是GDT还是LDT,TI为0表示在GDT中索引描述符,TI为1表示在LDT中索引描述符

在代码中我们可以定义对应的结构体来定义GDT和选择子以及全局描述符表指针

// 全局描述符
typedef struct descriptor_t /* 共 8 个字节 */
{
unsigned short limit_low; // 段界限 0 ~ 15 位
unsigned int base_low : 24; // 基地址 0 ~ 23 位 16M
unsigned char type : 4; // 段类型
unsigned char segment : 1; // 1 表示代码段或数据段即非系统段,0 表示系统段
unsigned char DPL : 2; // Descriptor Privilege Level 描述符特权等级 0 ~ 3
unsigned char present : 1; // 存在位,1 在内存中,0 在磁盘上
unsigned char limit_high : 4; // 段界限 16 ~ 19;
unsigned char available : 1; // 该安排的都安排了,送给操作系统吧
unsigned char long_mode : 1; // 64 位扩展标志
unsigned char big : 1; // 32 位 还是 16 位;
unsigned char granularity : 1; // 粒度 4KB 或 1B
unsigned char base_high; // 基地址 24 ~ 31 位
} _packed descriptor_t;


// 段选择子
typedef struct selector_t
{
u8 RPL : 2; // Request Privilege Level
u8 TI : 1; // Table Indicator
u16 index : 13;
} selector_t;

// 全局描述符表指针
typedef struct pointer_t
{
u16 limit;
u32 base;
} _packed pointer_t;

下一步就是填充GDT和GDT_PTR

// 填充GDT	
descriptor_t gdt[GDT_SIZE]; //内核全局描述符表
memset(gdt, 0, sizeof(gdt));

descriptor_t *desc;
desc = gdt + KERNEL_CODE_IDX;
descriptor_init(desc, 0, 0xFFFFF);
desc->segment = 1; // 代码段
desc->granularity = 1; // 4K
desc->big = 1; // 32 位
desc->long_mode = 0; // 不是 64 位
desc->present = 1; // 在内存中
desc->DPL = 0; // 内核特权级
desc->type = 0b1010; // 代码 / 非依从 / 可读 / 没有被访问过
//填充 gdt_ptr
gdt_ptr.base = (u32)&gdt;
gdt_ptr.limit = sizeof(gdt) - 1;

然后使用lgdt命令将全局描述符表指针加载到GDTR寄存器中:

lgdt [gdt_ptr]

3.4.2 IDTR寄存器

idtr寄存器用于存储中断描述符表的地址和表界限

image-20230626145545075

在中断描述符表中可以存储的是中断描述符,这里的中断描述符分为四类,以不同门的叫法来描述,在上面描述GDT的时候,提到S字段和type字段一起决定了这个描述符是什么,对于GDT来说,我们设置S位为1,代表非系统段,对于中断描述符来说需要将S位设为0,由此延申出了四种描述符:

  • 任务门描述符

​ 任务门和任务状态段 (Task Status Segment,TSS) 是Intel处理器在硬件一级提供的任务切换机制,所以任务门需要和TSS配合在一起使用,在任务门中记录的是TSS选择子,偏移量未使用。任务门可以存在于全局描述符表GDT、局部描述符表LDT、中断描述符表IDT中。描述符中任务门的type值为二进制0101,其结构如下图所示。顺便说一句大多数操作系统 (包括Linux) 都未用TSS实现任务切换

image-20230626150722494

  • 中断门描述符

​ 中断门包含了中断处理程序所在段的段选择子和段内偏移地址。当通过此方式进入中断后,标志寄存器eflags中的IF位自动置0,也就是在进入中断后,自动把中断关闭,避免中断嵌套。Linux就是利用中断门实现的系统调用,就是那个著名的int0x80。中断门只允许存在于IDT中。描述符中中断门的type值为二进制1110,其结构如下图所示

image-20230626150752429

  • 陷阱门描述符

​ 陷阱门和中断门非常相似,区别是由陷阱门进入中断后,标志寄存器eflags中的IF位不会自动置0。陷阱门只允许存在于IDT中。描述符中陷阱门的type值为二进制1111。其结构如下图所示

image-20230626150816386

  • 调用门描述符

​ 调用门是提供给用户进程进入特权0级的方式,其DPL为3。调用门中记录例程的地址,它不能用int指令调用,只能用call和imp指令。调用门可以安装在GDT和LDT中。描述符中调用门的type值为二进制1100。其结构如下图所示

image-20230626150841553

同样我们可以使用一个结构体来描述中断描述符:

typedef struct gate_t
{
u16 offset0; //段内偏移 0 ~ 15位
u16 selector; //代码段选择子
u8 reserved; //保留不用
u8 type : 4; //任务们/中断门/陷阱门
u8 segment : 1; //segment = 0 表示系统段
u8 DPL : 2; //使用 int 指令访问的最低权限
u8 present : 1; //是否有效
u16 offset1; //段内偏移 16 ~ 31位
} _packed gate_t;

然后填充中断描述符和中断

   typedef void* handler_t; // 中断处理函数
//定义idt
gate_t idt[IDT_SIZE];
//中断处理程序段内偏移
extern handler_t handler_entry_table[ENTRY_SIZE];
//初始化中断描述符表
for (size_t i = 0; i < IDT_SIZE; i++)
{
gate_t *gate = &idt[i];
handler_t handler = handler_entry_table[i];
gate->offset0 = (u32)handler & 0xffff; //段内偏移 0 ~ 15位
gate->offset1 = ((u32)handler >> 16) & 0xffff; //段内偏移 16 ~ 31位
gate->selector = 1 << 3; //代码段选择子
gate->reserved = 0; //保留不用
gate->type = 0b1110;
gate->DPL = 0; // 使用 int 指令访问的最低权限
gate->present = 1; // 是否有效
}

最后将idt_ptr填充进IDTR寄存器中

//加载中断描述符表
idt_ptr.base = (u32)idt;
idt_ptr.limit = sizeof(idt) - 1;

asm volatile("lidt idt_ptr\n");

3.4.3 LDTR寄存器

3.4.4 TR寄存器

4.长模式下寄存器(64bit)

在长模式下,也称为64位保护模式(64-bit Protected Mode)或x86-64架构,x86处理器提供了更广泛的寄存器集合。以下是长模式下可以使用的寄存器:

  1. 通用寄存器(General Purpose Registers):
    • RAX:累加器寄存器(Accumulator Register)。
    • RBX:基址寄存器(Base Register)。
    • RCX:计数寄存器(Counter Register)。
    • RDX:数据寄存器(Data Register)。
    • RSI:源索引寄存器(Source Index Register)。
    • RDI:目的索引寄存器(Destination Index Register)。
    • RBP:基址指针寄存器(Base Pointer Register)。
    • RSP:堆栈指针寄存器(Stack Pointer Register)。
    • R8-R15:扩展的通用寄存器。
  2. 扩展通用寄存器:
    • RIP:指令指针寄存器(Instruction Pointer Register)。
    • RFLAGS:标志寄存器(Flags Register),包含各种标志位。
  3. 段寄存器(Segment Registers):
    • CS:代码段寄存器(Code Segment Register)。
    • DS:数据段寄存器(Data Segment Register)。
    • ES:附加段寄存器(Extra Segment Register)。
    • FS、GS、SS:附加段寄存器,用于访问额外的数据段。
  4. 控制寄存器(Control Registers):
    • CR0、CR2、CR3、CR4、CR8:用于控制和管理保护模式的特性,如分页机制、特权级等。
  5. 段描述符寄存器(Descriptor Registers):
    • GDTR:全局描述符表寄存器(Global Descriptor Table Register)。
    • IDTR:中断描述符表寄存器(Interrupt Descriptor Table Register)。
    • LDTR:局部描述符表寄存器(Local Descriptor Table Register)。
    • TR:任务寄存器(Task Register)。
  6. XMM寄存器(SSE寄存器):
    • XMM0-XMM15:128位的向量寄存器,用于执行SSE(Streaming SIMD Extensions)指令集中的向量运算。
  7. YMM寄存器(AVX寄存器):
    • YMM0-YMM15:256位的向量寄存器,用于执行AVX(Advanced Vector Extensions)指令集中的向量运算。
  8. ZMM寄存器(AVX-512寄存器):
    • ZMM0-ZMM31:512位的向量寄存器,用于执行AVX-512指令集中的向量运算。

5. 参考链接