现在是11.1早上10:50,我刚看完youtube上的一个讲解os的直播视频,感觉讲的还是非常清晰的,有必要梳理一下先前离散的知识点,形成一个较为完整的知识体系。我大致用笔记了下一些关键信息,但还是决定让ai帮我生成一份完整的视频总结,以下是ai总结的视频内容:
原视频链接:https://www.youtube.com/watch?v=0y_2mZqP3cc&t=2679s
操作系统到底是怎么工作的:进程、线程与虚拟内存
本文是对 YouTube 视频 《How the OS actually works》(作者 Ben Dicken)的完整知识梳理文档。
视频本身是一场 80 分钟的直播式讲解,中间夹杂了大量观众问答和闲聊,本文已剔除闲聊、保留并重组全部技术内容,按「为什么 → 是什么 → 怎么实现」的逻辑重新组织,并补充了少量必要的背景说明(凡属补充内容均以 补充 标注)。
0. 文档信息与阅读导航
| 项目 | 内容 |
|---|---|
| 视频标题 | How the OS actually works(操作系统实际上是如何工作的) |
| 作者 | Ben Dicken(软件工程 / 数据库 / 分布式系统方向的内容创作者,前高校讲师,曾讲授操作系统课程) |
| 形式 | 直播讲解(Live Stream)+ 白板绘图 |
| 时长 | 约 80 分钟(4815 秒) |
| 依据教材 | Systems Performance(《系统性能》)— Brendan Gregg,800+ 页,作者只挑选部分章节讲解 |
| 核心技术主题 | 硬件模型 / 进程抽象 / fork 与 exec / 进程地址空间 / 文件描述符 / 进程与线程 / 进程状态与调度 / 虚拟内存与分页 |
| 文档整理时间 | 2026-11-01 |
视频时间轴(便于回看原片)
| 时间 | 内容 |
|---|---|
| 00:00–13:30 | 开场闲聊、书籍介绍、延伸阅读(Cursor 的 agent swarm 重构 SQLite 博客) |
| 13:30–22:30 | 为什么要进程抽象:硬件模型、单道执行的历史、I/O 等待 |
| 22:30–31:30 | 时间分片与上下文切换;不做抽象会带来的冲突与安全问题 |
| 31:30–39:00 | 如何创建进程:fork / clone、exec / execve、init 进程 |
| 39:00–47:50 | 进程的解剖:用户地址空间 + 内核上下文;栈与堆;文件描述符 |
| 49:00–52:00 | 进程 vs 线程;多进程架构 vs 多线程架构 |
| 52:00–53:30 | 进程生命周期与四种状态 |
| 55:50–79:30 | 虚拟内存:MMU、分页、页表项、地址翻译全流程、多级页表 |
| 79:30–结尾 | 收尾 |
1. 为什么需要「进程」这个抽象
1.1 先看裸硬件:极简计算机模型
作者用一个极简模型开始讲解(19:00 起):
1 | ┌──────────────────────────────┐ |
要点:
- CPU 是指令真正执行的地方,每个核独立执行指令流。
- RAM 与 CPU 交互极其频繁,因为这里是工作数据的存放地。
- 磁盘 容量大但慢得多,RAM 的内容会溢出/持久化到磁盘。
这个模型里只有「一个程序从头跑到尾」的世界:早期计算机确实如此——一台昂贵的大机器,喂进去一个程序(甚至用打孔卡片),跑完就结束,同一时刻只运行一个程序。
1.2 两个推动抽象诞生的现实问题
问题一:I/O 太慢,CPU 在空等。
程序在 CPU 上执行得非常快,但一旦要去读/写磁盘、发网络请求,就要等很久。如果只有一个程序,这段时间 CPU 就是闲着的。我们希望「A 程序等 I/O 的时候,CPU 去执行 B 程序」。
问题二:人想同时干很多事。
一边算数学、一边放音乐、一边管理直播、一边开聊天窗口——一台机器要同时承载多个程序。
1.3 解决办法:时间分片 + 上下文切换
1 | 4 个核:Core0→聊天 Core1→音乐 Core2→数据库 Core3→终端 (真并行) |
- 单核上「同时运行多个程序」的本质是把 CPU 时间切成小片轮流分配,即分时(time sharing)。
- 切换的核心动作叫上下文切换(context switch):保存当前进程状态 → 装入下一个进程状态 → 继续执行。
- 谁决定「哪个程序何时运行、运行多久」?——调度器(scheduler)。这是操作系统里一个极其复杂、有大量策略的子系统(视频中只点到为止)。
1.4 如果没有抽象,会发生什么?
假设你作为工程师要写一个自制视频编辑器,而系统没有进程抽象,那么你的代码必须自己操心:
- 数据库程序占了哪段 RAM?音乐程序占了哪段?我的编辑器只能用哪一小块?
- 我的程序该用磁盘的哪些区域?会不会写到别人的数据上?
- 更糟的是安全问题:如果聊天程序被恶意篡改,它可以去读视频编辑器占用的内存,甚至读取别人的密钥。——因为没有任何边界。
结论:如果没有统一抽象,每个程序都要自己解决「资源互不冲突」的问题,写任意程序的通用操作系统就不可能成立。
2. 进程:给你「独占一整台机器」的幻觉
2.1 定义
进程(Process)= 一个正在运行的程序的自包含实例(a self-contained instance of a running program)。
这是 Unix(macOS、Linux/Ubuntu)与 Windows 上基本一致的抽象。创建一个进程时,需要把一个程序加载进去,然后由操作系统维护这套抽象,让该进程能够「假装自己是这台机器上唯一在跑的东西」:
| 资源 | 进程看到的「幻觉」 | 实际上由 OS 负责的事 |
|---|---|---|
| CPU | 「整个 CPU 都是我的」 | 调度、时间片分配、优先级 |
| 内存 | 「全部 RAM 都是我的」,甚至「我有近乎无限的内存」 | 虚拟内存、页表映射、换页 |
| 磁盘 | 「文件系统随我用」 | 文件系统、权限控制、缓存 |
关键点:应用工程师不需要做这些决策。操作系统通过虚拟内存、CPU 调度、优先级、文件系统等机制,替所有进程协调冲突、公平共享资源。
2.2 这个抽象的价值
正因为大家都建立在「进程」这个稳定原语之上,我们才能:
- 写应用时不必关心物理内存布局;
- 让 AI/编译器/框架有一个行为可预期的基座(作者强调:AI 写代码同样受益于「可靠原语」,进程、数据库接口都是这种原语);
- 在需要时换一套抽象(例如为实时性优化的专用 OS),但那属于另一个权衡方向。
顺带一提:Linux 内核代码量超过一百万行,要同时支持多种 CPU 架构、处理各种边界情况并保证安全,这正是「自己造一个 OS」远没有听起来那么轻松的原因。
3. 进程是怎么被创建出来的:fork 与 exec
上一节讲了「进程是什么」,本节讲「进程从哪来」。这需要回到系统调用(system call)——应用与内核之间的 API,是应用进入内核态去操作磁盘、网卡等资源的方式。
3.1 fork:克隆自己
1 | ┌──────────────┐ fork() ┌──────────────┐ |
fork() 的行为(视频中直接读了 man fork):
- 创建一个新进程,新进程(子进程)是调用进程(父进程)的精确副本;
- 差别只有几处:
- 子进程有唯一的 PID;
- 子进程有不同的父进程(PPID 指向调用者);
- 子进程有自己的一份文件描述符副本;
- 子进程的资源使用统计(resource utilizations)清零。
也就是说,如果视频编辑器进程调用 fork(),系统上就真的出现了两个一模一样的视频编辑器进程(除 PID 外)。
关于命名:fork 是 C 库函数名;在 Linux 内核里,进程和线程统一用 clone() 这类系统调用创建(视频中作者也提到这点)。
补充:现代内核为了避免「真的复制一整份内存」的巨大开销,通常配合 写时复制(Copy-on-Write, COW)——父子进程先共享同一批物理页,只有某一方写入时才真正复制那一页。
3.2 exec / execve:换掉自己正在跑的程序
1 | 进程 A'(fork 出来的副本) |
execve() 的行为(视频中读了 man execve):
- 把调用进程本身转换成「一个新程序」;
- 新程序由路径指向的可执行文件构建,这个文件要么是可执行目标文件,要么是交给解释器处理的数据文件;
- 可执行目标文件由「标识头 + 表示初始程序的若干数据页」组成(即编译产物,如 GCC 输出的 ELF)。
注意:exec 不创建新进程,它替换的是当前进程正在运行的程序镜像(代码/数据/堆栈被重置)。
3.3 标准启动模式:fork + exec
1 | 1. 父进程 fork() ──▶ 得到一个子进程(父进程的副本) |
这是 shell 执行一条命令的标准套路:先复制一个自己,再让复制品变身成目标程序。这样做的好处是父进程(shell)自己的状态完全不受影响。
3.4 一切的源头:init 进程
系统启动时,硬件上电、固件(firmware)执行完之后,会启动一个初始进程(Unix 传统上叫 init,现代 Linux 多为 systemd,PID 为 1)。
- 内核会维护一张「当前所有活跃进程」的列表,负责给它们分配 CPU 时间、管理内存;
- 其余几乎所有进程——无论内核进程还是用户进程——都是从这个初始进程 fork 出来的。
这形成了一棵进程树:init(1) → ... → 你的终端 → shell → 你敲下的每个命令。
4. 进程的解剖:它到底「包含」了什么
这是全片结构最清晰的一张图(39:00 起,作者取自 Brendan Gregg 的书)。一个进程由两大块组成:
1 | ┌───────────────────────── 进程 ──────────────────────────┐ |
4.1 用户地址空间内的布局
| 区域 | 内容 | 特点 |
|---|---|---|
| text(程序文本) | 真正被执行的指令:add / mov / jump 等汇编指令 | 大小固定,通常只读 |
| data / bss | 常量、已初始化/未初始化全局数据 | 大小固定 |
| libraries | 动态链接进来的库 | 加载时映射 |
| stack(栈) | 函数调用帧:局部变量、返回地址、当前执行位置 | 随函数调用/返回增长或收缩;深度递归可达上千帧 |
| heap(堆) | 与某次函数调用无关的通用内存:数组、图、哈希表、对象等 | 单向增长,但会产生碎片(分配与释放交错) |
栈 vs 堆的一句话区分:
- 栈回答「我现在执行到哪、这一层层函数调用的状态是什么」——是执行记录;
- 堆回答「我需要一块不属于某次调用的通用内存,用来放要传递、共享的数据」——是数据存放地。
两者在地址空间里通常相向增长(栈向下、堆向上),中间的空隙留给彼此扩张。
4.2 内核上下文
当进程需要做 I/O(读磁盘、发网络请求等)时会切换到内核态执行,此时用的是另一套栈——内核栈,因为内核会执行完全不同的函数调用序列。
内核还会为该进程保存:
- 元数据:进程 ID(PID)、用户 ID(UID)、累计 CPU 时间等;
- 文件描述符表:这个进程当前打开了哪些文件。
4.3 文件描述符(file descriptor)是什么
观众提问「能解释一下文件描述符吗」,作者的解答可以整理为:
- 在 Unix 中打开文件要调用
open()(C 里常用fopen()作为其封装); open()的语义:按路径打开文件,返回一个文件描述符给调用进程;- 文件描述符就是这个进程「打开了这个文件」的标识符/句柄;
- 内核必须知道每个进程当前打开着哪些文件,原因有二:
- 读写请求要能定位到具体文件;
- 权限控制:文件系统有完善的读/写/执行权限模型,内核要结合「谁(UID)」和「打开了什么」来判断是否允许操作。
补充:Unix 中 0/1/2 固定为 stdin/stdout/stderr;文件描述符本质是进程级「打开文件表」的索引,
fork后子进程会继承一份副本,因此重定向、管道才能在进程间传递。
5. 进程 vs 线程
5.1 核心区别:共享地址空间,独享栈
1 | 单线程进程 多线程进程 |
一句话概括:线程是进程的子部件——它共享同一个虚拟地址空间和同一个堆,但拥有自己独立的程序栈(用户栈 + 内核栈各一套),因此可以停在不同执行位置。
5.2 对比表
| 维度 | 进程(Process) | 线程(Thread) |
|---|---|---|
| 隔离性 | 完全隔离(独立地址空间) | 共享地址空间、堆、全局数据、文件描述符 |
| 独立拥有的东西 | 地址空间、堆、栈、页表、FD 表 | 仅程序栈(用户栈 + 内核栈)、寄存器上下文 |
| 切换开销 | 较大(要切换地址空间/页表) | 较小(同地址空间内切换) |
| 通信方式 | 需要 IPC:消息传递 或 共享内存 | 直接读写共享内存(因此需要同步机制) |
| 崩溃影响 | 一个进程崩溃通常不影响其他进程 | 一个线程崩溃可能拖垮整个进程 |
| 协调成本 | 低耦合,但通信成本高 | 耦合紧,需要锁/原子操作等协调 |
5.3 两种架构风格
- 多进程架构(multi-process):一个程序主动 fork 出多个进程,用消息传递或共享内存通信,从而利用多核并行。优点是隔离性好、健壮(类似浏览器的多进程模型)。
- 多线程架构(multi-threaded):单进程内创建 2 / 5 / 10 / 100 个线程,每个线程有独立执行栈、可被调度到不同 CPU 核上真正并行。优点是轻量、共享数据方便。
作者的立场值得记下:有观众说「进程是一个糟糕的抽象」,他强烈反对——正因为有了进程这个抽象,应用层才不必处理物理内存冲突与隔离问题。
补充:在 Linux 内核视角里,进程与线程都表示为 task(
task_struct),区别只在于是否与其他 task 共享地址空间等资源(clone()的参数决定共享哪些)。所谓「轻量级进程」即指此。
6. 进程的生命周期与调度
系统可能同时要跑上百个进程,但只有 4 个或 8 个核,物理上不可能同时执行。调度器除了选「谁上 CPU」,还要维护每个进程处于哪个状态。
1 | ┌─────────────────────────────────────┐ |
| 状态 | 含义 | 何时进入 | 何时离开 |
|---|---|---|---|
| Ready(就绪) | 万事俱备,只等 CPU 时间;没被任何东西阻塞 | 新建、I/O 完成、被抢占 | 被调度上 CPU |
| Running(运行) | 正在 CPU 上执行指令 | 调度器选中 | 时间片用完、被更高优先级抢占、发起 I/O |
| Waiting / Blocked(等待) | 因等待 I/O、网络、锁等而让出 CPU | 发起阻塞操作 | 事件完成,回到 Ready |
| Zombie / Terminated(终止/僵尸) | 已执行完毕,等待父进程或 OS 清理 | 进程退出 | 被回收后彻底消失 |
要点:
- 「等待」状态是提高 CPU 利用率的关键——进程等 I/O 时把 CPU 让给别人;
- 只有在 Running 状态的进程数 ≤ 核数时才是真并行,其余都是分时复用;
- 调度策略(如 MLFQ 多级反馈队列)决定 Ready 队列里的谁先上、给多少时间片。视频中观众提到 OSTEP 书里的 MLFQ,作者表示没读过那本书但认可其价值。
7. 虚拟内存:让每个进程都以为自己拥有全部内存
这是全片技术密度最高的部分(55:50–79:30)。
7.1 要解决的问题
进程抽象承诺「你拥有全部 RAM,甚至近乎无限的内存」。但物理 RAM 只有那么多,还被几十上百个进程共享。如何同时兑现「独占幻觉」和「物理共享」?——虚拟内存(Virtual Memory)。
7.2 中间人:MMU
1 | CPU MMU RAM |
- CPU 执行的每条访存指令用的都是虚拟地址(如
0x12345); - 这个地址不会直接送到 RAM,而是先交给 MMU(Memory Management Unit,内存管理单元);
- MMU 查表把虚拟地址翻译成物理地址(可能是完全不同的
0x83145),再去 RAM 取数据; - MMU 由硬件 + 操作系统内核软件共同构成(具体划分依架构/OS 而异)。
作者用的类比很贴切:MMU 之于内存,就像数据库代理(proxy)之于数据库——应用不直连数据库,而是连 proxy,由 proxy 决定实际路由到哪个后端。MMU 就是 CPU 与物理内存之间的「代理」,负责把虚拟地址平滑地翻译成物理地址。
7.3 分页(paging):翻译的基本单位
- 内存不是按字节映射的,而是按**页(page)**映射;
- 常见页大小:4 KB ~ 16 KB;部分系统支持大页(如 2 MB);
- 页是「装入内存 / 换出到磁盘」的基本单位;
- 单条指令可能只请求 1 字节或 8 字节,但翻译只发生在页粒度上。
7.4 地址怎么拆:页号 + 页内偏移
1 | 虚拟地址 0x2C8 (视频采用的极简示例:地址只有 3 位十六进制) |
真实系统中(x86-64 常用 48 位地址)则是高位做多级页表索引、低位做偏移。
7.5 页表项里存了什么
视频用一张极简「页映射表(page map)」来讲,每一项至少包含:
| 字段 | 含义 | 作用 |
|---|---|---|
| R(Resident,驻留位) | 该页当前是否真的在物理内存中 | 若为 0,需要先从磁盘调入(缺页) |
| D(Dirty,脏位) | 自上次从磁盘载入后,该页内容是否被修改过 | 回收该页前若 D=1,必须先写回磁盘;D=0 则可直接丢弃/复用 |
| 物理页框号 / 偏移 | 该虚拟页实际映射到物理内存的哪个位置 | 完成翻译 |
D 位与 R 位的意义都来自「swap / 换页」机制:当 RAM 不够装下所有进程的内存时,OS 会把一些页推到磁盘上做后备存储,腾出物理内存给别人用。等需要时再换回来。所以必须记录「这页脏了没有」和「这页现在在不在内存里」。
7.6 一次完整的地址翻译(逐步走一遍)
以「把地址 0x2C8 的内容 load 到寄存器 R1」为例:
- CPU 执行 load 指令,产生虚拟地址
0x2C8; - 拆成 页表索引 = 2,偏移 = 0xC8;
- 去当前进程的页映射表查第 2 项;
- 读该项的 R 位:
- R = 1(已在内存) → 进入第 5 步;
- R = 0(不在内存) → 触发缺页异常,进入下面 7.7 的流程;
- 读该项记录的物理页框号(示例中假设是 9);
- 定位到物理内存第 9 块页的起始处,再加上偏移
0xC8; - 从该精确位置取出所需的 4 / 8 字节,送回 CPU 完成这条指令。
7.7 当 R = 0:缺页(page fault)流程
1 | CPU 请求 0x2C8 |
对 CPU 来说,这次访问墙钟时间(wall clock)变长了,但等待期间系统做了别的活——这就是虚拟内存与调度协同带来的收益。
7.8 为什么要多级页表
- 现代 x86-64 虽然指令集是 64 位,实际使用的地址通常是 48 位;
- 48 位地址空间意味着页表条目数量极其庞大(数亿级),不可能给每个进程都维护一张平铺大表;
- 关键观察:真实程序对地址空间的使用极其稀疏——99.9% 的情况下只用到了零散的小块,绝大部分虚拟地址从未被访问;
- 解法:多级 / 树状页表。先把地址空间切成很大的块,块内再分块……只为实际用到的部分分配下级页表,从而大幅压缩存储。
补充:为加速翻译,CPU 内还有 TLB(Translation Lookaside Buffer) 缓存最近的地址翻译结果,避免每次都走多级页表(视频未展开)。
7.9 虚拟内存最终解决的三个问题
- 隔离:程序读写的「地址」只在自己的映射里有效,无法踩到别的进程的物理内存;
- 安全:在 OS 层、乃至硬件层保证你不能读取别人的数据;
- 无冲突共享:OS 为每个进程维护一份页映射,并确保不会把同一块物理内存同时分配给两个进程。
于是程序可以随意执行「load 地址 0x1352 到寄存器」「把 R4 的内容存到某地址」这类指令,完全不去想物理布局——而这一切又完全不影响其他进程。
8. 核心概念速查表
| 概念 | 一句话定义 | 关键词 |
|---|---|---|
| 进程 Process | 一个正在运行的程序的自包含实例,拥有独占机器的幻觉 | 隔离、PID、地址空间 |
| 线程 Thread | 进程内的执行单元,共享地址空间与堆,独享栈 | 轻量、并行、共享 |
| 上下文切换 | 保存当前执行流状态、装入另一个执行流状态 | 分时、开销 |
| 调度器 Scheduler | 决定哪个进程何时上 CPU、运行多久 | 时间片、优先级、MLFQ |
| fork / clone | 克隆出一个几乎完全相同的进程 | 新 PID、FD 副本 |
| exec / execve | 用新程序替换当前进程的映像 | 不新建进程 |
| init(PID 1) | 系统第一个进程,几乎所有进程的祖先 | 进程树 |
| 用户地址空间 | text / data / 库 / 堆 / 栈 | 程序运行地 |
| 内核上下文 | 内核栈 + 元数据 + 文件描述符表 | 内核态、陷入 |
| 文件描述符 FD | 进程打开文件的标识符,由 open() 返回 | 权限、读写 |
| 堆 Heap | 与函数调用无关的通用动态内存 | 碎片、单向增长 |
| 栈 Stack | 函数调用帧记录 | 递归、增长/收缩 |
| 虚拟内存 | 让每个进程以为独占全部内存的抽象 | MMU、页表 |
| MMU | 虚拟地址 → 物理地址的翻译单元(硬件+软件) | 代理、映射 |
| 页 Page | 映射与换入换出的基本单位(4–16 KB,大页 2 MB) | 页号 + 偏移 |
| 页表项 PTE | 记录 R 位、D 位、物理页框号 | 驻留、脏页 |
| 缺页 Page Fault | 访问的页不在内存,需从磁盘调入 | 阻塞、trap |
| 多级页表 | 树状结构,只为实际使用的地址段分配 | 稀疏、48 位地址 |
9. 常见误解澄清
- 「进程是糟糕的抽象」 —— 作者明确反对。没有进程抽象,每个应用都要自己处理内存冲突与隔离,通用 OS 无法成立。
- 「exec 会创建新进程」 —— 不会。exec 是替换当前进程的程序映像;创建新进程靠 fork/clone。
- 「多线程一定比多进程快」 —— 不一定。线程切换更轻、通信更方便,但共享带来同步成本,且一个线程崩溃可能拖垮整个进程。
- 「虚拟内存就是硬盘当内存用」 —— 只是其中一部分(swap)。虚拟内存的核心是地址翻译与隔离,让每个进程拥有独立、连续、可稀疏使用的地址空间。
- 「CPU 直接访问物理地址」 —— 不。程序发出的都是虚拟地址,必须经 MMU/页表翻译;每次访问前还要检查 R 位。
- 「一个核只能跑一个进程、不能同时跑多个」 —— 单核可以通过时间分片在多个进程间快速切换,产生「同时」的观感。
10. 延伸学习(视频中提到的资料)
| 资料 | 说明 |
|---|---|
| Systems Performance — Brendan Gregg | 本片依据的教材,800+ 页,含 CPU / 内存 / 文件系统等章节;作者建议按需选读,不必从头读到尾 |
| MIT 的操作系统课程虚拟内存讲解视频 | 作者推荐:比书里更具体地讲了「虚拟页号 + 偏移 → 页表 → 物理地址」的机制(片中示范的 0x2C8 例子即来自该视频) |
| OSTEP(Operating Systems: Three Easy Pieces) | 观众推荐的经典 OS 教材,其中 MLFQ 调度章节评价很高;作者尚未细读 |
| Cursor 博客《Agent swarms in the new model economics》 | 片中提到的延伸阅读:用「任务树分解 + 多模型协作」的 agent 集群按 SQLite 规格重写 SQLite,并把该 swarm 类比为编译器(planner 把意图逐层 lowering 成可执行任务;区别是编译器每一步保义,而 swarm 每一步都是概率性的) |
mmap |
观众提问;作者指出它是「把文件内容映射进内存」的重要机制,本片未展开 |
| fork bomb / 双 fork(double fork)/ 会话与进程组 | 观众提问,片中未展开;双 fork 常用于让守护进程脱离终端会话 |
| 作者本人的博客 | 关于 processes vs threads 的文章发布在 PlanetScale 博客,个人站 benjdd.com |
11. 自测题
- 如果一台机器只有 1 个核,为什么还能「同时」运行浏览器和音乐播放器?请描述其机制。
fork()创建的子进程与父进程有哪些具体差异?为什么这些差异是必要的?- 为什么「启动一个程序」需要 fork 和 exec 两步,而不是只要一步?
- 画出进程地址空间,标出 text、data、堆、栈,并说明堆和栈的差异与增长方向。
- 文件描述符是什么?内核为什么必须为每个进程维护一张 FD 表?(提示:权限)
- 线程与进程最本质的区别是什么?由此带来哪些性能与工程上的权衡?
- 列出进程的四种状态及各自转换条件。
- 虚拟地址
0x2C8在「页表索引=高位、偏移=低位」的模型中如何拆分?若对应页表项 R=0,接下来会发生什么? - 页表项中的 D 位和 R 位分别解决什么问题?如果 D 位始终为 0、永不置位,会有什么后果?
- 为什么 48 位地址空间必须用多级页表而不能用单级平铺页表?
12. 一页速记
- 硬件只有 CPU、RAM、磁盘;多程序并发靠分时 + 上下文切换。
- 进程是「让你以为自己独占整台机器」的自包含运行实例,隔离与安全都靠它。
- 创建进程 = fork 克隆自己 + exec 变身新程序;系统一切进程都从 PID 1 派生。
- 一个进程 = 用户地址空间(text/data/库/堆/栈)+ 内核上下文(内核栈/PID/UID/FD 表)。
- 栈记执行轨迹,堆放通用数据;FD 是进程打开文件的句柄,受权限约束。
- 线程共享地址空间与堆、独享栈,故更轻量,但需同步、隔离性弱。
- 进程状态:Ready → Running → Waiting →(Zombie),由调度器在核间分配。
- 虚拟内存用 MMU + 分页 + 页表(R/D 位、多级树)把虚拟地址翻译成物理地址,实现「独占幻觉 + 物理共享 + 安全隔离」。