Skip to content

X86-32 平台操作系统的加载 #16

@LLLeon

Description

@LLLeon

这段时间学操作系统,好奇计算机是怎么从通电到成功加载操作系统的,看了一些文章顺便做下总结。

第 0、1 小节介绍了一些地址和寄存器的基本概念,后面介绍了 80386 从通电后,怎么把操作系统加载到内存中来运行的过程。

0. 几个地址的概念

先来理解这几个地址的概念:物理地址、虚拟地址(线性地址)、逻辑地址。

任何时候,计算机上都存在一个程序能够产生的地址集合,我们称之为地址范围。这个范围的大小由 CPU 的位数决定,例如一个 32 位的 CPU,它的地址范围是 0~0xFFFFFFFF(4G), 而对于一个 64 位的 CPU,它的地址范围为 0~0xFFFFFFFFFFFFFFFF(64T)。 这个范围就是程序能够产生的地址范围,我们把这个地址范围称为虚拟地址空间,该空间中的某一个地址我们称之为虚拟地址。与虚拟地址空间和虚拟地址相对应的则是物理地址空间和物理地址,大多数时候我们的系统所具备的物理地址空间只是虚拟地址空间的一个子集。

举一个最简单的例子直观地说明这两者:对于一台内存为 256M 的 32bit X86 主机来说,它的虚拟地址空间范围是 0~0xFFFFFFFF(4G), 而物理地址空间范围是 0x000000000~0x0FFFFFFF(256M)。

这里有一个虚拟内存的概念,虚拟内存(virtual memory)是对整个内存(不要和机器上插那条对上号)的抽像描述。它是相对于物理内存来讲的,能直接理解成 “不直实的”,“假的” 内存。现代操作系统都提供了一种内存管理的抽像,即虚拟内存(virtual memory)。进程使用虚拟内存中的地址,由操作系统协助相关硬件,把它 “转换” 成真正的物理地址。这个“转换”,是所有问题讨论的关键。
有了这样的抽像,一个程序就能使用比真实物理地址大得多的地址空间(拆东墙,补西墙,银行也是这样子做的),甚至多个进程能使用相同的地址。这不奇怪,因为转换后的物理地址并非相同的。

  • 逻辑地址(Logical Address):是指由程序产生的与段相关的偏移地址部分。是在有地址变换功能的计算机中,访内指令给出的地址(操作数)叫逻辑地址, 也叫相对地址,也就是是机器语言指令中,用来指定一个操作数或是一条指令的地址。要经过寻址方式的计算或变换才得到内存储器中的实际有效地址即物理地址。

    一个逻辑地址由两部分组成,段标识符: 段内偏移量。段标识符是由一个 16 位长的字段组成,称为段选择子(Segment Selector),其中前 13 位是个索引号,后面 3 位包含其它信息 (后面有介绍)。而偏移量是一个 32 位长的字段。

  • 线性地址(Linear Address):也叫虚拟地址(Virtual Address)。是逻辑地址到物理地址变换之间的中间层。在分段部件中,逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。

    线性地址是一个 32 位无符号整数,可以用来表示高达 4GB 的地址,也就是,高达 4294967296 个内存单元。线性地址通常用十六进制数字表示,值的范围从 0x00000000 ~ 0xffffffff 。程序代码会产生逻辑地址,通过逻辑地址变换就可以生成一个线性地址。

    如果启用了分页机制,那么线性地址可以再经过变换以产生一个物理地址。如果没有启用分页机制,那么线性地址就是物理地址。

  • 物理地址(Physical Address):CPU 地址总线传来的地址,由硬件电路控制(现在这些硬件是可编程的了)其具体含义。物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存、BIOS 等)。

    在没有使用虚拟存储器的机器上,虚拟地址被直接送到内存总线上,使具有相同地址的物理存储器被读写;而在使用了虚拟存储器的情况下,虚拟地址不是被直接送到内存地址总线上,而是送到存储器管理单元 MMU,把虚拟地址映射为物理地址。

CPU 将一个逻辑地址转换为物理地址,需要进行两步:

  1. 将给定的逻辑地址(其实是段内偏移量,这个一定要理解!),CPU 内存管理单元(MMU)利用其段式内存管理单元(segmentation unit),先将逻辑地址转换成一个线性地址。
  2. 利用其页式内存管理单元(paging unit),把线性地址转换为物理地址

这样做两次转换,的确是非常麻烦而且没有必要的,因为直接可以把线性地址抽象给进程。之所以这样冗余,Intel 完全是为了兼容而已(Intel 为了兼容,将远古时代的段式内存管理方式保留了下来,x86 体系的处理器刚开始时只有 20 根地址线,寻址寄存器是 16 位。我们知道 16 位的寄存器可以访问 64K 的地址空间,如果程序要想访问大于 64K 的内存,就需要把内存分段,每段 64K,用段地址+偏移量的方式来访问,这样使 20 根地址线全用上,最大的寻址空间就可以到 1M 字节,这在当时已经是非常大的内存空间了。

现代的多用户多进程操作系统,需要 MMU 才能达到每个用户进程都拥有自己独立的地址空间的目标。使用 MMU,操作系统划分出一段地址区域,在这块地址区域中, 每个进程看到的内容都不一定一样。例如 Windows 操作系统将地址范围 4M-2G 划分为用户地址空间,进程 A 在地址 0X400000(4M)映射了可执行文件,进程 B 同样在地址 0X400000(4M)映射了可执行文件,如果 A 进程读地址 0X400000,读到的是 A 的可执行文件映射到 RAM 的内容,而进程 B 读取地址 0X400000 时,读到的则是 B 的可执行文件映射到 RAM 的内容。这就是 MMU 在当中进行地址转换所起的作用。

1. X86 寄存器说明

32 位 CPU 所含有的寄存器有:

  • 4 个数据寄存器:EAX、EBX、ECX 和 EDX。
  • 2 个变址和指针寄存器:ESI 和 EDI。
  • 2 个指针寄存器:ESP 和 EBP。
  • 6 个段寄存器:ES、CS、SS、DS、FS 和 GS。
  • 1 个指令指针寄存器:EIP。
  • 1 个标志寄存器:EFlags。

1.1 数据寄存器

数据寄存器主要用来保存操作数和运算结果等信息,从而节省读取操作数所需占用总线和访问存储器的时间。

32 位 CPU 有 4 个 32 位的通用寄存器 EAX、EBX、ECX 和 EDX。对低 16 位数据的存取,不会影响高 16 位的数据。这些低 16 位寄存器分别命名为:AX、BX、CX 和 DX,它和先前的 16 位 CPU 中的寄存器相一致。

4 个 16 位寄存器又可分割成 8 个独立的 8 位寄存器(AX:AH-AL、BX:BH-BL、CX:CH-CL、DX:DH-DL),每个寄存器都有自己的名称,可独立存取。程序员可利用数据寄存器的这种“可分可合”的特性,灵活地处理字/字节的信息。

  • 寄存器 AX 和 AL 通常称为累加器(Accumulator),用累加器进行的操作可能需要更少时间。累加器可用于乘、除、输入/输出等操作,它们的使用频率很高。
  • 寄存器 BX 称为基地址寄存器(BaseRegister)。它可作为存储器指针来使用。
  • 寄存器 CX 称为计数寄存器(CountRegister)。在循环和字符串操作时,要用它来控制循环次数;在位操作中,当移多位时,要用 CL 来指明移位的位数。
  • 寄存器 DX 称为数据寄存器(DataRegister)。在进行乘、除运算时,它可作为默认的操作数参与运算,也可用于存放 I/O 的端口地址。

在 16 位 CPU 中,AX、BX、CX 和 DX 不能作为基址和变址寄存器来存放存储单元的地址,但在 32 位 CPU 中,其 32 位寄存器 EAX、EBX、ECX 和 EDX 不仅可传送数据、暂存数据保存算术逻辑运算结果,而且可作为指针寄存器,所以,这些 32 位寄存器更具有通用性。

1.2 变址寄存器

32 位 CPU 有 2 个 32 位通用寄存器 ESI 和 EDI。其低 16 位对应先前 16 位 CPU 中的 SI 和 DI,对低 16 位数据的存取,不影响高 16 位的数据。

寄存器 ESI、EDI、SI 和 DI 称为变址寄存器(IndexRegister),它们主要用于存放存储单元在段内的偏移量,用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。

变址寄存器不可分割成 8 位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果

它们可作一般的存储器指针使用。在字符串操作指令的执行过程中,对它们有特定的要求,而且还具有特殊的功能。

1.3 指针寄存器

32 位 CPU 有 2 个 32 位通用寄存器 EBP 和 ESP。其低 16 位对应先前 16 位 CPU 中的 BP 和 SP,对低 16 位数据的存取,不影响高 16 位的数据。

寄存器 EBP、ESP、BP 和 SP 称为指针寄存器(PointerRegister),主要用于存放堆栈内存储单元的偏移量,用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。

指针寄存器不可分割成 8 位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果

它们主要用于访问堆栈内的存储单元,并且规定:

  • BP 为基指针(BasePointer)寄存器,用它可直接存取堆栈中的数据
  • SP 为堆栈指针(StackPointer)寄存器,用它只可访问栈顶

1.4 段寄存器

在 32 位 CPU 中,有 6 个 16 位的段寄存器,所以,在此环境下开发的程序最多可同时访问 6 个段。

段寄存器是根据内存分段的管理模式而设置的。内存单元的物理地址是由段寄存器的值和一个偏移量组合而成
的,这样可用两个较少位数的值组合成一个可访问较大物理空间的内存地址。

CPU 内部的段寄存器为:

CS(Code Segment):代码段寄存器,其值为代码段的段值。

DS(Data Segment):数据段寄存器,其值为数据段的段值。

SS(Stack Segment):堆栈段寄存器,其值为堆栈段的段值。

ES(Extra Segment):附加段寄存器,其值为附加数据段的段值。

FS(Extra Segment):附加段寄存器,其值为附加数据段的段值。

GS(Extra Segment):附加段寄存器,其值为附加数据段的段值。

尽管只有 6 个段寄存器,但程序可以把同一个段寄存器用于不同地目的,这 6 个段寄存器中的 3 个有专门的用途:

  1. CS:代码段寄存器,指向包含程序指令的段。
  2. SS:栈段寄存器,指向包含当前程序栈的段。
  3. DS:数据段寄存器,指向包含静态数据或者全局数据段。

其它 3 个段寄存器用作一般用途,可以指向任意的数据段。

1.4.1 实模式与保护模式简述

32 位 CPU 有两个不同的工作模式:实模式和保护模式。实模式和保护模式都是 CPU 的工作模式,而 CPU 的工作模式是指 CPU 的寻址方式、寄存器大小等用来反应 CPU 在该环境下如何工作的概念。

在这两种模式下,段寄存器的作用是不同的。有关规定简单描述如下:

实模式: 前 4 个段寄存器 CS、DS、SS 和 ES 与先前 CPU 中的所对应的段寄存器的含义完全一致,内存单元的逻辑地址仍为段基址:段偏移量的形式。为访问某内存段内的数据,必须使用该段寄存器和存储单元的偏移量。

保护模式: 在此模式下,情况要复杂得多,装入段寄存器的不再是段值,而是被称为“段选择子”(Segment Selector)的某个值,下面 1.4.3 小节会介绍。

1.4.2 实模式原理

实模式出现于早期 8086 和 8088 CPU 时期。当时由于 CPU 的性能有限,一共只有 20 位地址线 A19 ~ A0,寻址 1MB 的存储空间,其物理地址范围为 00000H ~ FFFFFH(即 2^20 = 1048576 Byte,所以地址空间只有 1MB)。由于复位后首先从地址高端的 FFFFFH 开始执行指令,所以将地址高端设置位 ROM 空间,而低端作为 RAM 空间。

80386 的实模式是为了与 8086 处理器兼容而设置的,在实模式下,80386 处理器就相当于一个快速的 8086 处理器。80386 处理器被复位或加电的时候以实模式启动,这时候处理器中的各寄存器以实模式的初始值工作。

此时 80386 的 32 位地址线只使用了低 20 位,即可访问 1MB 的物理地址空间。在实模式下,80386 处理器不能对内存进行分页机制的管理,所以指令寻址的地址就是内存中实际的物理地址。在实模式下,所有的段都是可以读、写和执行的。实模式下 80386 不支持优先级,所有的指令相当于工作在特权级(即优先级 0),所以它可以执行所有特权指令,包括读写控制寄存器 CR0 等。这实际上使得在实模式下不太可能设计一个有保护能力的操作系统。

它有 8 个 16 位的通用寄存器,以及 4 个 16 位的段寄存器。所以为了能够通过这些 16 位的寄存器去构成 20 位的主存地址,必须采取一种特殊的方式。当某个指令想要访问某个内存地址时,它通常需要用下面的这种格式来表示:

段基址:段偏移量

其中第一个字段是段基址,它的值是由段寄存器提供的(一般来说,段寄存器有 6 种,上面有介绍)。

第二个字段是段内偏移量,代表要访问的这个内存地址距离这个段基址的偏移。它的值就是由通用寄存器来提供的,所以也是 16 位。那么两个 16 位的值如何组合成一个 20 位的地址呢?CPU 采用的方式是把段寄存器所提供的段基址先向左移4位。这样就变成了一个 20 位的值,然后再与段偏移量相加。即:

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

所以假设段寄存器中的值是 0xff00,段偏移量为 0x0110。则这个地址对应的真实物理地址是:0xff00<<4 + 0x0110 = 0xff110

由上面的介绍可见,实模式的“实”更多地体现在其地址是真实的物理地址。

1.4.3 保护模式原理

随着 CPU 的发展,CPU 地址线的个数也从原来的 20 根变为现在的 32 根,可以访问的内存空间也从 1MB 变为现在4GB,寄存器的位数也变为 32 位。所以实模式下的内存地址计算方式就已经不再适合了,因此就引入了现在的保护模式,实现更大空间的、更灵活也更安全的内存访问。

简单地说,通过保护模式,可以把虚拟地址空间映射到不同的物理地址空间,且在超出预设的空间范围会报错(一种保护机制的体现),且可以保证处于低特权级的代码无法访问高特权级的数据(另外一种保护机制的体现)。

在保护模式下,80386 的 32 条地址线全部有效,但是内存寻址方式还是得兼容老办法,即(段基址:段偏移量)的表示方式,这使得其:

  • 可寻址高达 4GB 的线性地址空间和物理地址空间。此时 CPU 中的通用寄存器都要换成 32 位寄存器(除了段寄存器)来保证寄存器能访问所有的 4GB 空间。
  • 可访问 64TB(有 2^14 个段,每个段最大空间为 2^32 字节)的虚拟地址空间。
  • 可采用分段存储管理机制和分页存储管理机制。

保护模式下的偏移值和实模式下是一样的,就是变成了 32 位而已。段值仍旧是存放在原来 16 位的段寄存器中,但是这些段寄存器存放的却不再是段基址了。之前说过实模式下寻址方式不安全,在保护模式下需要加一些限制,而这些限制不是一个寄存器能够容纳的,于是把这些关于内存段的限制信息放在一个叫做全局描述符表(GDT:Global Descriptor Table)的结构里。全局描述符表中含有一个个表项,每一个表项称为段描述符。在保护模式下,段寄存器存放的便是相当于一个数组索引的东西,即段选择子(Segment Selector),通过这个索引,可以找到对应的表项。

在保护模式下,每个内存段就是一个段描述符。段描述符存放了段基址(Base)、段界限(Limit)、内存段类型属性(比如是数据段还是代码段,注意一个段描述符只能用来定义一个内存段)等许多属性,具体信息见下图:

segment

其中,段界限表示段边界的扩张最值,即最大扩展多少或最小扩展多少,用 20 位来表示,它的单位可以是字节,也可以是 4KB,这是由 G 位决定的(G 为 1 时表示单位为 4KB)。

实际段界限边界值 =(描述符中的段界限+1)*(段界限的单位大小(即字节或 4KB))-1,如果偏移地址超过了段界限,CPU 会抛出异常。

此外, 扩充的存储器分段管理机制和可选的存储器分页管理机制,有如下好处:

  • 不仅为存储器共享和保护提供了硬件支持,而且为实现虚拟存储器提供了硬件支持。
  • 支持多任务,能够快速地进行任务切换(switch)和保护任务环境(context)。
  • 4 个特权级和完善的特权检查机制,既能实现资源共享又能保证代码和数据的安全和保密及任务的隔离。
  • 支持虚拟 8086 方式,便于执行 8086 程序。

1.4.4 延伸知识:段选择子、特权级细节

这一小节会对段选择子及特权级的细节进行总结。

特权级
  • 通过提供 4 个特权级和完善的特权检查机制,既能实现资源共享又能保证代码和数据的安全和保密及任务的隔离。
  • 在保护模式下,特权级总共有 4 个,编号从 0(最高特权)到 3(最低特权)。有 3 种主要的资源受到保护:内存,I/O 地址空间以及执行特殊机器指令的能力。Linux 只用 0 级和 3 级,分别称之为内核态和用户态。
  • 在任一时刻,80386 CPU 都是在一个特定的特权级下运行的,从而决定了代码可以做什么,不可以做什么。这些特权级经常被称为为保护环(protection ring),最内的环(ring 0)对应于最高特权 0,最外面的环(ring 3)一般给应用程序使用,对应最低特权 3。
  • 在保护模式下,可以通过查看 CS 寄存器的低 2 位来了解进程的当前特权级(CPL)。
CPL、RPL 与 DPL
  • CPL 是当前进程的权限级别(Current Privilege Level),是当前正在执行的代码所在的段的特权级,存在于 CS 寄存器的低 2 位

  • RPL 说明的是进程对段访问的请求权限(Request Privilege Level),它存在于段选择子的低 2 位,每个段选择子有自己的 RPL。它说明的是进程对段访问的请求权限,有点像函数参数。而且 RPL 对每个段来说不是固定的,两次访问同一段时的 RPL 可以不同。RPL 可能会削弱 CPL 的作用,例如当前 CPL=0 的进程要访问一个数据段,它把段选择子中的 RPL 设为 3,这样它对该段只有特权为 3 的访问权限。

    RPL 引入的目的是避免低特权级的程序访问高特权级的资源。

  • DPL 存储在段描述符中,规定访问该段所需的权限级别(Descriptor Privilege Level),每个段的 DPL 是固定的。

  • 当进程访问一个段时,即处理器将段选择子载入段寄存器之前,要进行特权级检查,比较当前运行的进程或任务的特权级(CPL),段选择子的 RPL,还有该段的段描述符的 DPL。一般要求在数值上: CPL <= DPL && RPL <= DPL(注意,数字越大特权级越低),满足此条件时处理器会将段选择子装载入段寄存器,否则不会装载到段寄存器。

段选择子的存储布局
  • Index:段寄存器的高 13 位存放的就是 GDT 的 Index。
  • Table Indicator(TI):Index 后面的 1 位,用来指示描述符表的类别,0 表示 GDT,1 表示 LDT(Local Descriptor Table)。
  • Requested Privilege Level(RPL):低 2 位存放的是进程对段访问的请求特权级。
段选择子(Segment Selector)

它是这样定义的:

A segment selector is a 16-bit identifier for a segment . It does not point directly to the segment, but instead points to the segment descriptor that defines the segment.

也就是前面说的,段选择子是一个 16 位的段标识符,它不直接指向段,而是指向 GDT 中定义该段的段描述符。此外还需要理解:

  • 处理器一共提供了 6 个段寄存器来保存段选择子。
  • 当一个进程要访问某个段的时候,这个段的段选择子必须被赋值到某一个段寄存器中。因此,尽管系统定义了数千个段,只有 6 个段是可以被直接使用的。其他段只有在它们的段选择子被置入这些寄存器中时才可以被使用。
  • 对任何程序的执行而言,至少要将代码段寄存器(CS),数据段寄存器(DS)和堆栈段寄存器(SS)赋予有效的段选择子。此外,处理器还提供了另外 3 个数据段寄存器(ES,FS 和 GS)供进程使用。

也就是说:

  • 在同一时刻程序中可以有多个段选择子,也就是可以有多个 RPL,然而只有 CS 寄存器(也就是存放正在执行的代码的寄存器)中的 RPL才等于 CPL。

CS 段寄存器指向的是 CPU 中当前运行的指令,所以 CS 的 RPL 位称为当前特权级 CPL。所以得出结论:CS.RPL 的值 = CPL 的值。

1.5 指令指针寄存器

32 位 CPU 把指令指针扩展到 32 位,并记作 EIP,EIP 的低 16 位与先前 16 位 CPU 中的 IP 作用相同。

指令指针 EIP、IP(InstructionPointer)是存放下次将要执行的指令在代码段的偏移量。在具有预取指令功能的系统中,下次要执行的指令通常已被预取到指令队列中,除非发生转移情况。所以,在理解它们的功能时,不考虑存在指令队列的情况。

在实模式下,由于每个段的最大范围为 64K,所以,EIP 中的高 16 位肯定都为 0,此时,相当于只用其低 16 位的 IP 来反映程序中指令的执行次序。

1.6 标志寄存器

以下是运算结果标志位。

1.6.1 进位标志 CF(CarryFlag)

进位标志 CF 主要用来反映运算是否产生进位或借位。如果运算结果的最高位产生了一个进位或借位,那么,其值为 1,否则其值为 0。

使用该标志位的情况有:多字(字节)数的加减运算,无符号数的大小比较运算,移位操作,字(字节)之间移位,专门改变 CF 值的指令等。

1.6.2 奇偶标志 PF(ParityFlag)

奇偶标志 PF 用于反映运算结果中“1”的个数的奇偶性。如果“1”的个数为偶数,则 PF 的值为 1,否则其值为 0。

利用 PF 可进行奇偶校验检查,或产生奇偶校验位。在数据传送过程中,为了提供传送的可靠性,如果采用奇偶校验的方法,就可使用该标志位。

1.6.3 辅助进位标志 AF(AuxiliaryCarryFlag)

在发生下列情况时,辅助进位标志 AF 的值被置为 1,否则其值为 0:

  1. 在字操作时,发生低字节向高字节进位或借位时。
  2. 在字节操作时,发生低 4 位向高 4 位进位或借位时。

1.6.4 零标志 ZF(ZeroFlag)

零标志 ZF 用来反映运算结果是否为 0。如果运算结果为 0,则其值为 1,否则其值为 0。在判断运算结果是否为 0 时,可使用此标志位。

16.5 符号标志 SF(SignFlag)

符号标志 SF 用来反映运算结果的符号位,它与运算结果的最高位相同。在微机系统中,有符号数采用补码表示法,所以,SF 也就反映运算结果的正负号。运算结果为正数时,SF 的值为 0,否则其值为 1。

1.6.6 溢出标志 OF(OverflowFlag)

溢出标志 OF 用于反映有符号数加减运算所得结果是否溢出。如果运算结果超过当前运算位数所能表示的范围,则称为溢出,OF 的值被置为 1,否则,OF 的值被清为 0。

对以上 6 个运算结果标志位,在一般编程情况下,标志位 CF、ZF、SF 和 OF 的使用频率较高,而标志位 PF 和 AF 的使用频率较低。

2. X86 平台启动过程

2.1 读取第一条指令

在计算机通电后,寄存器在初始状态下会有一个缺省值。

CS(16 位的段寄存器的基址) 和 EIP(段内偏移) 结合在一起形成了启动后的第一条地址,CPU 会从该地址获取指令来执行。

上面有介绍,X86 为了向下兼容 8086,刚启动时是处于 16 位的实模式,会按照实模式的寻址方式来寻址:

CS: IP 的值:: IP 表示 CS 左移了 4 位再加上 IP 地址。

CS:16 位

EIP:16 位

所以第一条地址的实际值如下:

  • CS = F000H,EIP = 0000FFF0H

  • 实际地址是:

    Base + EIP = FFFF0000H + 0000FFF0H = FFFFFFF0H,这就是 BIOS 的 EPROM(Erasable Programmable Read Only Memory)所在地址。

  • 当 CS 被新值加载,地址转换规则将开始起作用。

  • 通常第一条指令是一条长跳转指令(这样 CS 和 EIP 都会更新),会跳转到 BIOS 代码中做初始化工作

2.2 BIOS 所做的工作

BIOS 主要做一些硬件的初始化工作,完成一些关键硬件的自检,随后:

  • BIOS 会加载存储设备的第一个扇区(Master Boot Record 即主引导扇区,简写为 MBR)中的 512 字节内容读取到内存的固定地址 0x7c00 处。
  • 然后 IP 寄存器地址会跳转到 0x7c00 这个地址,使得 CPU 可以执行 MBR 中的的代码,即 Bootloader 的代码。
  • Bootloader 会完成操作系统的进一步加载。

2.3 Bootloader 所做的工作

Bootloader 所做的工作,简单说就是进入保护模式,加载操作系统到内存,如下:

  • 从16 位寻址空间的实模式切换到32 位寻址空间的保护模式(即从 1MB 到 4GB),为后续操作系统的执行做准备,同时段机制(Segment-Level Protection)也被加载。
  • 从硬盘中 MBR 后面的扇区读取操作系统 kernel 代码,加载到内存中固定位置。
  • 根据 CS 寄存器中 Index 值来找到操作系统所在内存的起始地址,即操作系统的入口点(entry point),来把控制权交给了操作系统。

这样 Bootloader 就完成了操作系统的加载工作。

当然这还没完,仅仅是把操作系统加载到内存中,接下来终于要轮到操作系统登场了。

在总结完后看到条 2017 年的新闻:Intel 已经决心在 2020 年之前,彻底淘汰 PC BIOS,全面向 UEFI 固件过渡,而这也意味着一个时代的终结。

继任者叫 UEFI。

3. 参考

  1. 段选择符 段寄存器
  2. 通用32位CPU 常用寄存器及其作用
  3. CPL RPL与DPL 之间的区别和联系

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions