从0开始的操作系统(3)

硬件视角的操作系统:计算机启动时到底发生了什么?------从 CPU Reset、Firmware 到 Bootloader

往期回顾

《指针合集》《c语言基础》《数据结构》《机器学习导论》《前端基础》

声明

这部分内容主要是笔者根据NJU的蒋炎岩老师的OS课程整理而成,大家如果感兴趣的话可以上课程的网站看看.

课程网站 https://jyywiki.cn


目录


一、前言:这次我们站在"硬件"这边看操作系统

前两篇文章我们分别从两个角度看了操作系统:

text 复制代码
第一讲:为什么需要操作系统?
第二讲:应用程序如何使用操作系统?

因此我们可以得到以下两个结论

1.操作系统是复杂度逼出来的抽象层。

2.应用程序通过系统调用使用操作系统提供的抽象。

到了这一讲,我们继续往下挖一层。

那就是:

从硬件的角度看,操作系统到底是什么?

那我们为何要干这个事情呢?

因为我们平时总觉得操作系统很神秘,好像它天生就在那里:

开机之后,Windows、Linux、macOS 就出现了;

然后程序能运行,文件能打开,鼠标键盘能用,网络也能连。

但如果从硬件视角看,事情其实非常"冷酷":

硬件根本不知道有没有操作系统。

CPU 不会想:

text 复制代码
我要启动 Windows 了。
我要加载 Linux 了。
我要运行用户程序了。

CPU 只会做一件事:

text 复制代码
从某个地址取指令,然后执行。
再取下一条,再执行。

所以这篇文章的主线就是给大家讲明白:

计算机硬件是一台无情执行指令的状态机;操作系统本质上也是一个被加载起来执行的二进制程序;而 Firmware 则是硬件和操作系统之间的第一座桥。


二、Everything is a state machine

前面我们一直在强调一个视角:

Everything is a state machine.

这句话是理解系统的钥匙。

2.1 软件是状态机

一个 C 程序运行时,它的状态包括:

text 复制代码
栈帧
全局变量
局部变量
堆区数据
当前执行到哪一条语句

执行一条语句,就是一次状态迁移:

text 复制代码
当前程序状态
      │ 执行一条语句
      ▼
新的程序状态

2.2 硬件也是状态机

硬件也一样。

一台处理器的状态大致包括:

text 复制代码
寄存器
内存
程序计数器 PC
控制状态寄存器
外设状态

它的状态迁移规则很简单:

text 复制代码
┌──────────────┐
│ 当前硬件状态  │
│ 寄存器 + 内存 │
└──────┬───────┘
       │ 从 PC 取指令
       ▼
┌──────────────┐
│ 执行这条指令  │
└──────┬───────┘
       │ 更新状态
       ▼
┌──────────────┐
│ 新的硬件状态  │
└──────┬───────┘
       │
       └── 继续取下一条指令

所以从硬件角度看,计算机没有什么魔法。

它只是:

text 复制代码
一条指令
一条指令
一条指令
......

一直执行下去。


三、硬件不知道有没有操作系统

这是这一讲最重要的观点之一:

硬件根本不知道有没有操作系统。

CPU 不会区分:

text 复制代码
这条指令来自操作系统
这条指令来自应用程序
这条指令来自 Bootloader
这条指令来自 Firmware

在 CPU 看来,它们都是指令。

CPU 的思维非常简单:

text 复制代码
见什么指令,执行什么指令。

它并不会天然理解:

  • 什么是进程
  • 什么是文件
  • 什么是用户态
  • 什么是内核态
  • 什么是操作系统

而这些概念,都是上层系统设计出来的抽象。


四、抽象:隔离复杂性的根本手段

既然硬件只会无情执行指令,那为什么我们还能写 C 程序、跑 Python、打开浏览器、使用文件系统?

答案是:

靠抽象。

抽象的作用就是:

text 复制代码
下层只负责提供能力
上层不需要知道下层全部细节

比如:

text 复制代码
┌──────────────────────┐
│      应用程序          │
│ 使用 OS API            │
└─────────▲────────────┘
          │ syscall
┌─────────┴────────────┐
│      操作系统          │
│ 管理进程 / 内存 / 文件 │
└─────────▲────────────┘
          │ 指令 / 中断 / I/O
┌─────────┴────────────┐
│        硬件            │
│ CPU / 内存 / 设备      │
└──────────────────────┘

这就是计算机系统的层次结构。

每一层都不用彻底理解另一层的全部细节,只要接口设计得好,系统就能继续往上搭。


五、硬件状态:不仅有寄存器和内存,还有整个外部世界

如果我们把计算机看成状态机,那么首先要问:

这个状态机的状态包括什么?

最容易想到的是:

text 复制代码
寄存器
内存
PC

但这还不够。

因为计算机不是关在真空里的。它还要和外界交互:

  • 键盘输入
  • 鼠标移动
  • 显示器输出
  • 磁盘读写
  • 网卡收包
  • GPIO 电平变化
  • 中断信号
  • Reset 信号

所以硬件状态可以粗略分成两类:

text 复制代码
┌──────────────────────────┐
│        CPU / 内存          │
│ 寄存器、PC、RAM            │
└───────────▲──────────────┘
            │ memory-mapped I/O / 指令
┌───────────┴──────────────┐
│        外部设备            │
│ 磁盘、网卡、键盘、GPIO      │
└──────────────────────────┘

这里要注意一个东西:

text 复制代码
memory-mapped I/O

意思是:

一些设备寄存器会被映射到内存地址空间中。

于是 CPU 访问某些"内存地址"时,实际上是在读写设备寄存器。

比如嵌入式系统里常见的 GPIO:

c 复制代码
gpio_set_value(GPIO_23, 1);

从上层看,这像是在调用函数。

从底层看,本质上可能就是把某个设备寄存器的某一位设置为 1。

所以,计算机和外部世界发生关系,很多时候就是通过这种方式完成的。


六、CPU Reset:计算机启动的第一步

那我们继续追问:

计算机一上电,CPU 从哪里开始执行?

答案是:

text 复制代码
从 CPU Reset 规定的初始状态开始。

也就是说,CPU Reset 不是"随便开始",而是体系结构规定好的行为。

Reset 之后,处理器会进入一个明确的初始状态

text 复制代码
按下电源 / Reset
      │
      ▼
CPU 进入 Reset 状态
      │
      ▼
PC 被设置到指定位置
      │
      ▼
从该地址取第一条指令
      │
      ▼
开始执行

这就引出了一个非常自然的问题:

CPU Reset 后取到的第一条指令是谁放在那里的?

毕竟,刚上电时,内存里还没有操作系统。

操作系统甚至都还没被加载。

那么第一段代码从哪里来?

答案是:

text 复制代码
Firmware

七、Firmware:硬件和操作系统之间的第一座桥

Firmware 中文一般叫:

text 复制代码
固件

它本质上就是厂商预先放在机器里的代码。

你可以把它理解成:

计算机出生时就自带的一小段程序。

这段程序非常特殊,因为 CPU Reset 后最先执行的就是它。

7.1 Firmware 做什么?

Firmware 主要负责:

text 复制代码
扫描硬件
初始化硬件
配置设备
提供基本输入输出能力
寻找可启动设备
加载 Bootloader 或操作系统

换句话说,它要先把机器"点亮"。

整个过程就是:

text 复制代码
CPU Reset
   │
   ▼
执行 Firmware
   │
   ├── 初始化 CPU / 内存 / 设备
   ├── 扫描磁盘 / USB / 网络启动项
   ├── 找到 Bootloader
   └── 把控制权交给 Bootloader / OS

所以 Firmware 是什么?

它不是硬件,也不是完整意义上的操作系统。

它更像是:

text 复制代码
硬件和操作系统之间的启动桥梁

八、BIOS 和 UEFI:两代固件体系

在 PC 世界里,最常见的两类 Firmware 是:

text 复制代码
Legacy BIOS
UEFI

8.1 Legacy BIOS

BIOS 全称是:

text 复制代码
Basic Input/Output System

在早期 PC 时代,BIOS 提供了很多基础能力:

  • 硬件初始化
  • 中断服务
  • 基本 I/O
  • 从磁盘加载启动扇区

经典 BIOS 启动流程大致是:

text 复制代码
CPU Reset
   │
   ▼
BIOS 执行
   │
   ▼
扫描可启动磁盘
   │
   ▼
读取第一个扇区到 0x7c00
   │
   ▼
检查末尾是否为 0x55AA
   │
   ▼
跳转到 0x7c00 执行

如果各位的数感比较好,可能这里发现了这两个数字:

text 复制代码
0x7c00
0x55AA
  • 0x7c00 是传统 BIOS 把启动扇区加载到内存的位置
  • 0x55AA 是启动扇区末尾的 Magic Number

而他们的差值刚好是512字节,因为那时候内存的最大值刚好是512.

也就是说,如果一个 512 字节的扇区最后两个字节是:

text 复制代码
55 aa

BIOS 就会把它当成可启动扇区。

8.2 UEFI

UEFI 是更现代的固件接口。

相比传统 BIOS,UEFI 提供了更丰富的启动服务、运行时服务、驱动支持和启动管理能力。

它不再简单依赖"读取第一个扇区然后跳转执行"的模型,而是有更完整的 Boot Manager。

简化来看:

text 复制代码
CPU Reset
   │
   ▼
UEFI Firmware
   │
   ├── 初始化硬件
   ├── 读取 NVRAM 中的启动项
   ├── 找到 EFI System Partition
   ├── 加载 .efi 启动程序
   └── 启动 OS Loader / Kernel

所以可以这样理解:

text 复制代码
BIOS 更像是"传统手工启动流程"
UEFI 更像是"现代化的启动平台"

九、启动扇区:512 字节里的第一个操作系统接口

传统 BIOS 会把磁盘第一个扇区加载到内存 0x7c00,然后执行它。

这个扇区通常是:

text 复制代码
MBR:Master Boot Record

一个典型 MBR 大小是 512 字节,大致结构如下:

text 复制代码
┌────────────────────────────┐
│ Boot Code                  │ 446 字节
├────────────────────────────┤
│ Partition Table            │ 64 字节
├────────────────────────────┤
│ Boot Signature             │ 2 字节:55 AA
└────────────────────────────┘

也就是说,真正能写启动代码的空间很小。

传统 MBR 里,Boot Code 部分只有大约 446 字节。

这么点空间显然放不下一个完整操作系统。

所以它通常只做一件事:

加载更大的 Bootloader。

这就是为什么后来会有分阶段启动。


十、Bootloader:把操作系统真正加载起来

Bootloader 的作用可以概括成一句话:

把操作系统内核加载到内存,并把控制权交给它。

以 GRUB 为例,它的启动常常可以理解成多阶段:

text 复制代码
Stage 1:
非常小,位于 MBR 或启动扇区中

Stage 1.5:
提供更多磁盘 / 文件系统识别能力

Stage 2:
完整的 GRUB,显示启动菜单,加载内核

这一层设计非常自然:

text 复制代码
512 字节太小
   │
   ▼
先加载一个更大的程序
   │
   ▼
再由更大的程序加载操作系统

这也是计算机系统里很常见的思路:

用一个简单程序加载一个更复杂的程序。

当然各位可能对这个事情感到有点不可思议,这里推荐各位可以去看看b站up主漫士沉思录的这个视频当作拓展


十一、一个最小启动扇区:eb fe 为什么会死循环?

课程里有一个非常经典的小实验:

bash 复制代码
(printf "\xeb\xfe"; cat /dev/zero | head -c 508; printf "\x55\xaa") > a.img

这条命令生成了一个 512 字节的镜像。

拆开看:

text 复制代码
eb fe       两个字节的机器指令
508 个 0    填充
55 aa       启动扇区标志

其中:

text 复制代码
eb fe

是一条短跳转指令,大意是:

text 复制代码
跳回自己

也就是:

asm 复制代码
jmp .

所以它会让 CPU 永远卡在原地:

text 复制代码
执行 eb fe
   │
   ▼
跳回自己
   │
   ▼
再次执行 eb fe
   │
   ▼
继续跳回自己

它让我们亲眼看到:

只要启动扇区合法,BIOS 就会把控制权交给它;至于它做什么,CPU 并不关心。

你写一个死循环,它就执行死循环。

你写一个 Bootloader,它就执行 Bootloader。

你写一个玩具 OS,它就开始启动你的玩具 OS。

计算机世界没有魔法,只有指令。


十二、从 Firmware 到 OS:控制权是一步步交接的

现在我们可以把启动过程串起来。

传统 BIOS 模式下,简化流程是:

text 复制代码
CPU Reset
   │
   ▼
执行 BIOS Firmware
   │
   ▼
BIOS 初始化硬件
   │
   ▼
读取磁盘第一个扇区到 0x7c00
   │
   ▼
检查 55 AA
   │
   ▼
执行 MBR / Bootloader Stage 1
   │
   ▼
加载更大的 Bootloader
   │
   ▼
Bootloader 加载 Kernel
   │
   ▼
Kernel 接管中断、I/O、内存管理
   │
   ▼
操作系统开始运行

从这张图我们可以理解:

操作系统不是一开始就存在的,它是被前面的代码一步步加载起来的。


十三、操作系统本质上也是一个普通二进制程序

现在再回到那个问题:

从硬件视角看,操作系统是什么?

答案其实有点"降维打击":

操作系统也是一个普通的二进制程序。

只不过它很特殊:

text 复制代码
它被 Firmware / Bootloader 加载
它运行在更高权限级
它接管中断
它管理 I/O
它控制内存
它调度应用程序

但从硬件角度看,它仍然是:

text 复制代码
一段指令
一些数据
从某个入口开始执行

和普通程序没有本质上的神秘差异。

区别在于:

text 复制代码
普通应用程序:
被操作系统加载

操作系统内核:
被 Firmware / Bootloader 加载

所以,OS 不是魔法。

它只是一个更早被加载、更有权限、负责管理其他程序的程序。


十四、操作系统启动后,它变成了什么?

操作系统启动后,最关键的一件事是:

接管中断、异常和 I/O。

也就是说,当应用程序执行时,CPU 大部分时间可能都在跑应用程序。

但一旦发生:

text 复制代码
系统调用
时钟中断
设备中断
异常
缺页

控制权就会回到操作系统。

所以可以形象地说:

操作系统启动后,很大程度上就变成了一个中断处理程序。

平时它把 CPU 交给应用程序跑。

一旦有事,它又重新接管。

这就像一个管理者:

text 复制代码
平时让员工干活
有事件发生时介入处理
处理完再把工作交回去

十五、中断:让操作系统重新拿回控制权

如果 CPU 只是一直无情执行当前程序的指令,会发生什么?

比如一个程序写了:

asm 复制代码
jmp .

那它就会永远卡住。

如果没有额外机制,操作系统就再也拿不回 CPU。

所以硬件必须提供一种机制,让外部事件可以打断当前执行流。

这就是:

text 复制代码
Interrupt,中断

中断可以来自:

text 复制代码
时钟
键盘
磁盘
网卡
外设

最典型的是时钟中断。

时钟中断让操作系统可以定期重新获得控制权:

text 复制代码
应用程序运行一会
      │
      ▼
时钟中断到来
      │
      ▼
CPU 跳转到中断处理程序
      │
      ▼
操作系统决定是否切换进程

这就是操作系统实现多任务的基础之一。


十六、固件安全:为什么 Firmware 也会成为攻击目标?

既然 Firmware 是 CPU Reset 后最先执行的代码,那么它的地位就非常敏感。

如果 Firmware 被篡改,会发生什么?

text 复制代码
CPU Reset
   │
   ▼
执行被篡改的 Firmware
   │
   ▼
攻击代码比操作系统更早获得控制权

这非常危险。

因为这意味着攻击者可能在操作系统启动之前就已经控制了机器。

16.1 CIH 病毒的启示

历史上有一些病毒并不只是破坏文件,而是试图破坏 Firmware。

其中一个经典例子是 CIH 病毒。

它曾经试图覆盖部分系统 Firmware,使机器无法正常启动。

这类攻击在当年影响很大,因为一旦 Firmware 损坏,普通用户往往无法通过重装系统解决。

这说明:

系统安全不是从操作系统开始的,而是从 Firmware 就已经开始了。

16.2 现代固件为什么要签名?

为防止随意写入恶意固件,现代系统通常会引入数字签名机制。

基本思想是:

text 复制代码
厂商发布 Firmware 更新
      │
      ▼
使用私钥签名
      │
      ▼
设备使用公钥验证签名
      │
      ▼
验证通过才允许更新

这背后就是现代计算机启动安全的基础逻辑之一。


十七、RISC-V 世界里的 Firmware:OpenSBI

在 RISC-V 平台上,经常会看到一个名字:

text 复制代码
OpenSBI

SBI 的全称是:

text 复制代码
Supervisor Binary Interface

它可以理解成:

RISC-V 平台上,Firmware 和操作系统之间的一层标准接口。

在 RISC-V 的特权级模型中,通常会有:

text 复制代码
┌──────────────────────┐
│ User Program          │ U-mode
└─────────▲────────────┘
          │ syscall
┌─────────┴────────────┐
│ Operating System      │ S-mode
└─────────▲────────────┘
          │ SBI call
┌─────────┴────────────┐
│ OpenSBI / Firmware    │ M-mode
└─────────▲────────────┘
          │
┌─────────┴────────────┐
│ Hardware              │
└──────────────────────┘

这个结构非常漂亮。

它说明不同平台上虽然细节不同,但核心思想类似:

下层隐藏硬件差异,上层通过接口获得服务。

这仍然是抽象的力量。


十八、从硬件视角重新理解系统调用

上一讲我们从应用程序视角看 syscall:

text 复制代码
应用程序通过 syscall 请求操作系统服务。

这一讲从硬件视角看,syscall 更像是一条特殊指令。

例如不同架构上会有类似机制:

text 复制代码
x86:syscall / int
RISC-V:ecall
ARM:svc

它们的共同作用是:

text 复制代码
让 CPU 从当前执行流切换到更高权限的操作系统处理逻辑。

所以 syscall 不是普通函数调用。

普通函数调用仍然在同一个权限世界里。

syscall 则跨过了用户态和内核态的边界。

这也是为什么它是操作系统中最核心的接口之一。


十九、总结

这一讲可以用一张图串起来:

text 复制代码
CPU Reset
   │
   ▼
Firmware
   │
   ├── 初始化硬件
   ├── 配置设备
   └── 寻找启动设备
   │
   ▼
Bootloader
   │
   ├── 加载内核
   └── 传递启动参数
   │
   ▼
Operating System Kernel
   │
   ├── 接管中断
   ├── 管理内存
   ├── 管理设备
   ├── 调度进程
   └── 提供 syscall API
   │
   ▼
User Programs
   │
   ├── 普通计算
   └── syscall 请求 OS 服务

再从"状态机"的角度看:

text 复制代码
硬件:
从 Reset 开始,无情执行指令

Firmware:
最早执行的厂商代码,初始化硬件并加载下一阶段

Bootloader:
加载操作系统内核

操作系统:
接管中断和 I/O,把应用程序放到 CPU 上运行

应用程序:
执行普通计算,通过 syscall 请求系统服务

二十、关系

现在我们可以把前三讲合起来看。

20.1 第一讲:为什么需要 OS?

text 复制代码
硬件越来越复杂
程序越来越复杂
用户越来越多
资源需要共享
所以需要操作系统这个抽象层

20.2 第二讲:应用如何使用 OS?

text 复制代码
应用程序 = 普通计算 + OS API
程序通过 syscall 请求操作系统服务

20.3 第三讲:硬件如何承载 OS?

text 复制代码
硬件是无情执行指令的状态机
CPU Reset 后执行 Firmware
Firmware 加载 Bootloader / OS
OS 接管中断和 I/O

二十一、 重点总结

21.1 硬件不知道有没有操作系统

CPU 只会执行指令。

操作系统只是被执行的程序之一,只是权限更高、职责更特殊。

21.2 CPU Reset 是启动的起点

计算机启动不是从操作系统开始,而是从 CPU Reset 后的第一条指令开始。

21.3 Firmware 是硬件和 OS 之间的桥

Firmware 负责初始化硬件,并把后续控制权交给 Bootloader 或操作系统。

21.4 Bootloader 负责加载操作系统

传统 BIOS 模式下,MBR 的 512 字节空间太小,所以通常需要多阶段加载。

21.5 中断让 OS 能重新拿回 CPU

如果没有中断,一个死循环程序可能永远霸占 CPU。

时钟中断让操作系统可以实现调度。

21.6 OS 本质上也是二进制程序

它不是魔法,只是一个更早被加载、更有权限、负责管理资源的程序。


二十二、结语:计算机世界没有魔法,只有一层层交接的控制权

很多人第一次学操作系统,会觉得它很神秘。

但从硬件视角看,事情反而变简单了:

text 复制代码
CPU 不知道 OS 是什么。
CPU 只知道从 PC 取指令执行。

于是问题就变成:

text 复制代码
第一条指令在哪里?
谁把它放在那里?
它又如何加载下一段代码?
下一段代码又如何加载操作系统?
操作系统如何接管中断和 I/O?

这一串问题回答完,操作系统启动的轮廓也就出来了。

最终我们看到的是一个非常朴素的过程:

text 复制代码
CPU Reset
   → Firmware
   → Bootloader
   → Kernel
   → User Program

每一层都只是把控制权交给下一层。

所以,操作系统不是从天而降的神秘存在。

它只是计算机启动链条中的一个关键阶段。

说到底:

计算机世界没有魔法。所谓操作系统,不过是一段被正确加载、获得足够权限、接管中断和设备,并负责管理其他程序的二进制代码。


相关推荐
05候补工程师1 小时前
【考研线代】矩阵相似与对角化核心解题套路与防坑指南 (附实战笔记)
经验分享·笔记·线性代数·考研·矩阵
开发者联盟league1 小时前
pip install出现报错ERROR: Cannot set --home and --prefix together
开发语言·python·pip
FlagOS智算系统软件栈1 小时前
众智FlagOS完成腾讯混元MT2多语翻译模型全系列多芯片适配:英伟达/华为/平头哥三芯开箱即用
开发语言·人工智能·开源
東隅已逝,桑榆非晚1 小时前
C语言内存函数
c语言·开发语言·笔记·算法
lly2024061 小时前
Docker 安装 MySQL
开发语言
techdashen1 小时前
在 Async Rust 中实现请求合并(Request Coalescing)
开发语言·后端·rust
东风破1371 小时前
DM达梦数据库安全、审计功能学习记录
数据库·学习·oracle·dm达梦数据库
RSTJ_16251 小时前
PYTHON+AI LLM DAY FIFITY-THREE
开发语言·人工智能·python
JAVA社区1 小时前
Java进阶全套教程(一)—— 数据框架Mybatis详解
java·开发语言·面试·职场和发展·mybatis