跳转至

操作系统笔记

操作系统位于应用程序和硬件之间。向上,它给程序提供进程、线程、虚拟地址、文件和套接字等稳定抽象;向下,它管理 CPU、内存、磁盘、网卡和各种设备,并阻止彼此不可信的程序越界干扰。

如果没有操作系统,每个程序都要自己处理键盘中断、磁盘扇区、物理内存地址和设备时序。更困难的是,多个程序会同时争用同一批硬件资源。操作系统解决的核心问题因此不是“怎样封装几个函数”,而是:

如何在共享而有限的硬件上,为许多程序提供方便、高效、可靠并且彼此隔离的运行环境?

三个核心角色

角色 解决的问题 典型抽象
抽象机器 硬件接口过于具体且差异很大 进程、虚拟内存、文件、套接字
资源管理器 多个任务争用有限资源 调度、内存分配、缓存、I/O 队列
隔离与保护者 程序可能出错,甚至具有恶意 用户态、页表权限、访问控制、命名空间

这三个角色经常互相拉扯。更强的抽象可能增加额外开销;更高的吞吐量可能损害交互延迟;更严格的隔离往往需要更多检查或硬件支持。学习操作系统的重点不是背诵某个算法,而是看清这些权衡发生在哪里。

一个最小但完整的例子

应用程序读取文件时,常见代码只有一行:

ssize_t n = read(fd, buf, 4096);

这行代码背后至少涉及:

用户态函数
  -> 系统调用入口
  -> 文件描述符表
  -> 虚拟文件系统
  -> inode 与页缓存
  -> 必要时提交块 I/O
  -> 驱动配置设备和 DMA
  -> 中断通知完成
  -> 数据复制或映射到用户缓冲区
  -> 返回用户态

如果数据已经在页缓存中,磁盘可能完全不参与;如果数据不在缓存中,线程可能睡眠,让出 CPU,直到设备完成 I/O。这个例子把特权级、调度、虚拟内存、文件系统、设备驱动和并发同步连成了一条线,也是本笔记最后的综合案例。

全局心智模型

可以把操作系统看成许多受事件驱动的状态机。事件可能来自系统调用、时钟中断、缺页异常、设备中断或其他 CPU;内核根据当前状态修改数据结构,并决定下一步运行谁、映射什么、唤醒谁或向哪个设备发出请求。

事件到达
  -> 保存必要现场
  -> 进入内核并识别事件
  -> 检查权限与参数
  -> 更新内核对象和队列
  -> 可能阻塞、唤醒或调度
  -> 恢复某个执行流

这个模型比“内核是一个一直运行的大程序”更准确:没有用户工作或外部事件时,内核通常没有独立的业务要做;它主要在需要仲裁和服务时获得控制权。

内容结构

章节 主要问题
第1章 操作系统怎样把硬件变成受保护的服务?
第2章 怎样表示执行流,并在有限 CPU 上调度它们?
第3章 并发访问为何出错,怎样同步并避免死锁?
第4章 怎样让每个进程拥有独立、按需扩展的地址空间?
第5章 怎样持久保存数据并高效驱动慢速、异步的设备?
第6章 怎样建立安全边界,并在一台机器上运行多个系统视图?
第7章 怎样串起完整路径,并用工具验证自己的判断?

推荐阅读顺序

第一次学习建议按章节顺序阅读。第1章给出特权转换和事件入口;第2章解释“谁正在运行”;第3章解释“多个执行流怎样共享状态”;第4章说明地址如何翻译;第5章再把持久化数据和设备接入这套模型。

如果已有基础,可以围绕具体问题纵向阅读:

  • 调试程序卡住:进程状态 → 条件变量 → 死锁 → I/O 等待 → 诊断工具。
  • 调试内存抖动:地址空间 → 页表与 TLB → 缺页 → 页面置换 → 页缓存。
  • 调试文件读取慢:文件与 inode → 页缓存 → 块 I/O → 驱动与 DMA → 综合案例。
  • 理解容器:保护机制 → 虚拟内存 → 进程 → namespace/cgroup → 容器。

学习方法

操作系统的概念必须和可观察证据对应。看到“上下文切换昂贵”时,应继续问:保存了哪些状态,缓存和 TLB 是否受影响,工具能否测到切换频率?看到“读取文件”时,应继续问:命中页缓存了吗,线程是否睡眠,实际块设备 I/O 有多少?

建议对每个机制做三层追问:

  1. 抽象层:它向上提供了什么稳定接口?
  2. 机制层:内核和硬件实际维护了哪些状态?
  3. 策略层:当选择不唯一时,系统采用什么权衡?

机制回答“能不能做到”,策略回答“应该怎样选择”。页表是机制,页面置换算法是策略;定时器中断和上下文保存是机制,调度算法是策略。把二者分开,很多概念会清楚得多。

实验边界

笔记中的命令优先采用只读观测方式。stracepsvmstatiostatperf stat 等工具的可用性取决于操作系统、内核配置和权限;执行前应先确认目标进程和运行环境,不在生产系统上盲目进行高开销跟踪。

继续阅读前,可以先带着三个问题:

  • 为什么普通函数调用不能直接替代系统调用?
  • 为什么一个线程等待磁盘时,另一个线程还能使用 CPU?
  • 为什么程序访问的地址通常不是内存条上的真实物理地址?

后续章节会逐步建立回答这些问题所需的模型。