主题
字号
CHAPTER 05 ≈ 45 MIN READ

第三章:操作系统——看不见的管家


【上章回顾】上一章我们认识了 CPU 和 GPU——那个每秒能执行数十亿条指令的"大厨",和那个靠流水线并行取胜的"工厂车间"。但你有没有想过:谁来决定 CPU 什么时候算哪件事?谁来分配内存给不同的程序?谁让你可以一边听歌一边写代码,还能随时切换窗口?

【本章目标】这一章,我们来认识那个"看不见的管家"——操作系统。它不会出现在你的桌面上,却决定了你电脑上发生的一切。


章节引入:那 0.5 秒里谁在忙?

你早上打开电脑,双击 Chrome 图标,0.5 秒后浏览器出现了。

看起来平平无奇,对吧?

但如果你仔细想想,就会发现这里面藏着一连串问题:

这些问题,统统由同一个角色来回答——操作系统(Operating System,简称 OS)

预计阅读时间:45 分钟

读完本章你会理解


3.1 没有操作系统会怎样

想象一个没有服务员的餐厅

假设你去一家餐厅吃饭,推开门发现——没有服务员,没有领位,没有厨师长协调出菜顺序。厨房里站着几个大厨(CPU),仓库里有食材(硬盘),桌上有餐具(内存),但你不知道该坐哪儿,没人接单,厨师们也不知道先做哪桌的菜,收银台也没人。

这就是没有操作系统的计算机

硬件是有的:CPU 会算,内存能存,硬盘有数据。但没有一个角色来统筹协调,一切都是混乱的。

每个想用这台电脑的程序,都得自己想办法:

这在早期的计算机上确实是这样的。1940-1950 年代,程序员需要写"裸机程序",直接控制硬件,每个程序都得解决所有底层问题。后来人们意识到,这些重复劳动可以被抽象成一个公共的"中间层"——操作系统就诞生了。

操作系统是什么

操作系统是硬件和应用程序之间的一层软件,负责管理资源、提供服务、保护安全。

你的电脑上跑着的,可能是 Windows 11、macOS Sequoia,或者某个 Linux 发行版(比如 Ubuntu)。手机上是 Android 或 iOS。这些,都是操作系统。

操作系统做的事情可以分成三大类:

① 资源管理

操作系统管理着计算机的所有资源:CPU 时间、内存空间、磁盘读写、网络带宽。每个应用程序需要这些资源时,不能自己去抢,而是向操作系统申请。操作系统统一调度,防止冲突。

② 提供抽象

操作系统把复杂的硬件操作包装成简单的接口。你写代码时调用 open("file.txt") 就能打开文件——你不需要知道文件存在磁盘的第几个扇区、用的是什么文件系统。这就是操作系统提供的抽象(Abstraction)

③ 保护隔离

操作系统确保每个程序在自己的"沙箱"里运行,不能随意访问其他程序的内存,也不能绕过权限直接操作硬件。这就是为什么一个崩溃的程序通常不会把整个系统搞挂。

内核:操作系统的核心

操作系统本身也是软件,分层结构如下:

最核心的部分叫做内核(Kernel),它直接和硬件打交道。内核是权限最高的程序,它运行在"内核态(Kernel Mode)",可以执行任意硬件指令。

而普通应用程序(Chrome、微信、你写的 Python 脚本)运行在"用户态(User Mode)",权限受限,不能直接访问硬件。想访问硬件,就得通过**系统调用(System Call)**请求内核代劳。

你的应用程序(Chrome、微信、VSCode)
       ↕ 系统调用(System Call)
操作系统内核(进程管理、内存管理、文件系统、驱动)
       ↕ 驱动程序接口
硬件(CPU、内存、硬盘、显卡、网卡……)

这个分层设计非常关键:一方面保证了安全(应用程序不能随便搞坏系统),另一方面简化了开发(程序员不用懂硬件细节)。

❌ 常见误区澄清

"操作系统就是桌面界面" → ✅ 桌面(Windows 的 Explorer、macOS 的 Finder)只是操作系统的一个应用层组件,不是核心。Linux 服务器通常没有图形界面,但操作系统照常运行。

"重装系统就能修好所有问题" → ✅ 重装系统解决的是软件层面的问题(系统文件损坏、病毒、配置混乱)。硬件故障(内存坏块、硬盘损坏)重装没用。

🔧 动手观察:看看你的系统在干什么

Windows 用户:按下 Ctrl + Shift + Esc 打开任务管理器,切到"详细信息"选项卡。你会看到几十甚至上百个进程在运行,其中大部分你根本没有主动启动。这些都是操作系统本身的组件,以及各种后台服务。

macOS 用户:打开"活动监视器"(Spotlight 搜索 Activity Monitor),同样能看到大量系统进程。注意看"用户"列,很多进程的用户名是 root_windowserver,这些是操作系统本身的进程。

注意:看看有多少进程在运行,但 CPU 总使用率可能只有 5-10%。这说明操作系统调度非常高效——大部分进程在"等待",只有真正需要运行的才占用 CPU。

✓ 本节要点


3.2 进程与线程:任务管理器的秘密

你真的"同时"在做多件事吗?

打开任务管理器,你会看到 Chrome 占着 15% 的 CPU,同时 VS Code 也在跑,QQ 音乐还在播歌。

问题来了:你的 CPU 就 8 个核心,凭什么能同时服务这么多程序?

答案是:大多数情况下,它们并不是真的"同时"在跑——而是在极快速地轮流

这就像一个大厨(单核 CPU)在同时接待 10 桌客人。他并不是同时炒 10 道菜,而是:切一会儿 A 桌的菜,放下刀,去翻一翻 B 桌的锅,再去 C 桌打个酱油……因为切换速度极快(毫秒级),每桌客人都感觉"好像一直有人在做我的菜"。

这种机制叫做时间分片(Time Slicing),是现代操作系统多任务的基础。

进程:程序的运行实例

**程序(Program)**是存在硬盘上的可执行文件,比如 chrome.exe。 **进程(Process)**是程序被载入内存、正在运行的实体。

举个例子:Chrome 的程序文件只有一份,但你可以开两个 Chrome 窗口,它们是两个独立的进程。Word 的程序文件只有一份,但你打开三个文档,就有三个 Word 进程。

每个进程有自己的:

这就是"隔离"的意义:一个进程崩溃,不会直接连累其他进程。

硬盘上:chrome.exe(一份可执行文件)
                ↓ 双击打开
内存里:
  ┌──────────────────────────┐
  │ 进程 A:Chrome 窗口 1     │ ← 独立内存空间
  │   代码 | 数据 | 堆 | 栈   │
  └──────────────────────────┘
  ┌──────────────────────────┐
  │ 进程 B:Chrome 窗口 2     │ ← 另一块独立内存空间
  │   代码 | 数据 | 堆 | 栈   │
  └──────────────────────────┘

线程:进程内部的"工人"

进程是资源的拥有者,但真正在 CPU 上执行的单位是线程(Thread)

一个进程可以包含多个线程,这些线程共享同一块内存,但可以同时在不同 CPU 核心上运行

还是用餐厅类比:进程是"一桌客人的整个订单系统",线程是"处理这桌订单的具体工作人员"。一桌大型聚餐,可以同时有人上菜、有人斟酒、有人收碟——这就是多线程。

以你熟悉的场景为例:你在 Chrome 里打开一个网页,Chrome 内部大概会有这些线程同时工作:

这四件事真的可以同时发生,分配给不同的 CPU 核心,这就是为什么现代浏览器"感觉很流畅"。

进程调度:谁先上 CPU?

操作系统的**调度器(Scheduler)**决定哪个进程/线程下一个上 CPU 执行。

调度是个复杂的问题,这里说几个关键概念:

优先级(Priority):每个进程有优先级,高优先级的进程更频繁地获得 CPU 时间。操作系统的关键进程(比如处理键盘输入的进程)优先级极高,确保你按下按键立刻有反应。

时间片(Time Slice/Quantum):每次分配给一个进程的 CPU 时间通常是几毫秒到几十毫秒。时间片用完,调度器强制切换到下一个进程。

上下文切换(Context Switch):从一个进程切换到另一个进程时,操作系统需要保存当前进程的"状态"(所有寄存器的值、程序运行到哪了),载入下一个进程的状态。这个操作本身需要消耗 CPU 时间,是有开销的。

这就是为什么同时运行太多程序会让电脑变慢:不是因为"内存不够"(那是另一个问题),而是调度器需要做更多上下文切换,总开销增加。

进程状态:不是每个进程都在"跑"

一个进程在任意时刻,处于以下几种状态之一:

     创建 ──→ 就绪(Ready)──→ 运行(Running)
                ↑                    ↓
                └──────── 等待(Waiting)
                           (等 IO、等网络、等用户输入)
               
              运行 ──→ 结束(Terminated)

就绪:准备好了,等待 CPU 调度 运行:正在 CPU 上执行 等待(阻塞):在等某个事件(比如等硬盘读取完成),这时 CPU 可以去跑别的进程

这就是为什么操作系统能高效利用 CPU:当进程 A 在等硬盘数据(IO 操作,可能要几毫秒),调度器立刻把 CPU 给进程 B 用。你感觉两件事"同时"在发生,其实是 CPU 在"空隙时间"做了别的事。

❌ 常见误区澄清

"多核 CPU 就是多个 CPU 焊在一起" → ✅ 多核 CPU 是单芯片上集成多个运算核心,共享缓存和内存控制器,延迟比多 CPU 系统低得多。

"关掉后台进程一定能让电脑变快" → ✅ 只有实际消耗资源的后台进程才值得关。很多进程处于"等待"状态,CPU 占用几乎为 0,关掉它们效果有限。真正拖慢电脑的是高 CPU/内存占用的进程。

🔧 动手观察:进程状态的实时观察

打开任务管理器(Windows)或活动监视器(macOS),观察以下几点:

  1. CPU 列:大多数进程 CPU 占用是 0%,只有少数在用。这说明大部分进程在"等待"状态,没在跑。

  2. 内存列:每个进程都占用独立的内存。注意 Chrome——如果你开了很多标签页,它的内存占用可能惊人地高,因为现代 Chrome 给每个标签开一个进程(为了隔离稳定性)。

  3. Windows 用户额外操作:打开命令提示符,输入:

    tasklist /fo table | more

    可以看到所有进程的 PID(进程 ID)、内存占用等信息。

  4. 观察一个程序从打开到关闭:打开计算器,在任务管理器里找到它的进程。关掉计算器,看进程消失。这就是进程的完整生命周期。

✓ 本节要点


3.3 文件系统:电脑如何记住一切

"删除"之后数据去哪了?

你把一个文件拖进回收站,清空,文件"消失"了。

但是——数据真的没了吗?

答案是:没有,至少短期内没有。

这就要从文件系统说起。

文件系统是什么

硬盘,从物理层面看,是一大片可以存储 0 和 1 的空间,被划分成固定大小的"扇区(Sector)",传统上每个扇区 512 字节,现代硬盘通常是 4096 字节。

一个 2TB 的硬盘,有大约 4 亿个扇区。问题来了:你怎么知道哪些扇区属于哪个文件?

文件系统(File System)就是解决这个问题的。它是一套管理磁盘空间的规则和数据结构,让操作系统知道:

常见的文件系统有:

目录树:文件如何被组织

文件系统把文件组织成树形结构:根目录在最顶层,下面是各种文件夹,文件夹里可以再有文件夹,最末端是文件。

Windows 上:

C:\
├── Windows\
│   ├── System32\
│   └── ...
├── Program Files\
│   ├── Google\
│   │   └── Chrome\
│   └── ...
└── Users\
    └── 你的用户名\
        ├── Desktop\
        ├── Documents\
        └── ...

Linux/macOS 上:

/
├── etc/       ← 系统配置文件
├── home/      ← 用户目录(类似 Windows 的 C:\Users\)
│   └── mav/
├── usr/       ← 程序文件
├── var/       ← 日志、临时文件
└── dev/       ← 设备文件(对,在 Linux 里硬盘也是文件)

索引节点(inode):文件的"身份证"

在 Linux/macOS 的 ext4/APFS 文件系统中(NTFS 有类似机制),每个文件对应一个索引节点(inode,Index Node),记录着:

注意:inode 不存储文件名。文件名存在**目录项(Directory Entry)**里,目录项把"文件名"和"inode 编号"关联起来。

这个设计带来一个有趣的特性:同一个 inode 可以有多个文件名(硬链接)。在 Linux 里:

ln 原文件.txt 新名字.txt   # 创建硬链接

现在两个名字指向同一份数据。删除其中一个,数据还在,因为还有另一个名字指着同一个 inode。

"删除"文件到底发生了什么

现在可以回答开头的问题了。

当你删除一个文件时,操作系统做的事情是:

  1. 删除目录项:把"文件名 → inode"的映射删掉
  2. 标记空间为可用:把这些扇区标记为"空闲",可以被新数据覆盖

数据本身仍然在磁盘上,只是没有人"指向"它了,等待被新数据覆盖。

这就是为什么:

【建议配图】文件删除示意图:

删除前:
  目录表:[ "论文.docx" → inode #1024 ]
  inode #1024:大小 50KB,数据在扇区 8000-8100
  扇区 8000-8100:[实际文件数据 ████████]

删除后(快速删除):
  目录表:[ (空) ]             ← 目录项已删除
  inode #1024:(标记为未使用)   ← inode 释放
  扇区 8000-8100:[实际文件数据 ████████]  ← 数据仍然存在!

文件碎片:为什么硬盘越用越慢

机械硬盘(HDD)时代,有一个经典问题叫文件碎片化(Fragmentation)

磁盘使用一段时间后,空闲空间变得"零散"——这里有一块、那里有一块,不连续。当系统要存储一个大文件时,找不到足够大的连续空间,就只能把文件切成碎片存在磁盘各处。

这时候,读取这个文件需要磁头在磁盘上来回跳动,大幅降低读取速度。这就是为什么 Windows 要定期"磁盘碎片整理"——整理程序会把碎片重新排列成连续的块。

固态硬盘(SSD)没有这个问题——SSD 访问任何位置的速度几乎相同(没有机械磁头),所以碎片对 SSD 影响极小。不要对 SSD 做碎片整理,频繁写入会磨损闪存颗粒(当然 Windows 11 已经足够聪明,不会对 SSD 做传统碎片整理,而是做"优化")。

日志文件系统:如何防止断电损坏

如果你在写文件时突然断电,会发生什么?

可能写到一半的文件系统元数据(比如 inode、目录项)被破坏,导致文件系统不一致——有些扇区既没被标记为属于文件,也没被标记为空闲,成了"孤儿"。

现代文件系统(NTFS、ext4、APFS 等)用**日志(Journal/Log)**来解决这个问题:

在真正写入数据前,先在一个专门的"日志区"记录"我接下来要做什么"。如果操作中途失败,系统重启时检查日志,可以撤销未完成的操作,恢复一致状态。

这就是为什么你的 Windows 电脑如果强制断电,重启时会有一个"检查磁盘"的过程——它在检查日志,修复可能的不一致。

❌ 常见误区澄清

"格式化就能彻底删除数据" → ✅ 快速格式化只是重建目录结构,数据依然在磁盘上。要出售或报废硬盘,必须做多次覆写(HDD)或加密擦除/厂商安全抹除(SSD)。

"FAT32 是老的,就是坏的,别用" → ✅ FAT32 的主要限制是单文件最大 4GB 和分区最大 2TB,但它的优点是跨平台兼容性最强(Windows、macOS、Linux、嵌入式系统都支持)。对于 U 盘传小文件,FAT32 很合适;需要传大文件用 exFAT。

🔧 动手观察:查看你的硬盘分区和文件系统

Windows: 打开命令提示符(管理员),输入:

diskpart
list disk
list volume

可以看到你的硬盘分区信息,注意"文件系统"一列(NTFS、FAT32 等)和每个分区的大小。

或者右键点击"此电脑"→ 管理 → 磁盘管理,图形化查看分区布局。

macOS: 打开终端,输入:

diskutil list

可以看到所有磁盘和分区,包括文件系统类型(APFS、FAT32 等)。

观察重点:注意你的系统盘(通常是 C 盘或 Macintosh HD)用的是什么文件系统,大小是多少。思考:为什么 U 盘通常用 FAT32 或 exFAT,而不是 NTFS?

✓ 本节要点


3.4 驱动程序:硬件和软件的翻译官

为什么插上新设备,系统要"安装驱动"?

你买了一块新的声卡,插进电脑,Windows 弹出"正在安装驱动程序"的提示。

为什么不能直接用?

因为操作系统不认识你的声卡。

这个道理很直观:如果你说中文,对方说阿拉伯语,你们需要一个翻译。操作系统是"中文方",硬件是"阿拉伯语方",**驱动程序(Driver)**就是翻译官。

驱动程序是什么

每种硬件设备都有自己的通信协议——发什么指令代表"播放声音",发什么指令代表"调节音量",响应什么格式的数据。这套协议是硬件厂商定义的,不同厂商、不同产品的协议都不一样。

驱动程序是由硬件厂商(或开源社区)开发的软件,作用是:

操作系统发出通用指令          驱动程序翻译            硬件执行
"播放 44100Hz 立体声音频" ──→ 转换成 声卡专属指令 ──→ 声卡发出声音
"显示这帧游戏画面"        ──→ 转换成 GPU 专属指令 ──→ 显卡渲染画面
"读取 /dev/sda 的第 100 个扇区" → 转换成 硬盘控制指令 → 硬盘返回数据

驱动程序运行在内核态(第 3.1 节说过的),权限极高。这就是为什么一个写得烂的驱动程序会导致蓝屏(BSOD,Blue Screen of Death)——它在内核里崩溃,没有更高权限的程序来拯救它,整个系统就挂了。

为什么驱动会出问题

你可能遇到过:"换了系统之后声音没了"、"鼠标用着突然卡顿"、"显卡驱动更新之后游戏崩溃"。

这些问题有几种来源:

① 没有驱动 硬件太新,操作系统里没有预置对应的驱动。尤其是在刚装好系统时,部分硬件可能处于"无驱动"状态(设备管理器里显示黄色叹号)。解决方法:去厂商官网下载对应驱动,或更新 Windows Update(微软会通过 Windows Update 推送驱动)。

② 版本不匹配 驱动程序有版本号,操作系统也有版本号。如果操作系统大更新(比如 Windows 10 → Windows 11),部分老驱动可能不再兼容,因为操作系统的内核接口可能变了。

③ 驱动本身有 Bug 驱动程序不是完美的。显卡驱动的代码量可能有数百万行,有 Bug 很正常。这就是为什么显卡厂商(NVIDIA、AMD)会不断推出驱动更新——修 Bug、提升新游戏的性能。

④ 驱动冲突 两个驱动程序同时想控制同一块硬件,或者争用同一个系统资源,就可能发生冲突。这在 Windows 上历史上是个大问题,现在好多了。

通用驱动 vs 专用驱动

操作系统预置了很多通用驱动(Generic Driver),实现了基础功能。比如:

安装**专用驱动(Vendor Driver)**后,才能解锁全部功能:

这也解释了为什么刚装完系统,显示器分辨率"不对"——在没有正确显卡驱动的情况下,系统用的是通用显示驱动,无法驱动显卡的全部能力。

驱动签名:安全机制

因为驱动运行在内核态,权限极高,恶意驱动能做的破坏远超普通恶意软件。为此,Windows 引入了**驱动签名(Driver Signing)**机制:

驱动程序必须由微软认证的数字证书签名,才能在 64 位 Windows 上安装。没有签名(或签名无效)的驱动,默认不允许加载。

这就是为什么安装某些老设备的驱动时,会弹出"Windows 无法验证此驱动程序的发布者"的警告。

某些黑客攻击(如 2022 年的 NVIDIA 证书泄露事件)会利用偷来的合法证书给恶意驱动签名,绕过这个保护机制——所以一旦证书泄露,微软会吊销该证书。

Linux 的驱动生态:开源的两面性

Linux 的驱动情况有点特殊,值得单独说一下。

好消息:大量硬件驱动直接集成在 Linux 内核里,插上就能用(尤其是网卡、键鼠、常见 USB 设备)。Linux 内核有数千万行代码,其中很大比例是驱动程序。

坏消息:部分硬件(尤其是 NVIDIA 显卡、部分无线网卡、某些打印机)的 Linux 驱动是闭源的,质量参差不齐,或者根本没有 Linux 驱动。

NVIDIA 的 Linux 驱动一度是 Linux 用户的心病。2022 年,NVIDIA 宣布开放其 GPU 内核驱动模块的源代码,Linux 社区欢呼雀跃——这是一个重要的里程碑。但闭源的 userspace 驱动(如 CUDA)依然存在。

这也是很多 Linux 用户推荐 AMD 显卡的原因:AMD 在 Linux 上用的是完全开源的驱动(amdgpu),由 AMD 官方维护且合并进了内核主线,兼容性和稳定性更好。

macOS 的驱动:紧耦合的优雅

macOS 的驱动情况则完全不同。

苹果对 Mac 的硬件有严格控制——只有特定的芯片、特定的组件才会出现在 Mac 上。苹果自己为这些硬件写驱动,预置在 macOS 里,用户几乎感知不到驱动的存在。

你买了一台 MacBook,开机,所有硬件立刻工作,不需要安装任何驱动。这是因为苹果的软硬件一体化策略:我控制硬件,我控制软件,我来保证两者完美配合。

代价是:如果你想用一块苹果不支持的显卡,几乎没有办法——苹果不提供驱动,第三方也没有权限写内核级驱动。这是 Mac 封闭性的体现。

对于 Apple Silicon(M1/M2/M3/M4 系列),这种一体化更进一步:CPU、GPU、内存、IO 都在同一颗芯片上,驱动层面的优化空间更大,macOS 能把每个硬件模块的性能发挥到极致。

❌ 常见误区澄清

"驱动越新越好,应该一直更新" → ✅ 驱动更新可能引入新 Bug,尤其是显卡驱动。如果目前系统稳定、游戏运行正常,没有必要追着最新版驱动跑。"不坏就别修"是有道理的。遇到特定问题(游戏崩溃、性能倒退)再针对性地更新或回退驱动。

"设备管理器里没有叹号就表示驱动完全没问题" → ✅ 没有叹号说明驱动加载成功,但不代表驱动工作正常。驱动可能加载了但有功能缺失。实际测试(声音能不能播放、显卡能不能渲染 3D)才是最终验证。

🔧 动手观察:查看你的驱动状态

Windows: 右键"此电脑"→ 属性 → 设备管理器(或者在搜索栏搜"设备管理器")。

展开各个分类,观察:

特别查看:

# 命令行查看所有已安装驱动(管理员权限)
driverquery /fo table

macOS: 打开"系统信息"(苹果菜单 → 关于本机 → 更多信息 → 系统报告),在左侧的"软件"分类下找"扩展",可以看到所有加载的内核扩展(kext),包括驱动。

✓ 本节要点


3.5 系统调用:应用程序和内核的边界(补充节)

一次"请求"的旅程

你在 Python 里写了一行:

with open("data.csv", "r") as f:
    content = f.read()

表面上这就是"打开文件,读取内容"。但在操作系统层面,发生了什么?

这里实际上触发了系统调用(System Call,简称 syscall)

为什么需要系统调用

我们在 3.1 节说过:应用程序运行在"用户态",权限受限;内核运行在"内核态",权限最高。

用户态的程序不能直接访问硬件(读硬盘、发网络包、申请更多内存……),必须通过一套特定的接口"请求"内核代劳。这套接口就叫系统调用。

这个设计是故意的

系统调用的过程(精简版)

当 Python 执行 open("data.csv", "r") 时:

① Python 解释器调用 C 标准库的 fopen()
② fopen() 内部调用操作系统提供的 open() 系统调用
③ CPU 执行特殊指令(x86 上是 syscall 指令)切换到内核态
④ 内核接管,检查:文件存在吗?你有权限读吗?
⑤ 内核通过文件系统找到文件,通过硬盘驱动读取数据
⑥ 数据放入内核缓冲区,复制到用户态内存
⑦ 内核返回,CPU 切回用户态,控制权交回 Python
⑧ Python 的 f.read() 拿到文件内容

整个过程对你透明,但每次"用户态 → 内核态 → 用户态"的切换是有开销的。这就是为什么高性能程序(比如数据库、游戏服务器)会想办法减少系统调用次数——比如一次性读大块数据,而不是一个字节一个字节地读。

Linux 上的系统调用一瞥

Linux 有几百个系统调用,按功能分类:

类别 代表系统调用 说明
文件 I/O open, read, write, close 操作文件
进程 fork, exec, wait, exit 创建/管理进程
内存 mmap, brk 申请/释放内存
网络 socket, connect, send, recv 网络通信
信号 kill, signal 进程间信号
时间 gettimeofday, nanosleep 时间相关

🔧 动手:用 strace 看系统调用(Linux/macOS)

如果你有 Linux 环境(比如 WSL 或 SSH 到服务器),可以用 strace 命令追踪一个程序的系统调用:

# 追踪 ls 命令的系统调用
strace ls

# 只显示系统调用的统计信息
strace -c ls

# 追踪文件操作相关的系统调用
strace -e trace=file ls

输出会很多,但你能看到 openat(打开文件)、read(读取)、write(写输出)等系统调用,以及每次调用的参数和返回值。

这是真正看到"底层发生了什么"的最直接方式。

macOS 上对应的工具是 dtruss(需要关闭 SIP)或 ktrace

✓ 本节要点


3.6 中断:硬件怎么"打断"CPU

键盘按下去,CPU 怎么知道?

你按下键盘上的 "A",屏幕上出现了 "a"。

这个过程看起来理所当然,但仔细想想有个问题:CPU 在一直执行代码,它怎么知道你按了键?它总不能一直"查"键盘有没有按键吧?

答案是:它不用查。键盘会主动通知 CPU。

这个机制叫做中断(Interrupt)

什么是中断

中断是硬件设备向 CPU 发出的一个信号,意思是:"我有事要汇报,请暂停你正在做的事,先来处理我。"

CPU 正在执行进程 A 的代码
          ↓
键盘发出中断信号(IRQ,中断请求)
          ↓
CPU 保存当前状态(寄存器值、程序计数器)
          ↓
CPU 跳转到对应的"中断处理程序(Interrupt Handler)"
          ↓
中断处理程序读取键盘缓冲区,获取按键信息,传给操作系统
          ↓
CPU 恢复之前的状态,继续执行进程 A

整个过程发生在微秒级别,进程 A 几乎感知不到被打断了。

中断的分类

硬件中断:由硬件设备触发

软件中断(也叫异常/陷阱):由软件或 CPU 本身触发

定时器中断:调度器的"心跳"

有一个特别重要的中断值得单独讲:定时器中断(Timer Interrupt)

计算机里有一个硬件定时器(在现代 PC 上是 HPET 或 APIC 定时器),它会以固定频率(通常是每秒 100 到 1000 次)发出中断。

每次定时器中断触发,操作系统调度器就会运行一次,决定:

调度器本质上是由定时器中断驱动的。没有这个定时器,调度器永远没机会运行,某个进程霸占 CPU 就没人能制止它。

这也解释了 Linux 内核的一个参数 CONFIG_HZ:它决定定时器中断的频率,比如 HZ=250 表示每秒 250 次定时器中断,每次调度器都有机会运行。

中断和系统调用的区别

这两个概念容易混淆,区分一下:

中断 系统调用
发起方 硬件(或 CPU 异常) 用户程序主动请求
时机 随时,异步 程序执行到特定指令时
目的 通知 CPU 某事件发生 请求内核提供服务
举例 网卡收到数据包、键盘按键 open 文件、malloc 内存

简单说:中断是硬件找操作系统,系统调用是程序找操作系统

✓ 本节要点


章节综合:把进程、文件、驱动串起来

我们来做一个完整的场景推演,把本章所有概念串联起来。

场景:你在 Windows 上打开一个 MP3 文件,用 Foobar2000 播放。

第一步:你双击 MP3 文件图标

Windows 的 Shell(Explorer.exe,一个进程)检测到你的双击操作。操作系统查找 .mp3 扩展名关联的程序,找到 Foobar2000。

第二步:操作系统创建 Foobar2000 进程

内核为 Foobar2000 分配一块独立内存空间,把 foobar2000.exe 从磁盘(通过文件系统 → NTFS 驱动 → 硬盘驱动)载入内存。进程创建完成,进入就绪状态。

第三步:调度器把 CPU 给 Foobar2000

Foobar2000 进程开始执行。它发起系统调用,请求操作系统打开那个 MP3 文件。操作系统通过文件系统找到对应的 inode,找到数据所在的磁盘扇区,通过硬盘驱动程序读取数据,把 MP3 的二进制数据放进内存。

第四步:Foobar2000 解码音频

Foobar2000 调用解码库,把 MP3 格式的压缩音频数据,解码成原始 PCM 波形数据(一堆数字,表示每个时刻的振幅)。

第五步:把音频传给声卡

Foobar2000 调用 Windows 音频 API(WASAPI 或 DirectSound),把 PCM 数据传给操作系统的音频子系统。音频子系统通过声卡驱动程序,将数据写入声卡缓冲区,声卡把数字信号转成模拟信号,耳机/音箱发出声音。

整个过程涉及了

这就是为什么操作系统被称为"看不见的管家"——每一次文件操作、每一个音符、每一次鼠标点击,背后都是操作系统在默默协调。


3.7 权限与安全:操作系统的"警卫"

为什么有些程序要"管理员权限"?

你在 Windows 上安装软件,弹出"是否允许此应用更改设备?"的 UAC(用户账户控制)提示。你在 Linux 上跑某个命令,系统说"Permission denied"。你在 macOS 上第一次让某个 App 访问相机,弹出授权提示。

这些都是操作系统的权限控制系统在工作。

用户和权限的基础概念

Unix/Linux/macOS 的权限模型

Unix 系的操作系统(Linux、macOS 都属于这一脉)有一套经典的权限模型:

每个文件/目录都有三组权限:

每组又有三种权限:

# ls -l 的输出示例:
-rwxr-xr-x  1 mav  staff  4096 Mar 28 10:00 my_script.sh
# ↑       ↑   ↑    ↑
# 权限    所有者  所属组
#
# 权限字段解读:-rwxr-xr-x
# 第1位 - :普通文件(d 是目录,l 是符号链接)
# 第2-4位 rwx:所有者(mav)可读、可写、可执行
# 第5-7位 r-x:所属组(staff)可读、不可写、可执行
# 第8-10位 r-x:其他人可读、不可写、可执行

超级用户 root

在 Unix 系统里,有一个特殊用户叫 root,权限不受任何限制——可以读任何文件、写任何文件、杀任何进程。

root 权限非常危险,日常使用不应该以 root 登录。这就是为什么 Linux 上很多操作要 sudo(Substitute User DO)临时提权,macOS/Ubuntu 默认禁止直接以 root 登录。

Windows 的权限模型

Windows 的权限系统更复杂,基于 ACL(访问控制列表)。每个文件/目录可以给特定用户或用户组设置精细的权限。

Windows 的 UAC(用户账户控制)则是另一层保护:即使你以管理员账户登录,程序默认也以"标准用户"权限运行。只有明确需要管理员权限时(安装软件、修改系统设置),才会弹出 UAC 提示要求确认。

这个设计来自一个教训:Windows XP 时代,用户默认以管理员运行,恶意软件可以轻易感染系统。Vista 引入 UAC 后,安全性大幅提升(尽管当时被嫌烦)。

沙箱:更严格的隔离

进程之间已经有了内存隔离,但操作系统的权限系统是"文件/资源级"的,粒度有限。

现代操作系统还提供了**沙箱(Sandbox)**机制:把一个程序限制在一个更小的权限范围内,即使它被黑了,也无法影响系统其他部分。

❌ 常见误区澄清

"以管理员运行所有程序更方便" → ✅ 管理员权限意味着程序可以做任何事,包括破坏系统或安装恶意软件。普通权限够用时就不要提权——这是"最小权限原则"(Principle of Least Privilege)。

"chmod 777 能解决所有权限问题"(Linux 用户) → ✅ chmod 777 给所有人读/写/执行权限,这很危险,尤其是对 Web 服务器的文件。正确做法是找到真正需要的最小权限,而不是无脑 777。

🔧 动手:查看文件权限

Linux/macOS

# 查看当前目录的文件权限
ls -la

# 查看自己是谁
whoami
id

# 查看一个文件的所有者和权限
stat 文件名
# 创建一个文件,修改权限,体验权限控制
touch test.txt           # 创建文件
ls -la test.txt          # 查看默认权限(通常是 -rw-r--r--)
chmod 000 test.txt       # 移除所有权限
cat test.txt             # 尝试读取——Permission denied
chmod 644 test.txt       # 恢复权限(所有者读写,其他人只读)
cat test.txt             # 现在可以读了

Windows: 右键任意文件 → 属性 → 安全选项卡,可以看到每个用户/组对这个文件的具体权限(完全控制、修改、读取等)。

✓ 本节要点


3.8 操作系统的启动:从按下电源键到桌面

那 30 秒里发生了什么

按下电源键,30 秒后桌面出现了。

这 30 秒对于很多人来说是个黑盒。让我们拆开看看。

启动流程全景

现代 PC 的启动过程大致分这几个阶段:

按下电源键
    ↓
① 上电自检(POST)
    ↓
② 固件(BIOS/UEFI)初始化
    ↓
③ 引导加载程序(Bootloader)
    ↓
④ 操作系统内核加载
    ↓
⑤ 用户空间初始化
    ↓
登录界面 / 桌面

第一步:上电自检(POST,Power-On Self Test)

你按下电源键,主板的固件(烧录在主板上的芯片里)首先运行。

POST 会检查:CPU 能用吗?内存能用吗?有没有显卡?基本硬件是否正常?

如果有问题,你会听到"哔哔"声(哔声代码,每种故障对应不同的声音模式),或者屏幕上出现错误信息。现代电脑通常 POST 非常快,你几乎感知不到。

第二步:BIOS/UEFI 初始化

**BIOS(Basic Input/Output System)**是老一代的固件标准,1970 年代就有了。 **UEFI(Unified Extensible Firmware Interface)**是 BIOS 的现代替代,2000 年代逐渐普及,现在几乎所有新电脑都用 UEFI。

固件的主要工作:

UEFI 比 BIOS 强在哪里?

Secure Boot 是一个安全特性:UEFI 固件只加载经过数字签名的引导程序,防止恶意软件在操作系统加载前就植入。这就是为什么在有些电脑上安装 Linux 时,需要先在 UEFI 里关掉 Secure Boot(或者用支持 Secure Boot 的 Linux 发行版,比如 Ubuntu)。

第三步:引导加载程序(Bootloader)

固件找到启动设备后,加载并运行引导加载程序

Windows 的引导加载程序:Windows Boot Managerbootmgr),存在 EFI 系统分区里 Linux 的主流引导加载程序:GRUB(GRand Unified Bootloader) macOS:苹果的专属引导程序,在 Apple Silicon 上集成在 iBoot 里

引导加载程序的任务:

第四步:内核初始化

内核拿到控制权后,开始进行一系列初始化:

Linux 上,第一个用户空间进程是 systemd(或老系统上的 init),PID(进程 ID)为 1。它是所有其他进程的"父亲",负责按顺序启动系统服务。

Windows 上对应的是 smss.exe(Session Manager Subsystem),然后是 wininit.exe,再启动各种 Windows 服务。

第五步:用户空间初始化

systemd(Linux)或 Windows 服务管理器启动各种服务:

最终,显示管理器启动,桌面环境(GNOME、KDE、Windows Shell……)加载,你看到了熟悉的桌面。

这整个过程,现代 SSD 加持下可以在 10 秒内完成。而在机械硬盘时代,同样的过程可能要 60 秒——因为内核和系统文件分散在磁盘各处,机械磁头需要大量寻道时间。

【建议配图】启动流程时序图:

时间轴(以 SSD 系统为例,约 10-15 秒):
0s    按下电源键
0-1s  POST 完成
1-2s  UEFI 初始化、寻找启动设备
2-4s  Windows Boot Manager 加载内核
4-8s  Windows 内核初始化、驱动加载
8-12s 服务启动(网络、音频、Shell……)
12s   登录界面出现

❌ 常见误区澄清

"开机密码能阻止别人读取我的硬盘数据" → ✅ 开机密码(Windows 登录密码)只是阻止别人登录操作系统,不能保护硬盘数据。把硬盘拔出来接到另一台电脑,或者用 U 盘引导 Linux,就能直接访问硬盘上的所有文件(除非开了 BitLocker 全盘加密)。要真正保护数据,必须用全盘加密(Windows BitLocker、macOS FileVault)。

"快速启动/休眠和关机是一样的" → ✅ Windows 的"快速启动"(默认开启)实际上是混合休眠:关机时把内核状态保存到磁盘(hiberfil.sys),下次开机直接恢复,跳过内核初始化阶段,所以快。但这意味着内核实际上没有被重新加载,有时候驱动更新需要"完全关机"(按 Shift + 关机,或者先禁用快速启动)才能生效。

🔧 动手:查看启动时间分析

Windows

# 查看最近一次开机的启动事件日志
eventvwr.msc
# → Windows 日志 → 系统 → 筛选"事件 ID 12"(系统启动)和"事件 ID 6005"(Event Log 服务启动)

# 或者使用 Performance Monitor 分析启动性能
perfmon /rel

也可以直接搜索"启动时间",Windows 10/11 的任务管理器 → 性能 → 右下角有"正常运行时间"。

Linux

# 分析 systemd 启动时间
systemd-analyze

# 查看每个服务的启动时间
systemd-analyze blame

# 生成启动过程的 SVG 时序图
systemd-analyze plot > boot.svg
# 然后用浏览器打开 boot.svg,直观看到哪个服务拖慢了启动

macOS

# 查看上次启动时间
last reboot | head -5

# 通过 Console.app(控制台)查看启动日志
# 过滤 "kernel" 相关条目可以看到内核初始化序列

✓ 本节要点


深入一点:操作系统的内存管理(补充阅读)

这一小节是补充内容,稍微深入,可选阅读。

虚拟内存:内存"变大"的魔法

在第一章,我们提到了虚拟内存(Virtual Memory)。现在可以更好地理解它了。

每个进程"看到"的内存地址,不是真实的物理内存地址,而是虚拟地址。操作系统(通过 CPU 中的内存管理单元,MMU)负责把虚拟地址翻译成物理地址。

【建议配图】虚拟地址到物理地址的映射:

进程 A 的虚拟地址空间:       物理内存:
0x00000000 ──────────────→  0x12340000 (进程 A 的代码)
0x00001000 ──────────────→  0x56780000 (进程 A 的数据)
...

进程 B 的虚拟地址空间:
0x00000000 ──────────────→  0x89AB0000 (进程 B 的代码,和 A 在不同物理位置)
...

两个进程都"以为"自己从地址 0 开始,互不干扰。这就是内存隔离的实现原理。

当物理内存不够用时,操作系统会把暂时用不到的内存页交换(Swap)到硬盘上,腾出物理内存给当前需要的进程。被换出到硬盘的部分,叫做页面文件(Windows)Swap 分区(Linux)

代价是:硬盘比内存慢几千倍,大量 Swap 会让系统变得极其缓慢——你可能听说过"内存不够,电脑卡死",本质就是 Swap 到硬盘,然后 CPU 大量时间在等硬盘。

这就是为什么内存容量很重要:内存越大,越不需要 Swap,系统越流畅。

内存分配:malloc 背后的故事

当你的程序需要内存(比如 Python 里创建一个 numpy 数组),会调用内存分配函数(C 里是 malloc,Python/C++ 有更高层的封装)。

这个函数最终会发出 mmapbrk 系统调用,让操作系统在这个进程的虚拟地址空间里划出一块区域。

注意:操作系统给的是虚拟内存,不一定立刻对应真实的物理内存。只有当你真正去读写那块内存时,操作系统才会为你分配物理内存页(这叫按需分页,Demand Paging)。

这就是为什么有时候你分配了 4GB 内存,但任务管理器显示程序的实际内存占用只有 500MB——因为程序还没访问那 3.5GB,操作系统还没真正给。


3.9 实战:操作系统的日常维护与调优

这一节是实用向的,帮你把本章的理论和日常使用联系起来。

理解为什么电脑"变慢"了

用了一两年的电脑,感觉比刚买时慢。这是真的吗?为什么?

根据本章的知识,可以从几个角度分析:

① 后台进程越来越多

每次安装软件,都可能顺带安装了一些"开机自启动"的后台程序(更新助手、云盘同步、输入法助手……)。这些程序常驻后台,占用内存,和其他进程竞争 CPU。

诊断方法:任务管理器 → 启动应用,看有哪些开机自启动项,禁用不需要的。

② 内存不够,频繁 Swap

随着使用习惯改变,你同时打开的程序越来越多。如果物理内存不足,操作系统开始把内存数据交换到硬盘,速度断崖式下降。

诊断方法:任务管理器 → 性能 → 内存,看"已提交"是否远超物理内存,或者看磁盘利用率是否异常高(说明在 Swap)。

③ 磁盘空间不足(机械硬盘尤其明显)

机械硬盘的碎片化,以及磁盘将满时文件系统的工作效率下降,都会影响性能。

SSD 则有另一个问题:接近写满时,NAND 闪存的写入放大效应加剧,性能下降。建议 SSD 保留 10-15% 的空闲空间。

④ 软件本身越来越重

Chrome 2015 年用 200MB 内存,2024 年用 1GB 不罕见。软件功能增加了,资源占用也增加了。电脑没变慢,是软件变重了。

这种现象有个戏谑的名字叫 Wirth's Law(沃斯定律):"软件的速度每隔 18 个月就会慢一半"——和摩尔定律(硬件性能翻倍)反着来。

内存使用诊断:一次完整的分析

打开任务管理器,切到"内存"性能图,认识这几个数字:

总计:16.0 GB       ← 你安装的物理内存
使用中:8.2 GB      ← 正在被进程使用的内存(代码+数据)
可用:5.1 GB        ← 操作系统随时可以分配的内存
已提交:12.4 / 22.0 GB  ← 已分配的虚拟内存 / 最大可用虚拟内存
已缓存:2.7 GB      ← 系统用来缓存磁盘数据的内存(可以被回收)

"已提交"超过物理内存:说明操作系统开始使用 Swap(页面文件),性能会下降 "可用"很低(低于 1GB):内存可能不够,关一些不用的程序 "已缓存"占很多:这是正常的!操作系统会把闲置内存用来缓存磁盘数据,加速文件访问。"空余内存是浪费的内存"——这些缓存随时可以被回收

进程管理:找到消耗资源的罪魁祸首

任务管理器使用技巧:

① 按 CPU 排序:找到 CPU 占用高的进程
   - 正常情况下大部分进程应该是 0%
   - 如果某个进程长时间 CPU 100%,说明它在死循环或者处理大量数据

② 按内存排序:找到内存占用大的进程
   - Chrome 通常排前几名(每个标签一个进程)
   - "系统"进程内存高可能是系统缓存(正常)

③ 右键进程 → "转到文件位置"
   - 可以查看这个进程对应的可执行文件在哪里
   - 帮助判断是否是可疑的后台进程

④ 右键进程 → "结束任务"
   - 强制终止进程(相当于给进程发 SIGKILL 信号)
   - 注意:结束任务会丢失该进程未保存的数据

Linux 下的进程管理工具

如果你用 Linux(或者通过 SSH 管理服务器),有几个必备工具:

# top:实时显示进程列表(类似任务管理器)
top
# 常用快捷键:q 退出,k 杀进程,M 按内存排序,P 按 CPU 排序

# htop:top 的增强版(需要安装:sudo apt install htop)
htop
# 更好看,支持鼠标,可以直接点击排序列

# ps:列出进程快照
ps aux              # 显示所有进程
ps aux | grep chrome # 过滤特定进程

# kill:发送信号给进程
kill 1234           # 给 PID 1234 的进程发 SIGTERM(优雅终止)
kill -9 1234        # 发 SIGKILL(强制杀死,进程无法捕获)
killall chrome      # 按名字杀所有同名进程

# nice/renice:调整进程优先级
nice -n 10 ./heavy_script.sh  # 以低优先级运行
renice -n 5 -p 1234           # 调整已运行进程的优先级

在 Robogame 项目的嵌入式开发里,你虽然用的是 ESP32(裸机或 FreeRTOS),但这些 Linux 进程管理知识在 Raspberry Pi 或 Linux 服务器上同样适用。

磁盘健康监测

硬盘是会"死"的,尤其是机械硬盘。及时发现硬盘问题,可以在数据丢失前备份。

SMART(Self-Monitoring, Analysis and Reporting Technology) 是硬盘自带的健康监测系统,记录了各种硬件指标(读写错误次数、重分配扇区数、通电时间等)。

# Linux:查看硬盘 SMART 信息(需要安装 smartmontools)
sudo smartctl -a /dev/sda

# 关键指标:
# Reallocated_Sector_Ct:重分配扇区数(坏块被备用扇区替换)
#   → 如果这个数字在增加,硬盘正在损坏
# Pending_Sector_Count:等待重分配的扇区数
#   → 大于 0 说明有读取不稳定的扇区
# Power_On_Hours:总通电时间(评估使用寿命)

Windows:可以用 CrystalDiskInfo(免费软件,日本开发者出品,非常可靠)查看硬盘 SMART 信息,界面直观,绿色是健康,黄色/红色要警惕。

SSD 特有指标

✓ 本节要点


操作系统家族小史(拓展阅读)

如果你对操作系统的发展历史感兴趣,这里有一个极简版:

1960s:大型机时代,"操作系统"主要是批处理系统,一次只能运行一个程序。

1969-1973:Unix 诞生(AT&T 贝尔实验室,Ken Thompson & Dennis Ritchie)。Unix 的多任务、多用户、树形文件系统等设计奠定了现代 OS 的基础。C 语言最初就是为了重写 Unix 而发明的。

1981:MS-DOS 发布(微软为 IBM PC 开发)。单任务,命令行,是 Windows 的前身。

1984:Mac OS 1.0 发布,第一个商业成功的图形用户界面操作系统(图形界面概念来自施乐 PARC 研究院)。

1991:Linus Torvalds 发布 Linux 内核 0.01,一个芬兰大学生的课余项目,后来成为服务器领域和 Android 的基石。

2001:Mac OS X 发布,苹果重新设计的 Unix-based 操作系统,基于 BSD(另一个 Unix 分支)。Windows XP 同年发布。

2007:iPhone 发布,iOS(基于 Mac OS X)让移动操作系统进入主流。Android 则基于 Linux 内核。

2020至今:Apple Silicon 的推出,重新定义了桌面操作系统和硬件的关系。


章节结尾:联系与展望

本章与前后章节的联系

上一章我们讲了 CPU 和 GPU 的计算能力。这一章告诉了你:这些计算能力是如何被操作系统协调分配的——进程调度器决定每颗 CPU 核心在什么时候跑什么任务;内存管理器决定 GPU 和 CPU 如何共享内存(在 Apple Silicon 上这一点尤为明显)。

下一章我们会看到:主板、芯片组是如何把 CPU、内存、存储、GPU、网卡这些部件物理连接起来的,以及 Apple 的 SoC 设计如何把本章讲的很多概念重新整合。

第一章遗留的问题:我们提过"虚拟内存",这章终于解释清楚了——虚拟内存是操作系统的内存管理机制,不是"额外的物理内存",而是把磁盘空间借用为内存的代偿方案,性能代价高昂。


扩展资源

如果你想更深入地了解操作系统,这里有几个真实存在的资源:

视频推荐

文章/书籍推荐


【下章预告】现在你理解了计算机里"管家"的工作——调度进程、管理文件、翻译驱动。但这些部件是如何被物理连接在一起的?主板上那些插槽、芯片组、总线是干什么的?为什么 Apple M 系列芯片一发布就让 PC 厂商紧张?下一章,我们把拼图的最后几块补上。


本章写作时间:2024 年末。涉及的产品价格和参数如有变化,请以当时市场价格为准。