From 7292b9eb56c6f00696be0ac61e3e61430611c008 Mon Sep 17 00:00:00 2001 From: Microindole Date: Tue, 12 May 2026 17:26:39 +0800 Subject: [PATCH] refactor(sched): centralize thread state transitions - docs/agents: add kernel design guide --- docs/agents/README.md | 2 + docs/agents/kernel-design.md | 272 +++++++++++++++++++++++++ docs/agents/tasks/04-time-scheduler.md | 3 +- kernel/sched/core.c | 23 +-- kernel/sched/sched.h | 55 +++++ kernel/sched/thread.c | 6 +- kernel/sched/wait.c | 15 +- 7 files changed, 350 insertions(+), 26 deletions(-) create mode 100644 docs/agents/kernel-design.md diff --git a/docs/agents/README.md b/docs/agents/README.md index b71be7f..86250f2 100644 --- a/docs/agents/README.md +++ b/docs/agents/README.md @@ -7,6 +7,7 @@ 建议内容: - `code-style.md`:agent 修改代码时必须遵守的代码组织和风格规则。 +- `kernel-design.md`:顶层内核设计约束,说明子系统边界、调用方向、生命周期和设计变更流程。 - `linux-layout.md`:Tianole 和 Linux 顶层目录的映射与取舍。 - `session-notes/`:实现日志和阶段结论。 - `tasks/`:适合 agent 执行的边界清晰任务,按路线阶段拆分。 @@ -18,6 +19,7 @@ 当前任务入口: - `code-style.md`:代码风格和文件组织规则。 +- `kernel-design.md`:顶层内核设计约束。 - `linux-layout.md`:Linux 顶层目录映射。 - `tasks/README.md`:agent 任务索引。 - `tasks/02-cpu-interrupts.md`:下一阶段,GDT/TSS/IDT、异常入口和 trap frame。 diff --git a/docs/agents/kernel-design.md b/docs/agents/kernel-design.md new file mode 100644 index 0000000..2c20291 --- /dev/null +++ b/docs/agents/kernel-design.md @@ -0,0 +1,272 @@ +# 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//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 代码实现。 diff --git a/docs/agents/tasks/04-time-scheduler.md b/docs/agents/tasks/04-time-scheduler.md index d2d7c4e..796a741 100644 --- a/docs/agents/tasks/04-time-scheduler.md +++ b/docs/agents/tasks/04-time-scheduler.md @@ -152,6 +152,7 @@ - 已把 `kernel_thread_create()` 中的线程 id 分配和 run queue 入队纳入 interrupt-safe lock 保护。 - 已建立 `sched_irq_exit()`,timer IRQ 只设置 `need_resched`,trap 的 IRQ 返回边界统一消费调度请求。 - 已建立最小 DEAD 线程回收路径,调度前会释放非当前 DEAD 线程的内核栈和线程对象。 +- 已在调度私有头中加入 thread state helper,调度核心、线程退出和 wait queue 路径不再直接散写主要状态转换。 - 已把调度代码按职责拆分为 `core.c`、`thread.c`、`wait.c`、`idle.c` 和私有 `sched.h`,并把当前阶段自测/演示线程移到 `kernel/selftest/sched.c`。 - `scripts/check.sh` 已验证 `timer initialized`、`timer tick=1/2/3`、`scheduler initialized`、`kernel thread selftest ok`、timer 驱动线程轮转、`sched_sleep()`、wait queue wakeup、条件等待和超时等待。 @@ -160,7 +161,7 @@ ### A. 调度状态机收紧 - 明确合法状态转换,例如 `READY -> RUNNING -> READY`、`RUNNING -> SLEEPING/WAITING`、`SLEEPING/WAITING -> READY`、`RUNNING -> DEAD`。 -- 增加状态转换辅助函数,减少外部代码直接写 `thread->state`。 +- 继续扩大状态转换辅助函数覆盖范围,减少外部代码直接写 `thread->state`。 - 自测非法状态转换和重复入队问题。 ### B. wait queue 锁语义 diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 1ee6abb..a7ca3df 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -45,19 +45,19 @@ static struct thread *next_runnable_thread(void) thread = start; while (thread != 0) { - if (thread->state == THREAD_READY && thread != idle_thread) { + if (thread_is_ready(thread) && thread != idle_thread) { return thread; } thread = thread->next; } for (thread = run_queue_head; thread != start; thread = thread->next) { - if (thread->state == THREAD_READY && thread != idle_thread) { + if (thread_is_ready(thread) && thread != idle_thread) { return thread; } } - if (idle_thread != 0 && idle_thread->state == THREAD_READY) { + if (thread_is_ready(idle_thread)) { return idle_thread; } @@ -69,10 +69,8 @@ static void wake_sleeping_threads(uint64_t tick) struct thread *thread; for (thread = run_queue_head; thread != 0; thread = thread->next) { - if (thread->state == THREAD_SLEEPING && - thread->wake_tick <= tick) { - thread->wake_tick = 0; - thread->state = THREAD_READY; + if (thread_is_sleeping(thread) && thread->wake_tick <= tick) { + thread_set_ready(thread); } } } @@ -97,11 +95,11 @@ void sched_yield(void) schedule_locked = 1; - if (prev != 0 && prev->state == THREAD_RUNNING) { - prev->state = THREAD_READY; + if (thread_is_running(prev)) { + thread_set_ready(prev); } - next->state = THREAD_RUNNING; + thread_set_running(next); current_thread = next; schedule_locked = 0; @@ -117,7 +115,7 @@ void sched_tick(uint64_t tick) { wake_sleeping_threads(tick); - if (current_thread != 0 && current_thread->state == THREAD_RUNNING) { + if (thread_is_running(current_thread)) { need_resched = 1; } } @@ -141,8 +139,7 @@ void sched_sleep(uint64_t ticks) } now = timer_ticks(); - current_thread->wake_tick = now + ticks; - current_thread->state = THREAD_SLEEPING; + thread_set_sleeping(current_thread, now + ticks); sched_yield(); } diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index df7b1b0..c4eaf6f 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -17,6 +17,61 @@ extern int need_resched; extern struct thread *idle_thread; extern struct spinlock scheduler_lock; +static inline int thread_is_ready(const struct thread *thread) +{ + return thread != 0 && thread->state == THREAD_READY; +} + +static inline int thread_is_running(const struct thread *thread) +{ + return thread != 0 && thread->state == THREAD_RUNNING; +} + +static inline int thread_is_sleeping(const struct thread *thread) +{ + return thread != 0 && thread->state == THREAD_SLEEPING; +} + +static inline int thread_is_waiting(const struct thread *thread) +{ + return thread != 0 && thread->state == THREAD_WAITING; +} + +static inline int thread_is_dead(const struct thread *thread) +{ + return thread != 0 && thread->state == THREAD_DEAD; +} + +static inline void thread_set_ready(struct thread *thread) +{ + thread->wake_tick = 0; + thread->state = THREAD_READY; +} + +static inline void thread_set_running(struct thread *thread) +{ + thread->state = THREAD_RUNNING; +} + +static inline void thread_set_sleeping( + struct thread *thread, uint64_t wake_tick) +{ + thread->wake_tick = wake_tick; + thread->state = THREAD_SLEEPING; +} + +static inline void thread_set_waiting(struct thread *thread) +{ + thread->wake_tick = 0; + thread->state = THREAD_WAITING; +} + +static inline void thread_set_dead(struct thread *thread) +{ + thread->wake_tick = 0; + thread->state = THREAD_DEAD; +} + void enqueue_thread(struct thread *thread); void sched_reap_dead_threads(void); void sched_selftest(void); diff --git a/kernel/sched/thread.c b/kernel/sched/thread.c index 6549b1d..36e0be4 100644 --- a/kernel/sched/thread.c +++ b/kernel/sched/thread.c @@ -77,7 +77,7 @@ struct thread *kernel_thread_create( stack_top = (uintptr_t)thread->stack_base + KERNEL_STACK_SIZE; - thread->state = THREAD_READY; + thread_set_ready(thread); thread->entry = entry; thread->arg = arg; thread->stack_top = align_down_uintptr(stack_top, STACK_ALIGNMENT); @@ -110,7 +110,7 @@ void sched_reap_dead_threads(void) while (thread != 0) { struct thread *next = thread->next; - if (thread->state == THREAD_DEAD && thread != current_thread) { + if (thread_is_dead(thread) && thread != current_thread) { if (prev != 0) { prev->next = next; } else { @@ -139,7 +139,7 @@ static void thread_trampoline(void) } thread->entry(thread->arg); - thread->state = THREAD_DEAD; + thread_set_dead(thread); for (;;) { sched_yield(); diff --git a/kernel/sched/wait.c b/kernel/sched/wait.c index c3b3dee..7b54512 100644 --- a/kernel/sched/wait.c +++ b/kernel/sched/wait.c @@ -60,10 +60,8 @@ static void wait_queue_remove_locked( static void wait_queue_mark_ready_locked(struct thread *thread) { - if (thread->state == THREAD_WAITING || - thread->state == THREAD_SLEEPING) { - thread->wake_tick = 0; - thread->state = THREAD_READY; + if (thread_is_waiting(thread) || thread_is_sleeping(thread)) { + thread_set_ready(thread); } } @@ -77,14 +75,14 @@ void wait_queue_sleep(struct wait_queue *queue) spin_lock_irqsave(&queue->lock, &flags); wait_queue_enqueue_locked(queue, current_thread); - current_thread->state = THREAD_WAITING; + thread_set_waiting(current_thread); spin_unlock_irqrestore(&queue->lock, flags); for (;;) { sched_yield(); spin_lock_irqsave(&queue->lock, &flags); - if (current_thread->state != THREAD_WAITING) { + if (!thread_is_waiting(current_thread)) { spin_unlock_irqrestore(&queue->lock, flags); return; } @@ -109,7 +107,7 @@ int wait_queue_wait( } wait_queue_enqueue_locked(queue, current_thread); - current_thread->state = THREAD_WAITING; + thread_set_waiting(current_thread); spin_unlock_irqrestore(&queue->lock, flags); sched_yield(); @@ -157,8 +155,7 @@ int wait_queue_wait_timeout(struct wait_queue *queue, return -1; } - current_thread->wake_tick = deadline; - current_thread->state = THREAD_SLEEPING; + thread_set_sleeping(current_thread, deadline); wait_queue_enqueue_locked(queue, current_thread); spin_unlock_irqrestore(&queue->lock, flags);