找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2636

积分

0

好友

348

主题
发表于 6 小时前 | 查看: 4| 回复: 0

在上一篇文章操作系统是如何启动的中,我们提到了实模式和保护模式,但未深入探讨。今天,我们就来系统性地剖析这两个关键的 CPU 工作模式。在开始之前,不妨先看一个来自极客时间《操作系统实战45讲》的例子,它能非常形象地说明引入保护模式的必要性。

请看下面这段 C 语言代码:

int main()
{
 int* addr = (int*)0;
 cli();  //关中断
 while(1)
 {
  *addr = 0;
  addr++;
 }
 return 0;
}

如果这段代码能被顺利执行,后果将非常可怕。它直接获取了内存地址 0,并开始顺序向下遍历,肆意向内存写入数据。更关键的是,它还关闭了中断(cli())。想象一下,在一台多人共享的服务器上,一旦某个用户的进程执行了这段代码,所有其他用户留在内存中的数据都会被无情地清零,且在此过程中你无法通过中断来终止这个“破坏者”进程。

这段代码所体现的,正是实模式下的内存访问方式。实模式的主要特点在于其寻址方式,而这种寻址方式也直接导致了其在寻址范围和保护性上的局限性。现代操作系统中保留实模式,主要是为了兼容早期的系统。通常,在加载bootsect.s以及由bootsect.s执行载入setupsystem模块的阶段,CPU 运行在实模式下;之后,便会切换到功能更强大的保护模式。接下来,我们将分别深入这两种模式的工作原理。

Part1:实模式下的寻址

为了更直观地理解实模式,我们先回顾一下 BIOS 的加载过程,这正是实模式在工作的典型场景。BIOS 由硬件厂商预先烧录在 ROM 中,其物理地址被固定在 0xFFFF0。CPU 上电后,首要任务就是找到并执行这个地址的指令。

那么,在实模式下,CPU 如何表示一个指令在内存中的位置呢?它通过两个 16 位的寄存器协同工作:CS(Code Segment,代码段)寄存器IP(Instruction Pointer,指令指针)寄存器。CS 寄存器存放段基址,IP 寄存器存放偏移量

这里简单解释一下“段”的概念。了解汇编语言就会知道,程序代码通常被划分为多个逻辑段,例如代码段、数据段等。程序载入内存时,也是按段加载的。因此,取指令时就需要先找到段的起始位置(基址),再加上段内的偏移,才能得到最终的物理地址。

具体计算方法如下:将 CS 寄存器中的值左移 4 位(二进制),然后加上 IP 寄存器的值。以寻找 BIOS 地址为例,假设 CS 为 0xFFFF,IP 为 0x00000xFFFF 左移 4 位(十六进制视角下相当于乘以 16)得到 0xFFFF0,再加上 0x0000,最终物理地址就是 0xFFFF0。这样,CPU 就成功定位到了 BIOS 的起始指令。

实模式下通过CS和IP寄存器寻址示意图

之所以要将 CS 左移 4 位,是因为 CS 和 IP 都是 16 位寄存器,单独只能寻址 64KB 的空间。通过“(CS << 4) + IP”这种计算方式,形成了 20 位的有效地址,使得实模式下的寻址范围达到了 1MB。

然而,1MB 的寻址范围对于现代计算机来说实在太小了(即便是 32 位 CPU,其 CS 寄存器在实模式下依然是 16 位的)。这构成了实模式的一大主要缺陷。那么,保护模式是如何突破这个限制的呢?

Part2:保护模式的革新与内存保护

对于 32 位计算机而言,仅能寻址 1MB 内存显然不够。理论上,32 位地址总线应能寻址 4GB 空间。但为了保持向后兼容(例如在加载 BIOS 和最初引导阶段仍需使用实模式寻址),CS 寄存器仍然保持 16 位宽度,只是用于存放偏移的寄存器扩展成了 32 位的 EIP

为了适应这种变化,保护模式下 CS 寄存器中存储的不再是直接的段基址,而是一个称为“段选择子(Segment Selector)”的索引。段选择子的低 2 位用于权限控制,高 13 位则是一个索引号,用于查找全局描述符表(GDT, Global Descriptor Table)

段描述符结构示意图

CPU 根据段选择子中的索引,在 GDT 中找到对应的“段描述符(Segment Descriptor)”。段描述符中包含了真正的 32 位段基址、段长度界限以及各种访问权限属性。最后,将段描述符提供的段基址与 EIP 中的 32 位偏移相加,就得到了最终的线性地址(再经过分页机制转换得到物理地址)。

这个 GDT 表至关重要,它不仅是实现扩展寻址的关键,更是实现内存管理和保护机制的基石。让我们看看段描述符中几个关键字段的作用:

  • 段基址:与实模式类似,但现在是完整的 32 位地址。
  • 段界限:规定了段内偏移地址的最大范围。如果程序试图访问超出此界限的地址,CPU 会触发异常,从而阻止非法访问。
  • 描述符特权级(DPL):定义了访问该段所需的CPU特权级别。只有当段选择子中记录的当前特权级(CPL)满足 DPL 要求时,访问才被允许。这为实现用户态与内核态的隔离提供了硬件支持。
  • 类型字段:明确标识该段是代码段还是数据段,并规定其可读、可写、可执行等属性。

可以看出,GDT 对每个内存段的性质(代码/数据)、大小、位置和访问权限都做了精确描述。每次通过段选择子访问内存时,CPU 都会自动进行一系列严格的检查。

下面这张流程图清晰地展示了保护模式下完整的取指过程:
保护模式寻址流程图:从段选择子到线性地址

通过对比可以发现,保护模式通过引入段描述符和权限检查机制,从根本上解决了实模式的两个核心问题:一是将寻址能力从 1MB 扩展到了 4GB(32位下),二是为内存访问提供了硬件级别的安全保护,防止了文章开头那段“破坏性”代码的执行。理解这两种模式的差异,是深入学习操作系统内核与系统编程的重要基础。想了解更多计算机底层原理的深入讨论,可以关注云栈社区的相关技术板块。




上一篇:C语言指针从零到一:用形象比喻理解内存的地图
下一篇:杭州集成电路产业地图:25家领先芯片设计、EDA与半导体设备公司盘点
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-29 10:46 , Processed in 0.649822 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表