从固件到操作系统

从固件到操作系统

2024 南京大学《操作系统:设计与实现》
从固件到操作系统

回到 40 年前

IBM PC/PC-DOS 2.0 (1983)

  • Firmware (BIOS) 会加载磁盘的前 512 字节到 0x7c00
    • 如果这 512 字节最后是 0x55, 0xAA
    • 01010101 和 10101010

让我们试一试

  • 我们喜欢 shell 的原因:Quck & Dirty
  • (printf "\xeb\xfe"; cat /dev/zero | head -c 508; printf "\x55\xaa") > a.img
    • eb fe 是 jmp .
2024 南京大学《操作系统:设计与实现》
从固件到操作系统

如果 Firmware 也是代码?

计算机系统从 CPU Reset 开始

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

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

2024 南京大学《操作系统:设计与实现》
从固件到操作系统

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

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

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

Grub 的例子

  • Stage 1: 扫描磁盘,找到附近的 ELF 文件头,加载到内存
    • 根据文件系统,可能会需要 Stage 1.5
  • Stage 2: 这个 ELF 文件是 Grub; 弹出熟悉的选择系统窗口
  • Stage 3: 加载 Linux Kernel
    • 忽然觉得没什么难的了!
2024 南京大学《操作系统:设计与实现》
从固件到操作系统

看看我们的启动加载器吧!

好消息

  • 我们提供了直接在硬件上运行 C 程序的框架代码

坏消息

  • 框架代码也太复杂了吧 😂
  • 被 ICS PA 支配的恐惧再次袭来……
    • 读懂 Makefile 需要 STFW, RTFM,大量的精力
    • 读了很久都没读到重要的地方 → 本能地放弃
2024 南京大学《操作系统:设计与实现》
从固件到操作系统

正确的方法

创建自己的工具

  • “构建理解工具”
    • UNIX Philosophy (但反过来使用)
    • 把复杂的构建过程分解成流程 + 可理解的单点

Get your hands dirty

  • 首先,相信这事情不难,也相信你可以做到
  • (实在不行让 GPT 鼓励你一下)
2024 南京大学《操作系统:设计与实现》
从固件到操作系统

Makefile 的正确打开方式

Make 也是程序 (状态机)

  • 我们可以观察状态机本身,也可以观察它的执行
make -nB \
  | grep -ve '^\(\#\|echo\|mkdir\|make\)' \
  | sed "s#$AM_HOME#\$AM_HOME#g" \
  | sed "s#$PWD#.#g" \
  | vim -

Command line tricks

  • make -nB (RTFM)
  • grep 过滤干扰项;sed 让输出更易读
2024 南京大学《操作系统:设计与实现》
从固件到操作系统

更进一步,改进文本可读性

:%s/ /\r /g

  • 使得每个命令就像 “一句话”
  • 战略:知道我们要做什么

编译/链接

  • -std=gnu11, -m64, -mno-sse, -I, -D, ...
    • 它们是导致 vscode 里红线的原因
  • -melf_x86_64, -N, -Ttext-segment=0x00100000
    • 链接了需要的库 (am-x86_64-qemu.a, klib-x86_64-qemu.a)
  • 战术 (具体、明确的细节):交给 AI 吧!
2024 南京大学《操作系统:设计与实现》
从固件到操作系统

启动加载器 (Boot Loader)

am/src/x86/qemu/boot/start.Smain.c

  • 最终完成了 C 程序的加载 (它们都可以调试)
if (elf32->e_machine == EM_X86_64) {
  ((void(*)())(uint32_t)elf64->e_entry)();
} else {
  ((void(*)())(uint32_t)elf32->e_entry)();
}
2024 南京大学《操作系统:设计与实现》