我们已经获得的能力

为硬件直接编程

  • 可以让机器运行任意不超过 510 字节的指令序列
  • 编写任何指令序列 (状态机)
    • 只要能问出问题,就可以 RTFM/STFW/ChatGPT 找到答案
      • “如何在汇编里生成 $n$ 个字节的 0”
      • “如何在 x86 16-bit mode 打印字符”

操作系统:就一个 C 程序

  • 用 510 字节的指令完成磁盘 → 内存的加载
  • 初始化 C 程序的执行环境
  • 操作系统就开始运行了!

实现操作系统:觉得 “心里没底”?

心路历程

  • 曾经的我:哇!这也可以?
  • 现在的我:哦。呵呵呵。

大学的真正意义

  • 迅速消化数十年来建立起的学科体系
    • 将已有的思想和方法重新组织,为大家建立好 “台阶”
  • 破除 “写操作系统很难”、“写操作系统很牛” 类似的错误认识
    • 操作系统真的就是个 C 程序
    • 你只是需要 “被正确告知” 一些额外的知识
      • 然后写代码、吃苦头
      • 从而建立正确的 “专业世界观”

Bare-metal 上的 C 代码

为了让下列程序能够 “运行起来”:

int main() {
  printf("Hello, World\n");
}

我们需要准备什么?

  • MBR 上的 “启动加载器” (Boot Loader)
  • 我们可以通过编译器控制 C 程序的行为
    • 静态链接/PIC (位置无关代码)
    • Freestanding (不使用任何标准库)
    • 自己手工实现库函数 (putch, printf, ...)
      • 有亿点点细节:RTFSC!

进入细节的海洋

好消息:我们提供了运行 C 程序的框架代码和库

坏消息:框架代码也太复杂了吧

  • 被 ICS PA 支配的恐惧再次袭来……
    • 读懂 Makefile 需要 STFW, RTFM,大量的精力
    • 读了很久都没读到重要的地方 → 本能地放弃

花一点时间想 “有更好的办法吗?”

  • 花几分钟创建一个小工具:“构建理解工具”
    • UNIX Philosophy
    • 把复杂的构建过程分解成流程 + 可理解的单点
  • Get out of your comfort zone

(0) 当然是先试试 AI 了

AI 已经正确 “消化” 了 Makefile 的主干 (说明我们写得 “好读”)

  • 但是忽略了其中的彩蛋;大多数是我们不想要的套话

(1) 生成镜像和启动虚拟机

观察 AbstractMachine 程序编译过程的正确方法:

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: 文本过滤,省略了一些干扰项
      • echo (提示信息), mkdir (目录建立), make (sub-goals)
    • sed: 让输出更易读
      • 将绝对路径替换成相对路径
    • vim: 更舒适的编辑/查看体验

(2) 改进文本可读性

想要看得更清楚一些?

  • :%s/ /\r /g
    • 每一个命令就像 “一句话”
    • AI 落后,一代人就落后
      • 我的学习历程 (~2010):看书、在床上刷 Wikipedia

编译/链接

  • -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)

(3) 启动加载器 (Boot Loader)

假设 MBR 后紧跟 ELF Binary (真正的的加载器有更多 stages)

  • 16-bit → 32-bit
  • ELF32/64 的加载器
    • 按照约定的磁盘镜像格式加载

代码讲解:

  • 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)();
}

我们承诺的 “操作系统”

就是一个 C 程序

  • 只不过调用了更多的 API (之后解释)
    • 使用了正确的工具,就没什么困难的

支持固定的 “线程”

  • $T_a$ - while (1) printf("a");
  • $T_b$ - while (1) printf("b");
    • 允许并发执行