操作系统的角色、结构与启动¶
操作系统最早并不是为了提供漂亮界面,而是为了提高昂贵机器的利用率。早期计算机一次装入一个作业,人工准备和切换任务的时间很长;批处理系统把作业排成队列,多道程序系统又在一个作业等待 I/O 时切换到另一个作业。分时系统进一步追求交互响应,让多个用户感觉自己独占机器。
这段历史揭示了操作系统持续面对的矛盾:硬件是共享的,用户却希望获得简单、稳定而近似独占的环境。
为什么需要操作系统¶
假设两段程序都直接控制磁盘控制器:
没有仲裁时,结果取决于设备命令交错,任一程序还可以覆盖另一程序的数据。即使程序都没有恶意,错误地址或失控循环也可能破坏整个机器。
操作系统通过三类工作解决问题:
- 抽象:把扇区变成文件,把 CPU 执行现场变成进程,把物理页框变成虚拟地址空间。
- 复用:在时间上切分 CPU,在空间上切分内存和存储,在队列中安排 I/O。
- 保护:验证访问权限,限制特权操作,把故障尽量约束在所属进程或隔离域内。
抽象并不只是为了易用。它也建立了可以检查和仲裁的接口:应用通过 open 请求打开文件时,内核才能在路径解析和权限检查后决定是否允许访问。
机制与策略¶
操作系统设计中一个重要区分是机制和策略。
- 机制提供能力,例如定时器中断让内核可以重新获得 CPU 控制权。
- 策略做选择,例如调度器决定下一个运行哪个线程。
把策略硬编码进机制会降低可演化性。相反,如果机制暴露过多自由度,策略实现又可能复杂或不安全。实际系统通常在二者之间选择稳定接口。
| 领域 | 机制 | 策略 |
|---|---|---|
| CPU | 中断、保存/恢复寄存器 | 优先级、时间片、负载均衡 |
| 内存 | 页表、缺页异常 | 页面置换、回收阈值 |
| 存储 | 块请求队列、DMA | 请求合并、调度顺序 |
| 保护 | 特权级、访问检查 | 哪个主体获得哪些权限 |
内核组织方式¶
单体内核¶
单体内核把进程管理、虚拟内存、文件系统、网络和许多驱动都放在同一内核地址空间。组件通过普通函数调用协作,路径短、共享数据方便。
优点:
- 内核内部调用开销低。
- 组件能高效共享缓存和数据结构。
- 性能分析和跨子系统优化相对直接。
代价:
- 一个驱动的越界写可能破坏整个内核。
- 可信计算基较大,接口耦合复杂。
- 模块之间缺乏强故障隔离。
Linux 常被归为模块化单体内核:核心服务运行在内核态,同时允许按需加载内核模块。模块化改善部署与扩展,但加载后的模块仍处在高权限边界内。
微内核¶
微内核尽量只在内核态保留地址空间、线程调度、中断和进程间通信等最小机制,把文件系统、网络栈和驱动放入用户态服务。
优点是服务之间隔离更强,组件可以单独重启,可信核心更小。代价是一次逻辑操作可能跨越多个地址空间,需要更多消息传递、调度和数据移动。现代微内核通过快速 IPC、共享内存和更好的接口设计降低这些成本。
混合内核与外内核思想¶
“混合内核”通常表示吸收微内核结构思想,但为了性能仍把较多服务放在内核态。不同系统对这一名称的使用并不完全统一,分类不应替代对真实边界的检查。
外内核思想则尝试让内核只安全地复用硬件资源,把高层抽象交给库操作系统。它强调:固定抽象可能限制应用优化;代价是应用或库必须承担更多资源管理复杂度。
最小结构比较¶
设一次文件操作需要经过 \(k\) 次保护域切换,每次切换平均固定成本为 \(c\),不考虑其他开销时,边界切换成本近似为:
这个式子不是实际性能模型,却指出结构选择的直接代价:把服务拆得更细通常增加边界跨越次数。另一方面,如果分离能减少故障影响范围或允许服务独立更新,这些额外成本可能完全值得。
系统如何启动¶
启动不是“内核突然开始运行”,而是一条逐步建立执行环境和信任链的路径。
1. 复位与固件¶
处理器复位后从体系结构规定的位置取指。固件完成最基本的硬件初始化、内存检测和启动设备选择。现代机器常使用 UEFI;更早的 PC 常使用 BIOS。
固件此时运行在高权限环境中。启用安全启动时,固件还会验证下一阶段加载对象的签名,防止未经授权的引导代码进入信任链。
2. 引导程序¶
引导程序负责找到内核映像和初始内存文件系统,把它们放到内存,并准备启动参数。它可能提供多系统选择、内核版本选择和恢复入口。
引导程序知道怎样“装入内核”,但不负责建立完整进程、文件系统和设备抽象。
3. 内核早期初始化¶
内核入口最初只能依赖非常有限的环境。典型工作包括:
- 建立页表和内核虚拟地址空间。
- 初始化异常向量、中断控制器和时钟。
- 发现 CPU、内存和关键设备。
- 初始化内存分配器、调度器和核心对象。
- 挂载临时或真正的根文件系统。
多核系统中,通常先由一个启动处理器执行主要初始化,再唤醒其他处理器并让它们进入调度循环。
4. 第一个用户空间进程¶
当内核具备创建进程和访问根文件系统的能力后,会启动第一个用户空间进程。该进程继续启动服务、挂载文件系统、配置设备与网络,并最终提供登录或图形环境。
启动失败怎样分层定位¶
| 现象 | 更可能的阶段 |
|---|---|
| 没有固件界面或硬件自检失败 | 固件或硬件 |
| 能选择内核但无法装入 | 引导程序、启动分区或映像 |
| 内核早期崩溃,没有根文件系统 | 内核、驱动、启动参数、initramfs |
| 内核启动完成但服务失败 | 用户空间初始化与服务配置 |
分层的价值在于减少猜测范围。看到“系统没起来”时,先确定最后一个成功阶段,比立刻重装系统更有信息。
权衡与限制¶
操作系统无法同时提供绝对隔离、零开销抽象、完美公平和最高性能。它必须基于工作负载和故障模型权衡:
- 共享缓存提高性能,却扩大侧信道和资源争用面。
- 延迟写提高吞吐,却使断电时尚未持久化的数据增多。
- 大内核减少跨域调用,却扩大可信计算基。
- 更细粒度锁提高并行度,却增加推理和调试难度。
评价一个设计时,应先问目标和假设,而不是只问它是否“先进”。
与其他知识的联系¶
- 与计算机组成原理:特权级、异常向量、MMU、缓存和设备控制器提供硬件机制。
- 与数据结构:运行队列、页表、inode 缓存、定时器堆和哈希表决定实际复杂度。
- 与网络:网络栈同样由协议状态机、缓冲区、队列和异步事件组成。
- 与分布式系统:微内核消息传递和分布式 RPC 都要面对边界、序列化、故障与命名问题,只是故障模型和延迟尺度不同。
自测¶
- 为什么把所有驱动放到用户态可能提高可靠性,却不一定提高性能?
- “模块化单体内核”中的模块化是否意味着强隔离?
- 启动过程中,页表和中断系统为何必须在普通用户进程前建立?
- 举出一个机制与策略被混淆后导致设计僵化的例子。
参考思路
用户态驱动故障通常不会直接覆盖内核,但请求会增加 IPC、调度和数据共享成本。内核模块虽可动态加载,加载后仍拥有内核权限。用户进程依赖虚拟地址、异常入口和调度机制,因此这些基础必须先初始化。机制与策略的例子可以从“时钟提供抢占能力,固定时间片选择属于策略”出发。