从零写OS(三十四):阻塞式管道与调度器的两个 Bug

上一章实现了阻塞式 TTY,这一章在同样的思路上让 pipe 的 read 也变成阻塞式,同时修复了调度器里隐藏的两个 Bug,让 fork + exec + wait4 的完整流程跑通。 问题:非阻塞 pipe read 的后果 ch33 之前,vfs_read 对 pipe 的实现是: if (pipe_buf_empty(p)) return 0; // 管道空 → 直接返回 0 对于 shell 管道命令,busybox sh 会: fork 出子进程执行左侧命令,写 pipe 父进程(或另一子进程)读 pipe,处理右侧命令 如果读端在写端还没写入时就读到 0,shell 认为 EOF,管道提前关闭,命令输出丢失。 解决方案:sti/hlt 等待 与 ch33 阻塞式 TTY read 相同的思路:在内核态用 sti; hlt 轮询等待。 // pipe read:等待数据或写端关闭 while (pipe_buf_empty(p) && pipe_has_writer(p)) { __asm__ volatile("sti" ::: "memory"); __asm__ volatile("hlt" ::: "memory"); __asm__ volatile("cli" ::: "memory"); } if (pipe_buf_empty(p)) return 0; // 写端已关闭且无数据 → EOF 关键点: ...

May 22, 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