Skip to content

操作系统与硬件 ​

  • 写作时间:2026-03-04 首次提交,2026-07-13 最近修改
  • 当前字符:7536

前言已经交代了全书路线:我们会对照 Linux 内核源码理解操作系统原理,也会观察真实系统的运行结果。现在进入基础与概览,第一步不是记住某个内核函数,而是先确定操作系统位于应用程序和硬件之间的什么位置。

来看一个最小场景。just linux 会启动课程使用的 Linux 容器;容器隔离了进程看到的部分用户空间环境,却仍共享 Docker 虚拟机提供的 Linux 内核。课程镜像默认跟随宿主的原生架构,因此 Apple Silicon 上通常是 AArch64,x86 主机上通常是 x86-64;涉及特定体系结构的正文会明确标注。后续命令都可以在其中重复执行:

console
$ 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   4168  3492 pts/0    Ss   06:56   0:00 bash
root         7  0.0  0.0   6008  3272 pts/0    R+   06:56   0:00 ps aux
root         8  0.0  0.0   2316  1204 pts/0    S+   06:56   0:00 head

ps 显示的是进程(process),也就是程序的一次执行实例;进程标识符(Process ID, PID)是内核分配给进程的数字编号。进程在采样时可以正在运行,也可以处于等待等其他状态。这个环境虽然只出现了 bash、ps 和 head,它们仍然要共享 CPU、内存和 I/O 设备,而且一个进程不能随意读写另一个进程的状态。这些要求由谁实现?

答案先落在 操作系统 上。操作系统一方面通过 资源管理 分配 CPU、内存与设备,另一方面通过 系统抽象 给程序提供稳定接口。继续观察硬件时,处理器拓扑 决定系统有多少可执行指令的逻辑 CPU,以及它们怎样共享资源;存储层次 解释不同存储部件为何存在巨大的速度差异;I/O 设备 则让数据能够在内存与外部世界之间传输。

基础与概览会沿着这条边界继续推进:操作系统与硬件建立整体关系;内核设计比较操作系统核心的组织方式;特权边界解释应用为什么不能直接控制硬件;系统调用再展示用户程序怎样向内核提出受控请求。

操作系统 ​

操作系统(Operating System, OS)是管理计算机硬件资源,并为应用程序提供运行环境与服务接口的系统软件。

这里的“操作系统”可以包含多个层次。内核(kernel)是以硬件特权运行、直接管理 CPU、内存和设备的核心程序;启动程序、系统服务和命令行工具构成用户空间中的系统环境。不同语境可能把整个环境称为操作系统,也可能只讨论内核。本书研究机制时会明确区分二者。

一个程序若独占机器,确实可以直接控制硬件。问题出现在多个程序同时存在以后:它们都可能希望运行指令、申请内存、保存文件或接收网络数据。操作系统位于这些程序与硬件之间,把每次请求纳入统一管理,并在一个程序出错时尽量限制影响范围。

操作系统并不替应用完成业务计算。文本编辑器仍要决定怎样修改文档,数据库仍要实现索引与查询;操作系统负责的是这些程序共同依赖、且必须由全局可信组件协调的基础能力。

资源管理 ​

资源管理(resource management)是操作系统记录、分配、复用并回收有限硬件资源的职责。

处理器只提供有限数量的逻辑执行上下文,操作系统要决定每个逻辑 CPU 接下来运行哪个任务;物理内存容量有限,操作系统要记录哪些区域属于哪个进程;磁盘、网卡和终端可能被多个进程同时使用,操作系统要把请求排队并在完成后通知正确的请求者。一个支持多个硬件线程的物理核心可以暴露多个逻辑 CPU,内部还可能乱序或并行执行同一指令流中的多条指令,因此不能把“一个核心”简单等同于“同一时刻只处理一条机器指令”。

资源管理同时包含共享与保护。共享回答“多个进程怎样使用同一硬件”,保护回答“一个进程能访问到什么范围”。如果只做共享而没有保护,某个错误指针就可能覆盖其他进程内存;如果只做保护而不做复用,许多程序又无法方便地同时运行。

开篇的 %CPU、%MEM、PID 和状态字段来自内核对进程的持续记账。ps 自己没有直接读取其他进程内核对象的能力,它读取内核通过 /proc 提供的进程状态;/proc 是一个由内核动态生成内容的特殊文件系统,进程生命周期会继续使用它观察进程。这里先注意结果:资源分配不是应用自行约定,而是内核掌握并通过接口报告。

系统抽象 ​

系统抽象(system abstraction)是操作系统把不同硬件和内核对象表示成一组稳定、可组合的程序接口。应用程序编程接口(Application Programming Interface, API)就是应用能够调用的一组操作、参数和返回规则。

应用若直接操作每种磁盘和网卡,就必须了解设备寄存器、命令格式与完成方式。设备驱动(device driver)是把内核的通用 I/O 请求转换成具体设备命令的系统软件。操作系统在设备之上提供文件、用于网络通信的套接字(socket)和进程等对象,应用针对这些对象编程,具体硬件差异由内核和驱动处理。抽象没有消除差异,而是把差异限制在接口背后的实现中。

Unix 系统常把不同对象接入文件描述符(file descriptor, fd)接口。fd 是进程用来引用一个已打开内核对象的小整数。普通文件、管道、终端和 socket 都可以通过 fd 使用 read()、write()、close() 等操作,但它们的具体语义仍然不同。例如普通文件支持偏移,传输控制协议(Transmission Control Protocol, TCP)套接字表示一条可靠的连续字节流。统一的是操作入口,不是所有对象的行为。

应用向内核请求服务的受控入口叫作系统调用(system call)。系统调用一课会解释它怎样跨越权限边界,这里先观察这类请求确实存在。strace 是 Linux 的系统调用追踪工具,下面只保留 ls 最关键的几类调用:

console
$ 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"], 0xffffcd1fec58 /* 10 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, 0xaaaacc2e3640 /* 3 entries */, 32768) = 80
getdents64(3, 0xaaaacc2e3640 /* 0 entries */, 32768) = 0
write(1, "hello.c\n", 8)                = 8
+++ exited with 0 +++

execve 启动 ls,openat 打开运行所需文件和目标目录,getdents64 读取目录项,write 把结果送到标准输出。一个看似简单的目录列表,已经经过了进程、文件系统和输出设备的共同配合。

再把行为缩小到一个最小 C 程序:

c
#include <unistd.h>

int main(void) {
    write(1, "hello\n", 6);
    return 0;
}
console
$ gcc course/basics/code/hello.c -o /tmp/hello && /tmp/hello
hello

write(1, "hello\n", 6) 请求内核向 fd 1 写入 6 个字节。fd 1 按约定是标准输出,但它可能连接终端、文件或管道;程序不必为每种去向改写打印逻辑。这就是系统抽象带来的可组合性。

如果没有操作系统会怎样?

没有操作系统时,单个程序需要自己初始化硬件、处理中断、管理内存并实现设备驱动。若还要同时运行两个程序,就必须另外实现 CPU 时间分配、内存保护和设备请求仲裁。

这些能力并不会因为没有“操作系统”这个名称而消失。只要多个程序要安全共享机器,就必须有一段可信软件统一实现它们;这段软件实际上已经承担了操作系统的核心职责。

处理器拓扑 ​

处理器拓扑(processor topology)是处理器插槽、物理核心和硬件线程之间的层级与共享关系。

一个 socket 表示主板上的处理器插槽,一个 core 表示能够执行指令的物理核心,一个 hardware thread 表示核心向操作系统暴露的逻辑执行上下文。这里的 hardware thread 也叫逻辑 CPU,不是线程中的软件线程。一个核心若支持同时多线程(Simultaneous Multithreading, SMT),就可以暴露多个逻辑 CPU。

lscpu 可以显示容器当前看到的拓扑:

console
$ lscpu | grep -E '^Architecture|^CPU\(s\):|^Thread\(s\) per core|^Core\(s\) per socket|^Socket\(s\):'
Architecture:                            aarch64
CPU(s):                                  10
Thread(s) per core:                      1
Socket(s):                               -

这组 AArch64 Docker 虚拟机(Virtual Machine, VM)向容器提供了 10 个逻辑 CPU,却没有在汇总信息中报告 socket 与 core 数量,- 表示未知而不是 0。物理机或信息完整的虚拟机可能满足“socket 数量 × 每 socket 核心数 × 每核心线程数 = 逻辑 CPU 数量”,但离线 CPU、容器 CPU 集合限制、虚拟拓扑或异构处理器都可能使简单乘法不再适用。

对称多处理(Symmetric Multiprocessing, SMP)表示各处理器以对等地位运行同一操作系统,并共享主存与 I/O 系统。它没有要求所有内存访问延迟完全相同。统一内存访问(Uniform Memory Access, UMA)机器中,各核心访问主存的代价近似一致;非一致性内存访问(Non-Uniform Memory Access, NUMA)机器则把 CPU 与部分内存组成节点,访问本节点内存通常快于远端节点。现代多插槽 NUMA 服务器仍然可以是 SMP 系统。

这个区别会直接影响后续机制。Linux 调度器不仅要选择可运行 CPU,还会考虑缓存和 NUMA 亲和性;内核内存分配也会根据内存策略和节点可用性选择页面。这里先建立拓扑含义,具体策略分别由对应课程展开。

存储层次 ​

存储层次(memory hierarchy)是按照容量、延迟、带宽和持久性组织寄存器、缓存、主存与外部存储的结构。

CPU 执行指令的速度远快于主存提供数据的速度,而主存又远快于固态硬盘(Solid-State Drive, SSD)与机械硬盘(Hard Disk Drive, HDD)。系统因此使用多级结构,让频繁访问的数据尽量位于更快但容量更小的层级。下面的数字只是常见数量级,不同 CPU、内存和设备会显著变化:

层级常见容量常见访问延迟是否掉电保留
寄存器数百字节小于 1 ns否
L1 cache每核心数十 KiB约 1 ns否
L2 cache每核心数百 KiB 到数 MiB数 ns否
Last-level cache数 MiB 到数十 MiB十几到数十 ns否
主存 RAM数 GiB 到数 TiB数十到数百 ns否
SSD数百 GiB 到数 TiB数十到数百 µs是
HDD数 TiB数 ms是

Cache 是否私有、由哪些核心共享取决于具体处理器,不能把“L2 一定私有、L3 一定全 socket 共享”当成架构保证。操作系统会读取固件与 CPU 提供的拓扑信息,再据此安排调度和内存策略。

缓存之所以能提高命中率,是因为程序访问常具有局部性(locality)。时间局部性表示刚访问的数据很可能很快再次访问;空间局部性表示访问一个地址后,附近地址很可能随后被访问。硬件通常按固定大小的缓存行(cache line)搬运相邻数据,而不是只搬一个字节。

操作系统也利用同一原理。页缓存(page cache)把近期使用的文件数据保留在主存中,后续读取可以不访问慢速设备;配置了交换空间(swap)时,内存管理还可以把暂时不活跃、且不直接对应文件的内存页写入该磁盘区域,为当前活跃数据释放物理内存。这里先说明它们与存储层次的关系,虚拟内存会完整推导具体机制。

I/O 设备 ​

输入/输出设备(input/output device, I/O device)是与 CPU 和主存交换外部数据或控制信号的硬件,例如磁盘、网卡、键盘和显示控制器。

设备通常通过控制器暴露寄存器和缓冲区,CPU 通过总线向控制器提交命令。控制器寄存器可以位于独立的 I/O 地址空间,也可以映射到处理器物理地址空间;后一种方式叫内存映射 I/O(Memory-Mapped I/O, MMIO)。少量数据可以由 CPU 直接读写设备寄存器;大量数据若仍逐字节经过 CPU,会消耗大量指令。因此现代设备常使用直接内存访问(Direct Memory Access, DMA):内核驱动准备包含 DMA 地址、长度和方向的描述符并通知控制器,控制器随后直接在设备与主存之间传输数据。

DMA 完成后,设备还要让内核发现结果。中断(interrupt)是硬件异步请求 CPU 执行处理程序的机制;高速设备也可能由内核轮询完成队列,以减少高频中断。于是一次 I/O 可以先形成最小模型:CPU 提交请求,设备通过 DMA 传输,内核再通过中断或轮询发现完成。内核设计会从软件组织继续推进,特权边界再解释为什么用户程序不能自行配置这条路径。

小结 ​

概念说明
操作系统管理硬件资源,并为应用提供运行环境与服务接口的系统软件
资源管理分配、复用、保护并回收 CPU、内存和设备等有限资源
系统抽象用进程、文件、Socket 与 fd 等稳定接口隐藏实现差异
处理器拓扑socket、core 与 hardware thread 的层级和共享关系
存储层次按容量、速度和持久性组织寄存器、缓存、主存与外部存储
I/O 设备通过控制器、DMA 和中断与 CPU、主存交换数据的硬件

操作系统的接口设计和内部机制都受硬件约束:资源必须被复用和保护,而硬件在并行度、访问延迟与完成方式上的差异决定了具体实现成本。


Linux 与硬件入口:

内核设计将继续观察这个直接管理硬件的核心程序怎样划分职责,以及不同组织方式为什么会产生不同取舍。