1. 先别急着背名词把操作系统的骨架搭起来复习操作系统的时候我一度觉得这是所有课程里最让人心里没底的一门。倒不是因为它难而是知识点太散今天讲进程调度明天讲内存分页后天又跑到磁盘算法去了每章单独看都能看懂合上书却串不起来。这篇我就把操作系统知识体系里的核心模块按一条主线串一遍结合我实际复习和在虚拟机里折腾系统时踩过的坑比如给虚拟机装系统时遇到的客户机操作系统已禁用CPU提示、64位Windows可用内存只有一半这类问题。内容定位偏向期末复习和面试前的快速梳理适合正在啃操作系统笔记的在校生也适合准备系统方向面试的开发者。如果你和我一样用的是王道操作系统笔记、慕课版教材这类资料会发现它们的章节顺序大致是进程管理、内存管理、文件管理、设备管理外加系统调用。这个顺序其实暗合了一条逻辑线操作系统是资源的管理者它管理的最核心资源有三种——CPU、内存、磁盘和I/O设备。所谓知识点汇总与其每个名词平铺一遍不如先把这条资源管理的主线立起来后面所有的算法、数据结构、策略都是围绕怎么把这些资源分配好、调度好、别出错展开的。1.1 操作系统解决的本质问题操作系统的定位可以压缩成一句话它是位于硬件和应用软件之间的系统软件对内管理硬件资源对外给应用程序提供使用硬件的统一接口。注意这里有两层关系很多人只看得到第一层也就是管理硬件却忽略了第二层提供接口。为什么第二层很重要因为应用程序不可能直接去操作磁盘、网卡、内存条。如果每次写文件都要自己去算柱面号、扇区号每个软件都得写一份磁盘驱动那软件生态就崩了。操作系统把硬件细节包起来给程序员提供文件进程网络连接这样的抽象概念程序员只需要调接口剩下的交给内核。我考研复习那会儿最深刻的体会就是操作系统这门课的每一章本质上都在讲一种资源 一套管理策略。进程管理管的是CPU资源内存管理管的是内存资源文件与设备管理管的是磁盘和I/O资源。把这句主线刻在脑子里再去看各种算法就不会觉得它们是孤立的了。1.2 四大核心模块与系统调用的关系顺着资源主线展开操作系统的核心模块可以分成四大块进程与线程管理负责CPU资源的分配、调度、回收以及多任务之间的同步与通信。内存管理负责给进程分配内存、地址转换、虚拟内存、内存回收。文件管理负责磁盘上数据的组织、目录结构、文件的读写与权限控制。设备管理负责键盘、鼠标、磁盘、网卡等I/O设备的驱动、分配与传输控制。四大模块之间的桥梁就是系统调用。用户进程要申请内存、创建进程、读写文件都要通过系统调用进入内核态让内核代劳。所以系统调用不是独立的一章而是连接用户程序和操作系统服务的通道。我后面会在第6部分专门展开这条路径因为只背概念不画路径图遇到实际问题是套不上的。1.3 用户态与内核态为什么切换会消耗性能先聊一个贯穿所有模块的基础设定CPU指令分特权级操作系统内核运行在高特权级的内核态能执行特权指令比如启动I/O、修改页表寄存器用户程序运行在低特权级的用户态很多硬件操作做不了。这个设计的初衷是安全。如果一个普通程序可以随便写内存管理寄存器那整个系统就乱套了。代价是用户态想干特权事时必须陷入内核态这个陷入和返回的动作本身有开销。所以现在很多高性能程序设计都在刻意减少系统调用次数比如用mmap映射大文件而不是反复read用io_uring这类批量异步机制降低陷入内核的频率。我在实际环境中遇到过指定的可执行文件不是此操作系统平台的有效应用程序的报错表面看是文件格式问题实际上也跟CPU指令集和系统调用接口相关一个为x86_64平台编译的可执行文件拿到ARM架构的机器上跑它的启动代码里包含的指令集和系统调用号跟当前内核不匹配自然没法运行。这种问题在操作系统原理里属于系统调用与可执行文件格式的范畴只是教材里讲得偏理论真碰到了才意识到它在现实世界一直存在。2. 进程与线程知识体系里的第一座大山进程管理是操作系统复习的重头戏分数占比往往最高。原因也很实在CPU的调度、并发执行、线程模型、进程通信、同步互斥全部压在这一块。我建议把这一章拆成几条线分别吃透别混在一起背。2.1 进程状态转换一张图串起所有调度逻辑经典进程状态图是新建态、就绪态、运行态、阻塞态、终止态。这里最容易出错的地方在于运行态到阻塞态是进程主动等待某事件比如等待I/O完成而就绪态到运行态是被调度程序选中的。很多初学者会把阻塞和就绪搞混其实判别标准很简单阻塞态的进程即使把CPU给它它也无法继续执行因为它在等资源就绪态的进程万事俱备只欠CPU。进程控制块PCB是理解状态转换的关键。PCB里记录了进程标识符、状态、程序计数器、寄存器保存区、内存分配信息、打开文件列表等。所谓进程切换就是保存当前进程的PCB、恢复下一个进程的PCB这个动作叫上下文切换。上下文切换的开销是实实在在的CPU时间损耗所以操作系统的调度目标之一就是尽量降低不必要的切换频率。2.2 线程为什么比进程轻量进程是资源分配的基本单位线程是CPU调度的基本单位。这句话在考试里几乎必考。它背后的意思是同一个进程里的多个线程共享进程的地址空间、打开的文件、全局变量所以线程切换不需要切换整个内存映射关系只需要切换线程自己的栈和寄存器上下文明显比进程切换轻量。但轻量不等于没有代价线程共享地址空间也意味着一个线程越界写内存整个进程直接崩掉。多线程程序里各种奇奇怪怪的bug比如数据竞争、死锁本质上都源于共享状态。我自己的经验是学线程的时候别只盯着概念一定要在Linux里实操一遍pthread_create和join。当你真正写出一个用4个线程并发累加共享计数器、却得出错误结果的程序时对临界区和原子性的理解绝对比背十遍教材都深刻。2.3 进程通信管道、共享内存、消息队列怎么选进程通信是期末简答题和面试题的高频区。常见的通信方式管道半双工、字节流、适合父子进程或兄弟进程之间简单传递数据。命名管道FIFO可以在无亲缘关系进程之间通信通过文件系统路径标识。消息队列按消息块传递有格式边界适合进程间交换结构化数据。共享内存最快多个进程直接映射同一块物理内存区域但必须自己加同步措施。信号量本质上不是用来传数据的而是用来保证进程间对共享资源的互斥访问。套接字Socket跨机器通信的通用方案本地也可以用Unix Domain Socket。选型逻辑分三个层面如果只是简单同步用信号量如果要传数据数据量小且需要格式边界用消息队列数据量大、追求吞吐用共享内存跨主机通信用Socket。共享内存本身不提供同步机制所以实际项目里往往是共享内存 信号量组合使用。2.4 处理机调度从算法公式到Linux实际行为调度这块考试最爱考的是各种调度算法的平均等待时间、平均周转时间计算。常见的算法我按适用场景分类先来先服务FCFS对长作业有利对短作业不利容易产生护航效应。短作业优先SJF平均周转时间理论最优但需要预知运行时间现实里很难实现。高响应比优先HRRN兼顾FCFS和SJF响应比 (等待时间 要求服务时间) / 要求服务时间。时间片轮转RR适合交互式系统时间片大小直接影响响应时间和切换开销。多级反馈队列MLFQ就是Linux等系统实际使用思路的简化版高优先级队列时间片短低优先级队列时间片长。算这些算法时有个技巧先画甘特图把所有进程的到达时间和运行时间按时间轴铺开再一格一格填调度决策。宁可多花两分钟画图也别凭直觉心算因为很多细节比如到达时间相同的进程、SJF是否抢占不画图很容易漏。Linux中实际用的是CFS完全公平调度器它的核心不是算时间片而是用vruntime记录每个进程的虚拟运行时间调度器每次选vruntime最小的进程运行。这个思想和教材里的多级反馈队列不完全一样更接近按比例分配CPU时间的理念。面试如果被问到Linux进程调度用的什么算法能讲出vruntime和红黑树这两点通常就过关了。3. 内存管理地址转换和虚拟内存是怎么配合的内存管理这一章很多人卡在为什么要有虚拟内存这个问题上。我先给一个直观答案为了让多个程序同时运行时不互相干扰并且让程序员以为自己的程序拥有连续的巨大地址空间。3.1 逻辑地址、物理地址与地址转换编译链接后生成的程序里指令和数据的地址是逻辑地址也叫虚拟地址。程序加载到内存后才对应到物理地址。操作系统负责在这两者之间做映射。这个映射过程有两种经典做法连续分配的基址限长寄存器以及非连续分配的页表。基址限长方式最简单进程的基址寄存器存起始物理地址限长寄存器存长度每个逻辑地址加上基址就是物理地址如果算出的地址超过基址限长就触发越界中断。这种方式的缺点是内存碎片严重而且是连续分配进程难以扩展。所以现代系统普遍用分页。物理内存被切成大小固定的页框一般4KB进程的逻辑地址空间被切成同样大小的页页表记录每个逻辑页对应哪个物理页框。逻辑地址除以页大小得到页号余数是页内偏移查页表得到物理页框号再拼接偏移就是物理地址。这里我踩过一个特别典型的坑。有一台64位的Windows机器插了4GB内存但系统显示只有2GB可用。排查到最后发现原因在BIOS里开启的内存重映射选项和系统版本支持上——某些32位内核或未开启PAE的环境物理地址总线宽度只能寻址到4GB以下的一块区域部分插槽的物理内存就映射不进去了。教材里的逻辑地址空间与物理地址空间的映射关系、地址总线宽度限制这些知识点在那一刻全部对上了。3.2 分页和分段两种思路的取舍分页和分段是两种不同的地址切分思路。分页是系统视角为了管理物理内存方便把地址空间切成等长的小块不考虑程序逻辑结构。优点是外部碎片消除、内存利用率高、分配简单缺点是一个程序的代码可能被切散不过对用户透明用户无感知。分段是用户视角按程序逻辑模块代码段、数据段、堆栈段切分每个段有自己的长度和属性。优点是方便共享比如多个进程共享同一份动态链接库的代码段、方便保护段表里可以记录只读、可执行权限缺点是段长不固定分配时产生外部碎片。很多教材讲到这儿就停了但实际系统是分页分段结合。x86-64架构的现代操作系统基本都采用分段简化 分页为主的策略用页表做地址映射段大部分时候只保留逻辑界定功能。考试里如果问分页分段区别记得从看问题的角度碎片类型共享与保护方式三个维度对比答题。3.3 虚拟内存与缺页中断局部性原理的实际作用虚拟内存的基本思想是进程运行的时候不必把所有页面全部装入内存只需装入当前要用的部分。如果访问的页面不在内存里会产生缺页中断操作系统从磁盘调入该页可能还要淘汰一个现有页面。支撑这个机制的理论基础是局部性原理。时间局部性指刚访问过的指令和数据很可能很快再次访问典型的循环空间局部性指地址邻近的数据很可能一起被访问典型的数组顺序遍历。因为有局部性按需调页才不至于让系统频繁在内存和磁盘之间倒腾。学这块时最好配合实际数据感受一下。我用mmap映射一个几个GB的文件然后顺序读观察内存占用曲线你会发现实际占用的物理内存远小于文件大小因为内核只把访问过的页面装入page cache未访问区域留在磁盘上。这就是虚拟内存的直观表现。3.4 页面置换算法从理论到Linux实际行为缺页中断发生后如果内存已满需要选择一个页面淘汰这个选择策略就是页面置换算法。常考的最佳置换OPT淘汰未来最长时间不会被访问的页面理想但不可实现通常用它做基准。先进先出FIFO可能出现Belady异常即分配的物理块增加反而缺页率上升。最近最久未使用LRU依据过去最久未用未来也不太可能马上用的假设硬件开销大。时钟算法Clock相当于LRU的近似实现用访问位循环扫描性能开销可控。Linux的页面回收机制并没有完全采用纯LRU因为真正的LRU需要精确记录每个页的访问时间代价太高。它用的是两个LRU链表活跃链表和不活跃链表页面在活跃链表不活跃链表之间移动配合访问位来近似判断热度。这个设计思路很值得细品理论算法到工业落地之间往往要加一层够用就好的工程取舍。4. 文件系统与磁盘应用题的重灾区文件系统这块刷题时经常出现磁盘块分配目录查找次数磁盘调度总寻道时间之类的计算题其实核心就两个问题数据在磁盘上怎么组织怎么访问最省事4.1 文件的逻辑结构与物理结构逻辑结构是用户视角文件是字节流还是有结构的记录集合。大多数操作系统的普通文件就是无结构的字节流由应用程序自己解释格式。物理结构是文件在磁盘上怎么存放的连续分配文件占据一段连续的磁盘块读取快、支持随机访问但容易出现外部碎片扩展困难。链式分配每个块里存一个指针指向下一块顺序访问好但随机访问极慢而且指针还会占空间。索引分配为每个文件建一个索引块里面存放所有数据块的地址随机访问能力强是现代文件系统的标配。Linux的ext系列文件系统在索引分配基础上做了多级索引和直接块支持小文件和大文件的高效访问。4.2 目录结构、索引节点与软硬链接目录本质上也是一种文件内容存的是目录项 inode编号的映射。在Linux里真正存储文件元数据权限、大小、时间戳、数据块指针的是inode节点文件名只是指向inode的一个链接。所以同一个文件可以有多个硬链接它们共享同一个inode删除其中一个链接数据不会被删除只有inode的链接计数降为0才会真正释放。软链接符号链接则是创建一个新文件里面存目标文件的路径字符串。目标文件被删除后软链接就断链了。考试中常问硬链接能不能跨文件系统——不能因为inode编号只有在自己文件系统内才有意义软链接可以跨文件系统因为它存的是路径名。我在实际使用中建议如果只是想做个快捷方式用软链接如果想让多个目录共享同一份文件且不想维护同步用硬链接。但别对目录做硬链接普通用户一般也不被允许否则目录结构会出现环。4.3 磁盘调度算法目标是什么怎么选磁盘调度的核心目标是减少寻道时间因为移动磁头是机械操作比读写数据本身慢好几个数量级。常见算法先来先服务FCFS公平但磁头臂可能来回乱跑。最短寻道时间优先SSTF优先处理离当前磁道最近的请求可能使远处的请求长期饿死。扫描算法电梯算法磁头臂沿一个方向移动处理沿途所有请求到端点再折返。循环扫描算法C-SCAN只朝一个方向服务到端点直接回到最远端再开始响应时间更均匀。考试计算时先画磁道数轴标出当前磁头位置和请求序列再按照算法规则一步步算移动距离就行。这里有个常见丢分点SSTF每次新请求到达前可能改变选择画图时要注意当前磁头位置是动态变化的不是固定在初始位置。5. 同步、互斥与死锁PV操作背后是真实的并发场景并发是操作系统这门课的灵魂所在也是让人觉得最难啃的部分。因为它需要你跳出顺序执行的思维惯性学会从多条执行流并行交织的角度看问题。5.1 临界区与互斥为什么原子性不是万能能访问共享资源的代码段叫临界区。要保证临界区互斥最简单粗暴的方案是禁止中断这在单CPU上有效但在多核CPU上不行因为关中断只管得了当前CPU其他CPU照样能进临界区。所以现代系统普遍用原子指令比如x86的xchg、cmpxchg它们保证了一个读-改-写操作在硬件层面不可被其他CPU插入。自旋锁就是基于这类指令实现的线程拿不到锁就在原地空转等着适合临界区极短的情况如果临界区长自旋浪费的CPU太多应该用可睡眠的互斥锁。操作系统课程的经典作业是用信号量实现哲学家就餐这道题的价值在于它模拟了多线程资源竞争时每个线程都只考虑自己、不考虑全局导致的死锁场景和真实分布式系统里的死锁成因高度一致。5.2 信号量与管程两种解决同步问题的思路信号量是一个整数加两个原子操作P操作wait减1若小于0则阻塞和V操作signal加1若小于等于0则唤醒一个等待进程。信号量的核心技巧是把可用资源数抽象成一个计数器用P操作申请资源、V操作释放资源。管程则是一种更高层的语言级同步机制它把共享资源和对它的操作封装在一个模块里同一时刻只允许一个进程进入管程。Java的synchronized、Go的sync.Mutex等都是类似思路的产物。考试里经常问信号量和管程的区别核心是信号量使用不当容易出错因为P、V操作分散在代码各处全凭程序员自觉管程把同步逻辑收敛在模块内部出错的概率小很多。5.3 死锁的四个必要条件、预防、避免与检测死锁的四个必要条件是互斥、持有并等待、不可剥夺、循环等待。这四个条件得背熟但更重要的是理解互斥资源不能同时被多个进程共享使用这是很多资源的天性没法直接消除。持有并等待进程拿着一个资源又去申请别的资源。可以通过一次申请所有资源来预防但实用中会降低资源利用率。不可剥夺已经分配的资源不能被强行抢走。可以设计成申请不到就主动释放已有资源实现复杂。循环等待进程间形成一个等待环。可以通过给资源编号、按序申请来破坏。银行家算法属于死锁避免它的本质是系统在分配资源前检查一下按当前分配方案是否还能找到一条让所有进程都能完成的路径找不到就拒绝分配。这个算法在期末考里年年出现因为公式化很强画好Available、Max、Allocation、Need四张表按安全序列检查。但我得说实话真正常用操作系统里没有用纯银行家算法的它更多是一个理论模型用来训练你对不安全状态的判断能力。6. 系统调用与Shell把理论知识接到真实机器上很多同学复习操作系统时有个通病概念背得很熟但问程序从启动到printf输出一行字内核里都发生了啥就答不上来。这一部分我想专门把系统调用从理论概念落到一条完整路径上。6.1 从printf到write系统调用的完整路径当你在Linux里调用C标准库的printf(hello\n)时背后大概是这样的流程程序在用户态执行printfC库负责格式化字符串。C库检测到输出目标是终端文件描述符1最终会调用write系统调用。在x86-64架构上write的封装函数会把系统调用号write对应1号放入rax寄存器参数放入rdi、rsi、rdx等寄存器然后执行syscall指令。syscall指令触发CPU切换到内核态跳转到内核预先设置好的系统调用入口。内核根据rax里的系统调用号查sys_call_table找到对应的sys_write函数。sys_write根据fd找到对应的文件对象再调用具体文件系统/设备驱动的写操作把数据写到终端或缓存区。完成后内核返回值放回rax通过sysret指令切回用户态恢复原程序的执行。这条链路能串起无数知识点系统调用号与内核函数映射可以看成内核给用户程序开的API清单、用户态内核态切换、文件描述符与文件表、设备驱动、终端驱动程序。考试如果让你解释系统调用和库函数的区别就从这条路径里随手拿例子讲比干巴巴背定义得分高多了。6.2 用户态与内核态的切换开销为什么频繁系统调用会慢因为每次syscall/sysret都要做这些事保存用户态寄存器现场、切换到内核栈、查系统调用表、执行内核代码、恢复用户态上下文。虽然现代CPU对syscall指令做了硬件加速但毕竟比普通函数调用多好几个数量级的开销。程序员层面的优化思路是减少切换次数。比如经典的批量小写问题逐字节调read/write和一次性调read/write配上大缓冲性能差距可以到几十倍。再比如用mmap代替read让页面通过缺页中断映射进进程地址空间本质上也是把高频小批量的系统调用换成一次映射加按需调页。面试聊I/O模型时零拷贝技术也是同样的思路在内核里把数据从页缓存直接发送到网卡绕开用户态减少两次上下文切换和内存拷贝。6.3 从启动流程看操作系统的边界最后一个我特别推荐的实操角度观察操作系统的启动流程。以虚拟机为例你在VMware里装Linux发行版比如麒麟或统信UOS安装过程包括分区、引导加载程序、内核启动、init进程初始化这些环节每一步都和教材挂钩引导加载程序GRUB加载内核镜像到内存负责从BIOS/UEFI接管控制权。内核初始化阶段先设置中断描述符表、页表再探测硬件设备挂载根文件系统。内核启动第一个用户进程通常是init或systemdPID为1它是所有用户进程的祖先。之后才由init拉起各种服务sshd、cron等最终到你打开终端、输入用户名密码。如果你在虚拟机里遇到过客户机操作系统已禁用CPU请关闭或重置虚拟机的报错大概率是虚拟机设置里的CPU虚拟化特性比如VT-x/AMD-V没有正确传递或者虚拟机内存分配与固件设置不匹配导致的。这时候要去检查宿主机BIOS是否开启虚拟化、虚拟机CPU配置是否勾选了正确的特性、以及客户机内核是否支持PAE/NX等。这个过程虽然麻烦但超级锻炼人你会被迫把CPU虚拟化内核态特权指令中断/异常处理这些概念全部联系起来比单纯刷题管用得多。7. 一些容易混淆的知识点和复习建议最后一章不塞新知识纯粹是把复习过程中容易踩的坑和总结的应试经验分享给你。7.1 高频混淆点清单我把自己刷王道操作系统和慕课版课后题时遇到的高频混淆点整理成了表考前过一遍能省很多时间容易混淆的知识点区分关键进程与线程进程是资源分配单位线程是CPU调度单位线程共享进程地址空间用户态与内核态切换、进程切换前者是CPU特权级变化后者是切换执行对象可能同时切换特权级阻塞与挂起阻塞是等资源/事件挂起是进程被整体从内存移到外存管程与信号量管程是语言机制封装同步逻辑信号量是底层原语需要程序员自行控制硬链接与软链接硬链接共享inode软链接存目标路径分页与分段分页等长、无外部碎片、对用户透明分段不等长、有外部碎片、用户可见逻辑单元SJF与SRTFSJF非抢占SRTF是抢占式短作业优先计算平均等待时间时结果可能不同我自己复习时犯过的错是把FCFS的平均等待时间和SJF搞混因为SJF的平均等待时间一定不大于同样负载下FCFS的平均等待时间但有些题会故意给出到达时间相同的前提目的就是为了简化为短作业优先。看清题目条件再动手比记住结论更重要。7.2 期末复习和面试准备的侧重点如果是期末复习我的建议是三步走先做主线串联自己动手画一张思维导图从资源管理出发把CPU调度、内存分配、文件系统、设备管理、并发同步全部连起来。再刷课后计算题调度算法甘特图、银行家算法安全序列、页表地址转换、磁盘寻道距离这四类题每次考试基本都会出务必练到闭眼能算。最后过概念简答题状态转换条件、死锁必要条件、虚拟内存特征、系统调用与库函数区别这类题考察精准记忆建议每个考点准备一个一句话解释 一个实例的答题模板。如果是面试准备重点反而有所不同。面试官更关注你对并发编程的理解锁、CAS、线程池、并发容器、对Linux内核机制的了解进程调度、内存回收、文件系统、I/O模型以及排查问题的思路。我面试时被问到过线上服务内存突然上涨你怎么排查这个问题虽然开放但本质上是在考察你对内存管理、页表、缺页中断、系统调用、进程地址空间布局等多个知识点的综合掌握程度。我个人非常推荐一个复现实验在虚拟机里从零编译一次Linux内核然后用它启动一个最小系统。这个过程会把内核配置、模块加载、内存管理、进程启动全部走一遍比任何速成笔记都记得牢。我当初花了一个周末做这件事很多曾经背了又忘的知识点从此再也没忘过。7.3 实际复习中的一点体会回到这篇汇总的开头那句话操作系统知识点确实散但只要骨架立住了散装的细节就能一点一点挂到骨架上去。我的体会是不要试图靠一遍一遍翻笔记来建立全局观要主动输出合上书拿出一张白纸把自己能记住的模块画出来画不出来的地方就是你的知识盲区再回到笔记里针对性补漏。这个方法同样适用于做题。遇到一道不会的题先别急着看答案写下你卡在哪一步——是不知道用哪个算法还是不会画调度图还是没理解某个概念的前提条件。把这些卡点攒起来你会发现自己的薄弱点高度集中补起来效率很高。另外操作系统的学习一定要落到实操里哪怕只是在自己电脑上跑几条命令。打开终端敲一下ps -ef看看进程树用top看看内存占用用strace跟踪一下系统调用都比单纯啃书收获大。这些工具不会出现在考卷上但能把教材里那些抽象概念变成你亲眼所见、亲手操作过的东西。到那时候操作系统对你来说就不再是一门需要死记硬背的学科而是一套能真实解释你电脑上一切现象的知识框架。
阅读完成 · 觉得有帮助?