Skip to content

内核设计

  • 写作时间:2026-03-23
  • 当前字符:4792

上一课看的是“操作系统和硬件”。这一课把视角收回来,专门看操作系统自己这个软件系统:它的核心代码应该怎样组织,为什么会有宏内核和微内核这些分歧,以及今天很多看起来理所当然的设计,其实是怎样从历史问题里长出来的。

内核结构

内核(kernel)是操作系统中运行在最高权限级别的核心程序,直接管理硬件资源并为上层软件提供服务。

操作系统的功能很多:进程管理、内存管理、文件系统、设备驱动、网络协议栈。这些功能怎么组织成一个可运行的程序?不同的组织方式产生了不同的内核结构。

宏内核(monolithic kernel) 把所有操作系统功能编译进同一个大程序中,运行在同一个地址空间里。进程管理、内存管理、文件系统、设备驱动、网络协议栈全部在内核态执行,函数之间通过普通的函数调用通信。Linux 就是宏内核。

宏内核的优点是性能:所有组件在同一个地址空间中,互相调用不需要跨越地址空间的开销。文件系统要读磁盘,直接调用磁盘驱动的函数即可。缺点是隔离性差:任何一个内核组件的 bug 都可能破坏整个内核的内存。一个有问题的设备驱动可以导致整个系统崩溃。

微内核(microkernel) 只在内核态保留最核心的功能:进程调度、进程间通信(IPC)和基本的内存管理。文件系统、设备驱动、网络协议栈等其余功能都作为独立的用户态进程运行。这些进程之间通过消息传递(message passing)通信。L4 和 QNX 是微内核的代表。

微内核的优点是隔离性:设备驱动运行在用户态,一个驱动崩溃不会影响内核和其他驱动,系统可以自动重启故障组件。缺点是性能:原来宏内核里一次函数调用能完成的事,在微内核中变成了用户态进程之间的消息传递。

混合内核(hybrid kernel) 试图兼顾两者。macOS 的 XNU 内核基于微内核 Mach,但把大量功能(包括 BSD 子系统、文件系统、网络栈)放回了内核态以减少 IPC 开销。Windows NT 内核也是类似的设计:架构上是微内核风格(分层、模块化),但大部分服务运行在内核态。

还有两条常被拿来对照的路线值得顺手记一下。外核(exokernel) 是一种学术研究中的极端设计:内核不主动提供高层抽象,只负责安全地复用硬件资源,文件系统、网络栈这类能力尽量交给库操作系统(library OS)去实现。它的优势是应用可以按自己的需要优化策略,缺点是系统和应用的整体复杂度都很高。

Linux 虽然是宏内核,但支持可加载内核模块(Loadable Kernel Module, LKM)。LKM 让内核可以在运行时动态加载和卸载功能模块,最典型的是设备驱动。它带来的是灵活性,而不是隔离性:模块一旦加载,仍然运行在内核态,与内核共享地址空间。

框内核与星绽(Asterinas)

如果把内核架构的发展粗略画成一条线,可以这样理解:

  • UNIX / Linux 把宏内核推成主流。原因很现实:所有核心组件都在同一地址空间里,函数调用直接、路径短、性能好,工程上更容易做出“完整可用”的系统。
  • Mach、L4、QNX 一类项目把微内核的理想推到前台。它们强调隔离、可恢复性和更小的特权核心,但也长期背着 IPC、地址空间切换和工程复杂度的成本。
  • Rust 时代重新打开了第三条路:既不完全回到传统宏内核“所有东西都在特权核心里”的做法,也不走经典微内核“服务进程大量消息通信”的路线,而是试图把“高性能”和“更小的可信基”重新拼在一起。

框内核(framekernel) 就是在这个背景下提出的一种新路线。它保留了宏内核的同地址空间执行模型,因此组件之间通信仍然很直接;但它又借鉴了微内核“尽量缩小特权核心”的思路,把内核分成两半:

  • Framework(框架):少量、特权、允许使用 unsafe Rust 的底层核心,负责把硬件操作和底层机制封装成高层安全接口
  • Services(服务):构建在这些接口之上的大部分内核功能,要求尽量用安全 Rust 编写,不直接接触危险底层操作

这样做的关键收益是:整个内核的内存安全问题,被尽量收缩到那一小块 Framework 上。 它不像经典微内核那样把服务拆到不同地址空间里,所以没有那么重的消息传递和上下文切换开销;但它也不像传统宏内核那样让所有内核代码都拥有同等危险的特权能力。

星绽(Asterinas) 是目前最有代表性的开源框内核项目。按照其官方 GitHub README 和官网的表述,Asterinas 的目标是做一个安全、快速、通用、且兼容 Linux ABI 的 Rust 内核。它把“宏内核的速度”和“微内核式的小 TCB / 更强安全边界”结合起来,试图重新回答一个老问题:现代通用操作系统,能不能既保留 Linux 这一类系统的工程实用性,又显著提高内存安全性?

从发展脉络上看,Asterinas 不是在否定宏内核,而是在承认宏内核长期胜出的工程事实之后,借 Rust 和更小 TCB 的设计,把这条路线往前推一步。也正因为如此,框内核更适合被理解成宏内核与微内核长期博弈之后,在新语言和新验证方法加持下出现的折中型新阶段,而不是一个完全脱离历史的新名词。

项目地址:

设计原则

操作系统的设计原则是指导内核架构和接口设计的一组核心思想。

机制与策略分离。机制回答“怎么做”:提供一种能力或操作方式。策略回答“做什么”:决定何时、对谁使用这种能力。把两者分开,意味着改变策略不需要修改机制的实现。

Linux 内核的进程调度是一个典型例子。机制是上下文切换(context switch):保存当前进程的寄存器状态,恢复下一个进程的寄存器状态,切换页表。无论选哪个进程来运行,这个切换过程都是一样的。策略是调度算法:从所有就绪进程中选择下一个运行的进程。Linux 通过 sched_class 结构体定义了调度策略的接口:

c
// kernel/sched/sched.h (simplified)
struct sched_class {
    void (*enqueue_task)(struct rq *rq, struct task_struct *p, int flags);
    void (*dequeue_task)(struct rq *rq, struct task_struct *p, int flags);
    struct task_struct *(*pick_next_task)(struct rq *rq);
    ...
};

不同的调度策略实现这同一组接口:CFS 适合普通进程,RT 适合延迟敏感的任务,Deadline 调度器适合有截止时间要求的任务。增加一种新的调度策略,只需要实现 sched_class 接口,不需要修改上下文切换的代码。

最小权限(principle of least privilege)。每个组件只应拥有完成其任务所必需的最小权限。权限越大,出错时的破坏范围越大。

这条原则在硬件层面的体现是 CPU 的特权级(privilege level)。x86-64 处理器定义了 4 个特权级(Ring 0 到 Ring 3),Linux 使用其中两个:内核运行在 Ring 0,用户程序运行在 Ring 3。用户程序如果试图执行特权指令,CPU 会触发异常,内核接管处理。后面一课的“特权边界”会把这件事具体展开。

即使在内核内部,Linux 也通过 Capabilities 机制细化了权限控制。传统 Unix 的权限模型只有两级:普通用户和 root。root 拥有所有权限,这违反了最小权限原则。Linux Capabilities 把 root 的权限拆成几十个独立的能力,比如 CAP_NET_BIND_SERVICECAP_SYS_PTRACECAP_KILL 等,让进程只拿自己真正需要的那一小部分权限。

操作系统历史

这些结构和原则都不是凭空出现的。操作系统的历史可以看作一条不断解决瓶颈的链条:每一代系统都在回答上一代遗留的问题。

批处理系统解决的是“CPU 因人工装卸作业而大量空转”的问题。作业被排队、自动装载,CPU 不再等操作员。

多道程序解决的是“单个作业等待 I/O 时,CPU 依然空转”的问题。内存里同时放多个作业,一个等待 I/O,另一个就上 CPU。也正是在这里,调度器和内存保护变成了操作系统的核心子系统。

分时系统解决的是“程序员不能和运行中的程序交互”的问题。操作系统给每个用户一小段时间片,快速轮转,让每个人都感觉自己独占整台机器。

UNIX 解决的是“系统太复杂、难以实现和组合”的问题。它保留了层级文件系统等关键想法,但把设计哲学压缩成简洁的接口:进程、fork/exec、文件描述符、管道。它们到今天仍然是操作系统教学和实践的主线。

Linux 解决的是“商业 UNIX 分裂、授权门槛高”的问题。它从一开始就开源,把统一的源码主线交给整个社区共同演化。本课程对照的正是 Linux 内核源码。

所以历史不是一串年份,而是一张“问题 -> 机制”的对照表。后面看到一个具体机制时,回头问一句“它最早是在解决哪类瓶颈”,很多设计就不再显得随意。

小结

概念说明
宏内核 / 微内核 / 混合内核三种典型的内核组织方式
LKM运行时可加载模块,提升灵活性但不提供隔离
机制与策略分离能力本身和使用规则分开演化
最小权限每个组件只拿完成任务所需的最小权限
Capabilities把 root 权限拆成更细的能力集合
历史问题链批处理 -> 多道程序 -> 分时 -> UNIX -> Linux

这一课把“内核怎么组织”“为什么这样组织”“这些结构是怎么长出来的”放在一起,是因为这三件事本来就是同一个问题的不同切面。


Linux 源码入口

下一课从设计回到硬件边界:CPU 怎样在用户态和内核态之间划出一条不能被普通程序随意跨过的线。