程序 = 状态机
今天我们稍微回到一点 “操作系统”
给大家一点时间体验 (并阅读理解) threads.h
调试固件 (Firmware)
调试 “操作系统这个 C 程序”
十几年以前自己 xjbg 的内核跑起来的时候的确感觉很惊奇
「护球像梅西,射门像贝利」的金山区齐达内,满怀期待地去和儿童预备队比赛,结果被灌了 20 多球。韩寒:“被小学生支配的恐惧,而我也曾对那种力量,一无所知。”
专业人士有正确的 “专业世界观” (和专业知识的积累和磨练有关)
专业训练:例子
❌ 踩坑 (自学成才)
printf
调试✅ 建立在前人经验的基础上
将已有的知识和方法重新消化,为大家建立好 “台阶”,在有限的时间里迅速
赶上 几十年来建立的科学体系。
写操作系统的最大困难:下不了手
我们已经知道如何写一个 “最小” 的 C 程序了:
#include <unistd.h>
#include <sys/syscall.h>
int main() {
syscall(SYS_exit, 42);
}
比较特殊的编译方式
$ gcc a.c -static -Wl,--entry=main
“程序 = 状态机” 没问题
“启动” 状态机是由 “加载器” 完成的
加载器也是一段程序 (状态机)
这个程序由是由谁加载的?
……
为了让计算机能
CPU Reset (Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A/3B)
EIP = 0x0000fff0
CR0 = 0x60000010
EFLAGS = 0x00000002
《计算机系统基础》:
CS:IP
) 指针处取指令、译码、执行……ffff0
通常是一条向 firmware 跳转的 jmp 指令Firmware: BIOS vs. UEFI
Firmware 必须提供机制,将用户数据载入内存
7c00
位置CS:IP = 0x7c00
, (R[CS] << 4) | R[IP] == 0x7c00
CS = 0x07c0, IP = 0
CS = 0, IP = 0x7c00
Talk is cheap. Show me the code. ——Linus Torvalds
有没有可能我们真的去看从 CPU Reset 以后每一条指令的执行?
亲眼确认 Firmware 到底是不是会加载启动盘第一个扇区到
0x7c00
内存位置!
调试 QEMU 模拟器: run-qemu.sh 和 hello.img
info registers
0x7c00
内存的加载watch *0x7c00
x/i ($cs * 16 + $rip)
x/16xb 0x7c00
0x7c00
代码的执行b *0x7c00
, c
(撒花)有个原始的鸡:Firmware
Firmware 的另一用处
Firmware 通常是只读的 (当然……)
CIH 的作者陈盈豪被逮捕,但并未被定罪
计算机系统启动后的初始状态?
我们已经知道:
还有一部分状态
make ARCH=x86_64-qemu
会创建如下格式的磁盘镜像一个迷你 “操作系统” thread-os.c
int main() {
cte_init(on_interrupt);
for (int i = 0; i < LENGTH(tasks); i++) {
Task *task = &tasks[i];
Area stack = (Area) { &task->context + 1, task + 1 };
task->context = kcontext(stack, task->entry, (void *)task->name);
task->next = &tasks[(i + 1) % LENGTH(tasks)];
}
mpe_init(mp_entry);
}
让我们观察 AbstractMachine 程序的编译过程:
make -nB \
| grep -ve '^\(\#\|echo\|mkdir\|make\)' \
| sed "s#$AM_HOME#\$AM_HOME#g" \
| sed "s#$PWD#.#g" \
| vim -
make -nB
(RTFM)如果使用 “土办法”,你很可能被淹没在 Makefile 中
- 读懂 Makefile 需要 STFW, RTFM,大量的精力
- 虽然花点时间读是值得的,但很可能读了很久都没读到重要的地方
花一点时间想 “应该怎么做”
想要看得更清楚一些?
:%s/ /\r /g
编译
-std=gnu11
, m64
, -mno-sse
, -I
, -D
, ... (这对你配置 vscode 很重要)链接
-melf_x86_64
, -N
, -Ttext-segment=0x00100000
.o
和库 (am-x86_64-qemu.a
, klib-x86_64-qemu.a
)彩蛋
make html
512 字节中的代码,假设了镜像格式 (真正的的加载器有很多 stages)
代码讲解:
am/src/x86/qemu/boot/start.S
和 main.c
if (elf32->e_machine == EM_X86_64) {
((void(*)())(uint32_t)elf64->e_entry)();
} else {
((void(*)())(uint32_t)elf32->e_entry)();
}
start64.S
Boot loader 执行的最后一句代码:
((void(*)())(uint32_t)elf64->e_entry)();
因为 ELF 文件已经加载到内存,这将跳转到 _start
start64.S
(切换到 64-bit 模式)ljmp
跳转到 64-bit 代码执行 (_start64
)_start_c
执行trm.c
巨多无比难以理解的代码
完成初始化后,执行一次堆栈切换调用,跳转到 call_main
(wrapper) 执行
static void call_main(const char *args) {
_halt(main(args));
}
main
在被调用时:
_heap
FL_IF
cleared)struct cpu_local
中)你也可以实现直接在硬件上运行的游戏等任何程序
本次课内容与目标
Take-away messages