[Make MyOS] Basic execution env & Interrupt
1 执行环境介绍
在 find loader 那一节之后,发现后面的代码真的看不懂了,直接抄也没有意义,于是补了一下相关的知识.. 希望能继续下去
1.1 CPU 工作模式
参考 Intel 手册卷 3 第 2.2 MODES OF OPERATION
32 位的支持三种工作模式和一种准工作模式
实地址模式 (Real-address mode),处理器上电 (power-up) 或复位 (reset) 后处于该模式。这个模式实际上是为了兼容 8086,因为地址线只有 20 根,限制了寻址只能是 1M 大小,也就是通过方式寻址。进入这个模式,也就只使用 20 根地址线,主要是因为 A20M# 引脚被屏蔽了
保护模式 (Protected mode),这个是 80386 时期引入的,也就是 32 位处理器原本的工作模式。在这个模式下不再使用的方式寻址,而是通过段描述符的方式描述基址和限长,并通过描述符中的属性设置实现对内存段的访问限制和数据保护。
虚拟 8086 模式 (Virtual-8086 mode),是一种准工作模式,目的是为了在保护模式下能够执行 8086 程序
系统管理模式 (System management mode),80386 时期引入,提供了一种对操作系统或执行程序透明的机制实现平台特定功能,如电源管理或系统安全或 oem 差异化的 feature。对操作系统透明 (这个透明的意思在计算机组成原理里就可以理解为不知道为什么,它就..),换句话说,就是操作系统不知道何时进入 SMM 的,也不知道 SMM 曾经执行过。进入 SMM 是通过给 CPU 引脚 SMI# 上电,或者可编程中断控制器 (APIC) 接收到 SMI,进入 SMM 后,一切被都屏蔽,包括所有的中断,处理器切换到单独的地址空间 (System Management RAM),同时保存当前运行的程序或任务的上下文。然后可以透明地执行 SMM 特定的代码。可以通过设置 SMBASE 的寄存器来设置 SMRAM 的空间。SMM 处理程序只能由系统固件实现。只能通过执行 RSM 指令退出,退出后,处理器将被置于 SMI 之前的状态。
64 位除了支持 32 位的几个模式,还支持 IA-32e modes
- IA-32e 模式 (IA-32e mode),在 IA-32e 模式下,处理器支持两种子模式:兼容模式和 64 位模式。64 位模式提供 64 位线性寻址并支持大于 64GBytes 的物理地址空间。兼容模式允许大多数传统的保护模式应用程序不变地运行。AMD 称之为长模式,参考 AMD 手册卷 1 第 1.2 节

1.2 基础执行环境
处理器上运行的任何程序或资源都会被赋予一组资源,用于执行指令,存储代码、数据和状态信息。就是一些寄存器和内存的使用
- 地址空间 (Address space),32 位 CPU,理论上单进程可以寻址bytes,也就是 4GB 线性地址 (虚拟地址) 空间,而可用的地址线有 36 根 (Intel),所以支持最大物理内存是bytes,也就是 64GB;64 位 CPU,理论上单进程可以寻址bytes 线性地址,也就是??? TB,只是实际上只有 46 根地址线 (听说已经来到了 48 根),所以支持最大物理地址是byte,也就是??? TB
- 基本程序执行寄存器 (Basic program execution registers),32 位 CPU,有 8 个通用寄存器 (GPR),6 个段寄存器 (SR),1 个 EFLAGS 寄存器,1 个 EIP (指令) 寄存器;64 位 CPU 有 16 个 GPR,6 个 SR,1 个 EFLAGS (其中高 32 位保留,不使用),一个 RIP
- x87 FPU 寄存器,32 位和 64 位都是 8 个,主要用于浮点计算的
- MMX 寄存器,32 位和 64 位都是 8 个,主要用于矩阵运算?用于支持 64bit 的 SIMD (single-instruction,multiple-data) 单指令多数据
- XMM 寄存器,32 位和 64 位都是 8 个,对 MMX 的扩展,用于支持 128 位的 SIMD
- YMM 寄存器,32 位和 64 位都是 8 个,对 MMX 的扩展,用于支持 256 位的 SIMD,PC 平台主要 SIMD 扩展发展简史
- 边界寄存器 (Bounds),BND0-BND3 寄存器存放的是内存缓冲区的上限指针,这些寄存器用于支持 MPX 指令。
- BNDCFGU 和 BNDSTATUS 寄存器,可以看出一个是配置寄存器,一个是状态寄存器,用于支持 MPX 指令。
- 堆栈 (Stack),为了支持过程或子例程调用以及过程或子例程之间的参数传递,在执行环境中包括堆栈和堆栈管理资源。它通过段寄存器 SS 指定位置,而实际指向的是一段内存或者 Cache

以上的运行环境是在 CPU core 级别的环境,另外处理器还提供了下列系统级别的运行环境,在多核处理器的环境中,处理器内部的多个 CPU core 共享这些运行环境。这个系统级别的运行环境主要包括:
- I/O 端口,IA-32 架构提供了 64KB 的地址空间,这些地址空间是被系统中的所有 CPU core 共用的。参考 Intel 手册卷 1 第 18.3 I/O ADDRESS SPACE
- 控制寄存器,包括 5 个控制寄存器 CR0-CR4,这些控制寄存器控制着当前的运行模式,并且是否使能处理器的某些功能,64 位多一个 CR8 (task priority register,TPR)
- 内存管理寄存器 (又被称为 DTR,描述表寄存器),包括 GDTR (global descriptor table register)、IDTR (interrupt descriptor table register)、TR (task register) 和 LDTR (local descriptor table register),这些寄存器用来指明在保护模式下,内存中数据结构的位置。
- Debug 寄存器,有 8 个寄存器,监视处理器调试操作
- 内存类型范围寄存器 (MTRR),用于指明系统内存的类型和区间
- MSR (Machine specific registers),主要用于控制处理器的配置并且汇报处理器的状态
- Machine check registers,主要用于硬件错误的检测,它其实也是属于 MSR 寄存器
- Performance monitoring counters,主要用于监测处理器的性能
你说这环境像不像 "线程" 的概念?
1.2.1 通用寄存器
32 位 CPU 有 4 个 32 位的通用寄存器 EAX、EBX、ECX 和 EDX。对低 16 位数据的存取,不会影响高 16 位的数据。这些低 16 位寄存器分别命名为:AX、BX、CX 和 DX,它和先前的 CPU 中的寄存器相一致。4 个 16 位寄存器又可分割成 8 个独立的 8 位寄存器 (AX:AH-AL、BX:BH-BL、CX:CH-CL、DX:DH-DL),每个寄存器都有自己的名称,可独立存取。程序员可利用数据寄存器的这种 “可分可合” 的特性,灵活地处理字 / 字节的信息。
寄存器 AX 称为累加器 (Accumulator),用累加器进行的操作可能需要更少时间。累加器可用于乘、除、输入 / 输出等操作,它们的使用频率很高;
寄存器 BX 称为基地址寄存器 (BaseRegister)。它可作为存储器指针来使用;
寄存器 CX 称为计数寄存器 (CountRegister)。在循环和字符串操作时,要用它来控制循环次数;在位操作中,当移多位时,要用 CL 来指明移位的位数;
寄存器 DX 称为数据寄存器 (DataRegister)。在进行乘、除运算时,它可作为默认的操作数参与运算,也可用于存放 I/O 的端口地址。
32 位 CPU 有 2 个 32 位通用寄存器 EBP 和 ESP。其低 16 位对应先前 CPU 中的 BP 和 SP,对低 16 位数据的存取,不影响高 16 位的数据。BP 为基指针 (BasePointer) 寄存器,用它可直接存取堆栈中的数据;SP 为堆栈指针 (StackPointer) 寄存器,用它只可访问栈顶。指针寄存器不可分割成 8 位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果。
32 位 CPU 有 2 个 32 位通用寄存器 ESI 和 EDI。其低 16 位对应先前 CPU 中的 SI (源变址寄存器,suorce index) 和 DI (目的变址寄存器,destination index),对低 16 位数据的存取,不影响高 16 位的数据。它们主要用于存放存储单元在段内的偏移量,用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。变址寄存器不可分割成 8 位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果。它们可作一般的存储器指针使用。在字符串操作指令的执行过程中,对它们有特定的要求,而且还具有特殊的功能。
- EAX — Accumulator for operands and results data
- EBX — Pointer to data in the DS segment
- ECX — Counter for string and loop operations
- EDX — I/O pointer
- ESI — Pointer to data in the segment pointed to by the DS register; source pointer for string operations
- EDI — Pointer to data (or destination) in the segment pointed to by the ES register; destination pointer for string operations
- ESP — Stack pointer (in the SS segment)
- EBP — Pointer to data on the stack (in the SS segment)
64 位除此之外还有 R8D-R15D/R8-R15 represent eight new general-purpose registers.
1.2.2 指令指针寄存器
32 位 CPU 把指令指针扩展到 32 位,并记作 EIP,EIP 的低 16 位与先前 CPU 中的 IP 作用相同。指令指针 EIP、IP (InstructionPointer) 是存放下次将要执行的指令在代码段的偏移量。在具有预取指令功能的系统中,下次要执行的指令通常已被预取到指令队列中,除非发生转移情况。所以,在理解它们的功能时,不考虑存在指令队列的情况。
EIP 寄存器不能通过软件直接访问;它由控制传输指令(例如 JMP、Jcc、CALL 和 RET)、中断和异常隐式控制。读取 EIP 寄存器的唯一方法是执行 CALL 指令,然后从过程堆栈中读取返回指令指针的值。
1.2.3 段寄存器
段寄存器是根据内存分段的管理模式而设置的。内存单元的物理地址由段寄存器的值和一个偏移量组合而成的,这样可用两个较少位数的值组合成一个可访问较大物理空间的内存地址。
CS - 代码段寄存器 (CodeSegmentRegister),其值为代码段的段值,保存正在执行的指令,处理器使用 CS:IP 组合的逻辑地址从代码段中获取指令;
DS - 数据段寄存器 (DataSegmentRegister),其值为数据段的段值;
ES - 附加段寄存器 (ExtraSegmentRegister),其值为附加数据段的段值;
FS - 附加段寄存器 (ExtraSegmentRegister),其值为附加数据段的段值;
GS - 附加段寄存器 (ExtraSegmentRegister),其值为附加数据段的段值。DS、ES、FS 和 GS 寄存器指向四个数据段。四个数据段的可用性允许对不同类型的数据结构进行高效和安全的访问。例如,可能会创建四个单独的数据段:一个用于当前模块的数据结构,另一个用于从更高级别模块导出的数据,第三个用于动态创建的数据结构,第四个用于与另一个共享的数据程序。要访问附加数据段,应用程序必须根据需要将这些段的段选择器加载到 DS、ES、FS 和 GS 寄存器中。
SS - 堆栈段寄存器 (StackSegmentRegister),其值为堆栈段的段值;存储当前正在执行的程序、任务或处理程序的过程堆栈。所有堆栈操作都使用 SS 寄存器来查找堆栈段。与 CS 寄存器不同,SS 寄存器可以显式加载,这允许应用程序建立多个堆栈并在它们之间切换
在 16 位 CPU 系统中,它只有 4 个段寄存器 (CS, DS, SS, and ES),所以,程序在任何时刻至多有 4 个正在使用的段可直接访问;在 32 位微机系统中,它有 6 个段寄存器,所以,在此环境下开发的程序最多可同时访问 6 个段。
每个段寄存器都分为两个部分,一个是可见部分 (visible),仅包括 16bit 的段选择子;一个是隐藏部分 (hidden),包括 32bit 的基址 (base address) 表示段从哪里开始,32bit 的段长 (limit) 表示基址的长度,16bit 的访问控制信息 (attributes) 表示当前段寄存器是可读可写可执行。所以段寄存器是 96 位的
1.2.4 EFLAGS 寄存器
64 位称作 RFLAGS 寄存器,RFLAGS 寄存器的高 32 位保留,RFLAGS 的低 32 位与 EFLAGS 相同。EFLAGS 包含一组状态标志、一个控制标志和一组系统标志。在处理器初始化之后(通过置位 RESET 引脚或 INIT 引脚),EFLAGS 寄存器的状态为 00000002H。该寄存器的位 1、3、5、15 和 22 至 31 被保留。软件不应使用或依赖任何这些位的状态。

EFLAGS 寄存器中的某些标志可以使用专用指令直接修改,没有允许直接检查或修改整个寄存器的指令。以下指令可用于将标志组移入和移出过程堆栈或 EAX 寄存器:LAHF、SAHF、PUSHF、PUSHFD、POPF 和 POPFD。在 EFLAGS 寄存器的内容传输到过程堆栈或 EAX 寄存器后,可以使用处理器的位操作指令(BT、BTS、BTR 和 BTC)检查和修改标志。
当调用中断或异常处理程序时,处理器会自动将 EFLAGS 寄存器的状态保存在过程堆栈中。当使用任务切换处理中断或异常时,EFLAGS 寄存器的状态将保存在 TSS 中以用于挂起的任务。
Status flags
EFLAGS 寄存器的状态标志(bit 0、2、4、6、7 和 11)指示算术指令的结果,例如 ADD、SUB、MUL 和 DIV 指令。参考 AMD 手册卷 1 第 3.1.4 节 Flags Register
- CF (bit 0),进位标志 (Carry flag),主要用来反映运算是否产生进位或借位。如果运算结果的最高位产生了一个进位或借位,那么,其值为 1,否则其值为 0。使用该标志位的情况有:多字 (字节) 数的加减运算,无符号数的大小比较运算,移位操作,字 (字节) 之间移位,专门改变 CF 值的指令等。
- PF (bit 2),奇偶标志 (Parity flag),用于反映运算结果中 “1” 的个数的奇偶性。如果 “1” 的个数为偶数,则 PF 的值为 1,否则其值为 0。利用 PF 可进行奇偶校验检查,或产生奇偶校验位。在数据传送过程中,为了提供传送的可靠性,如果采用奇偶校验的方法,就可使用该标志位。
- AF (bit 4),辅助进位标志 (Auxiliary Carry flag),如果算术运算或二进制编码的十进制 (BCD) 运算从结果的第 3 位生成进位(在加法的情况下)或借位(在减法的情况下),则硬件设置辅助进位标志。否则,AF 清零。
- ZF (bit 6),零标志 (Zero flag),如果最后一次算术运算的结果为零,则硬件将零标志设置为 1。否则(对于非零结果),硬件将该标志清零。比较和测试指令也会影响零标志。零标志通常用于测试算术或逻辑运算的结果是否为零,或测试两个操作数是否相等。
- SF (bit 7),符号标志 (Sign flag),设置为等于最后一次运算结果的最高有效位,即有符号整数的符号位。(0 表示正值,1 表示负值。)
- OF (bit 11),溢出标志 (Overflow flag),硬件将溢出标志设置为 1,以指示最后一个有符号整数运算结果的最高有效(符号)位与两个源操作数的符号不同。否则,硬件将该标志清除为 0。设置溢出标志意味着正或负结果的幅度太大(溢出)或太小(下溢)以适合其定义的数据类型。在 DIV 指令之后和移位多于一位之后,OF 标志是未定义的。逻辑指令清除溢出标志。
DF Flag
DF (bit 10),方向标志 (Direction flag) 确定处理字符串的顺序。软件可以将方向标志设置为 1,以指定递减下一条字符串指令(LODSx、STOSx、MOVSx、SCASx、CMPSx、OUTSx 或 INSx)的数据指针。 将方向标志清除为 0 指定递增数据指针。 指针存储在 rSI 或 rDI 寄存器中。软件可以分别使用 STD 和 CLD 指令设置或清除标志。
System Flags and IOPL Field
EFLAGS 寄存器中的系统标志和 IOPL 字段控制操作系统或执行操作,它们不应被应用程序修改。参考 AMD 手册卷 2 第 3.1.6 节 RFLAGS Register
- TF (bit 8),追踪标志 (Trap),置 1 为启用单步调试模式;清零以禁用单步模式。在单步调试模式下,处理器在每条指令后生成一个调试异常。这允许在每条指令之后检查程序的执行状态。如果应用程序使用 POPF、POPFD 或 IRET 指令设置 TF 标志,则在 POPF、POPFD 或 IRET 之后的指令后会产生调试异常。
- IF (bit 9),中断使能标志 (Interrupt enable),置为 1 以启用可屏蔽中断 (INTR 中断),清零会导致处理器忽略可屏蔽中断。IF 标志不影响异常或不可屏蔽中断(NMI 中断)。CPL (当前特权级别)、IOPL (IO 权限级别) 和控制寄存器 CR4 中 VME (虚拟 8086 模式扩展) 及 PVI (保护模式虚拟中断) 标志的状态决定了 IF 标志是否可以被 CLI、STI、POPF、POPFD 和 IRET 修改。
- IOPL (bit 13:12),IO 特权等级字段 (I/O privilege level field),IOPL 字段指定执行 I/O 地址空间指令所需的权限级别(寻址 I/O 空间而不是内存映射 I/O 的指令,例如 IN、OUT、INS、OUTS 等)。当前权限级别 (CPL) 必须等于或高于(低于数值)IOPL (CPL <= IOPL) 指定的权限。如果 CPL 低于(高于)IOPL 指定的值(CPL> IOPL),则当软件尝试执行 I/O 指令时,处理器会导致一般保护异常 (#GP)。当启用虚拟 8086 模式扩展 (CR4.VME=1) 时,虚拟 8086 模式使用 IOPL 来控制虚拟中断和 IF 位。保护模式虚拟中断机制 (PVI) 还使用 IOPL 来控制虚拟中断和启用 PVI 时的 IF 位(CR4.PVI=1)
- NT (bit 14),嵌套任务标志 (Nested task),IRET 读取 NT 位以确定当前任务是否嵌套在另一个任务中。当 NT 设置为 1 时,当前任务嵌套在另一个任务中。当 NT 清除为 0 时,当前任务处于顶层(非嵌套)。处理器在调用以 CALL 指令、中断或异常启动的任务时设置此标志。它在从使用 IRET 指令启动的任务返回时检查并修改此标志。可以使用 POPF/POPFD 指令显式设置或清除该标志;但是,更改此标志的状态可能会在应用程序中产生意外异常。
- RF (bit 16),恢复标志 (Resume),主要功能是允许在由指令断点条件引起的调试异常之后重新启动指令。置为 1 时,会暂时禁用指令断点报告,以防止发生重复的调试异常 (#DB)。处理器在每条指令成功执行后清除 RF 位,除非指令是:设置 RF 位的 IRET;通过任务门执行的 JMP、CALL 或 INTn。
- VM (bit 17),虚拟 8086 模式 (Virtual-8086 mode),软件将 VM 置为 1 以启用虚拟 8086 模式,清零以禁用虚拟 8086 模式 (返回保护模式)。系统软件只能使用任务开关或 IRET 更改此位。它不能使用 POPFD 指令修改位。
- AC (bit 18),对齐检查或访问控制 (Alignment check or access control),当 CR0.AM=1 时,软件通过将 AC 位设置为 1 来启用自动对齐检查。可以通过将 AC 或 CR0.AM 清除为 0 来禁用对齐检查。当启用自动对齐检查并且当前特权级别 (CPL) 为 3(最低特权)时,对未对齐操作数的内存引用会导致对齐检查异常 (#AC)。当启用超级用户模式访问阻止功能 (CR4.SMAP=1) 时,只有当 RFLAGS.AC=1 时,才允许某些超级用户模式数据访问用户模式可访问的页面。
- VIF (bit 19),虚拟中断 (Virtual Interrupt),
- VIP (bit 20),虚拟中断挂起 (Virtual interrupt pending),由软件置位以指示中断正在挂起;清零表示没有待处理的中断。此标志与 VIF 标志结合使用。这个标志是只读的。处理器仅在控制寄存器 CR4 中的 VME 标志或 PVI 标志置位且 IOPL 小于 3 时才识别 VIP 标志。VME 标志启用虚拟 8086 模式扩展;PVI 标志启用保护模式虚拟中断。
- ID (bit 21),身份标志 (Identification),软件修改该位的能力表明处理器实现支持 CPUID 指令。
1.2.5 内存管理寄存器
当启用保护模式 (CR0.PE=1) 时,分段机制使用描述符表。这些表包含描述段的位置、大小和特权属性的描述符条目。保护模式下的所有内存引用访问一个描述符表条目。 x86 分段机制支持三种类型的描述符表:全局描述符表 (GDT),本地描述符表 (LDT),中断描述符表 (IDT)。软件通过初始化其对应的描述符表寄存器来建立描述符表在内存中的位置。

先把这些表当成一个数组,这些寄存器的作用就是存储这些数组.. 细节到内存管理再详细看。
GDTR
GDTR 寄存器保存基地址 (Base address)(在保护模式下为 32 位;在 IA-32e 模式下为 64 位)和 GDT 的 16 位表限 (table limit)。基地址指定 GDT 第 0 字节的线性地址;表限指定表中的字节数。 LGDT 和 SGDT 指令分别加载和存储 GDTR 寄存器。在处理器上电或复位时,基地址设置为默认值 0,限制设置为 0FFFFH。作为保护模式操作的处理器初始化过程的一部分,必须将新基址加载到 GDTR 中。
LDTR
LDTR 寄存器保存 LDT 的 16 位段选择器、基地址(在保护模式下为 32 位;在 IA-32e 模式下为 64 位)、段限 (Segment limit) 和描述符属性。基地址指定 LDT 段第 0 字节的线性地址; 段限指定段中的字节数。
LLDT 和 SLDT 指令分别加载和存储 LDTR 寄存器的段选择器部分。包含 LDT 的段必须在 GDT 中具有段描述符。当 LLDT 指令在 LDTR 中加载段选择器时:LDTR 描述符中的基地址、段限和描述符属性会自动加载到 LDTR 中。
IDTR
IDTR 寄存器保存基地址(在保护模式下为 32 位;在 IA-32e 模式下为 64 位)和 IDT 的 16 位表限 (table limit)。基地址指定 IDT 第 0 字节的线性地址;表限指定表中的字节数。
LIDT 和 SIDT 指令分别加载和存储 IDTR 寄存器。在处理器上电或复位时,基地址设置为默认值 0,限制设置为 0FFFFH。然后,作为处理器初始化过程的一部分,可以更改寄存器中的基址和限制。
TR
TR 寄存器保存当前任务的 TSS (task-state segment) 的 16 位段选择器、基地址(在保护模式下为 32 位;在 IA-32e 模式下为 64 位)、段限制和描述符属性。选择器引用 GDT 中的 TSS 描述符。基地址指定 TSS 第 0 字节的线性地址;段限制指定 TSS 中的字节数。 LTR 和 STR 指令分别加载和存储任务寄存器的段选择器部分。当 LTR 指令在任务寄存器中加载段选择器时,来自 TSS 描述符的基地址、限制和描述符属性会自动加载到任务寄存器中。在处理器上电或复位时,基地址设置为默认值 0,限制设置为 0FFFFH。 当任务切换发生时,任务寄存器会自动加载新任务的 TSS 段选择器和描述符。在将新的 TSS 信息写入寄存器之前,不会自动保存任务寄存器的内容。
1.2.6 控制寄存器
决定了处理器的工作模式和当前执行任务的特性,在 64 位模式下,控制寄存器扩展到 64 位。参考 AMD 手册卷 2 第 3.1 节 System-Control Registers
CR0 — 包含控制处理器的操作模式和状态的系统控制标志以及处理器特性的控制标志。
CR2 — 该寄存器由页面转换机制使用。 当页面错误异常发生时,它由处理器加载页面错误虚拟地址。
CR3 — 该寄存器也被页面转换机制使用。 它包含最高级别页转换表的基地址,还包含指定表的缓存控制。
CR4 — 该寄存器包含对各种操作模式功能的附加控制。
CR8 — 该寄存器仅在 64 位模式下可用,可以使用 REX 前缀在 64 位模式下访问。CR8 用于区分外部中断的优先级,称为任务优先级寄存器 (TPR)。
Control registers CR1, CR5–CR7, and CR9–CR15 are reserved.

参考 Intel 手册卷 2 第 2.5 节 CONTROL REGISTERS
CR0.PG Paging (bit 31 of CR0) — Enables paging when set; disables paging when clear. When paging is disabled, all linear addresses are treated as physical addresses. The PG flag has no effect if the PE flag (bit 0 of register CR0) is not also set; setting the PG flag when the PE flag is clear causes a general-protection exception (#GP).
CR0.PE Protection Enable (bit 0 of CR0) — Enables protected mode when set; enables real-address mode when clear. This flag does not enable paging directly. It only enables segment-level protection. To enable paging, both the PE and PG flags must be set.
CR3.PCD Page-level Cache Disable (bit 4 of CR3) — 置 1 时,禁止某个页写入缓存,直接写内存。比如,做页表用的页,已经存储在TLB中了,可能不需要再缓存了。This bit is not used if paging is disabled, with PAE paging, or with 4-level paging or 5-level paging if CR4.PCIDE=1.
CR3.PWT Page-level Write-Through (bit 3 of CR3) — 置 1 时,写 Cache 的时候也要将数据写入内存中。This bit is not used if paging is disabled, with PAE paging, or with 4-level paging or 5-level paging if CR4.PCIDE=1.
CR4.PSE Page Size Extensions (bit 4 of CR4) — Enables 4-MByte pages with 32-bit paging when set; restricts 32-bit paging to pages of 4 KBytes when clear.
CR4.PAE Physical Address Extension (bit 5 of CR4) — When set, enables paging to produce physical addresses with more than 32 bits. When clear, restricts physical addresses to 32 bits. PAE must be set before entering IA-32e mode.
CR4.PGE Page Global Enable (bit 7 of CR4) — Enables the global page feature when set; disables the global page feature when clear. The global page feature allows frequently used or shared pages to be marked as global to all users (done with the global flag, bit 8, in a page-directory-pointer-table entry, a page-directory entry, or a page-table entry). Global pages are not flushed from the translation-lookaside buffer (TLB) on a task switch or a write to register CR3. When enabling the global page feature, paging must be enabled (by setting the PG flag in control register CR0) before the PGE flag is set. Reversing this sequence may affect program correctness, and processor performance will be impacted.
CR4.UMIP User-Mode Instruction Prevention (bit 11 of CR4) — When set, the following instructions cannot be executed if CPL > 0: SGDT, SIDT, SLDT, SMSW, and STR. An attempt at such execution causes a generalprotection exception (#GP).
CR4.LA57 57-bit linear addresses (bit 12 of CR4) — When set in IA-32e mode, the processor uses 5-level paging to translate 57-bit linear addresses. When clear in IA-32e mode, the processor uses 4-level paging to translate 48-bit linear addresses. This bit cannot be modified in IA-32e mode.
CR4.PCIDE PCID-Enable Bit (bit 17 of CR4) — Enables process-context identifiers (PCIDs) when set. Applies only in IA-32e mode (if IA32_EFER.LMA = 1).
CR4.PKE Enable protection keys for user-mode pages (bit 22 of CR4) — 4-level paging and 5-level paging associate each user-mode linear address with a protection key. When set, this flag indicates (via CPUID.(EAX=07H,ECX=0H):ECX.OSPKE [bit 4]) that the operating system supports use of the PKRU register to specify, for each protection key, whether user-mode linear addresses with that protection key can be read or written. This bit also enables access to the PKRU register using the RDPKRU and WRPKRU instructions.
CR4.PKS Enable protection keys for supervisor-mode pages (bit 24 of CR4) — 4-level paging and 5-level paging associate each supervisor-mode linear address with a protection key. When set, this flag allows use of the IA32_PKRS MSR to specify, for each protection key, whether supervisor-mode linear addresses with that protection key can be read or written.
CR8.TPL Task Priority Level (bit 3:0 of CR8) — This sets the threshold value corresponding to the highestpriority interrupt to be blocked. A value of 0 means all interrupts are enabled. This field is available in 64-bit mode. A value of 15 means all interrupts will be disabled.
1.3 内存管理
很久以前,计算机只能运行单个进程,所以直接用物理内存地址就 ok 了,大家的程序都从 0 开始没有问题;后来计算机可以同时运行多个程序了,大家的程序还是都从 0 开始,这时候不就相互影响了吗,这时候就有了分段机制,隔离单个代码、数据和堆栈模块的机制,以便多个程序(或任务)可以在同一处理器上运行而不会相互干扰;再后来发现这种分段机制对内存的使用不够精细,因为段是一块一块的,后来就有了分页机制,按需分页虚拟内存系统的机制,其中程序执行环境的部分根据需要映射到物理内存中。
IA-32 架构的内存管理工具分为两部分:分段和分页,如下图,其中分段是必须的,分页是可选的。如果不使用分页,则将处理器的线性地址空间直接映射到处理器的物理地址空间。

1.3.1 内存模型
程序不直接寻址物理内存,需要通过下面三种内存模型之一来访问
- 平面内存模型 (Flat memory model),又称之为线性内存模型,最好理解的一种,内存对于一个程序来说就是一个单一的,连续的地址空间。代码,数据,堆栈都包含在这个空间当中。线性地址空间中任何字节的地址都被称之为线性地址。The linear address space contains all the segments and system tables defined for a system.
- 分段内存模型 (Segmented memory model),内存在程序中表现为一组称为段 (常说的代码段,数据段,堆栈段) 的独立地址空间。为寻址段中的一个字节,程序需要发出一个逻辑地址 (通常又称作远指针),由一个段选择器和一个偏移地址构成,表示查找某个段的某个字节。在 IA-32 处理器上运行的程序最多可以寻址 16383 () 个不同大小和类型的段,每个段可以大到byte。在内部,为系统定义的所有段都映射到处理器的线性地址空间。为了访问内存位置,处理器因此将每个逻辑地址转换为线性地址。这种转换对应用程序是透明的。使用分段内存的主要原因是为了增加程序和系统的可靠性。例如,将程序的堆栈放在单独的段中可以防止堆栈分别增长到代码或数据空间并覆盖指令或数据。
- 实地址模式内存模型 (Real-address mode memory model),8086 的内存模型,是用分段特性实现的。段是 64KB (数据总线只有 16 根,) 固定大小,但是地址线有 20 根,所以最大线性地址空间是 1MB ()。16 位的段寄存器必然访问不到 1M 的空间,所以也是采用 “段基址 + 段内偏移地址” 访问的

这里有线性地址,逻辑地址了,还缺一个物理地址,就是挂载 CPU 地址总线上的内存空间划分的地址呗,就和 8086 的 1M 是一个道理,这个前面也提到了 32 位一般 36 根,64 位是 46 根,但是想要使用扩展的地址线,需要将 CR4.PAE 置 1 或者 CR4.PSE 置 1。
逻辑地址翻译成线性地址,处理器需要:
1 使用段选择子 (segment selector) 中的偏移量 (offset) 在 GDT 或 LDT 中定位段的段描述符 (segment descriptor) 并将其读入处理器(仅当新的段选择器加载到段寄存器中时才需要此步骤)
2 查看段描述符以检查段的访问权限和范围,以确保该段可访问且偏移量在该段的限制内。
3 将段描述符中的段基地址与偏移量相加,形成一个线性地址。

如果不使用分页,则处理器将线性地址直接映射到物理地址(即线性地址在处理器的地址总线上输出)。如果线性地址空间被分页,则使用第二级地址转换将线性地址转换为物理地址。
再说这个段选择子,A segment selector is a 16-bit identifier for a segment. 它不直接指向段,而是指向定义段的段描述符。由 Index,Table Indicator,Requested Privilege Level 三项组成

Index(bit 15~3),选择 GDT 或 LDT 中的 8192 () 个描述符之一 ()。处理器将索引值乘以 8(段描述符中的字节数)并将其结果加上 GDT 或 LDT 的基地址(分别来自 GDTR 或 LDTR 寄存器)
TI(bit 2),指定使用哪个描述表,0 是 GDT,1 是 LDT
RPL(bit 1~0),指定选择子的权限级别。特权级别的范围可以从 0 到 3,其中 0 是最高特权级别。
处理器不使用 GDT 的第一个条目。指向 GDT 的该条目的段选择子(即索引为 0 且 TI 标志设置为 0 的段选择子)用作 “空段选择子”。当段寄存器(除了 CS 或 SS 寄存器)加载有空选择子时,处理器不会产生异常。但是,当使用包含空选择子的段寄存器访问内存时,它确实会产生异常。空选择子可用于初始化未使用的段寄存器。使用空段选择子加载 CS 或 SS 寄存器会导致生成一般保护异常 (#GP)。段选择子作为指针变量的一部分对应用程序可见,但选择子的值通常由链接编辑器或链接加载器而不是应用程序分配或修改。
1.3.2 段描述符
这段直接看 Intel 手册卷 3 第 3.4.5 Segment Descriptors 就好了..
为什么只能同时使用 6 个寄存器?
For a program to access a segment, the segment selector for the segment must have been loaded in one of the segment registers. So, although a system can define thousands of segments, only 6 can be available for immediate use. Other segments can be made available by loading their segment selectors into these registers during program execution.
段描述符,当然就是描述段的了。当段选择子被加载到段寄存器的可见部分时,处理器还会从段选择子指向的段描述符中加载段寄存器的隐藏部分:基地址、段限和访问控制信息。两类指令可以加载段选择子
- Direct load instructions such as the MOV, POP, LDS, LES, LSS, LGS, and LFS instructions. These instructions explicitly reference the segment registers.
- Implied load instructions such as the far pointer versions of the CALL, JMP, and RET instructions, the SYSENTER and SYSEXIT instructions, and the IRET, INT n, INTO, INT3, and INT1 instructions. These instructions change the contents of the CS register (and sometimes other segment registers) as an incidental part of their operation.
The MOV instruction can also be used to store the visible part of a segment register in a general-purpose register.
接下来才是主题,段选择子且当成一个数组的 index,那么段描述符就是这个数组中的一项了。具体的定义是:A segment descriptor is a data structure in a GDT or LDT that provides the processor with the size and location of a segment, as well as access control and status information. 段描述符通常由编译器、链接器、加载器或操作系统或执行程序创建,而不是由应用程序创建。

首先,我们知道段寄存器有 96 位,减去 16 位的段选择子,还剩 80 位,而 attribute 对应的就是高 4 字节的 23:8 (凑成 16bit);而 base 是三段 (低 4 字节 31:16+ 高 4 字节 7:0+ 高 4 字节 31:24) 凑成 32 位;而 limit 由低 4 字节 15:00+ 高 4 字节 19:16,才 20bit,也就是还缺 12bit,这缺的根据 G flag,在高位补 0,或在低位补 1。
Base Address (高 4 字节 bit 31:24+ 高 4 字节 bit 7:0+ 低 4 字节 bit 31:16)
将三个基地址字段凑一块形成一个 32 位的值,定义段的字节在 0~4GB 线性空间的位置
Segment Limit (高 4 字节 bit 19:16+ 低 4 字节 bit 15:0)
用于指定段的大小 (应该是能访问段的大小),由两截组成,高四位 19:16 加上低四位 15:00,凑成 20 位。IA-32 architecture stacks always grow downwards, making this mechanism convenient for expandable stacks.
G flag (高 4 字节 bit 23)
limit 的粒度 (granularity) 标志。置 0,limit 以 byte 为单位,20 位的 limit 则寻址范围是 1MB (0x000FFFFF);置 1,limit 以 4KB 为单位,20 位的 limit 则寻址范围是 4GB (0xFFFFFFFF)
D/B flag (高 4 字节 bit 22)
根据段描述符是代码段、向下扩展数据段还是堆栈段执行不同的功能。(对于 32 位代码和数据段,此标志应始终设置为 1,对于 16 位代码和数据段,应始终设置为 0。)
- Executable code segment:这个 flag 称为 D flag,表示段中指令引用的有效地址和操作数的默认长度。置 1,假定为 32 位地址和 32 位或 8 位操作数 (采用 32 位寻址方式);置 0,假定为 16 位地址和 16 位或 8 位操作数 (采用 16 位寻址方式)。指令前缀 66H 可用于选择默认值以外的操作数大小,前缀 67H 可用于选择默认值以外的地址大小
- Stack segment (data segment pointed to by the SS register):这个 flag 称为 B flag,指定用于隐式堆栈操作(例如入栈、出栈和调用)的堆栈指针的大小。B=1,隐式堆栈访问指令(如:PUSH POP CALL)使用 32 位堆栈指针寄存器 ESP;B=0,隐式堆栈访问指令(如:PUSH POP CALL)使用 16 位堆栈指针寄存器 SP。
- Expand-down data segment:这个 flag 称为 B flag,指定段的上限。B=1,上限为 0xFFFFFFFF (4GB);B=0,上限为 0xFFFF (64KB)
L flag (高 4 字节 bit 21)
值为 1 时,以 64 位模式执行代码;值为 0 时,以兼容模式执行。当不在 64 位模式下或对于非代码段时,bit 21 是保留的,应始终设置为 0。如果设置了 L 位,则必须清除 D 位
AVL flag (高 4 字节 bit 20)
供系统软件使用或作为保留段
P flag (高 4 字节 bit 15)
置 1 表示该段存在内存中;置 0 表示该段不在内存中。(如果 P=0,其他标志都不用看了,不起作用)
DPL field (高 4 字节 bit 14:13)
表示段描述符特权级,只有想要加载这个段描述符的选择子的请求特权级别高于或者等于 DPL 才能加载这个段
S flag (高 4 字节 bit 12)
置 0,表示系统段描述符;置 1,表示代码段或数据段
Type field (高 4 字节 bit 11:8)
用于指定该段的访问类型 (读写执行) 和增长方向。代码,数据,系统段的编码方式不一样
也就是说,这里描述符根据 S flag 和 Type Field 分为好几种,也就是

当 S=1 (bit 12),为数据段或代码段,这个时候从 type 的取值 (bit 11) 可以区分是 data 还是 code:

当 S=0,表示系统描述符:Local descriptor-table (LDT) segment descriptor,Task-state segment (TSS) descriptor,Call-gate descriptor,Interrupt-gate descriptor,Trap-gate descriptor,Task-gate descriptor

再来说特权等级:CPL,DPL,RPL,参考 Intel 卷 3 第 5.5 PRIVILEGE LEVELS
CPL,Current privilege level。它存储在 CS 和 SS 段寄存器的位 0 和 1 中。通常,CPL 等于从中提取指令的代码段的特权级别。也就是 CS 或 SS 的段选择子中的 RPL
DPL,Descriptor privilege level。访问该段所需要的特权级别是什么
RPL,Requested privilege level。用什么权限去访问一个段
1.3.3 分页机制
Intel 手册卷 3 CHAPTER 4 PAGING
通过上面一通操作,已经拿到了一个线性地址了,现在需要通过分页模型将这个线性地址与物理地址对应起来。简单介绍下分页的思想
一级分页,假设将线性地址分为两部分 table (页表),offset (页内偏移),然后能页表里面包含 entry (页项),每个 entry 存的是一个起始地址 (一个起始地址是 32 位也就是 4B),CPU 从 table 中拿到对应的 entry 存储物理地址加上偏移,就得到实际的物理地址了。假设 offset 的范围是 0~4KB,那么 offset 需要 12 位 ( ) 来表示,假设是 32 位 PC,剩下的 20 位用来表达 entry,那么此时一个 32 位的线性地址能够表达 ,可以映射 4G,但是需要 的物理内存来保存 entry
二级分页,将线性地址分为三部分 directory (页表目录),table (页表),offset (页内偏移),假设 offset 的范围还是 0~4KB 用 12 位来表达,假设剩下来的 20 位,directory 和 table 各占 10 位,那么此时一个 32 位线性地址能够表达 ,但是只需要 来保存整个页表关系
N 级页表..

通过上面解释可以看到,同样的 32 位线性地址,划分不同级别的页表所需要存储的页表关系的物理内存不一样,简单来说高级的分页占用的物理内存越小,但是 CPU 访问次数越多
Intel 手册中提到了四种分页模式:32bit paging,PAE paging,4-level paging,5-level paging。其中前两个专门用于 32 位,后面两个专门用于 64 位

可以看到,经典的 page size 就是 4KB 了,至于每个 entry 存储的细节就不看了.. 具体的去手册找好了
分页级数太高会导致 CPU 处理变慢,解决效率问题,那就只有加缓存,于是有个新的东西 TLB (Translation Lookaside Buffers)。首先了解几个名词:线性地址的高位(称为页号)决定了物理地址(称为页帧)的高位;线性地址的低位(称为页偏移)决定了物理地址的低位。页码和页偏移量之间的边界由 page size 决定。而 TLB 就是存储页号和页帧的映射。
2 中断机制
The processor provides two mechanisms for interrupting program execution, interrupts and exceptions: • An interrupt is an asynchronous event that is typically triggered by an I/O device. • An exception is a synchronous event that is generated when the processor detects one or more predefined conditions while executing an instruction. The IA-32 architecture specifies three classes of exceptions: faults, traps, and aborts.
处理器提供了两个打断程序执行的机制,中断和异常
- 中断是异步事件,通常由 IO 设备触发。Interrupts occur at random times during the execution of a program, in response to signals from hardware. System hardware uses interrupts to handle events external to the processor, such as requests to service peripheral devices. Software can also generate interrupts by executing the INT n instruction.Interrupts occur at random times during the execution of a program, in response to signals from hardware. System hardware uses interrupts to handle events external to the processor, such as requests to service peripheral devices. Software can also generate interrupts by executing the INT n instruction.
- External (hardware generated) interrupts:外部中断通过处理器上的引脚或通过本地 APIC (Advanced Programmable Interrupt Controller) 接收。当本地 APIC 启用时,LINT [1:0] 引脚可以通过 APIC 的本地向量表 (LVT) 进行编程,以与任何处理器的异常或中断向量相关联。当本地 APIC 全局 / 硬件禁用时,这些引脚分别配置为 INTR 和 NMI 引脚。通过 INTR 引脚或通过本地 APIC 传递给处理器的任何外部中断称为可屏蔽硬件中断,EFLAGS 寄存器中的 IF 标志允许将所有可屏蔽的硬件中断作为一组进行屏蔽。通过 NMI 引脚以及系统总线或 APIC 串行总线上以 NMI 传递模式传递给处理器的任何外部中断称为不可屏蔽中断,EFLAGS 寄存器的 IF 标志对这个无效
- Software-generated interrupts:通过 INT 指令 + 中断号直接触发对应的中断
- 异常是同步事件,处理器在执行指令时检测到一个或多个预定义条件生成的。异常分为三类:错误,陷阱,中止。Exceptions occur when the processor detects an error condition while executing an instruction, such as division by zero. The processor detects a variety of error conditions including protection violations, page faults, and internal machine faults.
- Program-Error Exceptions:The processor generates one or more exceptions when it detects program errors during the execution in an application program or the operating system or executive.
- Software-Generated Exceptions: The INTO, INT1, INT3, and BOUND instructions permit exceptions to be generated in software.
- Machine-Check Exceptions:
无论是外部中断,INT 指令,触发异常,都是为了给 CPU 传一个中断号 (也叫向量号)。为了帮助处理异常和中断,每个架构定义的异常和每个需要处理器特殊处理的中断条件都被分配了一个唯一的标识号,称为向量号。向量号的范围是 0~255,其中 0~31 被系统保留,32~255 可以由用户自己定义。
当处理器拿到向量号后会以此作为索引去中断描述符表 (IDT) 查询对应的中断描述符,通过计算中断描述符得到处理异常或中断程序的入口点,之后处理器会挂起当前执行的程序或任务,执行中断或异常处理,执行完毕后再唤醒之前执行的程序
INTR 只有一个引脚,怎么知道各种外设的中断呢?这个引脚级联了一堆中断控制器 8259A.. 详情百度一下
2.1 中断描述符表
interrupt descriptor table,就是一个大小为 256 的数组,这个数组保存的中断描述符。操作系统将这个数组保存到内存中,再通过 LIDT 指令将内存操作数中保存的基地址和限制 (可以理解成中断描述表在内存的起始地址) 加载到 IDTR 寄存器中。该指令只有在 CPL 为 0 时才能执行,一般操作系统的初始化代码在创建 IDT 时使用。通过 SIDT 指令将存储在 IDTR 中的基值和限值复制到内存中。
根据中断向量号去 IDT 中索引中断描述符,具体操作:取出 IDTR 中的 IDT 地址,加上中断向量号 * 8,得到的地址指向所要的中断描述符
就是下图这个感觉..

再说这数组中的数据中断描述符,分为三种:Task-gate descriptor,Interrupt-gate descriptor,Trap-gate descriptor

看起来和段描述差不多,其中 task gate 就与 GDT 或 LDT 中的 task gate 操作是差不多的;interrupt gate 和 trap gate 与 call gate 差不多,中断门和陷阱门的区别是中断门不允许中断嵌套,处理中断的时候将 EFLAGS.IF 置 1,屏蔽中断,陷阱门允许中断嵌套,不处理 EFLAGS.IF 值
也就是说从 IDT 中拿到中断描述符大致有两种处理方式,一种是类似任务门的 (Intel 手册卷 3 第 7.3 TASK SWITCHING),一种是类似调用门的 (Intel 手册卷 3 第 5.8.2 Gate Descriptors 到 5.8.6 Returning from a Called Procedure)。这两种方式都已经能够拿到处理异常或中断程序的入口地址了
2.2 中断 / 陷阱门
根据中断描述符中的段选择子去 GDT 或 LDT 中索引段描述符,具体操作:取出 GDTR/LDTR 中的 GDT/LDT 地址。加上段选择子高 13 位 * 8,得到的地址为中断处理程序所在段的段基址;段基址加上中断描述符的段内偏移量得到的地址便为中断服务程序的地址。就是下图这个感觉:

拿到中断程序地址之后呢?最简单的办法就是,直接把中断描述符里的中断程序地址取出来,放在自己的 CS:IP 寄存器中,因为这里存的值就是下一跳指令的地址,只要放进去了,到下一个 CPU 指令周期时,就会去那里继续执行了,但 CPU 并没有这样简单粗暴。CPU 据中断向量号去 IDT 中获取中断描述符,取出选择子中的 DPL 与当前特权级 CPL 进行比较,若特权级发生变化,则需要切换栈。(不同特权级有着不同的栈,如 Linux 使用了 0,3 特权级,则有两个栈,一个内核栈,一个用户栈)
a. The segment selector and stack pointer for the stack to be used by the handler are obtained from the TSS for the currently executing task. On this new stack, the processor pushes the stack segment selector and stack pointer of the interrupted procedure. b. The processor then saves the current state of the EFLAGS, CS, and EIP registers on the new stack (see Figure 6-4). c. If an exception causes an error code to be saved, it is pushed on the new stack after the EIP value.
处理器临时保存当前的旧栈 SS 和 ESP 的值,从 TSS(每一个任务有一个 TSS 结构,其中保存着不同特权级栈的 SS 和 ESP 值)中获取与 DPL 特权级相同的栈信息加载到 SS 和 ESP 寄存器。再将旧栈 SS 和 ESP 的值压入新栈中。若没有特权级变化,则跳过此步骤。
然后处理器将 EFLAGS、CS 和 EIP 寄存器的当前状态保存在新堆栈上。
若该中断有错误码,压入错误码。

要从异常或中断处理程序过程返回,处理程序必须使用 IRET(或 IRETD)指令。IRET 指令与 RET 指令类似,只是它将保存的标志恢复到 EFLAGS 寄存器中。EFLAGS 寄存器的 IOPL 字段只有在 CPL 为 0 时才恢复。只有在 CPL 小于或等于 IOPL 时才改变 IF 标志。如果在调用处理程序过程时发生堆栈切换,则 IRET 指令在返回时切换回被中断过程的堆栈。
2.3 任务门
这个在 Linux 上貌似没有使用.. 手册上提到使用 task gate 有三个优点
- 被中断的程序或任务的整个上下文会自动保存
- 新的 TSS 允许处理程序在处理异常或中断时使用新的特权级别 0 堆栈。如果在当前权限级别 0 堆栈损坏时发生异常或中断,通过任务门访问处理程序可以通过为处理程序提供新的权限级别 0 堆栈来防止系统崩溃
- 处理程序可以通过给它一个单独的地址空间来进一步与其他任务隔离。这是通过给它一个单独的 LDT 来完成的
优点即是缺点,使用单独的任务处理中断的缺点是必须在任务切换时保存的机器状态量使其比使用中断门慢,从而导致中断延迟增加。
这个逻辑与普通的 task 切换相同,错误码会被拷贝到新的 TSS 中..
