tianole/docs/agents/kernel-design.md
2026-05-12 17:26:39 +08:00

273 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Kernel Design Guide
本文档记录 Tianole 当前阶段的顶层内核设计约束。它不是最终架构说明,也不是 Linux 的缩小版;它的作用是让后续实现不再只围绕单个文件局部修补,而是先确认子系统边界、调用方向和生命周期规则。
当代码改动影响目录职责、公共结构体、跨子系统 API、锁语义、生命周期或启动阶段顺序时应该同步更新本文档或对应任务文档。
## 设计原则
- 参考 Linux 的分层和长期演进方式,但不照搬 Linux 的历史复杂度。
- 入口代码只编排流程,不承载具体策略。
- 子系统通过明确 API 交互,不直接访问对方内部结构。
- 公共结构体越少越好;公共头文件暴露的是契约,不是临时实现细节。
- 当前可以是单 CPU、单地址空间、简单调度但接口不能把这些简化写成永久事实。
- 能力先闭环,再收紧边界;收紧边界时要补文档和自测。
## 顶层目录职责
- `arch/`架构相关代码包括启动入口、GDT/TSS/IDT、trap/IRQ entry、上下文切换、架构 timer、页表格式、TLB 和 CPU 指令封装。
- `kernel/`:架构无关的核心内核逻辑,包括 early log 前端、panic、IRQ 分发、调度、锁、时间抽象、进程和 syscall 的长期位置。
- `mm/`架构无关内存管理包括物理页、heap、后续 page metadata、buddy/slab、VMA、page cache 和回收。
- `drivers/`:设备驱动和设备模型,包括 input、block、PCI、ACPI、platform、GPU、network、sound 等。
- `block/`:通用块层。具体硬件驱动在 `drivers/`,块请求调度和块设备抽象在 `block/`
- `fs/`VFS 和具体文件系统。VFS 不依赖 shell、ELF loader 或具体块设备。
- `include/tianole/`:架构无关公共内核 API。只有跨子系统调用方真正需要的类型和函数才能放这里。
- `arch/<arch>/include/`:架构公开 API例如中断控制、I/O port、上下文切换声明。
- `kernel/selftest/`:启动阶段自测和当前阶段演示线程,不放进具体实现目录。
- `scripts/`构建、检查、QEMU 启动和结构规则工具。
- `docs/`:面向人的设计、路线和约束文档。
## 调用方向
允许的主要调用方向:
- `kernel/` 可以调用 `arch_` 前缀的架构 API。
- `kernel/` 可以调用 `mm/` 提供的分配和映射 API。
- `drivers/` 可以调用 IRQ、timer、wait queue、workqueue、MM 和 bus/resource API。
- `fs/` 可以调用 `mm/`、`block/` 和锁/等待 API。
- `user/``usr/` 里的用户程序只能通过 syscall/libc/VFS 暴露的接口进入 kernel。
禁止或需要重新设计的调用方向:
- 通用层不能直接读取 x86 页表编码、PIC/PIT 端口或 IDT gate 格式。
- 驱动不能直接调用 shell、VFS 内部对象或调度器内部链表。
- shell 和用户态工具不能包含 kernel 私有头或读取 kernel 内部结构体。
- 文件系统不能直接控制具体磁盘硬件。
- keyboard/input driver 不能直接操作 terminal 或 shell。
- boot/selftest 代码不能混进核心实现文件。
## 公共头文件规则
`include/tianole/` 只能放公共契约:
- 跨子系统需要调用的函数声明。
- 调用方必须知道的 opaque handle 或稳定结构。
- 明确属于公共 ABI 的枚举、flag 和错误码。
避免放入公共头文件:
- 只被一个子系统内部使用的字段。
- 具体链表节点、缓存节点、临时统计字段。
- 架构私有 bit 编码和寄存器布局。
- 调试或 selftest 专用结构。
如果公共结构体开始泄漏太多内部字段,优先考虑:
- 把结构改成 opaque。
- 拆出私有头,例如 `kernel/sched/sched.h`
- 提供 accessor 或 helper而不是让调用方直接改字段。
当前允许的阶段性妥协:
- `struct thread``struct wait_queue` 仍在公共头中暴露,方便早期调度和自测推进。
- 后续收紧调度状态机时,应逐步减少外部直接写 `thread->state`、`wait_next`、`next` 等字段。
## 启动阶段边界
启动路径按阶段推进:
1. bootloader 收集 boot info、memory map、kernel image。
2. kernel entry 建立最小运行环境。
3. early log 和 panic 可用。
4. trap/IRQ 基础可用。
5. MM 基础可用物理页、页表、heap。
6. timer、thread、scheduler、wait queue 可用。
7. input、VFS、user mode、drivers 逐步接入。
启动阶段约束:
- early log/panic 不能依赖 heap、scheduler 或 VFS。
- trap/IRQ 初始化不能依赖复杂驱动模型。
- MM 初始化前不能使用 `kmalloc()`
- scheduler 初始化前不能 sleep 或 wait。
- 驱动初始化不能假设 shell、VFS 或用户态已经存在。
## 中断、锁和 deferred work
中断路径分层:
- arch entry 保存现场并进入 C trap/IRQ 分发。
- IRQ core 根据 IRQ number 调用注册 handler。
- 具体设备 handler 做最短路径工作。
- 复杂处理应推迟到 deferred work、workqueue 或线程上下文。
锁规则:
- 中断和普通内核路径共享的数据,先使用 `spin_lock_irqsave()` 建立单 CPU 正确语义。
- 持有不可睡眠锁时不能调用 `sched_yield()`、`sched_sleep()` 或 wait queue sleep。
- 可睡眠路径和不可睡眠路径要从 API 命名、文档或调用约束上区分。
- 后续 SMP 前,需要把当前单 CPU lock 语义整理成可扩展接口。
等待规则:
- wait queue 负责“入队、睡眠、唤醒”的同步边界。
- 条件等待遵循“检查条件、入队睡眠、醒后重新检查”的模式。
- 唤醒方先修改受保护条件,再调用 wakeup。
- 条件所属数据如果不在 wait queue 内部,必须明确由哪把锁保护。
## 调度与线程生命周期
调度子系统职责:
- run queue 维护。
- 当前线程选择和上下文切换。
- thread state 转换。
- sleep/wakeup 和 wait queue 交互。
- DEAD/ZOMBIE 线程的安全回收。
调度状态机应逐步收紧:
- `READY -> RUNNING -> READY`
- `RUNNING -> SLEEPING/WAITING`
- `SLEEPING/WAITING -> READY`
- `RUNNING -> DEAD`
- 未来加入 `ZOMBIE` 支撑 join/wait 和进程退出。
约束:
- 外部子系统不应直接修改 run queue。
- 外部子系统不应长期直接写 `thread->state`
- 当前线程不能释放自身内核栈。
- 线程入口返回必须进入统一 exit 路径。
- IRQ 中不直接做完整线程切换;调度请求应在明确的 interrupt-exit 边界消费。
## 内存、VFS 和缓存边界
MM 层负责:
- boot memory map 转换成内核长期内存区域模型。
- 物理页分配和 page metadata。
- 内核 heap 和后续 slab/slub。
- 用户地址空间、VMA、page fault 策略。
- page cache 和内存回收。
VFS 层负责:
- superblock、inode、dentry、file 等对象边界。
- path lookup。
- open/read/write/close/readdir 等通用文件操作。
- mount、权限、时间戳和错误码语义的长期位置。
block 层负责:
- block device 抽象。
- block request 和完成状态。
- 具体硬件驱动与 VFS 之间的隔离。
约束:
- 具体文件系统不直接访问磁盘硬件。
- 具体块设备驱动不解析文件系统。
- page cache 不属于某个具体文件系统或驱动。
- ELF loader 通过 VFS 读文件,不直接读取块设备或 initramfs 私有结构。
## 设备模型与驱动边界
长期设备模型包含:
- device
- driver
- bus
- resource
- probe/remove
- IRQ/DMA/MMIO/PIO resource 管理
驱动约束:
- probe 失败必须释放已申请资源。
- 驱动不修改无关子系统内部状态。
- 驱动不能长期 busy wait应使用 IRQ、wait queue 或 deferred work。
- QEMU 专用设备支持不能破坏真机路径。
- ACPI/PCI/Device Tree 等平台信息隔离在平台层,通用驱动消费抽象资源。
输入路径:
- keyboard driver 产生 input event。
- input core 提供队列和等待。
- terminal/tty 消费 input event 并生成字节流或行。
- shell 读取 terminal不读取 scancode。
存储路径:
- storage driver 接入 block layer。
- block layer 服务 filesystem。
- VFS 服务 kernel loader、用户态程序和 shell。
## 用户态与 syscall 边界
用户态相关对象要分开:
- process
- thread
- address space
- open file table
- credentials
- signal state
syscall 规则:
- syscall number、参数寄存器、返回值和错误码必须稳定记录。
- 用户指针必须复制和检查,不能直接解引用。
- syscall handler 不暴露 kernel 内部结构体。
- 用户态异常终止进程或转换成后续 signal不能默认 panic。
- `fork/exec/wait`、signal、pipe、poll/select、futex 等长期路径要在 ABI 上预留。
用户程序规则:
- shell 和工具通过 libc 或 syscall ABI 访问 kernel。
- 用户程序不包含 kernel 私有头。
- 调试信息通过 syscall、procfs/debugfs 或等价虚拟文件接口暴露。
## 测试和回归边界
测试分层:
- `kernel/selftest/`:启动阶段内核自测,验证当前内核能力。
- `scripts/checks/`:主机侧检查,例如 build、boot、trap、page fault、structure。
- `scripts/tools/`:项目结构和自定义规则工具。
规则:
- 具体实现目录不放 selftest 主体。
- 新增公共 API 要有自测或 boot check 观察点。
- 调整目录边界要更新 `docs/agents/linux-layout.md`、`code-style.md` 或本文档。
- `scripts/check.sh` 是本地和 CI 的完整回归入口。
- 只改文档至少运行 `git diff --check`
## 设计变更流程
遇到以下情况,先更新设计或任务文档,再改代码:
- 新增顶层目录或改变目录职责。
- 公共头文件新增跨子系统结构体。
- 修改 thread、wait queue、page、inode、file、process 等核心对象生命周期。
- 新增锁规则、等待规则或 IRQ 调用约束。
- 引入新硬件路径或新驱动模型。
- 新增 syscall ABI 或用户态可见行为。
遇到以下情况,可以直接小步改代码,但完成后要同步文档:
- 当前任务文档已经明确要求的局部实现。
- 不改变 API 的内部重构。
- selftest 或 check 脚本补强。
- 修复明显 bug且不改变子系统边界。
## 当前主线
当前主线仍是 `docs/agents/tasks/04-time-scheduler.md`
- wait queue 内部锁已经建立。
- 下一步应收紧 thread state helper减少外部直接写状态。
- 然后继续完善 thread lifecycle 和 interrupt-exit reschedule。
- 在这些边界稳定前,不进入 keyboard/input 代码实现。