Appearance
操作系统与硬件
- 写作时间:
2026-03-23 - 当前字符:
5697
前言已经交代了整本书的路线:我们会一边对照 Linux 内核源码理解操作系统原理,一边用 Zig 逐步做出真正可运行的系统组件。现在进入基础与概览这一章,我们先不急着讨论某个局部机制,而是先把整台机器和操作系统在其中的位置看清楚。
这一章会按 4 个问题往前推:
- 操作系统到底是什么,它在管理什么硬件
- 操作系统自己怎么组织、为什么会演化成今天这样
- 用户态和内核态之间的权限边界是什么
- 用户程序怎样通过系统调用跨过这条边界
本课先回答第一个问题:操作系统是什么,以及它面对的是一台怎样的机器。
先用 just linux 进入一个最小的 Linux 容器,再运行 ps aux | head:
$ just linux
root@hands-on-os:/project# ps aux | head
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 289680 7216 pts/0 Ss 16:48 0:00 bash
root 104 75.0 0.0 291480 7112 pts/0 R+ 16:51 0:00 ps aux
root 105 25.0 0.0 287520 4572 pts/0 S+ 16:51 0:00 head这个容器非常干净,但哪怕只是为了执行一个简单命令,也已经同时出现了 bash、ps、head 这几个进程。把视角换到一个完整发行版,列表会立刻膨胀到几十甚至上百个进程。它们共用 CPU、内存和 I/O 设备,却又不能互相踩坏。谁在管理这件事?
答案是操作系统。
操作系统
操作系统(Operating System, OS)是管理计算机硬件资源、并为应用程序提供服务接口的系统软件。
这个定义包含两层职责。第一层是资源管理:CPU 时间怎么分配给各个进程,物理内存怎么划分,磁盘空间怎么组织。开篇的 ps aux 只展示了一个极简容器中的 3 个进程,但这已经足够说明问题:只要系统里存在多个进程,操作系统就必须决定谁先运行、谁在等待、谁可以访问哪些资源。换到完整 Linux 系统时,这种协调只会变得更复杂。
第二层是抽象:应用程序不需要知道硬盘是 SSD 还是机械盘,不需要知道网卡的具体型号。操作系统把硬件差异藏在统一的接口背后。对程序来说,写文件就是调用 write(),不管底层是什么设备。读网络数据也是 read(),不管底层是以太网还是 WiFi。“一切皆文件”是 UNIX 系统最核心的抽象思想之一:文件、设备、管道、网络连接,都可以通过文件描述符访问,使用相同的 read/write/close 接口。
操作系统有多无处不在?用 strace 只保留最关键的几类系统调用,追踪一个最简单的命令 ls:
$ strace -e trace=execve,openat,getdents64,write ls course/basics/code 2>&1 >/dev/null | grep -E 'execve|/etc/ld.so.cache|/proc/filesystems|course/basics/code|getdents64|write|exited'
execve("/usr/bin/ls", ["ls", "course/basics/code"], 0xffffd7910d08 /* 7 vars */) = 0
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/proc/filesystems", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "course/basics/code", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
getdents64(3, 0xaaaafb341640 /* 3 entries */, 32768) = 80
getdents64(3, 0xaaaafb341640 /* 0 entries */, 32768) = 0
write(1, "hello.c\n", 8) = 8
+++ exited with 0 +++这里没有把 ls 的所有系统调用都展开,只保留了最能说明主线的几类,但已经足够看出操作系统无处不在:execve 启动程序本身,openat 打开缓存和目标目录,getdents64 读取目录项,write 把结果送到标准输出。即使是最简单的命令,背后也有一串与操作系统的交互。
我们来写一个最小的 C 程序,让它通过操作系统在终端打印一行文字:
c
#include <unistd.h>
int main(void) {
write(1, "hello\n", 6);
return 0;
}$ gcc course/basics/code/hello.c -o /tmp/hello && /tmp/hello
hellowrite(1, "hello\n", 6) 告诉操作系统:“往文件描述符 1(标准输出)写入 6 个字节”。程序自己不知道标准输出连接的是终端模拟器、串口还是网络套接字。操作系统负责把这 6 个字节送到正确的地方。这就是抽象的力量:程序只需要知道“写到 fd 1”,其余的细节由操作系统处理。
如果没有操作系统会怎样?
假设没有操作系统,每个程序都要自己管理硬件。一个文本编辑器需要自己写磁盘驱动来保存文件,自己写显卡驱动来显示文字,自己写键盘驱动来接收输入。
更严重的问题是多个程序的共存。如果两个程序同时运行,它们怎么分配 CPU 时间?怎么避免一个程序覆盖另一个程序的内存?怎么防止一个程序直接读取另一个程序的数据?
没有操作系统,每个程序员都必须重新实现这些基础功能,而且程序之间没有任何隔离保护。一个程序的 bug 可以摧毁整个系统中所有正在运行的程序。操作系统的存在让程序员可以专注于业务逻辑,把硬件管理和资源隔离交给一个经过充分测试的公共层。
计算机体系结构
操作系统管理的是硬件,所以“操作系统是什么”这个问题不能只停在软件接口层面,还要继续追问:它面对的是一台怎样的机器?
处理器组织。 最简单的计算机只有一个处理器核心,所有程序轮流使用这一个核心。现代计算机通常有多个核心,它们的组织方式直接影响操作系统的调度策略。用 lscpu 可以先抽出几项和处理器拓扑最相关的字段:
$ lscpu | grep -E '^Architecture|^CPU\(s\):|^Thread\(s\) per core|^Core\(s\) per socket|^Socket\(s\):'
Architecture: x86_64
CPU(s): 10
Thread(s) per core: 1
Core(s) per socket: 10
Socket(s): 1这个容器当前看到的是 1 个 socket、10 个 core、每个 core 1 个 thread,总共 10 个逻辑 CPU。具体数字会随着宿主机和容器配置变化,但 socket、core、thread 这几个字段的含义不变,操作系统的调度器正是围绕这套处理器拓扑来分配工作。
多核处理器有两种主要的组织方式:
对称多处理(Symmetric Multiprocessing, SMP) 是所有核心共享同一块物理内存,每个核心访问任意内存地址的延迟相同。大多数消费级多核 CPU 都是 SMP 架构。操作系统的调度器可以把进程调度到任意核心上运行,不需要关心内存位置。
非一致性内存访问(Non-Uniform Memory Access, NUMA) 是每个处理器(或处理器组)有自己的本地内存。访问本地内存很快,访问其他处理器的远端内存则要慢得多。服务器和高性能计算系统通常采用 NUMA 架构。操作系统的调度器需要尽量把进程调度到它的数据所在的 NUMA 节点上,减少跨节点内存访问。
存储层次。 CPU 执行指令的速度远快于内存提供数据的速度。如果 CPU 每次都要等内存返回数据,大量时间会浪费在等待上。解决方案是在 CPU 和主存之间插入多级容量递增、速度递减的缓存(cache):
| 层级 | 典型容量 | 典型延迟 | 说明 |
|---|---|---|---|
| 寄存器 | 几十到几百字节 | < 1 ns | CPU 内部,速度最快 |
| L1 缓存 | 32-64 KB(每核心) | ~1 ns | 分为数据缓存(L1d)和指令缓存(L1i) |
| L2 缓存 | 256 KB - 1 MB(每核心) | ~3-4 ns | 每个核心私有 |
| L3 缓存 | 8-32 MB(共享) | ~10-12 ns | 同一插槽内所有核心共享 |
| 主存(RAM) | 8-512 GB | ~50-100 ns | 所有核心共享,掉电丢失 |
| SSD | 256 GB - 数 TB | ~10-100 μs | 持久存储,比 RAM 慢约 1000 倍 |
| HDD | 1-20 TB | ~3-10 ms | 机械磁盘,比 SSD 慢约 100 倍 |
从寄存器到 HDD,延迟跨越了 7 个数量级。这个巨大的差距决定了操作系统的很多设计。页缓存(page cache)把频繁访问的磁盘数据缓存在内存中,避免每次都去读慢速磁盘。虚拟内存(virtual memory)在物理内存不足时把不常用的页面换出到磁盘,为当前活跃的程序腾出空间。这些机制的本质都是利用存储层次的速度差异,把热数据尽量放在离 CPU 近的位置。
I/O 与 DMA。 CPU、内存和 I/O 设备之间通过总线互联。现代系统里,设备数据通常不会让 CPU 亲自逐字节搬运,而是由 DMA(Direct Memory Access) 负责把数据直接搬进内存,等传输完成后再通过中断通知 CPU 收尾。这里先建立一个硬件直觉:I/O 不是“CPU 自己去搬数据”,而是多个硬件组件协作完成。后面讲特权边界时,我们会把这条链路重新展开。
小结
| 概念 | 说明 |
|---|---|
| 操作系统 | 管理硬件资源、为应用程序提供抽象接口的系统软件 |
| 资源管理与抽象 | OS 的两个核心职责 |
| SMP / NUMA | 两种多处理器组织方式 |
| 存储层次 | 寄存器到磁盘形成速度差异巨大的分层结构 |
| 局部性 | 缓存之所以有效的根本原因 |
| DMA | 设备直接把数据搬进内存,CPU 负责发起和收尾 |
这一课把“操作系统是什么”和“它面对的是怎样的硬件”放在一起,是因为两者本来就不能分开。后面的内核设计、调度、内存管理和 I/O 子系统,全部建立在这层硬件现实之上。
Linux / 硬件入口:
include/linux/sched.h—task_struct等核心结构Documentation/core-api/dma-api.rst— DMA 的内核接口视角
下一课进入操作系统作为“软件”的另一面:它自己怎么组织、背后遵循什么设计原则、又为什么会演化成今天这样。