跳转至

操作系统的角色、结构与启动

操作系统最早并不是为了提供漂亮界面,而是为了提高昂贵机器的利用率。早期计算机一次装入一个作业,人工准备和切换任务的时间很长;批处理系统把作业排成队列,多道程序系统又在一个作业等待 I/O 时切换到另一个作业。分时系统进一步追求交互响应,让多个用户感觉自己独占机器。

这段历史揭示了操作系统持续面对的矛盾:硬件是共享的,用户却希望获得简单、稳定而近似独占的环境。

为什么需要操作系统

假设两段程序都直接控制磁盘控制器:

程序 A:把数据写到扇区 100
程序 B:把数据写到扇区 100

没有仲裁时,结果取决于设备命令交错,任一程序还可以覆盖另一程序的数据。即使程序都没有恶意,错误地址或失控循环也可能破坏整个机器。

操作系统通过三类工作解决问题:

  1. 抽象:把扇区变成文件,把 CPU 执行现场变成进程,把物理页框变成虚拟地址空间。
  2. 复用:在时间上切分 CPU,在空间上切分内存和存储,在队列中安排 I/O。
  3. 保护:验证访问权限,限制特权操作,把故障尽量约束在所属进程或隔离域内。

抽象并不只是为了易用。它也建立了可以检查和仲裁的接口:应用通过 open 请求打开文件时,内核才能在路径解析和权限检查后决定是否允许访问。

机制与策略

操作系统设计中一个重要区分是机制和策略。

  • 机制提供能力,例如定时器中断让内核可以重新获得 CPU 控制权。
  • 策略做选择,例如调度器决定下一个运行哪个线程。

把策略硬编码进机制会降低可演化性。相反,如果机制暴露过多自由度,策略实现又可能复杂或不安全。实际系统通常在二者之间选择稳定接口。

领域 机制 策略
CPU 中断、保存/恢复寄存器 优先级、时间片、负载均衡
内存 页表、缺页异常 页面置换、回收阈值
存储 块请求队列、DMA 请求合并、调度顺序
保护 特权级、访问检查 哪个主体获得哪些权限

内核组织方式

单体内核

单体内核把进程管理、虚拟内存、文件系统、网络和许多驱动都放在同一内核地址空间。组件通过普通函数调用协作,路径短、共享数据方便。

优点:

  • 内核内部调用开销低。
  • 组件能高效共享缓存和数据结构。
  • 性能分析和跨子系统优化相对直接。

代价:

  • 一个驱动的越界写可能破坏整个内核。
  • 可信计算基较大,接口耦合复杂。
  • 模块之间缺乏强故障隔离。

Linux 常被归为模块化单体内核:核心服务运行在内核态,同时允许按需加载内核模块。模块化改善部署与扩展,但加载后的模块仍处在高权限边界内。

微内核

微内核尽量只在内核态保留地址空间、线程调度、中断和进程间通信等最小机制,把文件系统、网络栈和驱动放入用户态服务。

应用
  -> IPC 请求文件服务
  -> 文件服务请求块设备服务
  -> 微内核负责消息传递与调度

优点是服务之间隔离更强,组件可以单独重启,可信核心更小。代价是一次逻辑操作可能跨越多个地址空间,需要更多消息传递、调度和数据移动。现代微内核通过快速 IPC、共享内存和更好的接口设计降低这些成本。

混合内核与外内核思想

“混合内核”通常表示吸收微内核结构思想,但为了性能仍把较多服务放在内核态。不同系统对这一名称的使用并不完全统一,分类不应替代对真实边界的检查。

外内核思想则尝试让内核只安全地复用硬件资源,把高层抽象交给库操作系统。它强调:固定抽象可能限制应用优化;代价是应用或库必须承担更多资源管理复杂度。

最小结构比较

设一次文件操作需要经过 \(k\) 次保护域切换,每次切换平均固定成本为 \(c\),不考虑其他开销时,边界切换成本近似为:

\[ T_{boundary}=kc. \]

这个式子不是实际性能模型,却指出结构选择的直接代价:把服务拆得更细通常增加边界跨越次数。另一方面,如果分离能减少故障影响范围或允许服务独立更新,这些额外成本可能完全值得。

系统如何启动

启动不是“内核突然开始运行”,而是一条逐步建立执行环境和信任链的路径。

1. 复位与固件

处理器复位后从体系结构规定的位置取指。固件完成最基本的硬件初始化、内存检测和启动设备选择。现代机器常使用 UEFI;更早的 PC 常使用 BIOS。

固件此时运行在高权限环境中。启用安全启动时,固件还会验证下一阶段加载对象的签名,防止未经授权的引导代码进入信任链。

2. 引导程序

引导程序负责找到内核映像和初始内存文件系统,把它们放到内存,并准备启动参数。它可能提供多系统选择、内核版本选择和恢复入口。

引导程序知道怎样“装入内核”,但不负责建立完整进程、文件系统和设备抽象。

3. 内核早期初始化

内核入口最初只能依赖非常有限的环境。典型工作包括:

  • 建立页表和内核虚拟地址空间。
  • 初始化异常向量、中断控制器和时钟。
  • 发现 CPU、内存和关键设备。
  • 初始化内存分配器、调度器和核心对象。
  • 挂载临时或真正的根文件系统。

多核系统中,通常先由一个启动处理器执行主要初始化,再唤醒其他处理器并让它们进入调度循环。

4. 第一个用户空间进程

当内核具备创建进程和访问根文件系统的能力后,会启动第一个用户空间进程。该进程继续启动服务、挂载文件系统、配置设备与网络,并最终提供登录或图形环境。

复位向量
  -> 固件
  -> 引导程序
  -> 内核入口
  -> 内核子系统初始化
  -> 第一个用户空间进程
  -> 系统服务与登录环境

启动失败怎样分层定位

现象 更可能的阶段
没有固件界面或硬件自检失败 固件或硬件
能选择内核但无法装入 引导程序、启动分区或映像
内核早期崩溃,没有根文件系统 内核、驱动、启动参数、initramfs
内核启动完成但服务失败 用户空间初始化与服务配置

分层的价值在于减少猜测范围。看到“系统没起来”时,先确定最后一个成功阶段,比立刻重装系统更有信息。

权衡与限制

操作系统无法同时提供绝对隔离、零开销抽象、完美公平和最高性能。它必须基于工作负载和故障模型权衡:

  • 共享缓存提高性能,却扩大侧信道和资源争用面。
  • 延迟写提高吞吐,却使断电时尚未持久化的数据增多。
  • 大内核减少跨域调用,却扩大可信计算基。
  • 更细粒度锁提高并行度,却增加推理和调试难度。

评价一个设计时,应先问目标和假设,而不是只问它是否“先进”。

与其他知识的联系

  • 与计算机组成原理:特权级、异常向量、MMU、缓存和设备控制器提供硬件机制。
  • 与数据结构:运行队列、页表、inode 缓存、定时器堆和哈希表决定实际复杂度。
  • 与网络:网络栈同样由协议状态机、缓冲区、队列和异步事件组成。
  • 与分布式系统:微内核消息传递和分布式 RPC 都要面对边界、序列化、故障与命名问题,只是故障模型和延迟尺度不同。

自测

  1. 为什么把所有驱动放到用户态可能提高可靠性,却不一定提高性能?
  2. “模块化单体内核”中的模块化是否意味着强隔离?
  3. 启动过程中,页表和中断系统为何必须在普通用户进程前建立?
  4. 举出一个机制与策略被混淆后导致设计僵化的例子。
参考思路

用户态驱动故障通常不会直接覆盖内核,但请求会增加 IPC、调度和数据共享成本。内核模块虽可动态加载,加载后仍拥有内核权限。用户进程依赖虚拟地址、异常入口和调度机制,因此这些基础必须先初始化。机制与策略的例子可以从“时钟提供抢占能力,固定时间片选择属于策略”出发。