资源
正文
3. 硬件视角的操作系统
3.1 硬件视角的操作系统
一句话:硬件根本不知道有没有操作系统
- 我就是个无情执行指令的状态机
- 见什么指令执行什么指令
计算机系统中的抽象
- 下层不需要知道上面怎么用,只管 “无情地提供服务”
- 系统调用支撑应用程序
- 指令集支撑高级语言程序
计算机系统的状态机模型
硬件也可视为是一个状态机,就是一个无情执行指令的机器。
1. 状态
- 内存、寄存器的数值
2. 初始状态
- 由系统设计者规定 (CPU Reset)
3. 状态迁移
- 从 PC 取指令执行
PC(Program Counter) 中文叫 程序计数器,作用是:存放下一条将要执行的指令在内存中的地址。
定义一个 CPU 状态结构体,用软件模拟 CPU 的内部组成。
1. 计算机系统:状态
寄存器、内存
1 |
|
成员 | 含义 |
---|---|
regs[32] |
通用寄存器数组(32 个寄存器,如 x0~x31) |
csrs[CSR_COUNT] |
控制状态寄存器组(例如程序计数器 PC、状态寄存器等) |
mem |
指向内存的指针(CPU 可以访问的主存) |
mem_offset , mem_size |
表示内存的起始位置和大小,用于确定可访问的范围 |
定义一个 CPU 状态结构体,描述 CPU 能直接操作的一切:寄存器 + 内存。
还有外部世界的态(除 CPU 自身的寄存器和内存外)
-
设备上的寄存器 (memory-mapped I/O 可以访问)
- 外设(I/O 设备,如键盘、网卡、显示器)
- 中断信号线(Interrupt Line)
- 复位信号线(Reset Line)
- 其他外部环境
-
Interrupt/Reset Line
-
客观存在,但计算机系统不能直接访问
-
外设发中断 → CPU 被迫响应;
-
复位线拉低 → CPU 状态被清零。
-
类比:进程只能通过 syscall 访问进程外的信息
-
计算机系统可分为 CPU 内部状态(寄存器、内存) 与 外部世界状态(设备、中断、复位线等)。
CPU 只能通过受限的接口(如 memory-mapped I/O 或中断)与外部世界交互,就像进程只能通过 syscall 与系统交互一样。
例如单片机 GPIO 口:
1 |
|
层次 | 角色 | 类比 |
---|---|---|
用户空间 | 应用调用 /sys/class/gpio 或驱动提供的接口 |
“按下按钮” |
内核空间 | gpio_set_value() |
“告诉电路控制器输出高电平” |
硬件层 | memory-mapped I/O 写入 GPIO 控制寄存器 | “真的把电压线拉高” |
表示在 Linux 内核中控制 GPIO 引脚输出高电平。
它通过 memory-mapped I/O 方式写入 GPIO 控制寄存器,从而影响外部世界(让引脚电压变化),这正是“CPU 访问外部世界状态”的例子。
2. 计算机系统:初始状态
老旧电脑配 RESET 按钮,是为了在系统死机或初始化混乱时,人为触发硬件复位信号,让 CPU 和外设回到“初始状态”,重新开始执行引导程序。
现代电脑仍然有复位逻辑,但通过电源管理芯片和固件自动完成,不再需要用户手动按按钮。
在 x86-64 CPU 上,Reset 状态意味着:
- CPU 回到 16 位实模式;
- CS:IP = F000:FFF0(物理地址 0xFFFFFFF0);
- 各控制寄存器、标志寄存器清零;
- 中断禁用、分页关闭;
- 从 BIOS ROM 顶端开始执行;
这就是计算机“上电的第一条指令”。
硬件视角的操作系统
RISC-V:百花齐放,百家争鸣
- 初始 PC 无规定;寄存器除了 x0 全部 undefined
- 少数 CSR 有规定 (例如 interrupt disabled)
- 设计原则:省电路
- 软件能做的,硬件绝对不管
3. 计算机系统:状态迁移
系统的每一个“动作”——例如执行一条指令、响应中断、读写外设——都会让这个状态发生改变,这就是“状态迁移”。
执行指令
- 如果有多个处理器?
- 可以想象成 “每次选一个处理器执行一条指令”
- 在并发部分会回到这个问题
响应中断
if (intr) goto vec;
(跳转到中断向量)- CPU 在正常执行指令;
- 外部世界(设备)发出中断信号;
- 系统检测到中断;
- 执行流立即“跳转”到中断处理程序(vector)。
输入输出
- 与 “计算机系统外” 交换数据
- 类似于系统调用的 “访问外部” 机制
计算机内部状态:寄存器、内存、设备寄存器
外部世界状态:例如键盘被按下、硬盘电机转动、显示器像素点亮
回到开头:硬件视角的操作系统
一句话:硬件根本不知道有没有操作系统
- 我就是个无情之行指令的状态机
- 见什么指令执行什么指令
CPU 的职责非常“机械”:
- 它从内存中取指(fetch);
- 译码(decode);
- 执行(execute);
- 然后继续下一条。
CPU 不会也不可能理解“这是不是操作系统”。
对它而言,内存里的所有内容都只是 0 和 1 的机器指令。
操作系统就是一个普通的(二进制)程序
- 接管了中断、I/O、……
- 应用程序不能直接访问
操作系统在启动后接管了硬件资源的访问权;
它设置了中断向量表(告诉 CPU:中断时跳到哪里执行);
它管理内存、进程、I/O、文件系统;
应用程序要访问硬件时,只能**通过操作系统提供的接口(系统调用)**间接完成。
- 把应用程序 “放” 到 CPU 上运行一会
- 中断后,操作系统又开始执行
- (操作系统启动后,操作系统就变成了一个中断处理程序)
操作系统只是“在中断发生时,硬件自动跳去执行的一段特别的程序”。
3.2 固件:硬件和操作系统之间的桥梁
CPU = 无情执行指令的机器
- 从 CPU Reset 开始执行
- 从
Mem[PC]
取指令 - 译码、执行,如此往复
- 从
- 这里必须得是合法的代码
- 那到底执行了什么代码呢?
- 代码是谁放在这里的呢?
CPU 本身没有程序存储器,也不会凭空知道“接下来要干嘛”,所以必须在它 Reset 后取指的那块内存区域里,已经有可以执行的机器码。
这些机器码就是主板或硬件厂商预先写入的,通常存在于**非易失性存储器(ROM/Flash)**里。
答案:系统厂商的代码
- 把一个特殊的存储器 memory-map 到 CPU Reset 后的代码
- 这段代码 “出生” 就有机器完整的控制权
平台 | 固件名称 | 作用 |
---|---|---|
x86 | BIOS / UEFI | 初始化硬件、加载操作系统 |
ARM(嵌入式) | BootROM / Bootloader | 初始化外设、启动内核 |
单片机 | 固件 (firmware) | 直接控制硬件,无操作系统 |
当 CPU 上电复位后,它不会直接执行操作系统,而是先执行固件。
固件是硬件厂商预置的一段程序,它拥有系统的完全控制权,并负责把控制权移交给操作系统。
Firmware
“固件”
- 厂商 “固定” 在计算机系统里的代码
- 早期:固件是 ROM
- 想升级?换芯片!(今天可以直接升级固件了,但要升级失败可能就变成砖了)
Firmware 的功能
- 运行程序前的计算机系统配置
- CPU 电压、内存时序、接口开关……
- (这些配置要生效可能需要重启计算机)
- 不严格地说,加载操作系统
- QEMU:可以绕过 Firmware 直接加载操作系统 (Manual)
Firmware 可以配置操作系统、加载引导程序,就是一段代码。
Firmware 就是一个小 “操作系统”
- CPU Reset 后初始化硬件;对接操作系统 Boot Loader
当计算机上电复位后,CPU 什么都不知道。
固件(BIOS 或 UEFI) 负责完成以下工作:
- 初始化各种硬件(CPU、内存、显卡、键盘、磁盘控制器等);
- 构建一个最小可用的执行环境;
- 寻找系统启动介质(硬盘、光盘、U盘、网络等);
- 加载并跳转执行 操作系统的 Boot Loader。
Legacy BIOS (Basic I/O System)
- IBM PC 所有设备/BIOS 中断是有 specification 的
- 16-bit DOS 时代 BIOS 常驻内存,提供 I/O 等功能
只要硬件厂商实现了同样的 BIOS 接口规范,操作系统就可以不关心底层硬件差异。
- 成就了百花齐放的 “兼容机” 时代
- AMI 和 Phoenix BIOS, 等都活到了今天!
BIOS 规范是公开的、文档化的(有完整的 Specification)。
于是:
- 任何厂商都可以做出“IBM PC 兼容”的机器;
- 只要 BIOS 符合规范,DOS/Windows 就能直接运行。
UEFI (Unified Extensible Firmware Interface)
- 提供更丰富的支持 (例如设备驱动程序):指纹锁、山寨网卡上的 PXE 网络启动、USB 蓝牙转接器连接的蓝牙键盘……
现代主板上的 UEFI,已经是一个“功能齐全的小操作系统”——它有文件系统、驱动、图形界面、甚至网络栈。
如何防止病毒破坏硬件?
Firmware 通常是只读的(当然……)
- (它可是接管了 CPU Reset,store 指令不会有任何效果)
但是 Firmware 也需要更新
- Intel 430TX (Pentium) 芯片组允许写入(更新)PROM
- 在那个时代,大家还没意识到问题……
- 有些主板有写保护的跳线 (但默认可写)
- 为防止 Bug 损坏 Firmware
- 只要向 BIOS 写入特定序列,写保护即打开
- 但这个序列就在手册里……导致曾经就有能够破坏硬件的病毒出现,坏了只能拆主板,换 EEPROM:cih-1.4.asm
只允许写入信任的固件更新
- 数字签名机制
- 公钥加密(Diffie-Hellman/RSA)
- 感谢今天的 SSL/TLS(HTTPS)!
- Intel Boot Guard 私钥泄露
为什么现在电脑病毒越来越少了?
- 更安全的操作系统
- AppStore 机制(软件包经过审查)
- 云端备份(“给钱恢复文件” 勒索越来越无效)
3.3 加载操作系统
回到 40 年前
IBM PC/PC-DOS 2.0 (1983)
- Firmware (BIOS) 会加载磁盘的前 512 字节到
0x7c00
- (如果这 512 字节最后是
0x55
,0xAA
) - 为什么是
0b01010101
和0b10101010
?(还能起到检查磁盘是否损坏的效果)
- (如果这 512 字节最后是
- BIOS 加载磁盘前 512 字节到
0x7C00
→ CPU 执行- Boot Sector 最后两个字节必须是 0x55AA(魔数)
- 简单示例代码:
EB FE
→ 无限循环- 508 个零填充
55 AA
→ 魔数这就是 IBM PC DOS 时代最基本的 操作系统加载流程。
让我们试一试
- 我们喜欢 shell 的原因:Quck & Dirty
(printf "\xeb\xfe"; cat /dev/zero | head -c 508; printf "\x55\xaa") > a.img
eb fe
是jmp
.
printf "\xeb\xfe"
EB FE
是 x86 机器码的jmp .
- 意思:无限循环自己,防止 CPU 执行到未知区域崩溃
cat /dev/zero | head -c 508
- 生成 508 个 0 字节,填充 Boot Sector 的剩余空间
printf "\x55\xaa"
- 在末尾添加 魔数 0x55AA,保证 BIOS 识别这是有效启动扇区
整个文件大小 = 512 字节 = Boot Sector 完整大小。
如果 Firmware 也是代码?
计算机系统从 CPU Reset 开始
- CPU Reset 的时候,
0x7c00
应该是啥也没有的 - Firmware 的代码扫描了磁盘、加载了它
那我们是不是可以看到 “加载” 的过程?
- 计算机系统公理:你想到的就一定有人做到
- 再次使用 QEMU, A fast and portable dynamic translator
- 如果你想知道是哪条指令加载了
0x7c00
位置的eb fe
,你需要?
- 如果你想知道是哪条指令加载了
Firmware 和系统程序员的第一个接口
我们可以写 446 字节的 16-bit 代码
- 446 = 512 - 2 (55 aa) - 64 (分区表)
Boot Sector = 512 字节(一个扇区)
结构划分:
区域 大小 用途 0–445 446 字节 Bootloader Stage 1 代码 446–509 64 字节 分区表(Partition Table) 510–511 2 字节 魔数 0x55AA 所以:
系统程序员在最底层,能直接写的 16-bit Bootloader 代码只有 446 字节。
这就是 “Firmware 和程序员的第一个接口”,也叫做 MBR Stage 1。
Grub 的例子
- Stage 1: 扫描磁盘,找到附近的 ELF 文件头(ELF 是 Linux 可执行文件格式),加载到内存
- 根据文件系统,可能会需要 Stage 1.5
- Stage 2: 这个 ELF 文件是 Grub; 弹出熟悉的选择系统窗口
- Stage 3: 加载 Linux Kernel
- 忽然觉得没什么难的了!
Grub 是一个 Bootloader(引导加载程序),作用是在固件(BIOS/UEFI)完成最初的硬件初始化后,把控制权交给操作系统内核。
RISC-V: 固件与操作系统引导
只需要提问 & 追问就行了
- RISC-V 系统是如何复位、执行什么固件、如何加载操作系统的?
- OpenSBI 的入口位于什么地方?
_start
开始的_try_lottery
是做什么的?
阶段 | 作用 | 地址/入口 |
---|---|---|
CPU Reset | 指向复位向量,启动固件 | 硬件定义地址,如 0x1000 |
固件(OpenSBI) | 初始化平台,提供 SBI 调用 | _start 函数 |
内核 | 接管系统,启动任务/进程 | _start ,第一个执行 _try_lottery 或类似函数 |