操作系统启动 CPU模式

Operating Modes

image-20241219213730449

  • x86-64 架构的处理器正常工作在 Long Mode,支持 64 位 OS/UEFI,有两个子模式
    • 64-bit Mode: 只能运行 64 位软件,32 位软件需要重新编译
    • Compatibility Mode: 兼容 32 位和 16 位保护模式软件,不支持实模式/虚拟86
  • IA-32 或 x86 架构的处理器(i286后)正常工作在 Protected Mode,支持 32 位 OS/UEFI
    • Protected Mode: 支持运行 32 位 和 16 位保护模式的软件
    • Virtual 8086 Mode: 类似 Compatibility Mode,可直接向下兼容运行 real mode 软件
  • 8086 处理器的 Real Mode,最高支持 16 位的操作系统,只能运行实模式软件

image-20241220141424897

  • 80186 和早期的 CPU 仅仅只有一种操作模式,也就是相当于后来芯片的这种 Real Mode;

  • 80286 和之后的 x86 CPU 都是以 Real Mode 开机,然后经过 BIOS/UEFI, Bootloader 等引导程序切换到 Protected Mode 或 Long Mode,以便运行 32 或 64 位的操作系统。

  • 启动操作系统之后,通常是在对应模式下运行,如果要运行向前兼容的程序只能使用子模式,切换模式需要重新初始化 CPU 代价太大

Real Mode:实模式

80286 以前:

Intel 80186Intel针对工业控制通信等嵌入式市场,于1982年推出的8086处理器的扩展产品,除8086内核,另外包括了中断控制器、定时器、DMA、I/O、UART、片选电路等外设。

实模式,Real mode[Real-Address Mode],是Intel 80286和之后的x86兼容CPU的操作模式。实模式的特性是20位寻址空间,最大寻址空间1MB,最大分段64KB,可以直接软件访问BIOS以及周边硬件,没有任何硬件等级的保护观念或多任务支持。所有的80286系列和之后的x86 CPU都是以实模式下开机;80186和早期的CPU仅仅只有一种操作模式,也就是相当于后来芯片的这种实模式。CPU 复位(reset)或加电(power on)的时候以实模式启动。

实模式出现于早期 8088 CPU 时期。当时由于 CPU 的性能有限,一共有 20 位地址线(所以地址空间只有1MB),以及 8 个 16 位的通用寄存器,以及 4 个 16 位的段寄存器。16 位寄存器只能支持64KB的线性地址空间,需要使用另外一个寄存器配合才能利用所有的地址线,因此这种管理内存的方式称为段式管理(segmentation)由于 80286 以前只有实模式一种,当时并不叫实模式,286 以后出现保护模式才给以前这个模式取名叫实模式,而硬件上有一定改进,因此 8086 和 80286 的实模式还有有一些细微区别的。详见A20 Gate

x86 寄存器

图中绿色标记为 8086 的 4 个段寄存器,还有剩下的 16 位寄存器

image-20241221225031752

  • 4 个段寄存器 CSSSDSES,用来描述特定段的基址,不能混用,都是 16 位;
  • 1 个指令指针寄存器 IP , 用于和 CS 组成 CS:IP 逻辑地址,指向下一条要执行的指令,16 位;
  • 8 个通用寄存器,其中 SP 一般固定用于保存堆栈指针,其他可以任意混用,16 位;
  • 1 个程序状态字 FLAGS(PSW, Program Status Word) 16 位,保存当前程序执行的一些状态和结果的某些信息

分段(80286 之前)

img

image-20241220001927471

image-20241220141646817

当某个指令想要访问某个内存地址时,它通常需要用下面的这种格式来表示:(段基址:段偏移量)

  • 段基址:它的值是由段寄存器提供的(一般来说,段寄存器有6种,分别为cs,ds,ss,es,fs,gs,这几种段寄存器都有自己的特殊意义,这里不做介绍)

    • 段寄存器除了有 16 位的可见部分,还有不可见的隐藏部分:描述符缓存“descriptor cache”或隐藏寄存器“shadow register” 当一个段选择子装入段寄存器的可见部分,处理器同时也把它指向的段表内容缓存cache中,避免在翻译逻辑地址时花费额外的开销去访问段表。处理器指令中可以明示使用哪些段寄存器,这将替换掉默认使用的段寄存器。
  • 段内偏移量:代表你要访问的这个内存地址距离这个段基址的偏移。它的值由通用数据寄存器来提供的,所以也是 16 位。那么两个 16 位的值如何组合成一个20位的地址呢?CPU采用的方式是把段寄存器所提供的段基址先向左移4位。这样就变成了一个20位的值,然后再与段偏移量相加。段偏移量16位,因此最大分段为 64 KB

  • 物理地址 = 段基址 << 4 位 + 段内偏移

    • 段寄存器是0xff00,段偏移量为0x0110,物理地址 0xff00<<4 + 0x0110 = 0xff110

实模式的”实”更多地体现在其地址是真实的物理地址(Real-Address Mode)

段基址 + 偏移,Segmentation 分段的雏形,逻辑地址

局限性

由于程序可以任意修改当前的 CS/DS 值,所有程序可以使用全部 1 MB 的内存,所以这个CPU几乎没有办法有效地支持多任务,因为两个程序一起运行的话很容易互相踩到内存。所以当时的使用的方式系统中同时运行的只有一个应用程序和一个DOS操作系统。操作系统和应用规定了各自能使用的内存地址范围,比如说DOS只使用高 64KB 的内存,其它的内存给应用程序使用。这样就可以互不影响。要想运行另一个应用程序必须先退出当前运行的应用程序。

A20 Gate

在 8086 时代使用CS<< 4 + IP计算物理地址, 从理论上讲,最大可以表示的数值是 0xFFFF0 + 0xFFFF = 0x10FFEF,即大约1M+64KB-16B,然而由于当时的地址线只有 20 根(A0~A19),这个地址最前面的1无法被表示,当CS=0xFFFF时,实际访问的地址0x10FFEF就变成了0xFFEF,这也导致当时程序编写者为了适应这个问题使用了特殊的技巧。到了80286,地址线变成24位,此时0x10FFEF可以访问到了。为了兼容性考虑,由A20 Gate来控制第21根地址总线的开关。能够在实模式下增加了对额外 65,520 字节(64 KB - 16 字节)内存的访问,而无需进行重大软件更改。

  • 开关打开:实模式能访问10000-10FFEF的高地址
  • 开关关闭:实模式无法访问10000-10FFEF,保护模式只能访问 0到1M,2M到3M,寻址空间减少一半。

另外实模式和8086还有中断向量的区别,详见虚拟 86 模式

Protected Mode:保护模式

80286 到 80386 开始:
保护模式,Protected Mode,内存保护模式,寻址采用32位段和偏移量,最大寻址空间为4GB,最大分段4GB 。保护模式拥有内存保护,分页系统,以及硬件支持的虚拟内存等功能,支持抢占式多任务调度,CPU 特权模式。在保护模式下,进行寻址时,段寄存器值不再被简单的解析为段基址,而是全局/局部描述符表(GDT/LDT)的索引,也即是所谓的段选择子。

80286 开始支持保护模式,但是寄存器仍然是 16 位,属于 16 位的保护模式。

80386 以后,CPU 寄存器变成 32 位,IA-32 的保护模式寻址发生了一定变化: 地址线的个数从原来的20根变为现在的32根,所以可以访问的内存空间也从 1 MB 变为 4 GB。实模式下的内存地址计算方式就已经不再适合了。

80286 保护模式(16-bit)

undefined

80286 新特性

  • Intel 80286地址总线增加到 24 位,物理最大可寻址空间为 2^24^(即16 MB)

  • 寄存器:

    • 通用寄存器的位数仍为 16 位,只能使用段式管理,增加了保护模式通过段表间接访存

    • 引入了 机器状态字 MSW(Machine Status Word)寄存器用来控制处理器整体的状态,比如保护模式与实模式的切换

    • 引入 GDTR LDTR IDTR TR,工作在保护模式,为分段服务,是多任务实现的基础

  • 80286 保护模式下的应用程序能访问的内存线性地址空间仅为 64 KB,非常有限。所以程序员编写使用大内存的应用程序时还必须使用远指针、近指针,相当繁琐。这影响了 80286 保护模式的推广使用。

x86 分段

Descriptor Table:描述符表

image-20241220142126857

在80286中,CS/DS/ES/FS寄存器存储的内容变成了选择子。使用段表管理之后,CPU 使用的就是逻辑地址(段选择子+偏移量),经过段表翻译才能有实际物理地址,而段描述符表只有系统内核才能修改。这就保证了一个进程只能访问内核分配给他的段上的物理内存。寻址时,依然是 base and bound 的思想,只不过要先去段表中查找段表项,里面有对应段的物理地址以及界限以及权限位,这里就体现出了虚拟内存的保护作用,之前偏移量受位数限制,现在偏移量不能超过界限,并且必须通过权限鉴别。

下图为段表(描述符表)的基本情况:共 3 个,可直接访问的有 GDT 与 LDT 两个,IDT 是中断表,里面的描述符指向的都是特定的段,也叫 Gate

image-20241220152155629

选择子一共有 16 位:

image-20241220175738856

  • 高 13 位是段表的 index;

  • TI(Table Index)为第 2 位,表示选择 GDT 还是 LDT,有专门的 GDTR、LDTR 寄存器保存段表基址 STBaseAddress。

    image-20241220142058130

    • LDT 存放在 GPT 的 LDT 类型描述符中,LDT 本身是一个段,而 GDT 不是一个段

    • 访问 LDT 需要使用段选择子,为了减少访问 LDT 时段转换的次数,LDT 的段选择符,段基址,界限都要放在 LDTR 寄存器之中。

    • GDT 本身不是一个段,而是线性地址空间的一个数据结构;GDT 的线性基地址和长度必须加载进 GDTR 之中。因为每个描述符长度是8,所以 GDT 的基地址最好进行8字节对齐。

    • 段寄存器仍然有之前类似 TLB 的 缓存 机制,有可见和不可见两个部分:

      image-20241220180035716

  • 0 - 1 为权限位(RPL) RPL 称为请求权限级别。

    image-20241221224507677

    • CPL 是当前正在执行的代码段的特权级(CS 寄存器的低 2 位)
      • 0 和 3 分别表示用户态和内核态.中间是驱动程序的优先界别
      • CPL只在代码段改变时改变,即跳转指令 JMP CALL
    • RPL 是对于一个段的请求特权级别
    • max{RPL,CPL} < DPL 方可访问此段

地址翻译

image-20241220180347195

image-20241220141603315

img

STEntry Address = STBaseAddress + 8 * index DTEntry 的大小 = 每条目 8 字节

Descriptor(DTEntry) 中含有段基址 界限 DPL 等 物理地址 = 段基址 + 偏移

CS:IP 组合称为逻辑地址,CS 唯一对应到段表的一个条目,应用程序内存不够用时,需要调用一些系统调用,让 DOS 分配一段内存,把这段内存的 base, limit 做成一个条目(Descriptor)加入到GDT或LDT中, 只有OS能更改CS,如果用户擅自更改CS,段表中找不到对应条目,会发生segmentation fault。逻辑地址一共有13+1=14位有效,偏移16位,因此虚拟内存 1 GB。但是地址线数量限制了物理内存大小最大 16 MB。

基于这种内存管理方式,用户应用程序可以实现动态链接。比如说一个程序分为代码段、数据段、零初始化段等,它依赖的库也是分段的,系统在加载程序时,只需为每个段分配一段内存,并为每个段设置一个描述符即可。 每个段的起始地址可以在加载时根据实际情况修改。

为了区分不同段的功能,可以在TYPE字段设置,比如代码段可读可执行,数据段可读可写等。

image-20241220180319764

工作流程

为了加深理解,用一个简单的指令执行流程来说明:

  1. 取指:CPU 从 CS:IP 逻辑地址 获取指令的物理地址,取指令(16bit),CS不变,IP+2;
    • CS 此时就是一个选择子,只要代码段无变化,当前指令的执行权限就不变
  2. 译码:翻译指令,指令被解析为 MOV AX, [BX] 操作数的逻辑地址 DS:BX 算出物理地址
  3. 执行:从物理地址取数,将 取来的数存到 AX 通用寄存器

IA-32 保护模式(32-bit)

80386 保护模式

  1. 首次在 x86 处理器中实现了 32 位系统(IA-32);

  2. 可配合使用 80387 数字辅助处理器增强浮点运算能力;

  3. 首次采用 高速缓存(外置)解决内存速度瓶颈问题;

  4. 在 IA-32 保护模式下,CPU 的 32 条地址线全部有效,可寻址高达 4 GB 的物理地址空间;

    Descriptor 的变化,可以看到变成 32位 基地址:

    img

  5. 寄存器变化:

    1. 在原来的四个段寄存器的基础上引入两个通用数据段寄存器 FS 和 GS;

    2. 除了段寄存器,其他寄存器全部升级到 32 位,名称加前缀 E,代表扩展;

    3. 将 80286 引入的 16 位 MSW 扩展为几个 32 位控制寄存器 CRx 用于控制机器特性。比如实模式、保护模式的切换以及分页机制的开启(CR0)页表的物理地址(CR3),相对静态,初始化或特性切换时才改动,因此只有内核态可访问,以及还有用于调试的DRx寄存器;

      image-20241220175501376

    4. 保护模式下的分段机制使用的寄存器 GDTR IDTR LDTR TR 有一些变化

扁平内存模型

最初的 Flat Model:

8086 以前,地址总线和数据寄存器只有 16 位,线性地址等于物理地址,最多支持64 KB的内存

Real-Address Mode model: 实模式分段

1978 年的 8086 开始引入了内存分段,这使得 16 位 CPU 可以访问超过 64 KB (65,536字节)的内存,实际上 8086 CPU到内存的地址总线是 20 位,即可访问2^20^=1MB内存。

在 16 位模式,要让应用程序使用多个存储器分段(能够访问大于64K的内存)相当复杂。根源在于:数据总线位数少于地址总线,并且没有适当的地址算术指令适合做整个存储器范围的平面寻址,平面寻址方式也可以用像实模式那样的两个寄存器配合的乘法操作完成,但这会导致较慢的程序执行速度。并且 8086 只支持固定大小的段,这就引出了真正的分段机制

Segmented Model: 保护模式分段

1982 年面世的 80286 不再将段寄存器左移 4 位作为段基址,而是索引到段表中获取段基址,这就是虚拟地址。

image-20241220002439505

分段机制有固有的问题:处理器的实模式与保护模式,以及 80386 推出的虚拟 86 模式,分段最大 64 KiB(使用 16 位索引寄存器)。在实模式下的分段架构的内存空间会有所重叠,这是一种不好的设计。

32-bit Flat Memory Model: 32 位分页

1985 年面世的 80386 及其后续处理器的 32 位保护模式下,一个分段长度上限是2^20^个粒度单位,粒度可以是 1 字节或 4K 字节(一页),因此分段长度上限可以是 4 GB,与 32 位数据寄存器匹配。随着 32 位操作系统的推出,以及更舒适的 32-bit Flat Memory Model,到 1990 年末期几乎淘汰了使用分段寻址,转而使用分页寻址。

然而使用 32-bit Flat Memory Model 最多只能访问 4 GB 的线性地址空间,这种限制并没有远离日常。此时,分段机制可以支持更多根地址线,比如奔腾Pro, 2, 3在 IA-32 的架构下拥有 36 条地址线,最大64 GB的内存,就靠分段的支持,但这种最终回归到分段的尴尬,经常被引述为朝着 64 位处理器发展的动机。

真正的 Flat Memory Model: 64 位分页

2003 年问世的 x86-64 架构下,强制实现了 Flat Memory Model 这种最简明有效的寻址模型,但保留了使用段寄存器 FS 或 GS 的 64 位下的分段寻址。

image-20241220153250680

分页:Paging

undefined

image-20241219214149653

页表:采用二级页表,10+10+12 划分

多进程:每个进程有一个页表,页表的物理地址存储在 CR3 寄存器

在Intel 80386及以后的版本中,保护模式保留了 80286 保护模式的分段机制,但增加了分页单元作为分段单元和物理总线之间的第二层地址转换。

  1. 逻辑地址是 48 位,16 位属于段号,32 位偏移量,段表项中的段基址也是 32 位
  2. 应用程序寻址首先根据段号和段表基址定位到段表项,段的基地址加上偏移量算出线性地址
  3. 若关闭分页单元,段基址就是物理地址,直接送到地址总线上进行访存。
  4. 若启用分页单元,段表项存储的段基址是线性地址,而不是 80286 那样的物理地址。分页单元负责最终查询页表将这些线性地址转换为物理地址。

80386 分页内存管理,比 80286 保护模式寻址具有更多的优点:

  • 操作系统可以控制与限制进程对页面的访问权限
  • 为应用程序创造一个连续的、独立的、线性的虚拟内存空间
  • 页面可以移出主存,存入更慢速的次级外存硬盘。这使得操作系统可以使用比物理内存更大的存储空间。

CR0: 开启特性

  1. 通过清除 CR0 控制寄存器中的最低位,可以返回实模式,但这是一项特权操作,以增强安全性和鲁棒性。相比之下,80286 只能通过强制处理器重置来返回实模式,例如由三重故障或使用外部硬件。
  2. 控制寄存器 CR0 中的位 0 用 PE 标记,控制分段管理机制的操作,所以把它们称为保护控制位。 PE 控制分段管理机制。 PE=0,处理器运行于实模式; PE=1,处理器运行于保护方式。
  3. 是否启用分页由 CR0 的位 31 标记

开启保护模式

进入保护模式前,必须初始化 GDT,并最少包含三个描述符:空描述符、CS 描述符以及 DS 描述符。并把(全局描述符表的所占用的字节数-1)和 GDT 的物理地址保存到 GDTR 寄存器中。如果是IBM兼容的机器,则还需要打开 A20总线

1
2
3
4
5
6
7
8
9
10
11
; 设置CR0寄存器的PE位
mov eax, cr0 ; 必须通过其他寄存器来修改CR0寄存器
or eax, 1
mov cr0, eax

; 远转移 (cs = 代码段描述符)
jmp cs:@pmode

[bits 32]
@pmode:
; 现在已经进入了保护模式

Virtual 8086 Mode

80286 开始的保护模式支持更大的寻址空间一定程度的保护措施,但是为了向下兼容运行在实模式下的软件,仍然保留了实模式(BIOS 工作在实模式,因此在正式启动操作系统之前必须运行在实模式,开始启动的第一步就是将实模式转换为保护模式)启动系统之后,80286 的 16 位保护模式,受硬件的限制,不支持分页,多任务支持也有限,因此不能向下兼容实模式的软件,必须遵循一定的标准将实模式代码重新编译、汇编才能在 16 位的保护模式运行,这就造成了诸多不便。

80386 开始的 IA-32 架构中,寄存器扩展至 32 位,随之而来的 32 位保护模式较完整,因此可以在 32 位保护模式直接运行 16 位实模式程序,也就是虚拟 8086 模式。

image-20241219214209871

  • 利用健全的多任务机制,多个虚拟 86 程序可以和 32 位程序并发执行,提升效率
  • 利用分页机制,模拟出和 8086 一样的寻址方式,段基址 << 4 + 段内的偏移地址,寻址空间为1 MB,将不同虚拟 86 程序的地址空间映射到不同的物理地址上,这样每个虚拟86任务看起来都认为自己在 0 ~ 1 MB 的地址空间。

实模式/Virtual 8086/8086

下表可以看出 实模式、8086、虚拟 86 的中断向量表是不完全一致的

image-20241220173857308

  • 和实模式、8086相比:
  1. 段描述符加载之后会缓存,加快之后的访存速度
  2. 虚拟 8086 模式并不是完美兼容的,因为在 16 位架构里没有保护概念,CPU 也没有特权指令这一说,所以改变段寄存器、直接访问硬件等操作会陷入 OS 或者抛出异常,这就导致这些指令无法正常运行,但也没有办法,为了适应现代操作系统,只能放弃对这些应用的支持。

IA-32 地址翻译

在 x86-64 架构下,长模式以外的三种模式也叫做 Legacy Mode

image-20241219214044009

长模式 (IA-32e Mode)

在x86-64 等现代新架构中,长模式是64位操作系统可以访问64位指令和寄存器的模式,有两个子模式。

64位程序在称为 64-bit Mode 的子模式下运行,32 位和 16 位保护模式程序在称为 Compatibility Mode 的子模式下执行,其允许 64 位操作系统运行现有的 16 位和 32 位 x86 应用程序。 在兼容模式下运行的应用程序使用 32 位或 16 位寻址,并且可以访问前 4 GB 虚拟地址空间。传统 x86 指令前缀在 16 位和 32 位地址和操作数大小之间切换。 与 64 位模式一样,兼容性模式由操作系统在单个代码段的基础上启用。

然而,与 64-bit Mode 不同的是,x86 分段功能与传统 x86 架构中相同,使用 16 位或 32 位保护模式语义。从应用程序的角度来看,兼容模式看起来就像传统的 x86 保护模式环境。然而,从操作系统的角度来看,地址转换、中断和异常处理以及系统数据结构都使用 64 位长模式机制。

删除了 HW Task Switch, TSS 变成一个堆栈表, 不再存储段相关的信息

x86-64 寄存器

x86-64 架构在长模式(64 位模式)下,大部分寄存器位数增加为 64 位,前缀位为 R

  • 分段的概念被无限弱化:其中四个段寄存器 CS、SS、DS 和 ES 被强制设置为基地址 0,并且限制为 2^64^ ,形式上还有内存分段,但实际上所有内存都在唯一的一个分段中。
  • 段寄存器 FS 和 GS 仍然可以具有非零基地址,这允许操作系统将这些段用于特殊目的。与传统模式使用的 GDT 机制不同,这些段的基地址存储在特定于模型的寄存器中。 x86-64架构还提供了特殊的 SWAPGS 指令,该指令允许交换内核模式和用户模式基地址。例如,x86-64 上的 Microsoft Windows 使用 FS 段指向线程环境块(TEB),这是每个线程的一个小型数据结构,其中包含有关异常处理、线程局部变量和其他每线程状态的信息。同样,Linux 内核使用 GS 段来进行类似的线程本地存储(TLS)

应用程序编程使用如下寄存器:

image-20241220183652263

系统编程使用如下寄存器:

image-20241220183817147

地址翻译

image-20241219213948185

image-20241220183850040

Legacy Mode

以前的模式统称 Legacy Mode

image-20241220184344031

Interrupt and Exception Handling

概念:中断和异常

PPT - BIOS and DOS Interrupts PowerPoint Presentation, free download ...

Interrupt(中断)

中断可以分为硬件和软件引起的中断

  • 硬件中断(Hardware) 通常是 CPU 执行指令过程中收到外部硬件的中断信号,属于外部中断

    img

    • 可屏蔽中断:INTR 引脚传入,可以通过 IF 标志位屏蔽
    • 不可屏蔽中断:NMI 引脚传入(Non-maskable Interrupt),电源掉电、内存读写错误、总线奇偶校验错误等灾难性的错误,不可屏蔽,CPU 必须立刻处理。有一个专用的中断向量号,一般是不可屏蔽的,这样可以防止嵌套执行,直到 IRET 从中断返回
    • 通过中断控制器从总线读取中断向量,高级可编程中断控制器 APIC 可以通过 LINT 引脚接收中断,可以处理 INTR 和 NMI,如果 APIC 禁用则会使用 INTR 和 NMI
    • CPU 收到硬中断以后需要保存执行现场,转去执行中断服务程序(ISR, Interrupt Service Routine)硬中断是异步、随机、无法预知的。
  • 软件中断(Software) 通常显式调用中断指令触发的中断,属于内部中断

    • 基础的汇编指令,由指令提供中断向量号 INT n
  • 中断服务程序的最后一条一定是 IRET 指令,恢复原先程序的执行

Exception(处理器)

异常主要是 CPU 执行指令过程中发现的,属于内部中断,从源头来看,大体分为三类:

  1. 处理器在执行指令的过程中检测到的程序错误(program error),比如 zero division
  2. 软件生成的异常:INTO, INT1, INT2, INT3, BOUND 指令。有一些异常提供错误码,发生异常需要将错误码压栈,以便正确处理。如果使用 INT n 模拟异常,不会提供错误码,会将 EIP 指令指针错误提供,可能会出现错误。
    • INTO: Overflow
    • INT3: Breakpoint, debugging
    • BOUND: Bound Range Exceeded
    • UD: Invalid Opcode
  3. 还有一些异常源是机器检查(Machine-check)提供的

Restart

下文的 中断/异常处理程序 泛指 处理中断或异常的程序

  1. Fault: (RETRY) 异常处理程序 返回指向 异常源指令 的指针,因此将会重新执行这条指令。一般是在无法正常通过地址访问到操作数就会触发这种异常,最典型的比如 Page Fault,为了能正确恢复需要 CPU 保存必要的寄存器(上下文)。
  2. Trap: (CONTINUE) 异常处理程序 返回指向 异常源的后一条指令 的指针,因此将会从下一条指令开始,最大特点就是不会影响程序执行的连贯性。比如 INTO 溢出异常,不过这里的下一条指的是逻辑上的下一条,他不一定和异常源相邻。比如执行 JMP 指令,返回的是指向 JMP 目的地的指针。
  3. Abort: (EXIT) 会影响程序的执行的连贯性,具体来说就是 异常处理程序 不能保证可靠的返回,旨在发生abort异常时收集有关处理器状态的诊断信息,然后尽可能优雅地关闭应用程序和系统。
  4. Interrupt: 中断严格支持程序的正确返回,不会影响可靠性与程序执行的连贯性,除非是掉电或者是硬件错误。中断虽然不可预知,但是 CPU 有完善的应对策略:首先,CPU 在每个指令周期都会检查是否有中断,一般是在最后阶段。第二,在开始执行 中断处理程序 之前,一定会保存当时指令执行的现场以便恢复执行,比如 I/O 操作,恢复时执行的指令就是中断前执行的最后一条指令的下一条

IDT

中断向量表

image-20241219181455217

中断描述符表是一张用于存储中断处理程序入口的表格,每个表项(Entry)是一个中断描述符(Gate Descriptor),用于指明当某个中断或异常发生时,中断/异常处理程序 的入口地址、权限等信息。为了帮助处理异常和中断,需要处理器进行特殊处理的每个体系结构定义的异常和每个中断条件都被分配了一个唯一的标识号,称为中断向量号。处理器使用分配给异常或中断的向量号作为中断描述符表 (IDT) 的索引。该表提供了异常或中断 中断/异常处理程序 的入口点。中断表的索引范围是 0 到 255。

  • 031 范围内的向量编号由 Intel 64 和 IA-32 体系结构保留,用于体系结构定义的异常和中断。并非所有中断都有相应的处理函数。该范围内未分配的向量编号被保留,不能使用。

  • 32255 范围内的向量编号被指定为用户定义的中断,并且不被 Intel 64 和 IA-32 体系结构保留,这些中断通常分配给外部 I/O 设备,以使这些设备能够通过外部硬件中断机制之一向处理器发送中断。

Trap Table IDT
适用范围 较简单的操作系统或教学架构中使用 主要用于 x86 架构的保护模式
数据结构 简单的映射表 复杂的描述符表,包含地址和其他元信息
功能 仅存储中断处理程序地址 支持权限管理、段选择、复杂异常和硬件中断处理
实现机制 直接使用固定大小的数组存储入口地址 通过描述符表实现,包含段选择子和偏移地址的组合

CPU 上电后(Real Mode阶段):

  • 在实模式下,CPU 使用一个简单的中断向量表(Interrupt Vector Table, IVT),这是一个固定位置的内存表,系统刚引导时,内存0x00000到0x0003FF共1KB的空间用于存放中断向量表。每个中断向量占用4个字节,共可存储 256 个中断向量,中断向量表中存储的是异常处理程序的起始地址。
  • 这个 IVT 是 16 位架构的中断处理机制,和 IDT 不同。

进入保护模式(Protected Mode)时:

  • 当系统进入保护模式后,操作系统需要配置自己的 IDT,因为保护模式支持更复杂的中断和异常处理。
  • 操作系统初始化过程中会:
    1. 分配一块内存用于存储 IDT。
    2. 填充 IDT 条目(包括中断号、处理程序地址、权限等)。
    3. 使用 lidt 指令加载 IDT 的基址和限制到 CPU 的 IDTR 寄存器。

进入长模式(Long Mode)时:

  • 在 64 位模式(长模式)下,IDT 同样需要重新设置,因为长模式支持更复杂的地址模式和更大的描述符。
  • 通常操作系统会重新配置或直接复用保护模式下的 IDT。

不过,本质都是中断向量表,本质存储的都是Handler入口

工作流程

image-20241219204336594

  1. 中断或异常发生

    • CPU 收到一个中断或异常信号。
    • 信号对应一个中断号(Interrupt Vector),范围是 0-255
  2. 查找 IDT(interrupt Descriptor Table)

    • CPU 从 IDTR 寄存器 中读取 IDT 的基地址(起始地址)。

image-20241220095504456

  1. 跳转到处理程序

    • 根据中断向量在 IDT 中找到对应的 Descriptor (比如中断门和陷阱门)

    • Descriptor 中存储了 段选择器(Selector)和偏移量用来定位 中断/异常处理程序 的位置、特权级、类型(中断门、陷阱门、任务门等)

    • CPU 跳转到 中断/异常处理程序 并开始执行中断或异常的处理。

Gate Descriptors

Interrupt/Trap Gate

image-20241219204408842

特性 中断门(Interrupt Gate) 陷阱门(Trap Gate) 任务门(Task Gate)
触发来源 硬件中断/软件中断 异常/软件触发 任务切换
IF 标志位 IF 自动清零(关中断) IF 不变(不屏蔽中断) 与任务无关
跳转目标 中断服务例程 异常或调试服务例程 任务状态段(TSS)
返回方式 IRET 指令 IRET 指令 任务切换完成后返回
典型用途 硬件中断处理 调试、异常处理 多任务

Call Gate

调用门(Call Gate):调用门可以通过 CALL JMP 调用,从一个低特权级代码段跳转到另外一个高特权级的代码段,存在 GDT 和 LDT 中,但从未被实际使用过。对于系统调用的实现来说,这是不方便的并且不是最佳实现。大多数操作系统使用陷阱门(Linux 中的 INT 0x80 和 Windows 中的 INT 0x2E)或更强大的 SYSENTER/SYSEXIT 指令来代替调用门

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
; CALL 指令与 JMP 指令示例
section .text
global _start

_start:
call 0x10:0x0000 ; 调用子程序 需要返回(my_function)
jmp 0x18:0x0000 ; 无条件跳转 不需要返回(end_program)

0x10:0x0000:
; 子程序代码
ret ; 返回主程序

0x18:0x0000:
; 程序结束
mov eax, 1 ; 系统调用号(exit)
xor ebx, ebx ; 返回值(0)
int 0x80 ; TRAP into kernel

Task Gate

详见下文的硬件任务切换

HW Task Switch(硬件任务切换)

Task Gate

image-20241220170638814

在中断处理过程(IDT)或者在GDT LDT里索引到任务门,会开启硬件任务切换,影响着。

任务门(Task Gate):为多任务处理提供硬件支持,跳转到 TSS,目前不被使用。

任务状态段(TSS, Task Status Segment): 保存了任务的执行上下文环境

Task Status Segment

TSS Descriptor 的结构,位于 GDT 中:

image-20241220094926788

一般 TSS 的 DPL 是小于3的,因为只有操作系统内核才有权调度任务


IA-32 TSS 内部的结构

  • I/O map的基地址, bitmap 本体通常映射到 TSS,通过 bitmap 限制进程对 IO 端口的访问

    image-20241220180936358

  • 本进程对应的 LDT Selector

  • 所有的x86普通寄存器:6 个段,8个通用,1 个指令指针,1 个程序状态字

  • CR3 页表地址

  • 其他特权级别的栈段和栈指针 SS20 ESP20 (内核栈)

  • 用于嵌套任务的 link,也就是上一个 父任务的 TSS Selector

image-20241220001641685

TSS 寻址:

image-20241220001517922

image-20241220094649039


Workflow

处理器能够自动保存执行上下文,响应来自硬件或软件的请求,恢复另一任务的上下文:

  1. 显式切换:CALL/JMP tss_selector CALL/JMP task_gate_selector
  2. 隐式切换:中断/错误处理程序触发
  3. 可以通过控制特定的中断向量陷入 IDT 中的特定 Task Gate 来完成跳转,比如INT n
  4. 嵌套任务的 IRET,EFLAGS 的 NT 标志位(Nested Tasks) 置位用于嵌套任务的跳转。

下面为通过 IDT 的 Task Gate 进行任务切换的例子:

image-20241220100119251

  • TR 寄存器结构如下:

    image-20241220001552753

  • 当 依赖 TSS 进行任务切换的时候,CPU 做了以下几件事情:

  1. 保存现场:当前 TSS 中所有寄存器值填写到当前的 TR 寄存器(task register)指向的 TSS 中
  2. 加载新现场:把新 TSS Selector 载入 TR按照一定的检验流程把新的 TSS 覆盖到寄存器。
  3. 开始执行新代码:新设置的 EIP 指向将要执行的新代码
  • 缺点: 受硬件限制较大,且流程繁杂,不灵活也不便于调试
  1. TSS 只能存在 GDT (最大长度只有 8,192)(TSS+LDT)*2+12=8192,最多 4090 个进程
  2. 算上检验流程要消耗 200 多个时钟周期,全部串行,中间出现一个差错就无法切换成功。
  3. 硬件的切换过于重量级,保存完整的上下文,实际上任务切换不一定需要那么多寄存器

ex. 嵌套任务切换

image-20241220172753514

SW Task Switch

操作系统 将关键的上下文内容存到类似 PCB 等自由可控的轻量环境中,可以完全控制任务切换逻辑,能够支持指令流水的并行优化技术,更加适合复杂的多任务调度算法,提升性能。

Linux 2.4之前的内核有进程最大数的限制,受限制的原因是,每一个进程都有自已的 TSS 和 LDT。Linux 2.4以后,在同一个CPU上的进程使用同一个 TSS,有效内容只剩下 ESP0IO MAP Address

  • ESP0: 内核堆栈指针,因为linux完全使用分页,所以SS段没有用处
  • IO bitmap: 控制进程的 I/O 许可

Stack Usage by Handler

有特权级别的转换(为防止恶意程序,一般会切换,比如系统调用、异常 陷入 OS 的内核模式):

image-20241220162644295

因为有特权级别切换,因此要根据 TSS 里的内核栈段 SS0 和 ESP0 切换到处理程序自己的栈上,保存好被中断程序原先的 SS 和 ESP,将他的 CS, EIP, EFLAGS 也搬过去,最后将错误码压栈。


如果没有特权级别转换,就不会切换执行堆栈,内核中发生了中断或者异常:

image-20241220165159628

并发事件

image-20241219194949413image-20241219204058993

中断

从 0 到 32 的任何向量的中断都可以通过 INTR 引脚传递到处理器,并且从 16 到 32 的任何向量都可以通过本地 APIC 传递。当通过 INTR 引脚模拟异常向量中断(比如 Page Fault),处理器不会将错误码压栈,因此异常处理程序可能无法正确运行。(和 INT n 的问题一样)

EFLAGS(PSW)

image-20241220002704824

屏蔽中断(Masking)

在这里插入图片描述

  1. STISET, CLICLEAR这两个特权指令可以改变 IF 标志位(位于程序状态字 PSW, aka EFLAGS)控制 CPU 是否能够响应外部中断请求,是多重中断的基本条件,IRET也可以改变
  2. 关中断:将 IF 中断标志位置零,用于保护现场、恢复现场和跳转到 ISR。
  3. 在执行 ISR 之前可以开中断,执行 ISR 的过程可以被其他中断打断,如果使用中断屏蔽技术(MASK),就可以实现多重中断,高优先级有权打断低优先级,反之则不行。
  4. 中断屏蔽技术:每个中断可以设置其他中断源的 mask ,被设置为0则被停止执行

OS Booting

一些早期的计算机系统,在接收到来自操作人员或外围设备的启动信号后,可以将极少量的固定指令加载到存储器的特定位置,初始化至少一个CPU,然后将CPU指向这些指令并执行指令这些指令通常从一些外围设备(可以由操作员通过开关选择)启动输入操作。其他系统可能会直接向外围设备或 I/O 控制器发送硬件命令,从而执行极其简单的输入操作(例如“将系统设备的扇区 0 读取到从位置 1000 开始的内存中”),从而有效地加载一个小文件。然后开始==链式引导系统启动==。

对于现代操作系统,当计算机关闭时,其软件(包括操作系统、应用程序代码和数据)仍存储在非易失性存储器中。当计算机开机时,它的 RAM 中通常没有操作系统或其加载程序。计算机首先执行存储在 ROM(后来的EEPROM,NOR Flash)中的相对较小的程序(也就是 ==BIOS== 与 ==UEFI==)。该程序支持就地执行,初始化 CPU 和主板,初始化 DRAM(特别是在x86系统上),访问非易失性存储器设备(通常是块寻址设备,例如 NAND Flash、SSD、HDD)或其他可以将操作系统程序和数据加载到 RAM 中的设备(U盘、CD-ROM、甚至是网络设备)此外,该程序还可以初始化显示设备(例如GPU)、文本输入设备(例如键盘)和指针输入设备(例如鼠标)加载到 RAM 中的第一个程序可能不足以加载操作系统,而必须加载另一个更大的程序,它加载的程序称为第二阶段引导加载程序(狭义上的 ==Bootloader==)

BIOS

Basic Input/Output System,基本输入输出系统,主要负责硬件层面的初始化和基本 I/O 管理,目标是找到设备上的 Bootloader,从Bootloader启动操作系统。

早年,BIOS 存储于ROM芯片上;现在的 BIOS 多存储于闪存芯片上,这方便了 BIOS 的更新。BIOS 也可从网卡等设备启动。

当电脑通电,BIOS 就会从存储器上加载,执行加电自检(POST),测试和初始化 CPURAMDMA控制器、芯片组键盘软盘硬盘等设备。

所有的 Option ROM(扩展 BIOS 程序)被加载后,BIOS 就试图从启动设备(如硬盘软盘光盘)加载 Bootloader,由 Bootloader 加载操作系统。BIOS 以 16 位实模式执行。现代操作系统以保护模式长模式执行。

BIOS 固件(Filmware)

BIOS 本身是汇编语言代码,是在 16 位实模式下执行的,由于 x86-64 是一个高度兼容的指令集,也为了迁就 BIOS 的 16 位实模式的运行环境,所以即使现在的 CPU 都已是 64 位,如果还是在 BIOS 启动(基本见于 09 年以前的主板),在开机时仍然都是在 16 位实模式下执行的。16 位实模式直接能访问的内存只有 1 MB,就算你安了 4G、8G 或者 16 G 还是 32 G 内存,到了 BIOS 上一律只先认前 1 MB。在这 1 M内存中,前 640 K 称为基本内存,后面 384 K 内存留给开机必要硬件和各类 BIOS 本身使用。

BIOS Setup

大约从80386 PC开始,个人电脑的 BIOS ROM 集成了设置程序(Setup)。主板的 CMOS 芯片用于存储 BIOS 设置值及硬件侦测值。

现代的 BIOS 可以让用户选择由哪个启动设备启动电脑,如光盘驱动器硬盘软盘U盘等等。现代大多数 BIOS 支持图形化交互界面,有一些是厂商制作的,用户可以用鼠标键盘完成操作。

CMOS

CMOS 是计算机上另一个重要的存储器。之所以提到它,是因为 BIOS 程序的设置值、硬件参数侦测值就保存在 CMOS 中。而且,在 BIOS 程序启动计算机时,需要加载 CMOS 中的设置值。CMOS 通常被集成在南桥芯片组中。UEFI 系统则多用 NVRAM 存储设置。

  • BIOS 芯片属于 ROM ,不需要供电保存信息,其中存储的是固件(filmware,程序代码)
  • CMOS 芯片属于 RAM,内容在断电会消失,存储的是普通信息。主板上的钮扣电池用于让 CMOS 存储 BIOS 设置值,以及电脑在断电时依然可以让系统时钟运作。把主板的电池拆出,便可重置其内容,拆出电池也会重置系统时钟。

Pre-booting

POST:加电自检

先进行 CPU 初始化:当按下电源开关时,电源就开始向主板和其他设备供电,这时电压还不稳定,在早期的南北桥主板上,由主板北桥向CPU发复位信号,对CPU初始化;稳定电压后复位信号便撤掉。而对于现在的单南桥主板,则由CPU自身调整稳定电压达到初始化的目的,当电压稳定后,CPU 便在系统BIOS保留的内存地址处执行跳转 BIOS 起始处指令,开始执行 POST 自检。

加电自检(POST, Power-On Self Test)是计算机 BIOS 的一个重要功能,主要用于在 BIOS 加载操作系统之前检查计算机设备硬件是否存在问题,进而保证计算机的正常运行。在设备启动的过程中,自检程序主要检查CPU内存I/O设备主板等对计算机正常运行会产生影响的设备硬件。

在POST自检中,BIOS 只检查系统的必要核心硬件是否有问题,主要是 CPU、640K基本内存、显卡是否正常,PS/2 键盘控制器、系统时钟是否有错误等等。由于 POST 检查在显卡初始化以前,因此在这个阶段如发生错误,是无法在屏幕上显示的,不过主板上还有个报警扬声器,而且如果主板的 8255 外围可编程接口芯片没有损坏的话,POST报警声音一定是会出来的。可以根据报警声的不同大致判断错误所在,一般情况下,一声短“嘀”声基本代表正常启动,不同的错误则是不同的短“嘀”声和长“嘀”声组合。POST 自检结束后,BIOS 开始调用中断完成各种硬件初始化工作。

BIOS 中断调用

image-20241220173802326

与中断相对的是轮询(polling)中断向量表 - jadeshu - 博客园

CPU 上电后(实模式阶段):

  • 在实模式下,CPU 使用一个简单的中断向量表(Interrupt Vector Table, IVT),这是一个固定位置的内存表,系统刚引导时,内存0x00000到0x0003FF共1KB的空间用于存放中断向量表。每个中断向量占用4个字节,共可存储256个中断向量,中断向量表中存储的是异常处理程序的起始地址。这个 IVT 是 16 位架构的中断处理机制,和 IDT 不同。

进入保护模式(Protected Mode)或长模式(Long Mode)时:

  • 当系统进入保护模式后,操作系统需要配置自己的 IDT,因为保护模式支持更复杂的中断和异常处理。
  • 操作系统初始化过程中会:
    1. 分配一块内存用于存储 IDT。
    2. 填充 IDT 条目(包括中断号、处理程序地址、权限等)。
    3. 使用 lidt 指令加载 IDT 的基址和限制到 CPU。

BIOS 可通过 BIOS 中断调用MS-DOS 操作系统及 MS-DOS 程序提供磁盘、键盘、显示等标准服务。通过 BIOS 中断调用访问视频硬件非常缓慢。许多现代操作系统(如WindowsLinux)的启动程序(Bootloader)会使用 BIOS 中断调用加载内核,然后由内核将处理器从16位实模式转换到32位保护模式(或64位长模式

在INTEL后续的32位CPU中,使用中断描述符表 IDT 来代替中断向量表 IVT。中断描述符表的起始地址由中断描述符表寄存器(IDTR)来定位,因此不再限于底部1K位置。另一方面,中断描述符表的每一个项目——称作门描述符——除了含有中断处理程序地址信息外,还包括许多属性/类型位。门描述符分为三类:任务门、中断门和自陷门。CPU对不同的门有不同的调用(处理)方式。

硬件初始化

硬件初始化工作是通过 BIOS 中断调用实现的,经过POST检测后,电脑终于出现了开机启动画面,这就是已经检测到了显卡并完成了初始化。但是请注意,由于BIOS是在16位实模式运行,因此该画面是以VGA分辨率(640*480,纵横比 4:3)显示的,因为实模式最高支持的就是 VGA。以前的小 14-17 寸CRT显示器由于都是 4:3 比例,最高分辨率也比较低,因此这个开机启动画面没有什么违和感,但现在的液晶显示器基本上都是宽屏 16:9 的,分辨率也较高,因此在这样的显示屏下,启动画面上的一切东西显示都可以说“惨不忍睹”——图形被拉长,字体很大很模糊,可以很明显看到显示字体的锯齿。

Bootloader 位置

引导启动的过程也是使用 BIOS 中断调用,因为 BIOS 处在实模式,Bootloader 才能切换模式

BIOS 根据 Setup 中用户指定的硬件启动顺序,如果将启动顺序设为“第一:DVD 驱动器;第二:硬盘驱动器”,固件会先尝试从 DVD 驱动器启动,再尝试从本地的硬盘驱动器启动。BIOS 负责硬件和软件间的相互通信。如果发现所有硬件都没有能引导操作系统的记录,则会在屏幕上显示相应错误信息(NO ROM BASIC)将电脑维持在 16 位实模式。BIOS 只识别到由主引导记录(MBR)初始化的硬盘。

MBR:主引导扇区

主引导扇区,Master Boot Record,BIOS 检查时会把硬盘最初一个扇区(MBR)加载到内存中。

它在硬盘上的三维地址(CHS 地址)为(柱面,磁头,扇区)=(0,0,1)

MBR 位于磁盘的第一个扇区(LBA 0),其大小为 512 字节,划分如下:

  • 前 446 字节: 引导代码(Bootloader Code)
  • 接下来的 64 字节: 分区表(DPT, Disk Partition Table),记录最多 4 个主分区的信息
  • 最后的 2 字节: 魔数(Signature, 0x55AA),表示这是一个有效的 MBR。

BIOS 硬件检查方式:这个存储设备的前 512 字节是不是以0x55 0xAA(10101010,01010101)结尾?如果不是就按照顺序检查下一个,如果是就加载这 512 字节内部的引导代码,然后执行它。

MBR 最开头是第一阶段引导代码。主要作用是在检查分区表是否正确和在系统硬件完成自检以后,在活跃分区的 PBR 找到并执行 Bootloader 主程序(如 GNU GRUB),不依赖任何操作系统,而且启动代码也是可以改变的,从而能够实现多系统引导

MBR 还记录着硬盘本身的相关信息以及硬盘各个分区的大小及位置信息(分区表),是数据信息的重要入口。如果它受到破坏,硬盘上的基本数据结构信息将会丢失,需要用繁琐的方式试探性的重建数据结构信息后才可能重新访问原先的数据。因为 512B 的限制,分区表也有限制,MBR 支持最大卷为2 TB(Terabytes)并且每个磁盘最多有4个主分区(或3个主分区,1个扩展分区和无限制的逻辑驱动器)

扇区与逻辑块地址(Sector & LBA)

在 GPT 分区中,每一个数据读写单元成为 LBA(逻辑块地址),一个“逻辑块”相当于传统 MBR 分区中的一个“扇区”,之所以会有区别,是因为GPT除了要支持传统硬盘,还需要支持以 NAND FLASH 为材料的 SSD 硬盘。

不像磁盘那样有磁片,而磁片又划分磁道和扇区来保存数据,因此,闪存材料需要采用模拟扇区来保持统一性。这些硬盘的一个读写单元是 2KB 或 4KB,所以,GPT 分区中干脆用 LBA 来表示一个基础读写块,当 GPT 分区用在传统硬盘上时,通常,LBA 就等于扇区号,有些物理硬盘支持 2KB 或 4KB 对齐,此时,LBA 所表示的一个逻辑块就是 2KB的空间,为了方便,我们后面仍然将逻辑块称为扇区。

以 CHS 寻址的硬盘, 最高容量是 512×63×256×1024=8064 MiB,BIOS 使用的是 LBA 寻址

UEFI

作为 BIOS 的替代方案,可扩展固件接口 UEFI 负责 加电自检(POST)、联系操作系统以及提供连接操作系统与硬件的接口。前身是 EFI

EFI

虽然 BIOS 作为电脑加电启动所必不可少的部分,但是从其于 1975 年诞生之日起近 30 余年,16 位汇编语言代码,1 M 内存寻址,调用中断一条条执行的理念和方式竟然一点都没有改变,虽然经各大主板商不懈努力,BIOS 也有了 ACPI、USB 设备支持,PnP 即插即用支持等新东西,但是这在根本上没有改变 BIOS 的本质,而英特尔为了迁就这些旧技术,不得不在一代又一代处理器中保留着 16 位实模式,否则根本无法开机。英特尔推出了可扩展固件接口(EFI, Extensible Filmware Interface) 和后继的 UEFI(Unified EFI) ,是现在电脑的主要预启动环境。

特性

  1. 摒弃 16 位实模式,完全是 32 位或 64 位模式,在 EFI 中可以实现处理器的最大寻址,因此可以在任何内存地址存放任何信息
  2. 模块化,C 语言风格的参数堆栈传递方式,动态链接的形式构建的系统,通用性和兼容性较好,在 EFI 驱动环境(DXE)中解释执行 EFI 字节码(虚拟机器指令)写成的 EFI 驱动,识别系统硬件并完成硬件初始化。EFI 的驱动开发非常简单,基于 EFI 的驱动模型原则上可以使 EFI 接触到所有硬件功能
  3. 和 OS 相比,EFI 没有中断访问机制,只能轮询
  4. 只有简单的存储器管理机制,在段保护模式下只将存储器分段,所有程序都可以存取任何一段位置,不提供真实的保护服务。
  5. 支持 GPT 分区模式
  6. 区分不同的开机模式,向前兼容模式(Legacy) 可以启动 16 和 32 位的操作系统,采用64位UEFI固件的PC,在UEFI 开机模式下只能执行64位操作系统启动程序

GPT:全局唯一表示分区表

全局唯一标识分区表 GUID Partition Table,使用通用唯一标识符(也称为全局唯一标识符(GUID))对物理计算机存储设备(例如硬盘驱动器或固态驱动器)的分区表进行布局

在MBR硬盘中,分区信息直接存储于主引导扇区中(其中还存储着引导 Bootloader 的引导代码)但在GPT硬盘中,分区表的位置信息储存在GPT头中。出于兼容性考虑,硬盘的第一个扇区仍然用作 MBR,之后才是 GPT 头。为了减少分区表损坏的风险,GPT在硬盘最后保存了一份分区表的副本。其中的 EFI 系统分区可以被 EFI 存取,用来存取部分驱动和应用程序。

undefined

GPT分区表的结构。此例中,每个逻辑块(LBA)为512字节,每个分区的记录为128字节。负数的LBA地址表示从最后的块开始倒数,−1表示最后一个块。

保护性 MBR(Protective MBR)

  • 位置: 分区表的第一个扇区(LBA 0)。
  • 作用: 这是兼容性区域,用于保护 GPT 磁盘免受旧式 MBR 工具的意外覆盖。保护性 MBR 声明整个磁盘为一个分区,以阻止不支持 GPT 的软件误将磁盘视为未分区。

GPT 标头(GPT Header)

  • 位置: 磁盘的第一个逻辑块地址(LBA 1)。
  • 作用: 包含 GPT 的全局信息,包括分区表的起始位置、大小和校验和。

分区条目表(Partition Entries)

  • 位置: 通常从 LBA 2 开始,连续占用一定数量的扇区。
  • 作用: 存储每个分区的详细信息,包括分区类型、GUID、起始和结束地址。

引导分区(EFI System Partition, ESP)

  • 位置: 通常是 GPT 分区中专门指定的一部分(由 EFI 分区条目指定)
  • 作用: 用于存储 Bootloader EFI 文件、操作系统引导管理器,以及其他必要的启动文件。EFI 系统分区可以位于任何地方,只要分区条目中有正确的指向即可。实际上是一个FAT32文件系统
  • 固定 GUID: C12A-7328-F81F-11D2-BA4B-00A0-C93E-C93B

备份 GPT 数据

  • 位置: 通常在磁盘的最后几个逻辑块地址(倒数第一个扇区存储备份 GPT Header,倒数第二个扇区起存储备份分区条目)。
  • 作用: 用于恢复主 GPT 数据结构。

UEFI 做的优化

  1. 拥有完整的图形驱动。EFI多数还是一种类DOS界面(仍然是640*480VGA分辨率),只支持PS/2键盘操作(极少数支持鼠标操作)。无论是PS/2还是USB键盘和鼠标,UEFI一律是支持的,而且UEFI在显卡也支持GOP VBIOS的时候,显示的设置界面是显卡高分辨率按640*480或1024*768显示
  2. 安全启动。固件验证:根据硬件签名对各硬件判断,只有符合认证的硬件驱动才会被加载

Pre-booting

undefined

POST:加电自检

当打开电源开关时,电脑的主要部件都开始有了供电,与 BIOS 不同的是,UEFI 预加载(Pre-EFI)环境首先开始执行,负责 CPU 和内存(是全部容量)的初始化工作,这里如出现重要问题,电脑即使有报警喇叭也不会响,因为 UEFI 没有去驱动 8255 发声,不过预加载环境只检查 CPU 和内存,如果这两个主要硬件出问题,屏幕没显示可以立即确定,另外一些主板会有提供LED提示,可根据CPU或内存亮灯大致判断故障。

硬件初始化

CPU 和内存初始化成功后,驱动执行环境(DXE)载入,当 DXE 载入后,UEFI 就具有了逐个加载UEFI 驱动的能力,在此阶段,UEFI 会迭代搜索各个硬件的 UEFI 驱动并相继加载,加载各种总线(包括PCI、SATA、USB、ISA)及硬件的 UEFI 驱动程序,完成硬件初始化工作,这相比 BIOS 的中断速度会快的多,同样如加载显卡的 UEFI 驱动成功,电脑也会出现启动画面,硬件驱动全部加载完毕后,最后同 BIOS 一样,去寻找硬盘上的操作系统的引导启动程序。

UEFI 应用程序(UEFI Application)和 UEFI 驱动程序(UEFI driver)是 PE格式.efi文件,可用C语言编写。

Bootloader 位置

UEFI 引导管理程序可以直接从支持的文件系统(如FAT32)中读取启动文件,而不依赖硬件中断和传统的16位服务调用,UEFI 整体就处在保护模式或者长模式下。

在启动操作系统的阶段,同样是根据启动记录的启动顺序,转到相应设备(GPT)引导记录,引导操作系统并进入,在 UEFI 开机模式下,Bootloader 本身也是 UEFI 应用程序,其 EFI 文件存储在 EFI 系统分区(ESP)

这里需要注意的是,UEFI 在检测到无任何操作系统启动设备时,会直接进入 UEFI 设置页面,而不是像 BIOS 那样黑屏显示相关信息。

如果启动传统 MBR 设备,则需要打开 CSM 支持。

Legacy + MBR

MBR+Legacy 是通过引导代码指向 Bootloader 文件.

  • Windows:

    image

    • Windows 中根据 MBR 分区表指向活跃分区记录 PBR,这里启动系统用的分区和真正装系统的分区不一定在一起,Windows 的 PBR 可以识别 FAT32 和 NTFS 两种分区,找到分区根目录的 bootmgr 文件,并加载、执行 bootmgr。

    • bootmgr 没有 MBR 和 PBR 的大小限制,可以做更多的事,它会加载并分析BCD启动项存储,而且 bootmgr 可以跨越磁盘读取文件。所以无论我们有几个磁盘,在多少块磁盘上装了 Windows,一个电脑只需要一个 bootmgr 就行了。bootmgr 会去加载某磁盘某 NTFS 分区的 \Windows\System32\WinLoad.exe,然后,由 WinLoad.exe 启动 Windows (ntoskrnl.exe) 系统分区和启动分区可能不是位于同一分区。

  • Linux:

    img

    • 写入 0 号扇区的 446 字节是第一阶段,其作用就是用来找到和加载真正的Grub bootloader主程序,也就是位于操作系统启动分区的Grub2第二阶段的程序。而且受限于446字节的大小,这个阶段的stage1 binary是不包含文件系统功能 对应 boot.img

    • 被加载Stage1加载后,解析/boot/grub2/grub.cfg配置文件,跟据该配置文件的定义,显示多系统的启动选择界面,或者直接加载Linux kernel和文件系统,然后就由Kernel来启动后续的过程。Grub2 Stage2的镜像对应于core.img,位置为/boot/grub2/i386-pc目录下。

UEFI + GPT

**GPT+UEFI **没有明显的引导代码指向 Bootloader EFI 文件

image

image

GPT 直接把 Bootloader 存到 EFI 分区

特性 MBR + Legacy GPT + UEFI
代码位置 磁盘第一个扇区 (LBA 0) EFI 分区
引导文件 BIOS 引导代码 支持 EFI 格式文件 (.efi)
机制 根据引导代码启动 Bootloader UEFI 直接去读取并运行 Bootloader
代码大小 446 字节 非 Bootloader 本身 Bootloader,上限取决于 EFI 分区大小
Bootloader MBR 同时存储分区表和引导代码,Bootloader 在其他位置 GPT 分区表 存储分区信息,ESP 分区直接存储 Bootloader

Legacy 无法识别 GPT 分区表格式,所以也就没有 Legacy + GPT 组合方式。

UEFI 可同时识别 MBR 分区(开启 CSM 模式)和 GPT 分区,所以在 UEFI 下,MBR 和 GPT 磁盘都可用于启动操作系统。不过由于微软限制,UEFI 下使用 Windows 安装程序安装操作系统是只能将系统安装在 GPT 磁盘中。

Bootloader

加载到 RAM 中的第一个程序可能不足以加载操作系统,而必须加载另一个更大的程序。第一个加载到 RAM 中的程序称为第一阶段引导加载程序(BIOS、UEFI),它加载的程序称为第二阶段引导加载程序(狭义上的 Bootloader)

Bootloader 有 GNU GRUBrEFIndSyslinux、Windows 的 BOOTMGR、 和 Windows NT/2000/XP 的 NTLDR 等,它们本身不是操作系统,但能够正确加载操作系统并将 CPU 控制权转移到它;操作系统随后会自行初始化并可能加载额外的设备驱动程序。

Bootloader 不需要驱动程序来进行自身操作,可以使用系统固件(例如 BIOS、UEFI 或开放固件)提供的通用存储访问方法,但通常硬件功能有限且性能较低。

许多 Bootloader 可以配置为给用户提供多种引导选择。这些选择可以包括不同的操作系统(用于从不同分区或驱动器进行双重或多重引导)、同一操作系统的不同版本(以防新版本出现意外问题)、不同的操作系统加载选项(例如,引导至不同的操作系统)、安全模式),以及一些无需操作系统即可运行的独立程序,例如内存测试程序(例如 memtest86+)、基本 shell(如 GNU GRUB 中),甚至游戏。

一些 Bootloader 可以加载其他 Bootloader,例如,GRUB 可以加载 BOOTMGR 而不是直接加载 Windows。通常,默认选择是预先选择的,并有一定的时间延迟,在此期间用户可以按某个键来更改选择;在此延迟之后,默认选择将自动运行,因此无需交互即可正常启动。