这一页从程序如何被 CPU 执行开始,逐步进入进程、内存、并发与设备管理。理解 CPU 的工作模型,是理解操作系统其余部分的起点。
01. 当我们谈论 CPU 时,大脑里应该有什么?
CPU 是程序的执行引擎,操作系统则负责为不同程序分配 CPU、内存和设备。即使不需要设计 CPU,理解它如何读取并执行指令,仍是理解虚拟内存、进程切换和系统调用的基础。
先建立一张全景图
1 | 磁盘上的可执行文件 |
程序启动后,机器指令和数据会被加载到内存。CPU 根据当前的指令地址取出下一条指令,译码后执行,再进入下一轮。缓存会加快访问,但从程序员和操作系统的视角看,指令与数据都位于进程的虚拟地址空间中。
1. CPU 到底在做什么
CPU 的基本循环可以概括为:取指、译码、执行、更新下一条指令地址。
- 取指:根据
RIP指向的地址取得下一条机器指令。 - 译码:识别这条指令需要做什么,例如加法、比较、读写内存或跳转。
- 执行:由执行单元完成计算、访存或跳转,并更新寄存器和状态位。
- 继续执行:通常
RIP顺序前进;遇到分支、函数调用或返回时,则跳到新的地址。
我们写的 C/C++、Rust 或 Python 源码不会被 CPU 直接执行。它们需要经过编译器、解释器或运行时,最终变成 CPU 能理解的机器指令。常见的 0x... 只是机器指令或数据的十六进制表示。
2. 寄存器:CPU 内部最快的工作区
寄存器是位于 CPU 核心内部的少量高速存储单元,用来保存正在计算的数据、地址和执行状态。以下名称以 x86-64 为例;不同架构名称不同,但角色相似。
| 类型 | 代表寄存器 | 在程序运行中的作用 |
|---|---|---|
| 通用寄存器 | RAX、RBX、RCX、RDX 等 |
保存整数、地址、临时变量和计算结果;按常见调用约定,RAX 常保存函数返回值。 |
| 指令指针 | RIP |
指向下一条将要执行的指令,是程序执行流的“当前位置”。修改它就能实现分支、循环、函数调用和返回。 |
| 栈指针 | RSP |
指向当前调用栈的栈顶,用于局部变量、返回地址和函数调用现场。 |
| 帧指针 | RBP |
常被编译器用来定位当前栈帧的基准位置;优化后的代码可能不使用它。 |
| 状态寄存器 | RFLAGS |
记录计算结果和控制状态,供条件跳转、调试和中断控制使用。 |
可以把寄存器理解为 CPU 手边的草稿纸。内存容量更大,但访问成本通常更高;CPU 会尽量把频繁使用的数据放在寄存器和缓存中。
3. RIP 如何决定程序往哪里走
RIP 常被称为指令指针。它保存的是下一条指令的地址:
- 顺序执行时,CPU 执行完一条指令后,让
RIP前进到下一条。 if-else、循环等条件分支,会根据比较结果决定是否改写RIP。- 调用函数时,CPU 跳到函数入口;函数返回时,再回到调用点之后的地址。
因此,程序的控制流本质上就是 RIP 在不同代码地址之间移动。调试器查看调用栈、单步执行或设置断点,本质上都与控制执行流有关。
4. 状态寄存器:让 CPU 能够“根据结果做决定”
RFLAGS 中包含许多标志位。其中最常见的是:
| 标志位 | 含义 | 常见用途 |
|---|---|---|
ZF(Zero Flag) |
运算结果是否为 0 | 判断相等、条件跳转。 |
CF(Carry Flag) |
无符号运算是否产生进位或借位 | 多精度整数运算、无符号比较。 |
OF(Overflow Flag) |
有符号运算结果是否溢出 | 有符号数运算检查。 |
TF(Trap Flag) |
启用单步陷阱 | 调试器可以借此逐条观察程序。 |
IF(Interrupt Flag) |
是否响应可屏蔽外部中断 | 影响 CPU 是否接收普通硬件中断;不影响异常和不可屏蔽中断。 |
例如,比较两数后,CPU 会设置相应标志位;紧随其后的条件跳转指令再读取这些标志位,从而决定是否跳转。
5. 控制寄存器:虚拟内存和权限机制的开关
控制寄存器主要由内核使用,普通用户态程序不能随意读写。仍以 x86 为例:
| 寄存器 | 核心作用 |
|---|---|
CR0 |
控制 CPU 的基础工作模式;其中的分页相关位参与启用分页机制。 |
CR2 |
发生页错误时,保存导致错误的线性(虚拟)地址。 |
CR3 |
保存当前地址空间顶级页表的物理地址,是虚拟地址翻译的入口。 |
CR4 |
控制 PAE 等扩展特性,并启用部分高级处理器能力。 |
这组寄存器把 CPU 与操作系统的内存管理紧密连接起来:进程切换时,内核可能切换 CR3,从而让同一个虚拟地址在不同进程中映射到不同的物理内存。
6. 指令从哪里来:冯·诺依曼模型
现代计算机仍可用冯·诺依曼模型来理解:输入设备提供数据,内存保存正在使用的程序和数据,CPU 负责运算与控制,输出设备呈现结果。
- 磁盘:长期保存可执行文件和数据。
- 操作系统与加载器:创建进程的虚拟地址空间,把程序需要的内容映射或加载进内存。
- 内存与缓存:提供 CPU 执行期间需要访问的代码和数据;缓存由硬件自动管理,用来降低访问延迟。
- CPU:从当前地址空间取指并执行。
所以,“程序在磁盘里”不等于“CPU 正在执行它”。只有程序被加载为进程,并获得 CPU 执行时间后,其中的指令才会真正运行。
7. 一个核心只能运行一个执行流,多任务是如何出现的
从操作系统调度的视角看,一个逻辑 CPU 在某一时刻只运行一个线程的执行上下文。时间片到期或发生中断时,内核会保存当前线程的寄存器状态,再恢复另一个线程的状态,然后从对方的 RIP 继续执行。
1 | 线程 A 运行一段时间 |
这就是上下文切换。CPU 通常不会在每条指令后切换任务,而是让一个线程连续运行一小段时间。高速轮转让人感觉多个程序同时执行;真正的多核 CPU 则可以让多个逻辑 CPU 并行运行不同线程。现代处理器还有流水线、乱序执行和超线程等机制,但它们不改变操作系统需要保存与恢复执行上下文这一事实。
小结:理解 CPU 时应抓住的六件事
- CPU 的基本职责是不断执行机器指令。
RIP决定下一条指令在哪里,控制流就是它的跳转轨迹。- 通用寄存器和
RSP保存当前计算与调用现场。 RFLAGS记录运算和控制状态,CR0到CR4参与内存与处理器特性的控制。- 程序先从磁盘进入进程的地址空间,才能被 CPU 执行。
- 多任务依赖调度器和上下文切换;进程、线程和虚拟内存将在后续章节展开。
学习导图
一、程序运行原理篇
- 说到CPU时你的大脑里应该有什么?
- 内存的本质是什么?
- 编程语言是怎么一回事?
- 到底该怎样理解堆区和栈区?
- 没有操作系统程序能运行起来吗?
- 程序是如何运行起来的?
- 为什么用C语言编写操作系统?
- 进程和程序之间有什么区别?
二、操作系统实现篇
- 操作系统是如何实现的?
- 操作系统是如何启动的?
- CPU权限管理的王者之争
- 到底什么是用户态、内核态?
- 什么是系统调用?
- 为什么需要系统调用?
- 系统调用时操作系统中发生了什么?
- 操作系统中的代码是如何运行的?
- 系统调用完成后怎样返回用户态?
- 实践篇:观察内核调用栈
- 实践篇:用strace观察系统调用的秘密
三、进程管理篇
- 为什么操作系统中需要进程这个概念?
- 操作系统是怎样同时运行多个程序的?
- 进程切换是怎样实现的?
- 进程切换与函数调用的区别是什么?
- 为什么进程会有各种状态信息?
- 操作系统是怎样调度进程的?
- 进程的生命周期
- 进程与操作系统的CPU争夺之战
- 假如让你来为进程创建设计API
- 为什么Linux需要fork与exec两个系统调用来创建进程?
- 操作系统是如何进一步创建出进程的?
- 进程是如何退出的?
- 进程间通信是如何实现的?
- 有了进程为什么还需要线程?
- 程序员该如何理解线程?
- 线程是如何实现的?
- 进程和线程之间到底有什么区别?
- 有了线程为什么还需要协程?
- 如何利用多线程来优化程序性能?
- 实践篇:多进程编程
- 实践篇:多进程多线程在浏览器中的应用
四、内存管理篇
- 黑客帝国与内存背后的真相
- 内存分配到底在解决什么问题?
- 程序能运行起来需要分配哪些内存?
- 操作系统如何为进程分配内存的?
- 虚拟地址空间的诞生
- 操作系统如何为进程分配内存之段式内存管理
- 操作系统如何为进程分配内存之页式内存管理
- 页式内存管理会有哪些问题?
- 操作系统如何加速页表的读取?
- 操作系统如何解决页表内存占用过多的问题?
- 为什么虚拟内存能节省内存?
- 为什么物理内存只有1G程序员却可以申请出4G内存?
- 上帝视角:操作系统是如何管理内存的?
- 实践篇:如何利用内存池优化程序性能?
- 实践篇:申请4G内存实际会消耗多少物理内存?
- 实践篇:进程在内存中是什么样子的?
五、中断管理篇
- 到底什么是中断?
- 操作系统在什么情况下开始运行?
- 操作系统是如何处理中断的?
- 程序是如何读取到网络数据的?
- 程序是如何读取到磁盘数据的?
六、并发篇
- 并发问题的根源是什么?
- 实践篇:实际动手感受一下并发问题
- 怎样解决并发问题?
- 一山不容二虎:锁是如何实现的?
- 有自旋锁为什么还需要互斥锁?
- 闻风而动:如何实现线程的同步等待机制?
- 到底什么是信号量?
- 信号量是怎么实现的?
- 实践篇:用信号量来解决互斥问题
- 实践篇:用信号量来解决同步等待问题
- 实践篇:生产者消费者问题
七、设备管理篇
- 一切皆为文件到底是什么意思?
- 操作系统是如何管理设备的?
- 磁盘是如何工作的?
- 文件这个概念在操作系统是如何实现的?
- 文件系统是如何实现的?
- 文件夹在操作系统中是如何实现的?
- 操作系统是如何打开、读写以及创建文件的?
- 操作系统是如何加速文件读写的?