回顾: 刚开始上电,操作系统在硬盘上。而计算机的工作是取指执行,也就是要把代码放到内存上。所以我们第一步就是要把操作系统从磁盘上载入到内存,也就是二中比喻的:把菜谱交给厨师。这个过程在上一节bootsect.s进行。它将操作系统setup分段读取,并打印logo,然后把操作系统后面的system部分读入,最后将bootsect自己从前几个内存位置移走,通过指针交接给setup执行。
setup 模块就是内核在正式接管电脑前进行的“人口普查”和“资产登记”。
因为当 CPU 进入 32 位保护模式(Protected Mode)后,原本由 BIOS 提供的便捷中断服务(如读取磁盘、获取内存容量)就再也无法直接使用了。内核必须在“关灯”切换模式之前,把所有关键的硬件规格记录在案。 | 为什么会有进入32位保护模式这个动作? 因为在刚开机的时候,CPU 运行在 实模式(Real Mode)。这时候整个世界是 16 位的,地址空间 1MB,很多硬件初始化工作都是通过 BIOS 提供的中断来完成的。而处理器设计为了兼容性永远从实模式启动,但是实模式的16位支撑不了现代操作系统。所以需要先从16位引导跳到32位才能接着启动系统。
在进入保护模式之前,内核需要利用 BIOS 中断(这些中断在保护模式下将不可用)来获取机器的物理配置:
光标位置:用于后续屏幕输出。
内存大小:确定系统可用的物理内存边界。
显卡参数:确定显示模式(文本或图形)。
硬盘参数(设备根号):获取硬盘的柱面数、磁头数等,存放在特定的内存位置(如 0x90000 处)供内核后续使用。
为了给后续的内存布局腾出空间,setup 会将紧随其后的 system 模块(内核主体)从内存的高地址处搬移到起始地址 0x00000。
位将要转向的保护模式的内存管理机制的基础做准备。
加载 GDT (Global Descriptor Table):规定CPU 怎么理解一个地址、有没有权限访问。定义代码段和数据段的基址与权限。此时通常会将基址设为 0,实现平坦内存模型。 用处:为进入保护模式的 CPU 提供一个“新的地址解释规则
加载 GDT 是在告诉 CPU:
> 从现在开始,地址不是简单拼接了,而是需要经过“规则校验”。
这就是“保护”模式名字的来源。
操作系统的内存管理:
第一层:分段(GDT)
解决“地址是否合法、权限对不对”
第二层:分页(Page Table)
解决“虚拟地址映射到物理地址”
第三层:内核策略
解决“哪个进程能用多少内存”
加载 IDT (Interrupt Descriptor Table):当发生“事件”时,CPU 应该跳到哪里执行代码? 虽然此时还没有真正的中断处理函数,但必须先初始化一个空的或占位的表。
第一类:异常(Exception)
比如:
除 0
访问非法内存(页错误)
执行非法指令
第二类:硬件中断(Interrupt)
比如:
键盘按下
网卡收到数据
定时器触发
第三类:软件中断
比如:
int 0x80(早期 Linux 的系统调用方式)
在实模式里,有一个叫 IVT(中断向量表)的东西,但是转向保护模式就失效了。所以现在重新构建保护模式下的这套机制
这是“惊险的一跳”。通过设置控制寄存器 CR0 的 PE(Protection Enable)位,CPU 正式进入 32 位保护模式。
| 内存地址 | 阶段一 (Real Mode) | 阶段二 (Protected Mode) |
|---|---|---|
0x00000 |
BIOS 中断向量表 | 内核 system 模块 (head.s) |
0x90000 |
bootsect 副本 | 硬件参数存储区 |
0x90200 |
setup 代码 | (完成使命后被废弃) |
从此操作系统接管硬件。