1. 保护模式简介

1.1. x86CPU的三种运行模式

  • 实模式
  • 保护模式
    • 保护操作系统的数据结构如GDT、IDT
    • 保护操作系统的寄存器
    • 保障多任务的并发进行
    • 现代操作系统大多采用保护模式
  • 虚拟8086模式

CPU刚启动的时候处于实模式,如果CPU开启了保护模式功能,CPU会在之后切换到保护模式

1.2. x86CPU的等级环

x86CPU存在4个等级环0-3

  • 0环为特权环
  • 0、1、2环为系统环
  • 3环为用户环

1.3. 保护模式的核心机制

段机制和页机制是保护模式的基础,也是实现现代操作系统的基础。只有深刻掌握和理解CPU的段页机制,才能掌握操作系统底层的一些原理。注意,段机制和页机制是CPU这个硬件提供的功能,并不是操作系统提供的,操作系统只是利用了CPU的这个功能

1.4. 学习原则

  • x64是x86的拓展,x86是x64的基础
  • 学完后可以不通过驱动来进入内核
  • 学完后可以不通过操作系统的API来实现相应功能

1.5. 实验环境搭建

  • 虚拟机内搭建Windows XP系统(配置成单核,否则后面的实验可能会出错),安装VC6和OllyDbg
  • 宿主机内安装Windbg,配置Symbols
  • 宿主机与虚拟机配置双机调试

2. 段机制

2.1. 段寄存器

mov eax,dword ptr ds:[0x401000],这里读取到eax的值实际位于地址ds.base + 0x401000处。总共存在CS、SS、DS、ES、FS、GS、LDTR、TR共八个段寄存器

2.1.1. 段寄存器读写

2.1.1.1. 读段寄存器

  • CS、SS、DS、ES、FS、GS:mov ax,es,只能读取可见的段选择子
  • LDTR:SLDT
  • TR:str axstr word ptr ds:[buff]

2.1.1.2. 写段寄存器

  • CS:JMP FAR、CALL FAR、RETF、INT、IRETED指令(详见后续章节)
  • SS、DS、ES、FS、GS:
    • mov es,ax,写段寄存器一次性写96位,其它64位由CPU填充
    • LES、LSS、LDS、LFS、LGS指令,les ecx,fword ptr ds:[buffer],高两个字节给段寄存器es,低四个字节给ecx
  • LDTR:LLDT
  • TR:ltr axltr word ptr ds:[buff],这个指令只能在0环执行,加载后TSS段描述符的Busy位会发生变化

2.1.2. 段寄存器结构

段寄存器为96位(只有16位可见),由以下四部分构成:

  • 段选择子,16位,可见,段的选择子
  • 段属性,16位,不可见,指示段是否可读、可写、可执行
  • 段基址,32位,不可见,段的起始地址
  • 段限长,32位,不可见,段的长度

此表为实验中的实例表:

段寄存器 段选择子 段属性 段基址 段限长
CS 001B 可读可执行 0x00000000 0xFFFFFFFF
SS 0023 可读可写 0x00000000 0xFFFFFFFF
DS 0023 可读可写 0x00000000 0xFFFFFFFF
ES 0023 可读可写 0x00000000 0xFFFFFFFF
FS 003B 可读可写 0x7FFDE000 0xFFF
GS - - - -
  • 不同的操作系统,段选择子FS的段基址可能不同
  • Windows操作系统未使用GS段寄存器
  • 在做寄存器读写实验的时候,尽量不要使用DS段寄存器,这个寄存器在操作系统生成的代码中被使用,可能会导致程序崩溃

2.1.3. 段选择子

比特位 15-3 2 1-0
结构 index TI RPL
  • index:段选择子索引
  • TI(Table Indicator):0代表查GDT表,1代表查IDT表
  • RPL(Requested Privilege Level):请求特权级别

2.2. 段选择子选择了什么:段描述符

2.2.1. 段描述符在哪儿:GDT、LDT、IDT

  • GDT:全局描述符表
    • gdtr寄存器:32位,存有GDT表的地址
    • gdtl寄存器:16位,存有GDT表的长度(单位为字节)
  • LDT:局部描述符表(Windows中并未使用该结构)
  • IDT:中断描述符表
    • idtr寄存器:32位,存有IDT表的地址
    • idtl寄存器:16位,存有IDT表的长度(单位为字节)

sgdt fword ptr ds:[buffer]汇编指令可以读取gdtr和gdtl寄存器,高四字节存储gdtr,低二字节存储gdtl(如果CR4.UMIP = 0,SGDT可以在3环执行)

2.2.2. 段描述符结构(8字节)

比特位 31-24 23 22 21 20 19-16 15 14-13 12 11-8 7-0 31-16 15-0
结构 Base 31-24 G D/B 0 AVL SL 19-16 P DPL S Type Base 23-16 Base 15-0 SL 15-0
  • Base(Base Address):段的基地址
  • G(Granularity):指示段限长的单位,1表示4KB,0表示字节
  • D/B(Default operation size):1表示32位段,0表示16位段
    • CS段(段加载到CS段寄存器中时):1表示默认用32位寻址方式,0表示默认用16位寻址方式(可以用67这个opcode强制更改寻址方式为16位)
    • SS段(段加载到SS段寄存器中时):影响PUSH、POP、CALL等隐式堆栈操作指令,1表示使用ESP指针,0表示使用SP指针
    • 向下拓展的数据段:1表示段上限为4GB,0表示段上限为64KB
  • AVL(Available for use by system software):可用位,提供给用户或者应用程序使用
  • SL(Segment Limit):段的限长
  • P(Segment Present):1表示段描述符有效,0表示段描述符无效(CPU检查的第一步)
  • DPL(Descriptor Privilege Level):描述符特权级别,Windows只使用了3和0
  • S(Descriptor type):1表示代码或数据段,0表示系统段(CPU检查的第二步)
  • Type(Segment type):段类型,代码或数据段和系统段的解析结果不同,详见下一小节

注意:FS对应的段描述符比较特殊,查分后与段寄存器中的值不符合,与操作系统(线程)相关

2.2.3. 段描述符Type结构

2.2.3.1. 代码或数据段

比特位 11 10 9 8
数据段结构 0 E W A
代码段结构 1 C R A
  • E:1表示向下拓展(0->base和base+limit->段上限有效),0表示向上拓展(base->base+limit有效)
  • W:1表示可读可写,0表示只读
  • A:指示该位被操作系统最后一次清零后,该段是否被访问过(CPU将该段置入段寄存器中),1表示已经被访问过,0表示未被访问过
  • C:1表示一致代码段(系统拿出来的共享段),低权限可以访问高权限但是特权级别不变,高权限不能访问低权限,0表示非一致代码段,禁止不同级别互相访问
  • R:1表示可读,0表示不可读

2.2.3.2. 系统段

比特位 11 10 9 8
保留 0 0 0 0
16位TSS(可用) 0 0 0 1
LDT 0 0 1 0
16位TSS(Busy) 0 0 1 1
16位调用门 0 1 0 0
任务门 0 1 0 1
16位中断门 0 1 1 0
16位陷阱门 0 1 1 1
保留 1 0 0 0
32位TSS(可用) 1 0 0 1
保留 1 0 1 0
32位TSS(Busy) 1 0 1 1
32位调用门 1 1 0 0
保留 1 1 0 1
32位中断门 1 1 1 0
32位陷阱门 1 1 1 1

2.3. 位于GDT表中的系统段

2.3.1. 调用门

比特位 31-16 15 14-13 12 11-8 7-5 4-0 31-16 15-0
结构 Offset in Segment 31-16 P DPL S(0) Type(1100) 000 Param Count Segment Selector Offset in Segment 15-0
  • Param Count:参数个数

    注意:Windows并未使用调用门

2.3.2. 任务段(Task-state Segment, TSS, 任务状态段)

2.3.2.1. TSS的结构

TSS是一块大小为104字节的内存块,存储了一堆寄存器,TSS的作用就在于可以替换一堆寄存器。Intel设计TSS初衷用于线程切换,但是Windows和Linux的线程切换均采用堆栈来保存寄存器而非TSS。Windows将TSS用于跨段跳转提权时,获取高权限的SS和ESP。

31-15比特位 15-0比特位 偏移字节
I/O Map Base Address Reserved(0位单独为T) 100
Reserved LDT Segment Selector 96
Reserved GS 92
Reserved FS 88
Reserved DS 84
Reserved SS 80
Reserved CS 76
Reserved ES 72
EDI < 68
ESI < 64
EBP < 60
ESP < 56
EBX < 52
EDX < 48
ECX < 44
EAX < 40
EFLAGS < 36
EIP < 32
CR3(PDBR) < 28
Reserved SS2 24
ESP2 < 20
Reserved SS1 16
ESP1 < 12
Reserved SS0 8
ESP0 < 4
Reserved Previous Task Link 0
  • I/O Map Base Address:IO许可位图基地址,当用户态进程试图通过in或out指令访问一个I/O端口时,CPU需要访问存放在TSS中的I/O许可位图以检查该进程是否有访问端口的权利。步骤如下:
    • 检查eflags寄存器中的2位IOPL字段,如果字段的值为3,控制单元就执行I/O指令。否则,执行下一个检查。
    • 访问tr寄存器以确定当前的TSS和相应的I/O许可权位图。
    • 检查I/O指令中指定的I/O端口在I/O许可权位图中对应的位,如果该位清,这条指令就执行,否则控制单元产生一个异常。

2.3.2.2. 如何找到TSS:TR寄存器

TR(Task Register)寄存器存储了TSS段描述符的段选择子

2.3.2.3. TSS段描述符结构

比特位 31-24 23 22-21 20 19-16 15 14-13 12 11-8 7-0 31-16 15-0
结构 Base 31-24 G 00 AVL Limit 19-16 P DPL S(0) Type(10B1) Base 23-16 Base 15-0 Limit 15-0
  • Type中的B:1表示Busy(已被加载至TR寄存器),0表示Available

2.3.2.4. 修改TR寄存器

  • 0环下的LTR指令

2.4. 位于IDT表中的系统段

2.4.1. 中断门

比特位 31-16 15 14-13 12 11-8 7-5 4-0 31-16 15-0
结构 Offset 31-16 P DPL S(0) Type(D110) 000 00000 Segment Selector Offset 15-0
  • Type中的D:1表示32位门,0表示16位门

2.4.2. 陷阱门

比特位 31-16 15 14-13 12 11-8 7-5 4-0 31-16 15-0
结构 Offset 31-16 P DPL S(0) Type(D111) 000 00000 Segment Selector Offset 15-0
  • Type中的D:1表示32位门,0表示16位门

2.4.3. 任务门

比特位 31-16 15 14-13 12 11-8 7-0 31-16 15-0
结构 Reserved P DPL S(0) Type(0101) Reserved TSS Segment Selector Reserved

2.5. 跨段访问

2.5.1. 跨段时的权限检查

2.5.1.1. 三类特权级别

  • CPL(Current Privilege Level):当前特权级别,见CS、SS段寄存器中的值的最后两个比特位,CS、SS段寄存器中的值的最后两个比特位永远相同
  • RPL(Requested Privilege Level):请求特权级别,位于段选择子中,指用什么权限去访问一个段,可以进行附加限制,防止误操作
  • DPL(Descriptor Privilege Level):描述符特权级别,位于段描述符结构中,指访问这个段需要的权限级别
    • 数据段:DPL规定了可以访问此段的最低特权级。比如,一个数据段的DPL是1,那么只有运行在CPL为0或者1的程序才有权访问它
    • 非一致代码段(不使用调用门的情况下):DPL规定访问此段的特权级。比如一个非一致代码段的特权级为0,那么只有CPL为0的程序才可以访问它
    • 调用门:DPL规定了当前执行的程序或任务可以访问此调用门的最低特权级(这与数据段的规则是一致的)
    • 一致代码段和通过调用门访问的非一致代码段:DPL规定了访问此段的最高特权级。比如,一个一致代码段的DPL是2,那么CPL为0和1的程序将无法访问此段

2.5.1.2. 权限检查与变化

  • 数据段:CPL <= DPL,RPL <= DPL,访问后CPL不变
  • 直接跳转、直接调用代码段(jmp far、call far)
    • 非一致代码段:CPL == DPL,RPL <= DPL,跳转后CPL=DPL,实际不变
    • 一致代码段:CPL >= DPL,跳转后CPL不变
  • 调用门:调用门描述符DPL为DPL_Date,调用门中包含的代码段描述符DPL为DPL_Code
    • 一致代码段:CPL <= DPL_Gate,RPL <= DPL_Gate,DPL_Code <= CPL,调用后CPL = DPL_Code
    • 非一致代码段:
      • call调用门:CPL <= DPL_Gate,RPL <= DPL_Gate,DPL_Code <= CPL,调用后CPL = DPL_Code
      • jmp调用门:CPL <= DPL_Gate,RPL <= DPL_Gate,DPL_Code = CPL,调用后CPL = DPL_Code
  • 跨段中断(中断门):CPL <= DPL

2.5.2. 直接跳转、直接调用代码段(jmp far、call far)

JMP far 0x1b:0x00401000call far 0x1b:0x00401000,这里0x20指向代码段

2.5.2.1. 流程

  • 拆分段选择子,确定index、TI和RPL,定位段描述符
  • 权限检查
  • 加载段描述符到段寄存器
  • 修改EIP为CS.Base + Offset

2.5.2.2. 堆栈变化(无论怎样都不会提权,所以不涉及堆栈切换)

2.5.2.2.1. jmp far

堆栈无变化

2.5.2.2.2. call far(retf)
  • 压入参数(如果有)
  • 压入CS
  • 压入返回地址

2.5.3. 调用门

CALL far CS:EIP或者call far fword ptr ds:[buff],这里的EIP并不使用,调用的地址由CS指向的调用门决定

2.5.3.1. 流程

  • 定位调用门描述符,并取出调用门中的代码段描述符
  • 根据代码段描述符的DPL,从TSS中读取新的SS、ESP,并检查合法性
  • 暂时性保存当前即原SS、ESP
  • 切换堆栈,压入原SS、ESP
  • 从原堆栈复制参数至新堆栈
  • 压入当前即原CS、EIP
  • 加载段描述符到段寄存器
  • 修改EIP为Base+调用门的Offset in Segment
  • 在用ret返回时,会检查ds、es、fs、gs的值,如果其中哪一个寄存器指向的段的DPL小于CPL(此规则不适合于一致代码段),那么一个空描述符会被加载到该寄存器中

2.5.3.2. Call时候的堆栈变化

2.5.3.2.1. 跨段不提权调用(不涉及堆栈切换)
  • 压栈参数(如果有)
  • 压栈3环CS
  • 压栈3环返回地址
2.5.3.2.2. 跨段提权调用(涉及堆栈切换,0环栈)
  • 3环栈压栈参数(如果有)
  • 0环栈压栈3环SS
  • 0环栈压栈3环ESP
  • 0环栈压栈参数(如果有)
  • 0环栈压栈3环CS
  • 0环栈压栈3环返回地址

2.5.4. 任务段

2.5.4.1. JMP任务段

JMP 0x48:0x12345678,这里0x48为任务段

  • 把当前所有寄存器(TSS结构中有的那些寄存器)的值填写到当前TR段寄存器指向的TSS中
  • 修改TR寄存器
  • 使用TR.Base指向的TSS的值修改当前的寄存器

2.5.4.2. CALL任务段

call 0x48:0x12345678,这里0x48为任务段
流程和JMP任务段基本一样,唯一的区别是会往前堆栈里面压入一个返回地址

2.5.5. 中断门

int 20h

2.5.5.1. 流程

  • 使用中断号为索引找到IDT表中的中断门描述符
  • 从中断门描述符中取出中断门中的段选择子
  • 根据中断门中的段选择子找到段描述符,得到Base即基地址
  • 加载段描述符到段寄存器
  • 修改EIP为Base+中断门的Offset in Segment

2.5.5.2. 堆栈变化(中断门无法传递参数)

2.5.5.2.1. 不提权
  • 压栈调用者CS
  • 压栈调用者EFLAG
  • 压栈调用者返回地址
2.5.5.2.2. 提权
  • 压栈调用者SS
  • 压栈调用者ESP
  • 压栈调用者EFLAG
  • 压栈调用者CS
  • 压栈调用者返回地址

2.5.6. 陷阱门

注意:陷阱门的机制和中断门基本相同,仅有一点不同,中断门在执行的时候会将EFlag的IF标志位清0,导致CPU不响应可屏蔽中断,但是陷阱门不会

2.5.7. 任务门

提供通过IDT表来访问任务段的方式,中断号找到任务门之后,会获取一个任务段的段选择子,之后的步骤和跳转任务段相同

3. 页机制

3.1. 四个地址类型

mov eax,ds:[0x12345678]

  • Virtual Address:虚拟地址,也称为逻辑地址(Logical Address),这里的0x12345678就是虚拟地址
  • Liner Address:线性地址,这里的ds.Base + 0x12345678就是线性地址,每个进程有独立的4GB线性地址空间
  • Physical Address:物理地址,线性地址经过页机制转换后就是物理地址
  • Board Address:板卡地址,物理地址经过硬件转换形成板卡地址(硬件层面,与操作系统无关)

3.2. 分页机制:每页4KB

为了能够方便地使用内存,CPU把4GB线性地址空间划分成了2的20次方个内存页,每一页的大小都是0x1000字节也就是4KB,物理地址空间也按照4KB大小进行划分。

3.3. 线性地址->物理地址:从CR3寄存器开始

CR3寄存器里面存储的是物理地址(唯一一个存放物理地址的寄存器).每个进程都有一个独立的CR3寄存器值,CR3寄存器指向页目录表(4字节 * 1024项),页目录表中每个表项指向页表(4字节 * 1024项),页表中每个表项指向物理页。

3.4. 第一种分页方式:10-10-12分页

3.4.1. 设置10-10-12分页

编辑C:\boot.ini文件,将noexecute改成execute,然后重启

3.4.2. 线性地址->物理地址的转换步骤

  • 将32位线性地址分割为10-10-12,第一个10为页目录索引(PDI),第二个10为页表索引(PTI),12为物理地址偏移
  • PDT基地址(页目录表基地址) = CR3
  • PTT基地址(页表的基地址) = PDI*4(每项4字节,得到页目录项即PDE)+ PDT基地址
  • 物理页的地址 = PTI*4(每项4字节,得到页表项项即PTE)+ PTT基地址
  • 物理地址 = 物理页的地址 + 物理地址偏移

物理页一页为4KB(10-10-12中还存在一种4MB的大页),PDT中有1024个PDE,PTT中有1024个PTE。PDE、PTE可以为空,也可以多个PTE指向同一个物理页(这些PTE的属性可以不同,这样可以实现对于同一片地址空间,操作系统可读可写而用户只读)。0地址不能访问就是因为0对应的PTE为空。

3.4.3. PDE、PTE结构

物理页属性= PDE属性 & PTE属性

比特位 31-12 11-9 8 7 6 5 4 3 2 1 0
PDE 页表基址 有效位 G PS 0 A PCD PWT U/S R/W P
PTE 页表基址 有效位 G PAT D A PCD PWT U/S R/W P
  • 有效位:
  • P:1代表有效,0代表无效
  • R/W:1代表可读可写,0代表只读
  • U/S(User/System):1代表可以被用户(3环)访问,0代表只能被系统(0、1、2环)访问(高2G内存保护的实现方式)
  • PS(Page Size):只针对PDE有效,1代表页大小为4MB即大页,且PDE直接指向物理页,0代表页大小为4KB即小页,且PDE指向PTT
  • A:指示对应物理页是否被访问过(访问一个字节也算被访问过),1代表被访问过,0代表没被访问过
  • D(Dirty):脏位,指示对应物理页是否被写过,1代表写过,0代表没被写过,可用于页面置换
  • G(Global):TLB刷新位,指示刷新TLB时刷不刷新该项,1代表刷新TLB时不刷新该项,0代表刷新TLB时刷新该项(对于PDE,PS=1时G位有效)
  • PAT:无意义
  • PWT(Page Write Through):1代表写Cache的时候需要同时写到内存中,0代表写内存时机由缓存控制器决定
  • PCD(Page Cache Disable):1代表禁止将某个页写入缓存,直接操作内存(比如TLB的存在使得有些页可以不用Cache)

3.4.4. 通过线性地址访问PDT基地址、PTT基地址

  • 0xC0300000(线性地址)指向PDT基地址,PDT占用地址空间4KB,如果要找PDE,0xC0300000 + PDI*4即可
  • 0xC0000000(线性地址)指向第一个PTT基地址,所有的PTT在线性地址上连续,占用0xC0000000-0xC0400000地址空间共4MB,如果要找PTE,0xC0000000 + PDI * 4096 + PTI * 4即可

0xC0300000根据拆分,PDI为0x300,该PDE指向的PTT为PDT表自身,即这里的PDT表也扮演着PTT表的角色;PTI也是0x300,该PTE指向的物理页为PTT表自身,即这里的PDT表=PTT表=物理页。

3.4.5. 一些细节

  • 高2G空间有一些大页,4MB大小
  • 不同进程低2G空间几乎不同
  • 不同进程高2G空间几乎相同(除了页表等一些地方)
  • Windows中,进程低2G空间的前后64KB没有使用(0-00010000;7FFF0000-7FFFFFFF),是因为要用于调试
  • 进程B的PDT、PTT是由创建进程B的进程A来填充的,填充完切换CR3即可

3.5. 第二种分页方式:2-9-9-12分页(PAE分页,物理地址拓展分页)

3.5.1. 为什么要有PAE分页

10-10-12分页只有4GB的物理地址寻找范围,不满足内存大小的发展要求

3.5.2. 如何开启PAE分页

编辑C:\boot.ini文件,将execute改成noexecute,然后重启,如果查看我的电脑->属性显示有物理地址拓展,那么说明修改成功

3.5.3. 为什么是2-9-9-12分页

页的大小需要保持为4KB不变(性能原因,AMD为2KB),所以需要通过增大PTE的长度(4字节->8字节)的方式来增大物理地址的寻找范围,同时因为页的大小不变,所以页项减少(1024项->512项),所以页项索引变成9位,最后物理地址寻找范围达到64GB(单进程其实可用空间仍为4GB,因为线性地址只有32位,但是多进程可用空间就会扩大)

3.5.4. 线性地址->物理地址的转换步骤

  • 将32位线性地址分割为2-9-9-12,第一个2为页目录指针表索引(PDPI),第二个9为页目录索引(PDI),第三个9为页表索引(PTI),12为物理地址偏移
  • PDPT基地址(页目录指针表,8字节*4项,基地址) = CR3
  • PDT基地址(页目录表基地址) = PDPI*8(每项8字节,得到页目录项即PDPE)+ PDPT基地址
  • PTT基地址(页表的基地址) = PDI*8(每项8字节,得到页目录项即PDE)+ PDT基地址
  • 物理页的地址 = PTI*4(每项4字节,得到页表项项即PTE)+ PTT基地址
  • 物理地址 = 物理页的地址 + 物理地址偏移

3.5.5. PDPE、PDE、PTE结构

物理页属性= PDPE属性 & PDE属性 & PTE属性

3.5.5.1. PDPE

比特位 63-36 35-12 11-9 8-5 4 3 2-1 0
PDPE Reserved 基地址 有效位 Reserved PCD PWT Reserved 1

3.5.5.2. PDE

比特位 63 62-36 35-21 20-13 12 11-9 8 7 6 5 4 3 2 1 0
PDE(2MB大页) XD Reserved 基地址 Reserved PAT Avail G ps(1) D A PCD PWT U/S R/W P
PDE(4KB小页) XD Reserved 基地址 < < Avail G(0) ps(0) D(0) A PCD PWT U/S R/W P
  • XD:AMD中称为NX位,1代表该页不可执行,0代表该页可以执行

3.5.5.3. PTE

比特位 63 62-36 35-12 11-9 8 7 6 5 4 3 2 1 0
PTE XD Reserved 基地址 Avail G 0 D A PCD PWT U/S R/W P

3.5.5.4. PAE分页下通过线性地址访问PDT、PTT:页目录基址、页表基址

  • pPDE = (int*)(0xc0600000 + ((addr >> 18) & 0x3ff8))
  • pPTE = (int*)(0xc0000000 + ((addr >> 9) & 0x7ffff8))

3.6. TLB(快表)

3.6.1. TLB是什么以及为什么要有TLB

通过线性地址访问物理页需要进行多层转换(如果读取的多个字节跨页会更麻烦),效率很低,所以CPU做了一个线性地址-物理页的缓存表来提高效率,称为TLB(Translation Lookaside Buffer)

3.6.2. TLB的结构

比特位
结构 Liner Address(线性地址) Physical Address(物理地址) Attribute LRU(统计)
  • Attribute(属性):PDE & PTE(PDPE & PDE & PTE)
  • 对于不同的CPU,TLB的大小不同。TLB越大,缓存效果越好,执行速度越快
  • 只要CR3发生改变,TLB立即刷新
  • 每个CPU核心单独有一个TLB
  • 当TLB满了,根据统计值,不常用的地址将会被替代

3.6.3. TLB分类

  • 缓存一般页表(4K字节页面)的指令页表缓存(Instruction-TLB)
  • 缓存一般页表(4K字节页面)的数据页表缓存(Data-TLB)
  • 缓存大尺寸页表(2M/4M字节页面)的指令页表缓存(Instruction-TLB)
  • 缓存大尺寸页表(2M/4M字节页面)的数据页表缓存(Data-TLB)

3.6.4. 强制刷新TLB:invlpg

invlpg 0x400000,invlpg指令可以无视G属性位强制刷新TLB中某个线性地址表项

4. 中断与异常

4.1. 中断

中断(中断请求)通常由CPU外部的输入输出设备(硬件)发起,供外部设备通知CPU,希望CPU暂时停止执行当前执行程序,转而处理中断请求所对应的中断例程(中断例程地址位于IDT表中)

4.1.1. 不可屏蔽中断

非屏蔽中断(NMI):NonMaskable Interrupt,不可屏蔽请求对应的中断例程固定位于0x2处,不可屏蔽中断不受EFLAG中IF位的影响,CPU必须立即响应,比如说断电。

4.1.2. 可屏蔽中断

可屏蔽中断(INTR):Interrupt Require,在硬件级,可屏蔽中断是由一块专门的芯片来管理的,通常称为中断控制器。它负责分配中断资源和管理各个中断源发出的中断请求。为了便于标识各个中断请求,中断管理器通常用IRQ(Interrupt Request) 后面加上数字来表示不同的中断(例如时钟中断为IRQ0,在设备管理器的设备属性->资源中可以查看对应编号)。

4.1.2.1. 可屏蔽中断类型

中断号 IRQ 中断类型
0x30 IRQ0 时钟中断
0x31-0x3F IRQ1-IRQ15 其他硬件设备的中断

硬件中断与IDT表中的对应关系并非固定不变的,参见APIC(高级可编程中断控制器)。

4.1.2.2. 如何屏蔽可屏蔽中断:EFLAG.IF

当EFLAG.IF=1的时候,表示CPU当前可以接受可屏蔽中断并进行处理,当EFLAG.IF=0的时候,任何可屏蔽中断都将会被CPU忽略。cli置位0,sti置位1。在单核模式下运行多线程程序时,可以关闭中断请求,从而不响应可屏蔽中断,这种方法可以实现原子操作。不过多核的话这种方法就失效了,需要采取其它手段。

4.2. 异常

异常通常是CPU在执行指令时检测到的某些错误,比如除0、访问无效页面等。异常例程也位于IDT表中。

4.2.1. 一些异常类型

异常类型 中断号
页错误 0xE
段错误 0xD
除零错误 0x0
双重错误 0x8

4.3. 异常与中断的区别

  • 中断来自于外部设备,产生于硬件,CPU被动接受
  • 异常来源于CPU内部,产生于软件,它是由CPU主动产生的

注意:INT N虽然被称为软件中断,但是它来源于CPU内部,其本质是异常,EFLAG的IF位对INT N无效

5. 控制寄存器

用于控制CPU的操作模式,有五个控制寄存器CR0-CR5

  • CR0
  • CR1:保留
  • CR2:存有32位缺页线性地址。当CPU访问某个无效页面时,会产生缺页异常,此时CPU将产生缺页异常的线性地址(原来的EIP)存放在CR2中
  • CR3:页目录表基址(两种分页模式下结构不同)
  • CR4

5.1. CR0寄存器

比特位 31 30 29 16 0
属性 PG CD NW WP PE
  • PG:1代表开启页机制(PE位必须为1才可以置位为1),0代表未开启页机制
  • PE(Protection Enable):1代表开启保护模式,启用段机制(页机制需要看PG位),0代表CPU工作在实模式下
  • WP(Write Protect):禁止特权环向用户环只读页面写数据

5.2. CR4寄存器

比特位 5 4
属性 PAE PSE
  • PAE:1代表2-9-9-12分页,0代表10-10-12分页
  • PSE:1代表PS位有意义(大页大小为4MB、2MB),0代表PS位无意义(大页大小为4KB)

6. 参考资料

注:以下参考资料为系列文章的参考资料,并不局限于本篇文章