Appearance
内核设计
- 写作时间:
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 让内核可以在运行时动态加载和卸载功能模块,最典型的是设备驱动。它带来的是灵活性,而不是隔离性:模块一旦加载,仍然运行在内核态,与内核共享地址空间。
设计原则
操作系统的设计原则是指导内核架构和接口设计的一组核心思想。
机制与策略分离。机制回答“怎么做”:提供一种能力或操作方式。策略回答“做什么”:决定何时、对谁使用这种能力。把两者分开,意味着改变策略不需要修改机制的实现。
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_SERVICE、CAP_SYS_PTRACE、CAP_KILL 等,让进程只拿自己真正需要的那一小部分权限。
操作系统历史
这些结构和原则都不是凭空出现的。操作系统的历史可以看作一条不断解决瓶颈的链条:每一代系统都在回答上一代遗留的问题。
批处理系统解决的是“CPU 因人工装卸作业而大量空转”的问题。作业被排队、自动装载,CPU 不再等操作员。
多道程序解决的是“单个作业等待 I/O 时,CPU 依然空转”的问题。内存里同时放多个作业,一个等待 I/O,另一个就上 CPU。也正是在这里,调度器和内存保护变成了操作系统的核心子系统。
分时系统解决的是“程序员不能和运行中的程序交互”的问题。操作系统给每个用户一小段时间片,快速轮转,让每个人都感觉自己独占整台机器。
UNIX 解决的是“系统太复杂、难以实现和组合”的问题。它保留了层级文件系统等关键想法,但把设计哲学压缩成简洁的接口:进程、fork/exec、文件描述符、管道。它们到今天仍然是操作系统教学和实践的主线。
Linux 解决的是“商业 UNIX 分裂、授权门槛高”的问题。它从一开始就开源,把统一的源码主线交给整个社区共同演化。本课程对照的正是 Linux 内核源码。
所以历史不是一串年份,而是一张“问题 -> 机制”的对照表。后面看到一个具体机制时,回头问一句“它最早是在解决哪类瓶颈”,很多设计就不再显得随意。
小结
| 概念 | 说明 |
|---|---|
| 宏内核 / 微内核 / 混合内核 | 三种典型的内核组织方式 |
| LKM | 运行时可加载模块,提升灵活性但不提供隔离 |
| 机制与策略分离 | 能力本身和使用规则分开演化 |
| 最小权限 | 每个组件只拿完成任务所需的最小权限 |
| Capabilities | 把 root 权限拆成更细的能力集合 |
| 历史问题链 | 批处理 -> 多道程序 -> 分时 -> UNIX -> Linux |
这一课把“内核怎么组织”“为什么这样组织”“这些结构是怎么长出来的”放在一起,是因为这三件事本来就是同一个问题的不同切面。
Linux 源码入口:
include/linux/sched.h—sched_class等核心结构kernel/capability.c— Capabilities 的实现入口
下一课从设计回到硬件边界:CPU 怎样在用户态和内核态之间划出一条不能被普通程序随意跨过的线。