Skip to content

架构总览

本文档介绍 NoobKernel 的整体设计思想、模块划分和核心概念,帮助读者建立对内核结构的全局认识。

内核是什么?

在深入架构之前,先理解内核的本质。

内核的角色

操作系统内核是硬件和应用程序之间的中介:

┌─────────────────────────────────────┐
│           应用程序                   │
│      浏览器、编辑器、Shell...        │
├─────────────────────────────────────┤
│           系统调用接口               │
│    open, read, write, fork...       │
├─────────────────────────────────────┤
│              内核                    │
│    进程管理、内存管理、文件系统...    │
├─────────────────────────────────────┤
│           硬件抽象层                 │
│      驱动、中断控制器...             │
├─────────────────────────────────────┤
│             硬件                     │
│    CPU、内存、磁盘、网卡...          │
└─────────────────────────────────────┘

内核的核心职责

  1. 资源管理:CPU 时间、内存、设备
  2. 抽象硬件:提供统一的接口
  3. 隔离保护:进程间互不干扰
  4. 并发支持:让多个任务"同时"运行

内核的特权

内核运行在最高特权级(RISC-V 的 S-Mode),可以: - 访问所有内存 - 执行特权指令 - 配置硬件

这种特权是双刃剑:强大的能力也意味着更大的责任和风险。

NoobKernel 的设计哲学

教学优先

NoobKernel 的目标是教学,不是生产。设计选择遵循:

  1. 简单优于完美:选择简单的实现,即使效率不是最优
  2. 清晰优于紧凑:代码结构清晰,注释充分
  3. 渐进优于一步到位:先实现核心功能,逐步扩展

参考 Linux,但简化

Linux 是成熟的内核,设计经过验证。NoobKernel 参考其架构,但: - 去除复杂特性(抢占、RCU 等) - 简化数据结构(简单调度器代替 CFS) - 减少代码量(约 5000 行 vs Linux 的数百万行)

RISC-V 优先

NoobKernel 专门为 RISC-V 设计,利用其特性: - 简洁的特权级模型 - 规范的中断机制 - 灵活的 CSR

这比移植一个 x86 内核到 RISC-V 更自然。

模块划分

功能模块

模块 职责 关键问题
boot 启动初始化 如何从硬件过渡到内核?
mm 内存管理 如何高效分配内存?如何隔离地址空间?
hal 硬件抽象 如何屏蔽硬件差异?
trap 中断处理 如何响应异步事件?
task 进程管理 如何切换进程?如何调度?
sync 同步机制 如何避免并发冲突?
fs 文件系统 如何组织持久化数据?
misc 基础库 提供通用数据结构

模块间的依赖关系

                    boot(启动)
                      │
        ┌─────────────┼─────────────┐
        │             │             │
      misc          hal          trap
        │             │             │
        │             │      ┌──────┴──────┐
        │             │      │             │
      sync ←───────→ mm ←──→ task ←──────→ fs
        │             │      │             │
        └─────────────┴──────┴─────────────┘

理解依赖关系的重要性: - 初始化顺序必须遵守依赖关系 - 修改一个模块可能影响依赖它的模块 - 调试时可以从底层模块开始

关键模块详解

内存管理(mm)

内存管理是内核最基础的子系统。设计要点:

  1. 分层分配:Slab 处理小对象,Buddy 处理大块
  2. 虚拟内存:Sv39 页表,恒等映射
  3. 缓存层:bcache 缓存磁盘块

为什么分层?因为单一分配器无法兼顾所有场景。

进程管理(task)

进程是执行单元。设计要点:

  1. 进程结构:记录进程的所有信息
  2. 上下文切换:保存/恢复寄存器
  3. 调度器:决定下一个运行谁

关键挑战:切换进程时要保证状态完整恢复。

中断处理(trap)

中断是硬件通知内核的方式。设计要点:

  1. 入口代码:汇编保存寄存器
  2. 分发处理:根据中断类型分发
  3. 调度触发:定时器中断驱动调度

关键设计:用 gp 寄存器快速定位 trapframe。

文件系统(fs)

文件系统组织持久化数据。设计要点:

  1. VFS 框架:统一接口,支持多种文件系统
  2. 对象模型:inode、dentry、file
  3. 缓存加速:dentry 缓存、bcache

关键思想:通过抽象层隔离具体实现。

核心概念

上下文切换

上下文切换是进程管理的心脏。核心问题:

  1. 保存什么:callee-saved 寄存器 + CSR
  2. 如何切换:修改寄存器,改变执行流
  3. 如何恢复:加载保存的状态

关键洞察:切换本质上是"改变返回地址",让 ret 指令跳到新进程的代码。

中断与调度

中断是驱动调度的机制:

定时器中断 → 设置调度标志 → 从中断返回时检查 → 触发调度

为什么不在中断中直接调度?因为: - 中断上下文有限制 - 需要完整保存/恢复流程 - 灵活性更好

锁与中断

锁和中断的关系微妙:

获取锁 → 关中断 → 保护临界区 → 开中断 → 释放锁

为什么?因为中断处理函数可能也要获取锁,导致死锁。

虚拟内存

虚拟内存提供隔离和抽象:

  1. 隔离:每个进程独立的地址空间
  2. 保护:权限控制(只读、不可执行)
  3. 抽象:连续的虚拟地址,不连续的物理内存

当前实现:内核使用恒等映射(虚拟地址 = 物理地址),简化管理。

数据流示例

读取文件的数据流

应用程序: read(fd, buf, size)
    │
    ▼ 系统调用(待实现)
    │
VFS: 根据文件描述符找到 file 结构
    │ 调用 file->f_op->read
    ▼
文件系统: 转换文件位置到块号
    │ 调用 bcache 读取块
    ▼
bcache: 查缓存,未命中则读磁盘
    │ 调用块设备驱动
    ▼
块设备驱动: 发送 I/O 请求
    │ 等待中断
    ▼
硬件: 磁盘返回数据

进程切换的数据流

定时器中断
    │
    ▼ kernelvec(汇编)
    │ 保存寄存器到 ktrapframe
    │ 调用 kerneltrap
    ▼
kerneltrap(C)
    │ handle_timer
    │ 设置 need_resched
    │ 检查 need_resched
    │ 调用 sched_yield
    ▼
sched_yield
    │ 当前进程入队
    │ 取出下一个进程
    │ context_switch
    ▼
context_switch(汇编)
    │ 保存旧进程上下文
    │ 加载新进程上下文
    │ ret(跳到新进程)
    ▼
新进程继续执行

与 xv6 的比较

方面 NoobKernel xv6
内存分配 Buddy + Slab 简单 kalloc
文件系统 VFS + ramfs 直接实现
调度器 FIFO FIFO
代码量 ~5000 行 ~8000 行
特点 强调模块化、内存管理 全功能、易上手

学习路径建议

初学者路径

  1. 理解启动流程:从硬件到内核,理解初始化过程
  2. 理解中断机制:中断是内核与硬件交互的核心
  3. 理解进程切换:上下文切换是并发的关键
  4. 理解内存管理:分配器设计体现了工程权衡
  5. 理解文件系统:VFS 框架体现了抽象的力量

进阶路径

  1. 阅读源码,对照文档
  2. 修改内核,观察效果
  3. 添加功能,如系统调用
  4. 移植到新平台

设计权衡总结

为什么选择这些设计?

设计 优点 缺点
Buddy + Slab 高效、低碎片 实现复杂
VFS 框架 可扩展 增加抽象层
FIFO 调度 简单公平 无优先级
恒等映射 简单 无隔离
单核 无锁竞争 性能受限

未来改进方向

  1. 抢占式调度:提高响应性
  2. 用户态支持:系统调用、进程隔离
  3. 多核支持:SMP、锁优化
  4. 持久化文件系统:ext2、FAT32

思考题

  1. 如果所有模块都用一个大锁保护,会有什么问题?
  2. 为什么内存管理要在中断处理之前初始化?
  3. 文件系统可以不用 VFS 吗?会有什么问题?
  4. 如何判断一个设计选择是"足够好"而不是"完美"?

下一步

根据兴趣选择学习路径: - 内存管理:深入内存分配 - 中断处理:理解异步机制 - 进程管理:理解并发 - 文件系统:理解数据组织