OS-硬件视角的操作系统

学习自 B 站 UP 主绿导师原谅你了。

资源

正文

3. 硬件视角的操作系统

3.1 硬件视角的操作系统

一句话:硬件根本不知道有没有操作系统

  • 我就是个无情执行指令的状态机
    • 见什么指令执行什么指令

计算机系统中的抽象

  • 下层不需要知道上面怎么用,只管 “无情地提供服务”
    • 系统调用支撑应用程序
    • 指令集支撑高级语言程序

计算机系统的状态机模型

硬件也可视为是一个状态机,就是一个无情执行指令的机器。

1. 状态

  • 内存、寄存器的数值

2. 初始状态

  • 由系统设计者规定 (CPU Reset)

3. 状态迁移

  • 从 PC 取指令执行

PC(Program Counter) 中文叫 程序计数器,作用是:存放下一条将要执行的指令在内存中的地址。

定义一个 CPU 状态结构体,用软件模拟 CPU 的内部组成。

1. 计算机系统:状态

寄存器、内存

C
struct CPUState {
    uint32_t regs[32], csrs[CSR_COUNT];
    uint8_t *mem;
    uint32_t mem_offset, mem_size;
};
成员含义
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 口:

C
gpio_set_value(GPIO_23, 1); // in Linux kernel
层次角色类比
用户空间应用调用 /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 后的代码
    • 这段代码 “出生” 就有机器完整的控制权
平台固件名称作用
x86BIOS / UEFI初始化硬件、加载操作系统
ARM(嵌入式)BootROM / Bootloader初始化外设、启动内核
单片机固件 (firmware)直接控制硬件,无操作系统

当 CPU 上电复位后,它不会直接执行操作系统,而是先执行固件。

固件是硬件厂商预置的一段程序,它拥有系统的完全控制权,并负责把控制权移交给操作系统。

Firmware

“固件”

  • 厂商 “固定” 在计算机系统里的代码
    • 早期:固件是 ROM
    • 想升级?换芯片!(今天可以直接升级固件了,但要升级失败可能就变成砖了)

Firmware 的功能

  • 运行程序前的计算机系统配置
    • CPU 电压、内存时序、接口开关……
    • (这些配置要生效可能需要重启计算机)
  • 不严格地说,加载操作系统
    • QEMU:可以绕过 Firmware 直接加载操作系统 (Manual)
webp

Firmware 可以配置操作系统加载引导程序,就是一段代码

Firmware 就是一个小 “操作系统”

  • CPU Reset 后初始化硬件;对接操作系统 Boot Loader

当计算机上电复位后,CPU 什么都不知道。

固件(BIOS 或 UEFI) 负责完成以下工作:

  1. 初始化各种硬件(CPU、内存、显卡、键盘、磁盘控制器等);
  2. 构建一个最小可用的执行环境
  3. 寻找系统启动介质(硬盘、光盘、U盘、网络等);
  4. 加载并跳转执行 操作系统的 Boot Loader

Legacy BIOS (Basic I/O System)

  • IBM PC 所有设备/BIOS 中断是有 specification 的
    • 16-bit DOS 时代 BIOS 常驻内存,提供 I/O 等功能

只要硬件厂商实现了同样的 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

只允许写入信任的固件更新

为什么现在电脑病毒越来越少了?

  • 更安全的操作系统
  • AppStore 机制(软件包经过审查)
  • 云端备份(“给钱恢复文件” 勒索越来越无效)

3.3 加载操作系统

回到 40 年前

IBM PC/PC-DOS 2.0 (1983)

  • Firmware (BIOS) 会加载磁盘的前 512 字节到 0x7c00
    • (如果这 512 字节最后是 0x55, 0xAA
    • 为什么是 0b010101010b10101010?(还能起到检查磁盘是否损坏的效果)
  • 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 fejmp .
  1. printf "\xeb\xfe"
    • EB FE 是 x86 机器码的 jmp .
    • 意思:无限循环自己,防止 CPU 执行到未知区域崩溃
  2. cat /dev/zero | head -c 508
    • 生成 508 个 0 字节,填充 Boot Sector 的剩余空间
  3. printf "\x55\xaa"
    • 在末尾添加 魔数 0x55AA,保证 BIOS 识别这是有效启动扇区

整个文件大小 = 512 字节 = Boot Sector 完整大小。

如果 Firmware 也是代码?

计算机系统从 CPU Reset 开始

  • CPU Reset 的时候,0x7c00 应该是啥也没有的
  • Firmware 的代码扫描了磁盘、加载了它

那我们是不是可以看到 “加载” 的过程?

Firmware 和系统程序员的第一个接口

我们可以写 446 字节的 16-bit 代码

  • 446 = 512 - 2 (55 aa) - 64 (分区表)

Boot Sector = 512 字节(一个扇区)

结构划分:

区域大小用途
0–445446 字节Bootloader Stage 1 代码
446–50964 字节分区表(Partition Table)
510–5112 字节魔数 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: 固件与操作系统引导

只需要提问 & 追问就行了

  1. RISC-V 系统是如何复位、执行什么固件、如何加载操作系统的?
  2. OpenSBI 的入口位于什么地方?
  3. _start 开始的 _try_lottery 是做什么的?
阶段作用地址/入口
CPU Reset指向复位向量,启动固件硬件定义地址,如 0x1000
固件(OpenSBI)初始化平台,提供 SBI 调用_start 函数
内核接管系统,启动任务/进程_start,第一个执行 _try_lottery 或类似函数