☰
手写《操作系统自己总结.pdf》的三阶段实践法
2026/9/29 1:52:27 网站建设 项目流程

简介:本资源是一份专为西北工业大学801考研考生编写的《操作系统》核心知识点系统性总结笔记,聚焦考试高频考点与易错难点,助力考生高效梳理知识框架、突破理解瓶颈。全文覆盖操作系统六大核心模块:概述与功能定位、进程管理(含状态转换、PCB、调度算法及PV同步机制)、内存管理(分区/分页/分段、虚拟内存与碎片分析)、文件系统(逻辑/物理结构、存取方式与保护机制)、设备管理(I/O控制方式、驱动与SPOOLing)以及作业调度策略与性能指标。资源为单个PDF文件,大小19.05MB,内容由手写扫描整理而成,排版清晰、重点突出,便于打印复习与碎片化学习。目前已有167人下载学习,适合作为教材补充、冲刺阶段查漏补缺及真题关联复习的权威参考材料。

1. 为什么一份《操作系统自己总结.pdf》比十本教材更难写出来:它不是笔记,而是你和内核对话后的回声

你翻过王道、汤小丹、Abraham Silberschatz 的《操作系统概念》,也跑过 xv6、Linux 0.11、Pintos 实验,但合上电脑那一刻——脑子里只剩碎片:进程调度像雾里看花,页表映射像黑匣子,系统调用从用户态跳进内核的那一步,总像踩在薄冰上。直到某天你硬着头皮打开空白 PDF,想把“进程、内存、文件、IO、死锁”五个词串成一条线,才发现:真正卡住你的不是知识量,而是知识之间的因果链断了三次以上。这份《操作系统自己总结.pdf》根本不是复习资料,它是你亲手拆过 kernel 启动流程、改过 sched_class、在 /proc 下扒过 task_struct 内存布局后,对“操作系统到底在替你做什么”给出的唯一可信回答。适合正在啃实验报告却写不出设计逻辑的本科生、刚接手 Linux 容器底层问题的运维工程师、以及被面试官一句“请说说 page fault 触发后内核做了什么”问到哑火的开发——它不教你背定义,它逼你重建操作系统的时间线与控制流。


2. 从零生成一份有血有肉的《操作系统自己总结.pdf》:三阶段构建法(非抄书,非摘录)

2.1 第一阶段:用「最小可运行内核」锚定所有抽象概念(以 xv6-riscv 为例)

别一上来就啃 Linux 源码。xv6-riscv 是目前教学级最干净的实现:3000 行 C 代码覆盖进程创建、调度、内存管理、文件系统四大核心模块,且每行都有注释。关键在于——你要让它真正在你机器上跑起来,并亲手触发每一个你打算写进 PDF 的机制。

# 在 Ubuntu 22.04 上快速启动 xv6(需先安装 riscv64-unknown-elf-gcc) git clone https://github.com/mit-pdos/xv6-riscv.git cd xv6-riscv make qemu # 启动 QEMU 模拟器,进入 shell

提示:make qemu后你会看到init: starting sh,此时输入ls查看根目录,cat README读源码说明——这不是演示,是让你确认“进程已创建、文件系统已挂载、shell 已接管控制权”这三件事真实发生了。只有亲眼看到ps命令输出init和sh两个 PID,你写“进程控制块 PCB”时才不会空谈结构体字段。

接着做三件必须动手的事:

  • 改kernel/proc.c中fork()函数:在allocproc()返回前加一行cprintf("PID %d created by PID %d\n", p->pid, myproc()->pid);,重新make qemu,执行sh→sh→echo hello,观察日志。你立刻明白:fork 不是复制内存,而是复制 PCB + 分配新 pid + 设置父子关系。
  • 在user/init.c里插入sleep(10):观察 QEMU 窗口时间流逝,再ps看进程状态从RUNNABLE变SLEEPING——调度器不是玄学,它真在扫描proc[]数组找state == RUNNABLE的进程。
  • 用strace跟踪cat README:在宿主机执行strace -e trace=openat,read,write,xstat ./user/cat README,对比 xv6 内sys_open()、sys_read()的实现。你会发现:用户态open()→ 系统调用号 →syscall()→sys_open()→kern/file.c,这条链路每一环都可验证。

这些操作的目的只有一个:把教科书里的“进程具有独立地址空间”变成你亲眼看到的p->pagetable地址值,把“虚拟内存由 MMU 管理”变成sbi_console_write()输出的satp寄存器内容。没有这一步,你的 PDF 里所有文字都是二手信息。

2.2 第二阶段:用「场景驱动表格」替代章节罗列(拒绝“第一章 进程管理”式结构)

别按教材目录写。操作系统不是并列知识点,而是一个为解决具体问题而层层叠加的工程方案。你的 PDF 目录应该长这样:

用户场景操作系统要解决的问题关键机制xv6/Linus 实现位置你验证过的证据
打开一个文本文件并显示内容如何让不同程序访问同一磁盘数据而不冲突?文件系统抽象(inode/dentry)、缓冲区缓存(buffer cache)kernel/fs.c,kernel/bio.cstrace cat README显示openat→read→write;cat /proc/mounts查挂载点
同时运行浏览器和音乐播放器如何让 CPU 在毫秒级切换任务而不崩溃?时间片轮转、上下文保存/恢复(trapframe)、调度队列kernel/proc.c,kernel/trap.cps输出多进程;grep -n "swtch"查汇编切换点;cprintf打印myproc()->state变化
编译大型项目时内存不足如何让 4GB 物理内存跑起 10GB 程序?请求分页、缺页中断(page fault)、页框回收(LRU)kernel/vm.c,kernel/swap.cvmmap命令查进程虚拟地址布局;cat /proc/meminfo看PageTables占用

注意:这张表不是让你填空,而是写作时的检查清单。每写一段“虚拟内存”,必须对应到“编译大型项目”这个场景,必须写出你在 xv6 里看到的trap.c中usertrap()如何识别scause == 13(即 page fault),必须写出你修改uvmunmap()后make qemu报错的具体行号。没有验证痕迹的文字,一律删掉。

2.3 第三阶段:用「对比式图解」固化易混淆机制(手绘 > Mermaid)

PDF 里禁止出现“如图所示”却无图。但你不需要专业绘图工具——用纯文本 ASCII 图 + 关键注释,比矢量图更直击本质。例如解释“用户栈 vs 内核栈”:

用户态进程 A(PID=3): +-------------------+ ← 用户栈顶(0x7fffffffe000) | local var 'i' | | return address | ← call sys_read() 时压入 +-------------------+ | ... | +-------------------+ ← 用户栈底(0x7fffffff8000) → 执行 read() 系统调用 → trap 进入内核 内核态(同一进程,但切换栈): +-------------------+ ← 内核栈顶(0xffffffff80002000,固定偏移) | struct trapframe | ← 保存用户寄存器(ra, sp, a0...) | | +-------------------+ | kernel stack vars | ← sys_read() 局部变量 | ... | +-------------------+ ← 内核栈底(0xffffffff80000000) → sys_read() 返回 → trap_return() 恢复用户寄存器 → ret

关键注释必须写:

  • 用户栈地址范围由mmap(MAP_ANONYMOUS)分配,内核栈地址由proc->kstack指向固定物理页;
  • trapframe是内核在用户栈切换前,用copyin()从用户空间拷贝的现场快照;
  • trap_return()最后执行sret指令,不是函数 return,它直接跳回用户ra寄存器值。

这种图你画一遍,就永远记得“系统调用不是函数调用,是特权级切换”。


3. 避坑:写《操作系统自己总结.pdf》时最常翻车的 4 个认知陷阱

3.1 现象:把“进程”当成一个静态结构体,写满struct proc字段却说不清生命周期

原因:只看了proc.h定义,没跟踪allocproc()→fork()→exit()→freeproc()全链路。state字段在RUNNABLE/RUNNING/SLEEPING/ZOMBIE间流转,但ZOMBIE状态下proc结构体仍存在,只为等父进程wait()清理——这是“进程”概念的精髓:它是一段持续的资源占用与状态变迁,不是一张快照。
解决:在proc.c的exit()里加cprintf("PID %d entering ZOMBIE\n", myproc()->pid);,在wait()里加cprintf("PID %d reaped child %d\n", myproc()->pid, p->pid);,运行sh→sh→exit,观察日志顺序。你会看到:子进程先变 ZOMBIE,父进程wait()后才freeproc()。

3.2 现象:描述“虚拟内存”时堆砌“MMU”“TLB”“页表项”术语,却无法解释malloc(1024)后cat /proc/pid/maps为何显示[heap]区域

原因:混淆了“地址空间抽象”和“物理内存分配”。malloc()只修改进程的brk值(即 heap 边界),并不立即分配物理页——直到第一次写该地址触发 page fault,内核才调kalloc()分配页框并建立页表映射。
解决:用gdb附加 xv6 的user/sh进程(需make qemu-gdb),在sys_brk()断点,观察myproc()->sz变化;再在usertrap()的 page fault 分支断点,查看r_scause和r_stval寄存器值。你会看到:brk调用后sz增大,但p->pagetable中对应虚拟地址的 PTE 仍是 0;首次写该地址时usertrap()捕获 fault,调uvmalloc()分配页。

3.3 现象:写“文件系统”时只提 inode、superblock,却说不清open("/README", O_RDONLY)后read()怎么找到磁盘扇区

原因:跳过了路径解析(path resolution)这一关键环节。open()不是直接查 inode,而是逐级解析/→README:先读根目录 inode(固定为 1),在目录数据块中线性搜索README名字,得到其 inode 编号,再读该 inode 获取数据块地址。
解决:在fs.c的namei()函数开头加cprintf("namei: resolving %s\n", path);,在dirlookup()中加cprintf("dirlookup: found %s -> inum %d\n", name, de.inum);。执行cat README,日志会显示namei: resolving /README→dirlookup: found README -> inum 2。这就是路径解析的实证。

3.4 现象:总结“IO 子系统”时罗列 DMA、中断、轮询,却无法解释printf("hello")为何不卡住整个内核

原因:忽略了字符设备的缓冲与异步特性。printf调用write()→sys_write()→consoloe_write(),但 console 设备驱动将字符写入cons.buf环形缓冲区后立即返回,由中断服务程序(uartintr())在后台逐字发送。
解决:在console.c的consolewrite()中加cprintf("consolewrite: %d chars queued\n", cons.outcount);,在uartintr()开头加cprintf("uartintr: sending %d\n", cons.outcount);。执行echo hello,你会看到consolewrite日志先出,uartintr日志延后几毫秒才出——这就是异步 IO 的呼吸感。


4. 让 PDF 具备“可验证性”的 3 个硬核技巧:拒绝纸上谈兵

4.1 技巧一:每个结论后标注「验证命令 + 预期输出片段」

教科书说“进程有独立虚拟地址空间”,你的 PDF 必须写:

结论:同一程序多次运行,其代码段虚拟地址相同(ASLR 关闭时),但物理页帧不同。
验证:在 Ubuntu 22.04 上关闭 ASLR:echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
运行两次./a.out(简单 C 程序),记录 PID:

$ ./a.out & echo $! # PID 12345 $ ./a.out & echo $! # PID 12346 $ cat /proc/12345/maps | grep r-xp | head -1 00400000-00401000 r-xp 00000000 08:01 1234567 /home/user/a.out $ cat /proc/12346/maps | grep r-xp | head -1 00400000-00401000 r-xp 00000000 08:01 1234568 /home/user/a.out

关键观察:两行虚拟地址范围完全一致(00400000-00401000),但inode号不同(1234567vs1234568),证明内核为每个进程分配独立页表,映射到不同物理页。

这种写法强迫你回归第一性原理:结论必须能被一行 shell 命令证伪或证实。没有验证路径的结论,就是空中楼阁。

4.2 技巧二:用「diff 式代码对比」揭示机制演进

操作系统不是静态规范,而是历史妥协。你的 PDF 应展示关键机制的迭代。例如“进程调度策略”:

xv6(2020)Linux 5.15(2022)差异本质
scheduler()循环扫描proc[]数组,找第一个state==RUNNABLEpick_next_task_fair()使用红黑树维护cfs_rq,按vruntime排序O(n) 遍历 → O(log n) 查找,支持千进程规模
无优先级,所有进程时间片相同支持 nice 值、实时调度类(SCHED_FIFO)、CFS 虚拟运行时间公平性从“轮流执行”升级为“按权重分配 CPU 时间”
swtch()汇编直接切换寄存器__switch_to()保存 FPU/SSE 状态、TLB 刷新、per-CPU 变量切换从单核裸机 → 多核复杂环境适配

提示:不要只写差异,要指出“为什么需要变”。比如 xv6 不需红黑树,因为最大进程数 < 64;Linux 必须支持百万级进程,O(n) 扫描会导致调度延迟飙升。机制演进的驱动力,永远是真实负载压力。

4.3 技巧三:嵌入「故障注入实验」证明你真懂机制

最高阶的验证,是主动制造故障。在 PDF 里加入这类实验:

实验:禁用页表项的R/W位,观察写保护异常
步骤:

  1. 修改kernel/vm.c的uvmmap(),在设置 PTE 时强制清除PTE_W位:
// 替换原 uvmmap 中 pte = PA2PTE(pa) | PTE_R; pte = PA2PTE(pa) | PTE_R; // 去掉 PTE_W!
  1. make clean && make qemu
  2. 在 shell 中运行:
#include "kernel/types.h" int main() { char *p = sbrk(4096); // 分配一页 p[0] = 'A'; // 触发写,应触发 page fault return 0; }
  1. 预期结果:QEMU 崩溃,日志显示usertrap(): unexpected scause 0x000000000000000d(即 store access fault)

意义:你亲手关闭了写权限,内核果然报错——这比背一百遍“PTE_W 控制写权限”更深刻。真正的理解,是你能预测故障,也能修复它。


5. 我坚持十年的 PDF 写作铁律:用「三色笔注释法」对抗遗忘曲线

我从 2014 年写第一份《Linux 内核精简笔记.pdf》开始,就用三种颜色笔在打印稿上手写批注,至今未改。这不是复古情怀,而是对抗操作系统知识高遗忘率的生理刚需——它的概念太抽象、链路太长、细节太琐碎,纯电子文档看三遍不如一次手写刺激海马体。

  • 蓝色:写「机制触发条件」。例如在“缺页中断”段落旁批:* 触发条件:CPU 访问有效虚拟地址,但 PTE.P == 0 或 PTE.U == 0(用户态访问内核页)。蓝色是冷知识,必须精确到寄存器位。
  • 红色:标「故障现象与定位指令」。例如在“进程僵死”旁批:! 现象:ps 显示 ZOMBIE;定位:cat /proc/PID/status | grep State;修复:父进程调 wait()。红色是救命指南,写在 PDF 页边,比 Ctrl+F 更快。
  • 绿色:记「个人血泪经验」。例如在“系统调用号”旁批:✓ 教训:xv6 的 SYS_write 是 16,Linux x86_64 是 1,ARM64 是 64 —— 别硬背数字,用 /usr/include/asm/unistd_64.h 查!。绿色是时间胶囊,十年后你重读,会笑自己当年怎么栽在这儿。

现在我用 Obsidian + PDF 插件模拟这套流程:蓝色用{{blue:...}},红色用{{red:...}},绿色用{{green:...}},导出 PDF 时自动渲染。但核心没变——知识必须经过你手的物理摩擦,才能从硬盘刻进神经突触。你写《操作系统自己总结.pdf》时,如果没在页边留下至少三处手写批注,它就只是另一份待删除的电子垃圾。

最后说句实在的:这份 PDF 写到第三遍时,你会突然发现——面试官问的“Linux 如何处理 page fault”,你脱口而出的不再是教材定义,而是mm/memory.c里do_page_fault()函数的第 47 行if (fault_code & PF_PROT)判断,以及你上周在 QEMU 里亲手触发它时,dmesg输出的pgtable_bad日志。那一刻你知道,操作系统终于从纸面跳进了你的肌肉记忆。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询