Skip to content

第一章:操作系统概述

操作系统管理 CPU、内存和设备,为程序提供执行、通信和保存数据的接口。多个程序共享这些资源时,内核记录资源的归属与使用状态,协调并发访问,并借助硬件限制程序能够执行的操作。

操作系统的组成

先看一台运行 xv6 或 Linux 这类宏内核的机器。硬件、内核和用户程序承担不同职责。

硬件提供 CPU、内存和设备,并执行特权、地址翻译与中断等规则。内核维护资源状态,设置硬件规则,并向程序提供创建进程、读写文件等接口。用户程序借助这些接口运行;库、shell 和大多数系统服务也处于用户态。

内核内部按职责划分子系统。进程管理维护执行身份、资源引用与生命周期;调度分配 CPU;内存管理维护映射和物理页;文件与通信层组织持久数据或通信端点;驱动控制设备并处理请求完成。同步、引用计数和权限检查则贯穿各子系统。

用户程序与内核之间有硬件保护边界。内核内部的调度器、文件系统和驱动则通常共享内核地址空间,可以直接调用函数、访问共同对象。它们是职责分工,并不是各自受到硬件隔离的“小进程”。第十二章会比较把部分服务放进独立进程的微内核设计。

广义的操作系统还包含系统服务和工具。本讲义主要研究内核及其对应用提供的接口。shell 负责解释命令,但通常只是用户程序;它通过系统调用请求内核创建进程、打开文件。

用户代码、内核代码与设备的执行

在一个 CPU 核心上,某个时刻正在执行的可能是用户指令,也可能是内核指令。内核处理当前进程的系统调用时,通常使用该线程的内核栈;处理中断时,则处于中断执行上下文。Linux 还会让专门的内核线程执行回写等后台工作。

多核机器可以同时有一个核心执行用户代码,另一个核心执行内核代码,第三个核心处理设备中断。因此“CPU 在用户态还是内核态”必须针对具体核心、具体时刻来说。

设备也会独立工作。DMA 传输期间,CPU 可以执行无关程序。多个 CPU 核心与设备之间通过共享内存、请求队列和中断协调。

资源与抽象

应用通常不直接领取“CPU 的第几微秒”“内存条的第几个字节”或“磁盘的第几个扇区”。内核提供应用能够使用的对象和接口,再把它们落实到有限资源上。

硬件资源应用看到的主要抽象内核需要维护的关系主要机制
CPU 执行能力可以暂停、恢复的执行流执行现场、运行状态、CPU 归属中断、调度、上下文切换
物理内存地址空间虚拟地址到物理页的映射与权限页表、缺页处理、页分配
存储设备文件与目录名字到文件身份、文件偏移到数据块路径查找、缓存、块分配、恢复协议
设备和通信通道可通过接口操作的端点打开引用、缓冲区、请求及完成状态驱动、队列、通知与等待

进程把执行身份和多种资源引用组织在一起。文件描述符则是进程引用打开对象的一种方式。进程不是一个与 CPU、内存并列的新硬件部件,文件描述符也不是文件本身。

这些抽象同时承担两个任务:让程序容易使用机器,以及让多个程序共享机器时仍有明确的归属与保护规则。

内核对象

下面是 xv6 中一份简化的内存对象图。箭头表示引用或映射,方框之间的关系不表示调用顺序。

text
某 CPU 的状态                  进程 A:struct proc
  当前进程指针 ───────────────→ state / pid / parent
  调度器 context                trapframe ─────→ 保存的用户寄存器
                                context ───────→ 保存的内核切换现场
                                内核栈 ────────→ 内核调用链
                                pagetable ─────→ 页表 ─────→ 物理页
                                ofile[0..] ────→ struct file ──→ inode 或 pipe
                                cwd ───────────→ 当前目录 inode

进程 B:另一个 struct proc
  独立的现场与页表
  某些 ofile 条目 ─────────────────────────────→ 同一个 struct file

这些对象之间包含三类关系。

执行状态回答“接下来从哪里继续”。寄存器里有指令位置、栈指针和计算中间值;暂停执行时,要把恢复所需的信息保存在内存里。用户现场与内核切换现场用途不同,第二、五章会分别展开。

资源引用回答“这个执行实例正在使用什么”。进程中的 FD 表引用打开文件对象,打开文件对象又引用 inode 或管道。两个进程可以引用同一个对象,因此“进程不同”不意味着所有资源都复制了一份。

映射关系回答“程序使用的标识怎样找到实际资源”。虚拟地址要经页表解释,整数 FD 要经当前进程的 FD 表解释,路径要经目录查找解释。它们都是间接关系,但负责的名字空间和权限不同,不能混成一种机制。

Linux 的结构会进一步拆分:每个线程有自己的任务对象,通过指针引用可能共享的地址空间、FD 表和凭证。xv6 把许多状态放在同一个 struct proc 中,是因为它的模型更简单。

内核数据与内核代码

内核对象大多是 RAM 中的数据结构。页表也是内存中的数据,只是硬件 MMU 会按约定格式读取它;进程表则主要由内核代码管理。一个进程不占用 CPU 时,它的状态和资源引用依然存在。

CPU 执行内核代码,内核代码读写这些对象以改变系统状态。例如,调度器在当前 CPU 上读取进程状态,选定可运行进程,再恢复它的执行现场。

特权切换、上下文切换与地址空间切换

“进入内核”“换进程”“换页表”经常相邻发生,但不是同一件事。

事件改变什么不必随之发生什么
系统调用或异常进入内核当前 CPU 的执行权限与指令位置,并保存必要现场不必换成另一个进程
上下文切换当前执行流及其寄存器、栈等现场同一进程的线程间切换不必更换地址空间
切换地址空间CPU 用来解释虚拟地址的映射上下文不等同于选择下一个执行流

例如,查询 PID 的系统调用通常可以进入内核后马上返回,进程身份不变。阻塞读取则可能先进入内核,再因数据未到而切换执行流。xv6 在用户和内核入口处更换页表,但这时仍可能在服务同一个进程。

还有一个常见混淆:普通地址翻译由 MMU 完成,通常不会执行一遍内核代码。只有翻译或权限检查失败等情况才产生异常,由内核处理。

子系统协作

子系统通过函数调用、对象引用和事件通知协作:

发起方协作对象交接的内容必须维持的关系
进程管理内存管理、文件层创建、复制、释放映射和引用退出后不再使用的资源最终回收
内存管理调度、存储文件缺页所需 I/O;等待与恢复页面准备好后再重试访问(Linux 场景)
文件系统缓存、驱动块号、缓冲区、读写请求正确的数据送到正确对象,错误能传回
设备完成处理等待者与调度器完成状态、唤醒完成后可运行,不等于立刻运行
所有共享对象同步机制锁、原子更新、等待协议并发访问不破坏对象不变量

文件读取示例

进程通过 FD 请求读取一个普通文件。文件层先找到打开对象,再按文件偏移查找数据。若缓存命中,可以复制数据并返回;若未命中,就要建立设备请求,并让当前执行流等待。设备完成后,驱动更新请求状态并唤醒等待者,调度器稍后恢复它,它才有机会继续读并返回用户态。

在一次读取中,FD 属于引用关系,文件偏移属于打开对象,缓冲区属于缓存,设备请求属于 I/O 路径,睡眠与就绪属于执行状态。它们参与同一次读取,却没有变成同一个对象。

如果把普通文件换成管道,FD 接口仍可使用,但数据来自另一进程写入的内核缓冲区,不必经过磁盘。同样的 read 接口会根据打开对象的类型,调用不同的实现。

运行时状态与持久数据

运行中的 OS 主要依赖 RAM 中的状态,而文件系统还维护设备上的长期结构。需要区分它们的寿命。

状态通常存放在哪里掉电后的处理
当前寄存器、进程表、内核栈CPU / RAM消失,启动后重新建立
页表、FD 表、等待队列RAM消失,不能靠磁盘上的程序文件直接恢复
文件缓存、未完成设备请求RAM 或设备易失缓存可能丢失,不能仅凭 write 返回判断已保存
文件数据、目录、磁盘元数据、日志已到达持久化边界的存储重启后根据文件系统规则读取与恢复

磁盘上有一个程序文件,不代表重启后自动恢复了之前那个进程;文件数据已经持久化,也不代表此前的锁、FD 和执行现场仍在。虚拟机快照等机制会额外保存部分运行状态,属于第十一章讨论的更强协议。

缓存中的修改需要经过写回与同步操作,才能满足持久化要求;重启后的恢复则依赖磁盘上的数据结构与更新协议。第八、九章分别讨论这些机制。

内核启动与初始化

上述结构不是机器上电时天然存在的。启动代码需要建立可用的内核执行环境,初始化物理内存分配、页表、trap 入口和设备,准备调度所需的每 CPU 状态,然后创建最初的用户进程。

之后系统进入持续的运行阶段:用户代码直接执行,系统调用、异常和中断让 CPU 进入内核;内核维护对象、处理事件,选择合适的执行流继续。多核和设备可以并行推进,没有一个单一循环串行解释整台机器上的所有事情。

在 xv6 中,启动与初始化可从 kernel/entry.Sstart.cmain.c 读起。这些代码负责在用户程序开始运行之前准备上述状态。

接口、机制与策略

同一项系统功能包含不同层次的设计:

  • 抽象与接口:外部使用者能请求什么,得到什么承诺。
  • 机制:状态保存在哪里,硬件和软件怎样改变它。
  • 策略:在多个合法选择中选哪个,例如挑选哪个可运行线程、回收哪一页。
  • 约束:哪些访问必须拒绝,哪些顺序不能颠倒,资源何时能释放。

例如,调度策略可以选择轮转或加权公平,但都需要保存与恢复执行现场的切换机制。改变策略会影响哪个进程先运行,不改变已阻塞进程必须等条件满足后才能继续的约束。

练习

  1. 两个进程共享一个物理页和一个打开文件,又轮流使用一个 CPU。哪些状态必须分别保存,哪些对象可以共用?
  2. 一个进程进入内核处理系统调用时,哪些状态发生变化,哪些身份和引用可以保持不变?
  3. 一个线程睡眠后,谁保存它的现场,谁记录它在等什么,谁使它重新可运行,谁决定它何时执行?
  4. 假如设备驱动被移到用户进程,哪些保护边界、调用和共享关系必须改变?
  5. 机器掉电后,哪些运行时对象消失,哪些持久数据需要被恢复?

上次更新: