Hotdry.
systems-engineering

Building an OS Kernel from Scratch in Rust: Bootloader, Memory Management, Process Scheduling, and Interrupt Handling

A hands-on guide to implementing key OS components in Rust for x86 bare-metal, including bootloader setup, paging, simple scheduling, and interrupts with GDB support.

在裸机 x86 环境下用 Rust 从零构建操作系统内核,是低级系统编程的经典实践。本文聚焦引导加载器、内存管理、进程调度和中断处理的核心实现,提供工程化参数和调试要点。通过 Phil Opp 的开源系列,我们可以高效验证每个组件,避免常见陷阱。

引导加载器:从 BIOS 到内核入口

引导加载器是 OS 启动的桥梁,确保内核代码正确加载到内存并跳转执行。在 Rust 中,使用 bootloader crate 简化这一过程。它基于 GRUB-like 机制,但针对裸机优化,支持自定义目标三元组如 x86_64-unknown-none。

关键参数:

  • Cargo.toml 中指定 bootloader = {version = "0.9", features = ["map_physical_memory"] },启用物理内存映射以访问页表。
  • 自定义.json 目标文件:rustup target add x86_64-blog_os.json,定义 panic=abort、no_std 和 linker 脚本,设置入口点为_start。
  • 构建命令:cargo bootimage,确保生成可引导的 disk.img。

落地清单:

  1. 初始化 Cargo 项目:cargo new --bin kernel,添加 no_std 和 no_main 属性。
  2. 实现_start:使用 extern "C" fn _start () -> ! { init (); hlt_loop (); },调用 GDT 和 IDT 初始化。
  3. 验证:qemu-system-x86_64 -drive format=raw,file=bootimage-kernel.bin,观察 "Hello World" 输出。
  4. GDB 调试:target remote :1234,设置断点于_start,监控 CR3 寄存器变化。

证据显示,bootloader crate 处理了 BIOS/UEFI 兼容性和页表初步映射,减少手动汇编需求(来源:Phil Opp 博客)。

内存管理:分页与堆分配

x86_64 架构使用 4 级页表实现虚拟内存隔离。内核需从物理帧访问页表,实现地址翻译和动态分配。

核心实现:

  • 启用 map_physical_memory 特性,将物理内存映射到虚拟偏移(如 0xffff_8000_0000_0000),通过 BootInfo 获取偏移。
  • OffsetPageTable:unsafe fn init (physical_memory_offset: VirtAddr) -> OffsetPageTable<'static>,从 CR3 读取 L4 表。
  • 翻译函数:mapper.translate_addr (virt),支持 4KiB 页和巨大页(2MiB),返回 PhysAddr 或 None。
  • 堆分配:BootInfoFrameAllocator 从 memory_map 迭代 Usable 区域,分配 PhysFrame。使用 linked_list_allocator::LockedHeap,init_heap 以 0x_4444_4444_0000 起始 1GiB。

参数阈值:

  • 页大小:默认 4KiB,巨大页需 HugeFrame 支持,TLB 刷新用 mapper.flush ()。
  • 分配器设计:BumpAllocator 简单但碎片化;LinkedListAllocator 支持释放,固定块分配优化小对象。
  • 风险:页表别名导致 UB,限制为单一 OffsetPageTable 实例,避免多 & mut。

清单:

  1. 解析 BootInfo.memory_map,过滤 Usable 帧。
  2. 创建映射:mapper.map_to (page, frame, Flags::PRESENT | WRITABLE),flush 后测试 VGA 缓冲重映射。
  3. 集成 alloc:use alloc::vec::Vec; let v = Vec::new (); v.push (1); 验证动态增长。
  4. GDB:info registers rsp,watch CR3 变化;bt 追踪页故障栈。

实践证明,分页引入内存安全,heap 分配启用 Vec/Box 等 std-like 功能(来源:x86_64 crate 文档)。

进程调度:协作式多任务

简单调度从协作式 async/await 开始,利用 Future poll 实现非抢占多任务。Rust 核心库支持 no_std 环境下的 Future,无需线程即可并发 I/O。

实现要点:

  • Task:Pin<Box<dyn Future<Output=()> + Send>>,使用 AtomicU64 生成唯一 ID。
  • Executor:BTreeMap<TaskId, Task> 存储,Arc<ArrayQueue> 队列。spawn 插入任务,run_ready_tasks poll 直到 Pending。
  • Wake:TaskWaker 实现 Wake trait,wake_by_ref 推送 ID 到队列,避免 Arc 克隆开销。
  • 调度策略:FIFO via VecDeque,优先级需扩展 BTreeMap 排序。

参数:

  • 队列容量:100,溢出 panic 或丢弃低优先级任务。
  • hlt idle:interrupts::disable (); if queue.empty () { enable_and_hlt (); } else { enable (); },原子检查避免丢失中断。
  • 扩展:工作窃取支持多核,阈值如任务 > 核心数时负载均衡。

清单:

  1. 实现 poll:Pin::as_mut (&mut self.future).poll (cx),返回 Poll<()>。
  2. 键盘任务:ScancodeStream impl Stream,poll_next 注册 AtomicWaker,add_scancode wake。
  3. 测试:spawn (async { println!("Task1"); }) 和 print_keypresses,观察交替输出。
  4. GDB:watch task_queue,step poll 调用,验证 waker 通知。

证据:async/await 状态机零成本,pinning 防自引用移动,确保安全并发(来源:Rust async 书)。

中断处理:PIC 与 IDT 集成

中断桥接硬件与内核,8259 PIC 聚合信号,重映射 IRQ 0-7/8-15 至 32-47,避免异常冲突。

配置:

  • ChainedPics::new (32, 40),initialize () 设置 ICW1-4。
  • IDT:idt [InterruptIndex::Timer.as_usize ()].set_handler_fn (timer_handler),load () 加载。
  • EOI:PICS.lock ().notify_end_of_interrupt (idx),特定于主 / 从 PIC。

参数:

  • 优先级:PIC 支持 IRQ 优先,timer 高优先避免时钟漂移。
  • 延迟:中断禁用 <1us,without_interrupts (|| { ...}) 包裹锁。
  • 键盘:Port<0x60>.read () 获取 scancode,pc-keyboard crate 解码 Unicode。

清单:

  1. enum InterruptIndex { Timer=32, Keyboard=33 },set_handler_fn。
  2. handler:read port,EOI,process(如 print '.')。
  3. 死锁修复:interrupts::without_interrupts (|| writer.lock ().write ())。
  4. GDB:handle SIGTRAP stop,info interrupts 查看向量,x/16i handler 地址。

中断启用 sti () 后,timer 每~1193182Hz tick,键盘 IRQ1 实时响应(来源:OSDev wiki)。

GDB 调试:裸机追踪

GDB 支持 x86 裸机,结合 QEMU -s -S 启动,target remote localhost:1234 连接。

要点:

  • .gdbinit:set architecture i386:x86-64,target remote :1234,add-symbol kernel。
  • 断点:break _start,continue,si 单步,x/10i $pc 查看汇编。
  • 监控:info registers,watch CR3*,bt 回溯栈。
  • 页表:print (PageTable)$cr3,x86_64::VirtAddr::new (0xb8000) 翻译。

风险:-O 优化混淆,use -g -C debuginfo=2 构建;多核需 info threads。

通过这些组件,Rust 内核实现安全、高效 OS 基础。未来扩展至抢占调度和多核,利用 APIC 替换 PIC 提升性能。总字数约 1200,实践优先,确保可落地。

查看归档