你在文档里敲完最后一个字,按下保存,界面只闪了一下“已保存”。从人的角度看,这件事像是一个瞬间完成的动作;从计算机内部看,它却是一场接力:应用程序先整理要写入的数据,通过受控入口请求操作系统服务;操作系统检查路径和权限,把文件名解析成具体的文件对象,再把数据交给缓存、文件系统和设备驱动;存储设备完成写入后,又用中断告诉处理器“这批工作做完了”。如果中途断电,文件系统还要尽量避免目录已经更新、数据却没有落盘的尴尬局面。
浏览器播放视频、聊天软件接收消息、系统下载更新,也都在争用处理器、内存、网络和存储设备。硬件资源有限,程序的请求却随时会来,而且彼此并不信任。谁先使用处理器?一个程序能占多少内存?两个程序同时写同一个文件怎么办?出错的程序为什么通常只会关闭自己,而不是拖垮整台电脑?这些矛盾,正是操作系统每天在处理的事。
操作系统可以先理解成计算机中的“规则执行者”。它向上给程序提供稳定、统一的抽象,向下控制真实硬件,同时在多个请求之间做分配、隔离和记账。这里的“操作系统”也不只等于一个图形桌面。桌面、命令行、系统服务和系统库都可能属于用户可见的系统环境,而真正长期驻留、拥有最高硬件权限的核心部分叫作内核。理解这层区别,后面很多概念就不会混在一起。
判断一项机制是不是操作系统问题,可以问两个问题:它是否在隐藏硬件差异,给程序提供更好用的抽象?它是否在多个程序之间分配、保护或回收共享资源?只要其中一个答案是“是”,通常就已经进入操作系统的职责范围。
应用程序喜欢面对简单对象:文件可以打开和读写,进程可以创建和结束,内存看起来像一大片连续地址,网络连接像一条可以收发字节的通道。硬件却没有这些现成概念。固态硬盘只理解控制器命令和数据块,网卡只负责发送与接收帧,处理器只会取指、执行和响应事件,内存则是一组按地址访问的存储单元。
操作系统把底层细节重新包装成上层容易使用的对象。应用调用 read 时,不必知道数据来自固态硬盘、机械硬盘、终端、管道还是网络设备;它拿到的是“从一个已打开对象中读取若干字节”这个统一动作。程序申请内存时,也不用先调查哪几条物理内存恰好空闲。内核负责把程序看到的虚拟地址翻译成实际存放位置。
这层抽象解决了兼容性问题。相同的应用接口可以落到不同品牌、不同结构的设备上,设备差异由驱动和内核消化。代价也很真实:每增加一层检查、翻译和缓存,就会增加状态与开销;抽象得越统一,越可能遮住某些硬件的独特能力。因此操作系统接口总在“足够通用”和“允许高效控制”之间找平衡。

操作系统位于应用程序与硬件之间,为上层提供统一接口并屏蔽底层硬件细节。
我们可以把一台正在使用的计算机粗略分成四层:
分层不是说每次操作都要机械地走过一条完全相同的路线,而是说明权限和职责的边界。应用通常不能绕开内核直接操作硬件;内核也不会替应用决定文档里该写什么。上层表达目标,下层完成机制,中间通过明确接口衔接。
拿保存文件来说,较完整的路径大致是这样的:应用先把文本编码成字节,调用系统库提供的文件接口;库函数最终进入系统调用;内核检查文件描述符、访问权限和参数,把字节复制或映射到内核管理的缓存;文件系统决定这些字节属于哪个文件、应对应哪些存储块;块设备层组织请求,驱动把通用请求翻译成设备控制器理解的命令;设备完成后产生中断,内核更新请求状态并唤醒等待的程序。
“写入成功”也有不同强度。如果数据只是进入内存中的页缓存,应用很快就能继续工作,但突然断电时仍可能丢失尚未持久化的内容。如果每次写入都等待设备确认,可靠性更容易解释,交互速度却会下降。现实中的操作系统通常采用缓存、批量写回、日志或写时复制等办法,在速度和崩溃后一致性之间折中。

一次保存操作会穿过应用程序、系统调用、文件系统和设备驱动,最终把数据写入固态硬盘。
下面的交互不连接网络,也不会真的写磁盘。点击“下一步”可以观察保存请求如何从应用抵达设备,再由中断返回;切换“等待持久化”则能比较两种完成语义。
打开任务管理器时,你可能同时看到上百个进程,但处理器核心数远少于进程数。单个核心在一个瞬间只能沿着一条指令流前进。所谓“都在运行”,一部分来自多核的真实并行,另一部分来自内核在多个可运行任务之间快速切换。
操作系统会记录每个任务的运行状态。正在占用核心的是运行态;已经准备好、只等处理器的是就绪态;等待磁盘、网络、计时器或其他事件的是等待态。等待态任务没有继续执行的条件,让它占着处理器只会浪费时间,所以内核会把核心交给就绪任务。事件完成后,中断或内核事件再把它唤醒,送回就绪队列。
时间片让一个可运行任务在用完一段处理器时间后暂时让位。切换时,内核要保存寄存器、程序计数器、栈位置和调度状态,再恢复下一个任务的上下文,这叫上下文切换。时间片短,交互任务较快获得响应,但切换更频繁;时间片长,切换开销下降,排在后面的任务却可能等得更久。调度从来不是单纯追求“最快”,它还要考虑响应时间、吞吐量、公平性、优先级、截止时间和能耗。

调度器在任务之间进行上下文切换,进程在就绪、运行与等待三种状态之间按事件转换。
多道程序设计先解决的是处理器利用率:内存中放着多个程序,一个程序等待输入输出时,让另一个程序运行,别让昂贵的处理器空转。分时系统更强调交互响应:频繁抢占并轮换任务,让每个用户或前台程序都能较快得到服务。现代桌面和服务器会同时使用这两种思路,只是目标权重不同。
“并发”和“并行”也要分清。并发描述多个任务在一段时间内交错推进,即使只有一个核心也能发生;并行是多个执行单元在同一时刻真的工作。并发带来共享状态与执行顺序的不确定性,多核又让这种不确定性真正重叠,于是锁、原子操作、信号量和消息传递才会成为必要工具。
在下面的沙盘里,三个任务需要不同数量的处理器时间。你可以调整时间片,然后逐步调度。注意:更短的时间片通常让每个任务更早得到第一次响应,但上下文切换次数也会增加。
物理内存容量有限,而且地址布局受硬件约束。如果所有程序直接使用物理地址,一个程序的错误指针就可能覆盖另一个程序或内核的数据,程序还得互相协调“谁使用哪段地址”。虚拟内存把这个麻烦挡在程序外面:每个进程看到自己的虚拟地址空间,处理器中的内存管理单元根据页表把虚拟页翻译成物理页框,并同时检查读、写、执行等权限。
虚拟内存不等于“用硬盘假装无限内存”。它首先是一套地址抽象和保护机制,其次才允许按需调入页面、回收暂时不用的页面,必要时把部分内容换出。一次访问找不到有效映射时,处理器产生缺页异常。缺页可能很便宜,例如第一次为匿名内存分配零页;也可能很昂贵,例如必须从存储设备读入数据。内存紧张时,频繁换入换出会让系统大部分时间花在搬运页面上,程序便出现明显卡顿。
内核还利用共享映射减少浪费。多个进程可以把同一份只读程序代码或共享库映射到各自地址空间;写时复制允许它们先共享物理页,只有某一方准备修改时才复制。这样既保留逻辑隔离,又避免一开始就复制所有数据。代价是页表、地址转换缓存失效、页面回收和缺页处理都要消耗时间,内核必须持续在隔离、空间利用率与访问延迟之间取舍。

操作系统像中央调度台一样,对处理器、内存、文件和设备进行统一分配、隔离与协调。
文件系统把零散存储块组织成文件和目录,维护名称、大小、时间、所有者和权限。路径让人按层次查找对象,文件描述符则让进程在打开对象后用一个小整数继续读写。虚拟文件系统再提供统一入口,使不同磁盘格式、内存文件系统和某些特殊对象能共存。统一接口不代表它们性能相同:顺序读大文件、随机改小块、追加日志,对缓存和存储设备造成的压力完全不同。
设备管理也依赖抽象。驱动知道怎样设置设备寄存器、准备命令队列和解释完成状态,内核的通用层则处理排队、权限、缓冲与错误。设备很慢时,内核通常不会让处理器原地循环等待,而是把请求任务置为等待态,改去运行别的任务;设备完成后通过中断通知内核。对于高速设备,大块数据还常由直接内存访问完成,处理器负责准备描述信息和处理完成事件,不必逐字节搬运。
驱动靠近硬件,常需要较高权限,因此错误的影响可能比普通应用大。把更多驱动功能放入用户态可以加强隔离和调试便利,但跨越保护边界与数据传递可能增加延迟。不同操作系统会根据设备类型、性能目标和安全模型采用不同安排。
假如每个应用都能关闭中断、修改页表或随意向设备寄存器写值,任何一个错误都可能破坏整个系统。处理器因此提供不同特权级别。普通应用在用户态运行,不能执行某些特权指令,也不能访问内核保留的地址;内核在更高特权级运行,能够配置内存映射、处理中断和控制设备。
这道边界不是为了把内核藏起来,而是为了让所有敏感操作经过检查。应用想读文件,内核会验证句柄、缓冲区和权限;想创建进程,内核会分配标识符、地址空间与调度状态;想发送网络数据,内核会检查接口状态并把请求排队。程序崩溃时,它通常只损坏自己的用户态地址空间。内核或高权限驱动若破坏共享状态,后果往往严重得多,这正说明高权限代码需要更小、更谨慎。
系统调用是程序主动请求内核服务。程序准备系统调用编号和参数,执行专门的陷入指令;处理器切到内核入口,保存必要状态,内核验证请求并执行服务,然后恢复用户态。一次系统调用比普通函数调用更重,因为它跨越保护边界,还可能触发调度、审计或安全检查,但它换来的是统一控制和隔离。
中断通常来自处理器外部的硬件,是异步事件。键盘到来、网卡收到数据、计时器到期、存储请求完成,都可能触发中断。处理器在允许响应时暂停当前执行流,保存现场,跳到内核登记的处理入口。中断处理要尽量短,把耗时工作推迟到更合适的上下文,否则其他任务会被无谓拖延。
异常来自当前指令执行,是同步事件。除零、非法指令、越权访问和缺页都属于异常;系统调用在一些架构的分类中也通过受控陷入实现。异常不一定代表程序犯错:缺页可能只是按需分配或加载页面的正常路径。关键是内核能根据原因、地址和权限决定修复、重试、向进程发送错误,还是终止进程。

系统调用由应用程序主动发起,中断来自硬件设备,异常源于当前程序执行,三者都会跨越用户态与内核态边界。
点击不同事件,观察它从哪里发生、为什么进入内核,以及处理完成后通常回到哪里。这个练习强调“谁发起”和“是否与当前指令同步”,而不是死记名字。
进入内核时,处理器和入口代码必须把用户态现场保存到受保护的位置,切换到合适的内核栈,识别事件编号,并确保内核不会误信用户提供的指针。返回前还要处理待决信号、抢占、调试与审计状态,最后恢复寄存器和特权级。看起来只是一道门,实际维护着许多不变量。
这也解释了为什么不能把“模式切换”和“进程切换”当成一回事。系统调用会进入内核,但处理完成后可能仍回到原进程,不一定换人;调度器也可能在内核中决定保存当前进程、恢复另一个进程,那才发生上下文切换。两者经常相邻出现,却是不同动作。
现代计算机不是只有“处理器加内存”。存储、USB、网络和图形设备通常由各自控制器管理,并通过片上互连或总线与处理器和内存通信。控制器有命令队列、状态寄存器和本地缓冲区,设备驱动知道这些硬件约定。内核的其他部分则尽量面对通用接口,例如块设备请求或网络数据包,不必了解每块网卡的寄存器布局。
一次输入输出通常包含三段工作:处理器和驱动准备请求;设备控制器独立执行;完成后设备通过中断或状态队列通知内核。对于大批量数据,直接内存访问可以让设备在得到授权后直接在设备与内存之间搬运数据,减少处理器参与。这样提高了吞吐量,也要求内核仔细管理缓冲区生命周期、地址映射和设备权限,否则设备可能读写错误的内存区域。
如果没有中断,处理器可以不断读取设备状态寄存器,直到发现请求完成,这叫轮询。轮询在事件密集、延迟要求极高或等待时间极短时并非总是坏选择,因为它避免了中断入口开销;但对于偶尔到来的键盘输入或较慢的存储请求,持续轮询会白白消耗处理器时间。
中断让设备在需要服务时主动通知。系统还会使用中断优先级、屏蔽、合并和多队列分发,避免高事件率把处理器淹没。高负载网卡可能把多个完成事件合并成一次通知,减少入口开销,却会稍微增加单个数据包的等待。这里再次出现熟悉的取舍:更低延迟往往意味着更多处理开销,更高吞吐量则可能依赖批处理。
处理器寄存器极快,却只能容纳极少状态;高速缓存靠近核心,速度快但成本高、容量小;主存容量大得多,访问延迟也更高;固态硬盘和机械硬盘能长期保存大量数据,但延迟远高于内存。系统把它们组合成层次结构,让最近、最可能再次访问的数据尽量留在更快的一层。
缓存有效依赖程序访问的局部性:刚访问过的数据往往还会再用,附近地址也常紧接着被访问。缓存命中时看起来很快,未命中时就要到更慢层级取数。操作系统直接管理页缓存和存储写回,也参与页表、换页与设备队列;处理器内部的多级缓存通常由硬件自动管理,但多核之间仍要维护缓存一致性。容量、延迟、成本和能耗不可能同时最优,层次结构就是对这组矛盾的工程回答。

存储层次越靠上速度越快、容量越小,越靠下容量越大、访问代价越高。
单核系统可以并发执行许多任务,但任一时刻仍只有一个任务真正使用该核心。多核处理器把多个执行核心放在同一芯片上,多个任务能够同时推进。操作系统为每个逻辑处理器维护调度状态,把可运行任务分布到不同核心,还要考虑缓存亲和性、负载均衡、功耗和不同核心性能差异。
在对称多处理系统中,各处理器通常都能执行内核和用户代码,并访问共享物理内存。名字里的“对称”强调处理器角色大体平等,不代表访问任何内存位置的成本永远相同。大型机器可能采用非一致内存访问结构:某个核心访问本地内存更快,访问其他节点的内存更慢。调度器若只看核心是否空闲,而忽略任务的数据在哪里,迁移后可能因缓存失效和远端内存访问变慢。
多个核心同时修改共享内核数据时,简单的“先读再改再写”会发生竞争。锁能保证临界区同一时间只由有限执行者进入,但锁太粗会让核心排队,太细又增加设计和验证难度。无锁算法也不是免费午餐,它依赖原子操作、内存顺序和复杂重试。并行度提高后,瓶颈常从计算能力转移到共享缓存、内存带宽和同步点。
如果程序主要是一条前后依赖的指令链,把它放到八个核心上也无法自动拆成八份。只有能分解成相对独立任务的部分才有机会并行,而且创建任务、传递数据、同步和合并结果都有成本。操作系统负责提供线程、进程、调度和通信机制,却不能替程序发明不存在的并行性。
因此,核心数增加带来的是“可用并行能力”,不是固定倍数的加速。后台任务很多、请求彼此独立的服务器通常容易利用多核;单条强依赖计算则可能受限于一个核心。调度器还会在性能和能耗之间切换策略,空闲时让核心休眠,负载上升时再唤醒,这些动作本身也有延迟。
程序是存储中的代码和数据,进程是一次正在运行的程序实例。内核为进程维护地址空间、打开文件、身份权限和资源使用情况,也保存让它能暂停后继续的执行上下文。线程则是在进程资源范围内的一条执行流,同一进程的线程通常共享地址空间和打开文件,但各自拥有寄存器状态与栈。
进程管理不只是创建和终止。它还包括调度、等待与唤醒、父子关系、信号、同步和进程间通信。管道适合传递字节流,消息队列适合传递离散消息,共享内存避免重复复制但需要额外同步。接口选择会改变吞吐、延迟、隔离强度和编程复杂度。
内存管理要回答三件事:哪些物理页正在使用,它们映射到谁的虚拟地址,访问权限是什么。分配器负责寻找和回收空间,页表建立地址翻译,缺页处理按需补齐映射,回收器在内存紧张时选择牺牲哪些缓存或页面。权限位限制读写执行,进程隔离避免无意互相覆盖,共享映射则在明确同意的范围内打通边界。
内存分配也会形成碎片。物理页零散时,总空闲量可能不少,却难以提供一段大的连续区域。分页降低了程序对物理连续性的要求,但设备直接内存访问、大页等场景仍可能需要更连续的空间。内核会使用不同分配器、回收与整理策略处理这些需求,代价是更多后台工作和偶发延迟。
文件系统把人类可理解的路径映射到持久对象,并管理数据块和元数据。目录是名字到对象的组织关系,不等同于文件内容本身。删除一个名字也不一定立刻擦除数据;当对象仍被进程打开或还有其他名字指向它时,系统可能继续保留对象,直到最后一个引用释放。
崩溃一致性是文件系统的难题。一次操作可能同时修改目录、空间分配信息和文件内容,断电可能发生在任何一步。日志文件系统先记录将要进行的元数据变更,恢复时根据日志完成或回滚;写时复制文件系统倾向于写入新位置,再原子地切换引用。它们都要付出额外写入、空间和实现复杂度,并不能替代应用自己定义事务边界与备份。
设备管理处理发现、配置、驱动绑定、请求排队、错误恢复和电源状态。字符设备、块设备、网络接口和图形设备的访问模型不同,内核会提供适合各类设备的通用子系统。多个进程争用同一设备时,队列和调度策略会影响吞吐与尾延迟;设备离线或超时时,内核还要决定重试、失败返回或重置。
网络子系统同样由内核管理缓冲区、协议状态、路由和接口。应用看到套接字,底层却要经历协议头处理、排队、分片或重传、网卡队列和中断。为了性能,系统会合并数据、延迟确认或批量处理;为了隔离,它又会限制缓冲区、连接数和带宽。资源管理的本质始终没变:共享是提高利用率的前提,限制是保证彼此不被拖垮的条件。
进程虚拟化了处理器时间和地址空间,而虚拟机把抽象再向下推一层:客户操作系统以为自己拥有处理器、内存、中断控制器和设备,实际资源由虚拟机监控器分配。监控器截获或配合硬件处理敏感操作,为每台虚拟机维护隔离的执行和内存状态。
硬件辅助虚拟化提供专门的客户运行模式、二级地址转换和中断虚拟化,让未经修改的客户系统也能较高效地运行。虚拟设备可以完整模拟真实硬件,兼容性好但路径较长;半虚拟化驱动知道自己处在虚拟环境中,通过更直接的队列与监控器通信,通常效率更高,却需要客户系统配合。
虚拟化提高了隔离、部署与资源整合能力,但不能凭空增加硬件。多台虚拟机超额分配同一批核心和内存,负载同时升高时就会争抢;虚拟化边界也增加了调度层次、地址翻译和设备路径。快照便于保存状态,却可能制造大量写时复制数据,并不天然等于可独立恢复的备份。
容器通常共享宿主机内核,通过命名空间隔离进程视图,再用资源控制机制限制处理器、内存和输入输出。它启动快、额外开销小,但隔离边界仍依赖同一个内核。虚拟机包含独立客户内核,边界更厚,也能运行与宿主不同的操作系统环境,代价是占用更多资源并增加管理层次。
两者不是简单的替代关系。需要快速部署大量同类服务时,容器很方便;需要不同内核、较强故障边界或完整系统实验时,虚拟机更合适;现实系统也常在虚拟机里运行容器,把两种边界叠加使用。
多核共享时钟和内存,节点之间却只能通过网络交换消息。网络会延迟、丢包、重复或暂时分区,远程机器也可能在你等待响应时崩溃。单机内核能直接检查进程和内存状态,分布式节点只能根据收到的消息推断对方发生了什么,因此“慢”与“坏了”常常无法立即区分。
客户端—服务器结构把服务集中到明确节点,便于统一管理和访问控制,但关键服务器需要冗余,否则会形成单点故障。对等结构让节点既请求也提供服务,能分散负载与故障风险,却让成员发现、数据一致性和信任管理更复杂。复制能提高可用性和读取能力,同时也带来副本何时一致、冲突怎样解决的问题。
操作系统为分布式程序提供网络、时钟、进程和存储基础,但不会自动解决跨机器一致性。分布式文件系统、数据库和协调服务在内核之上继续建立协议。它们延续了操作系统的老矛盾:抽象希望远程资源像本地一样好用,现实却要求我们正视延迟、部分故障和并发更新。
学习操作系统时,开放源码系统提供了从接口一路追到实现的机会。Linux 内核、BSD 家族和许多教学操作系统都允许读者查看调度器、内存管理、文件系统与驱动代码。Linux 通常与系统库、命令行工具、服务管理器和桌面环境组合成完整发行版;内核只是其中拥有核心权限的一层。
开放源码意味着源代码可以在许可证规定下查看、修改和分发,不等于所有组件都采用同一种许可证,也不保证某个版本天然安全。代码公开有利于审查、移植和实验,但可靠性仍来自维护流程、测试、更新与正确配置。Windows、macOS 等系统也实现了相似的基本机制,只是在内核结构、接口、源码开放范围和工程取舍上不同。
这一章不要求你现在记住每个子系统的实现。更有用的框架是:看到进程,就问它在竞争什么资源;看到系统调用,就问权限边界如何跨越;看到缓存,就问命中时省了什么、未命中时付出什么;看到一个统一接口,就问它替上层隐藏了哪些差异。后续的调度、同步、内存和文件系统,都会沿着这些问题继续展开。