从零写OS(十九):exec 与用户程序

之前所有代码都跑在内核态(ring0)。这一章第一次让用户程序在 ring3 里运行——加载 ELF 文件,跳到用户态执行,用户程序通过 syscall 和内核通信。 这是操作系统最核心的一个边界:内核和用户程序之间的隔离。 exec 做什么 exec 的语义是"用一个新程序替换当前进程": 从文件系统读 ELF 文件 解析 ELF header,找到各个段的加载地址 创建新的用户页表,把各段加载进去 分配用户栈 设置进程的 rip = 入口地址,rsp = 栈顶,cs/ss = 用户段,放入就绪队列 调度器下次选中这个进程,就会跳到用户态开始执行。 进入用户态:iretq 第一次进入用户态不能用 sysret——没有对应的 syscall。用 iretq: static void enter_usermode(uint64_t rip, uint64_t cs, uint64_t rflags, uint64_t rsp, uint64_t ss) { __asm__ volatile( "push %4\n" // ss "push %3\n" // rsp "push %2\n" // rflags(IF=1,开中断) "push %1\n" // cs(0x23,ring3 代码段) "push %0\n" // rip(entry point) "iretq" :: "r"(rip), "r"(cs), "r"(rflags), "r"(rsp), "r"(ss) ); } iretq 弹出这 5 个字段,CPU 检查 cs 的 RPL 发现是 ring3,自动完成特权级切换。 ...

May 9, 2026 · 2 min · 大飞

从零写OS(十):系统调用,用户和内核的边界

前几章的"进程"其实是假的——它们直接跑在内核态,和内核同等权限,可以随意读写任何内存、操作任何硬件。 真实的操作系统里,用户程序跑在用户态(Ring 3),权限受限,不能直接操作硬件。需要内核帮忙时,必须通过系统调用这扇受控的门进入内核,做完事再回去。 这一章实现 syscall / sysret:用户态和内核态之间最快速的切换机制。 两种特权级 x86-64 有 4 个特权级(Ring 0~3),操作系统只用两个: Ring 0(内核态):可以执行任何指令,访问任何地址,操作 CR3、MSR 等特权寄存器 Ring 3(用户态):不能执行特权指令,访问不属于自己的内存会触发 #GP 或 Page Fault CS 段寄存器的低 2 位(CPL,Current Privilege Level)表示当前特权级。syscall 指令把 CPL 从 3 切到 0,sysret 把 CPL 从 0 切回 3。 为什么用 syscall 而不是中断 早期 Linux 用 int 0x80 触发系统调用——软件中断,要保存完整的中断栈帧,走 IDT 查表,开销大。 syscall / sysret 是专门为系统调用设计的快速路径:不走 IDT,入口地址直接写在 MSR 里,省去了大量压栈操作。现代 x86-64 系统全部用这对指令。 配置 MSR MSR(Model Specific Register,型号特定寄存器,CPU 内部的一组控制寄存器,用 rdmsr / wrmsr 访问)控制 syscall 的行为。需要配置 4 个: ...

May 6, 2026 · 3 min · 大飞
京ICP备14031575号-3