架构决策记录
本文档记录 OpenOS 开发过程中的重大架构决策。
每条 ADR 遵循以下格式:
- Status: Proposed | Accepted | Deprecated | Superseded
- Context: What is the issue that motivates this decision?
- Decision: What is the change being proposed?
- Consequences: What are the resulting implications?
ADR-001: 语言选择 — Rust (Nightly)
Section titled “ADR-001: 语言选择 — Rust (Nightly)”Status: Accepted Date: 2026-07-23
Context
Section titled “Context”内核必须直接管理硬件——内存、CPU、I/O。传统选择是 C 和汇编。C 缺乏内存安全;汇编易错且不可移植。我们需要一种语言提供:
- 无运行时开销的内存安全
- 硬件交互的零成本抽象
#![no_std]支持(无标准库依赖)- 强类型系统用于状态机建模
Decision
Section titled “Decision”使用 Rust nightly 工具链,配合 #![no_std] 和 #![no_main]。
使用的 nightly 特性:
abi_x86_interrupt— 允许extern "x86-interrupt"用于 ISR 函数alloc_error_handler— 自定义分配失败处理函数build-std— 为裸机目标重新构建core和alloc
Consequences
Section titled “Consequences”Positive:
- 借用检查器在编译期捕获 use-after-free、数据竞争
#[repr(C)]结构体直接映射到硬件寄存器- 模式匹配确保 CPU 异常的穷尽处理
unsafe边界明确标记硬件交互点
Negative:
- 仅 nightly 的特性可能在工具链更新时中断
- 硬件访问仍然需要
unsafe - OS 开发的生态系统成熟度低于 C
ADR-002: 内核架构 — 微内核
Section titled “ADR-002: 内核架构 — 微内核”Status: Accepted Date: 2026-07-23
Context
Section titled “Context”宏内核(Linux、Windows NT)将所有服务运行在内核态——驱动中的 bug 可以导致整个系统崩溃。微内核(L4、seL4、MINIX)仅在内核态运行核心服务,隔离故障。
Decision
Section titled “Decision”采用微内核架构。内核仅处理:
| 服务 | 内核空间 | 用户空间(未来) |
|---|---|---|
| 内存管理 | ✓ | |
| 任务调度 | ✓ | |
| IPC | ✓ | |
| 中断分发 | ✓ | |
| 设备驱动 | ✓ | |
| 文件系统 | ✓ | |
| 网络栈 | ✓ | |
| 显示服务器 | ✓ |
Consequences
Section titled “Consequences”Positive:
- 更小的可信计算基(TCB)
- 驱动 bug 不会导致内核崩溃
- 服务可以独立重启
- 更容易进行形式化验证(参见 seL4)
Negative:
- 每个系统调用的 IPC 开销
- 更复杂的启动序列(用户空间服务必须加载)
- 更难实现与宏内核的性能对等
ADR-003: 内存模型 — 高半内核
Section titled “ADR-003: 内存模型 — 高半内核”Status: Accepted Date: 2026-07-23
Context
Section titled “Context”x86_64 虚拟地址空间为 48 位(256 TiB)。内核可以放置在地址空间底部(0x0)或顶部(0xFFFF…)。放置在顶部(高半)是现代操作系统的标准做法。
Decision
Section titled “Decision”高半内核映射在 0xFFFFFFFF80100000。
KERNEL_OFFSET = 0xFFFFFFFF80000000Physical load address: 0x100000 (1 MiB, 常规)Virtual base: KERNEL_OFFSET + 0x100000 = 0xFFFFFFFF80100000链接脚本使用 AT(ADDR(.section) - KERNEL_OFFSET) 在二进制中输出物理地址,而内核在虚拟地址运行。
Consequences
Section titled “Consequences”Positive:
- 低 2 GiB 空闲用于用户空间程序(可以使用 32 位偏移量的
call、jmp) - 内核受页表保护——用户空间页错误不会触及内核页面
- 与 Linux、Windows 和大多数现代 OS 布局匹配
Negative:
- 在内核可以运行在其虚拟地址之前需要工作的页表
- 引导加载器必须将内核的物理页面映射到高半区域
ADR-004: 构建目标 — x86_64-unknown-none
Section titled “ADR-004: 构建目标 — x86_64-unknown-none”Status: Accepted Date: 2026-07-23
Context
Section titled “Context”裸机 Rust 需要一个禁用操作系统特性的目标规范(无 libc、无线程、无文件系统)。选项:自定义 JSON 目标 vs 内置目标。
Decision
Section titled “Decision”使用内置 x86_64-unknown-none 目标,而非自定义 JSON 文件。
.cargo/config.toml 中的配置:
[build]target = "x86_64-unknown-none"
[unstable]build-std = ["core", "compiler_builtins", "alloc"]build-std-features = ["compiler-builtins-mem"]Consequences
Section titled “Consequences”Positive:
- 无需维护自定义目标 JSON
- 设置
.cargo/config.toml后可与cargo build配合工作 - Rust 团队维护目标规范
Negative:
- 无法自定义特性(例如,无法在没有自定义 JSON 的情况下禁用 SSE)
- 未使用的
x86_64-openos.json中的-mmx,-sse,-sse2特性需要自定义规范——但内置目标正确处理了这一点
ADR-005: 同步 — 自旋锁
Section titled “ADR-005: 同步 — 自旋锁”Status: Accepted Date: 2026-07-23
Context
Section titled “Context”内核需要对共享数据结构(调度器队列、IPC 端口注册表、VGA 缓冲区)进行互斥访问。选项:自旋锁、票据锁、MCS 锁或禁中断锁。
Decision
Section titled “Decision”使用 spin crate (v0.9) 的 spin::Mutex。
对于 VGA 输出,配合 x86_64::instructions::interrupts::without_interrupts 使用,防止中断处理函数尝试打印时死锁。
Consequences
Section titled “Consequences”Positive:
- 简单,易于理解
- 不依赖操作系统特性(在
no_std中工作) - 确定性——不睡眠,不与调度器交互
Negative:
- 忙等待——浪费 CPU 周期
- 不公平——没有等待者的排序保证
- 如果在中断在同一 CPU 上触发时持有自旋锁可能死锁(通过打印锁的
without_interrupts缓解)
ADR-006: IPC 模型 — 基于端口的消息传递
Section titled “ADR-006: IPC 模型 — 基于端口的消息传递”Status: Accepted Date: 2026-07-23
Context
Section titled “Context”微内核需要高效的进程间通信。两种主要模型:共享内存和消息传递。L4(微内核 IPC 的黄金标准)使用同步消息传递。
Decision
Section titled “Decision”实现基于端口的消息传递:
- 每个服务拥有一个端口(由
u64ID 标识) - 消息为
Message { sender, receiver, data: MessageData } MessageData枚举支持:Text、Bytes、Request { id, data }、Response { id, data }- 端口存储在
IpcManager内的BTreeMap<u64, Port>中 - 发送是异步的(消息排队在接收方的收件箱中)
- 接收是同步的(阻塞直到消息可用——尚未实现)
Consequences
Section titled “Consequences”Positive:
- 发送方和接收方之间的清晰分离
- 请求/响应模式内置于消息类型中
- 可扩展——以后可以添加基于能力的访问控制
Negative:
- 基于复制(尚无零复制)——大消息开销大
- 无共享内存用于批量数据传输(例如帧缓冲区)
- BTreeMap 查找是 O(log n)——可以使用数组用于已知端口数量
ADR-007: Panic 策略 — Abort
Section titled “ADR-007: Panic 策略 — Abort”Status: Accepted Date: 2026-07-23
Context
Section titled “Context”Rust panic 可以选择栈展开(默认)或立即中止。展开需要大量运行时支持(着陆垫、异常表),在裸机内核中实现复杂。
Decision
Section titled “Decision”在 [profile.dev] 和 [profile.release] 中均设置 panic = "abort"。
[profile.dev]panic = "abort"
[profile.release]panic = "abort"opt-level = "s"lto = trueConsequences
Section titled “Consequences”Positive:
- 无展开表——更小的二进制
- 无需实现
eh_personality或着陆垫 - 更简单的 panic 处理函数——只需打印并停机
Negative:
- 无法使用
catch_unwind捕获 panic - 任何 panic 都是致命的——无优雅降级
- 依赖展开的库无法正常工作
ADR-008: 堆分配器 — linked_list_allocator
Section titled “ADR-008: 堆分配器 — linked_list_allocator”Status: Accepted Date: 2026-07-23
Context
Section titled “Context”内核需要动态内存分配(Box、Vec、String)。选项:bump 分配器、链表分配器、slab 分配器、伙伴分配器。
Decision
Section titled “Decision”使用 linked_list_allocator crate (v0.10) 作为全局分配器。
#[global_allocator]static ALLOCATOR: LockedHeap = LockedHeap::empty();
pub fn init_heap() { unsafe { ALLOCATOR.lock().init(HEAP_START as *mut u8, HEAP_SIZE); }}堆:100 KiB,起始于虚拟地址 0x4444_4444_0000。
Consequences
Section titled “Consequences”Positive:
- 简单,无外部依赖
- 首次适配分配——适合内核使用
- 线程安全(包装在
spin::Mutex中)
Negative:
- 随时间产生碎片——不适合长期运行的生产内核
- 无每 CPU 缓存——高分配负载下的锁竞争
- 固定堆大小(100 KiB)——必须通过页分配器使其动态化
ADR-009: VGA 输出 — 直接缓冲区访问
Section titled “ADR-009: VGA 输出 — 直接缓冲区访问”Status: Accepted Date: 2026-07-23
Context
Section titled “Context”内核需要输出用于调试和用户反馈。VGA 文本模式在物理地址 0xB8000 提供 80×25 字符缓冲区。替代方案:串口、帧缓冲、VESA。
Decision
Section titled “Decision”同时实现 VGA 文本缓冲区和串口输出:
- VGA (
drivers/vga.rs):通过Volatile<ScreenChar>直接写入0xB8000,提供print!/println!宏 - 串口 (
drivers/serial.rs):0x3F8的 UART 16550,提供serial_print!/serial_println!宏
VGA 使用 volatile crate 防止编译器优化掉写操作。
Consequences
Section titled “Consequences”Positive:
- 启动期间的即时视觉反馈
- 使用
-serial stdio在 QEMU 中可见的串口输出 Volatile包装器防止重排/消除
Negative:
- 没有传统支持的现代硬件上不可用 VGA
- 直接缓冲区访问绕过任何抽象——以后更难替换
- 除了简单的行移位外没有滚动缓冲区管理
ADR-010: Lint 配置 — 严格 Clippy
Section titled “ADR-010: Lint 配置 — 严格 Clippy”Status: Accepted Date: 2026-07-23
Context
Section titled “Context”内核代码必须正确——bug 是灾难性的。Rust 的 clippy 检查器捕获常见错误。我们需要在严格性和脚手架代码(dead code、unused variables)之间取得平衡。
Decision
Section titled “Decision”在 src/main.rs 中启用严格 clippy:
#![warn(missing_docs)]#![warn(clippy::all, clippy::pedantic, clippy::nursery)]#![allow( clippy::module_inception, dead_code, unused_imports, unused_variables, clippy::missing_const_for_fn, clippy::used_underscore_items)]格式化由 rustfmt.toml 强制执行,group_imports = "StdExternalCrate"。
CI 门控:make check 运行 fmt → clippy (-D warnings) → build。
Consequences
Section titled “Consequences”Positive:
- 及早捕获 bug(例如
uninlined_format_args、use_self) - 贡献者之间一致的代码风格
missing_docs确保所有公共 API 被记录
Negative:
- crate 级别允许
dead_code——某些警告被抑制 clippy::nursery有不稳定的 lint,可能改变行为- 提交前必须运行
cargo fmt(在设置 CI 之前是手动步骤)