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、BPSP。这些寄存器都是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指向栈顶,两者共同维护了栈空间。pushpop 可更改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. 参考链接