计算机系统底层逻辑:从冯诺依曼到操作系统
by Obsidian · 26 Aug 2026·computer systems / os / architecture
← /u/obsidian/blog
by Obsidian · 26 Aug 2026·computer systems / os / architecture
学习任何一个操作系统,都绕不开它脚下的那层地基。处理器如何执行指令、内存如何组织数据、操作系统如何调度资源——这些问题的答案,早在 Linux 诞生之前就已成形。这篇文章从冯诺依曼体系结构出发,逐层向上,梳理计算机系统从硬件到操作系统的核心概念,为后续深入 Linux 打下概念基础。
现代计算机几乎都建立在冯诺依曼(John von Neumann)于1945年提出的体系结构之上。这一结构之所以影响深远,在于它回答了一个根本问题:计算机如何做到"通用"而非只能完成一种固定任务? 答案是:将程序本身也作为数据存储在内存中,计算机按顺序取出指令、逐条执行——这便是"存储程序"思想的精髓。
冯诺依曼体系有三个核心约定:计算机内部采用二进制表示数据与指令;程序指令和数据统一存放在同一存储器中;计算机按照程序规定的顺序依次执行指令。正是这种"顺序执行"的模型,与后来的流水线、并行计算等技术形成了鲜明的历史对比。
五大部件中,控制器与运算器合称 CPU(中央处理器)。控制器负责取出指令、分析指令并发出控制信号,统一调度其余四个部件;运算器执行算术与逻辑运算;存储器负责存放程序与数据;输入/输出设备是人机交互的边界。
理解这张结构图时,有一点容易忽视:数据流与控制流是分开的。控制器发出的是命令信号,它自己并不直接搬运数据——数据在存储器、运算器和输入输出设备之间流动,控制器只是幕后的指挥者。
计算机系统从底层硬件到上层应用,构成了一个清晰的层次抽象。每一层对上层提供服务,同时屏蔽下层的实现细节——用户写 Python,不需要了解 CPU 的门电路;编译器生成机器码,不需要知道晶体管如何工作。
软件与硬件的分界线正落在 L2(指令集体系结构,ISA) 处。ISA 定义了处理器能够理解的全部机器指令,是软件开发者能够触达的最底层接口:向上,操作系统和所有软件都建立在这套指令之上;向下,硬件工程师的工作是让芯片正确实现这套接口。两侧可以独立演进——x86 软件不需要关心芯片是怎么造的,芯片厂商也可以在不改变 ISA 的前提下重新设计内部微结构。
一条指令由两部分组成:操作码(做什么)和操作数/地址(对谁做)。操作数的获取方式称为寻址方式——操作数可以直接编码在指令本身中(立即数寻址),可以存放在寄存器里(寄存器寻址),也可以存放在主存的某个地址(主存寻址),或者通过堆栈隐式传递(堆栈寻址)。寻址方式的设计直接影响指令的灵活性和执行速度,这也是 ISA 设计中权衡最多的环节之一。
CISC 强调"一条指令能做更多事",减轻程序员和编译器的负担;RISC 强调指令规整简单,让每条指令都易于在一个时钟周期内完成,便于流水线高效执行。这两种哲学在历史上曾有激烈争论,而现代处理器设计实际上已趋于融合——x86 处理器在内部将 CISC 指令翻译为类 RISC 的微操作来执行,借鉴了 RISC 的流水线优势,同时保留了向后兼容的软件接口。
存储系统是理解计算机性能的关键。速度越快的存储介质,造价越高、容量越小;而程序对存储的需求恰恰相反——希望容量尽可能大,速度尽可能快。解决这一矛盾的方案,是构建一个多层次的存储层次结构:用少量的快速存储紧贴 CPU,用大量的慢速存储作为后备。
程序运行时,CPU 优先在最近的缓存层寻找数据;找不到再逐层向外查找(称为"缺失"或 Cache miss)。这一机制能否高效运作,取决于一个关键的经验规律——程序访问的局部性。
局部性原理分为两个维度。时间局部性是指:一个数据或指令被访问后,在短时间内很可能被再次访问。最典型的例子是循环——循环体内的指令在整个循环过程中会被反复执行,只需在第一次执行时从主存加载到 Cache,之后的访问便可在 Cache 中命中。空间局部性是指:访问某个地址后,其相邻地址也很快会被访问。数组的顺序遍历是最直接的例证——访问 a[0] 之后,a[1]、a[2] 大概率紧随其后。这也是为什么 Cache 调入数据时不是逐字节调入,而是以"行(Cache Line)"为单位批量调入的原因。
正是这两条规律,使得 Cache 虽然容量极小,却能覆盖绝大多数的内存访问,从而大幅提升整体性能。
局部性原理指引了两个重要的工程实践:
第一级(Cache-主存)完全由硬件自动维护,程序员通常无需介入——处理器在发现 Cache miss 时自动从主存调入数据,程序对此毫无感知。第二级(主存-辅存)则由操作系统的虚拟存储管理模块负责,使得程序可以使用远超物理内存大小的地址空间。前者解决速度问题,后者解决容量问题,两套机制彼此独立,共同构成了现代存储系统的骨架。
当主存数据需要调入 Cache 时,需要决定放在哪里,这称为地址映射。全相联映射允许任意主存块放入 Cache 任意位置,灵活但查找电路复杂;直接映射将每个主存块固定对应 Cache 中的唯一位置,实现简单但容易在少数几个热点块上产生冲突;组相联映射是两者的折中,将 Cache 分成若干组,主存块可以映射到指定组内的任意行,既限制了查找范围,又避免了直接映射的冲突集中问题。
当 Cache 已满、需要为新调入的块腾出位置时,就涉及替换策略。随机替换(RAND)实现最简单但命中率难以保证;先进先出(FIFO)替换最早调入的块,隐含假设越老的数据越不重要,但这并不总是成立;最近最少使用(LRU)替换最久未被访问的块,借助时间局部性原理进行推断,通常能获得最高的命中率,也是实践中最常用的策略。
当 CPU 修改 Cache 中的数据时,有两种策略将数据同步到主存: 写直达(Write Through) 在每次写入时立即更新主存,保证了 Cache 与主存的实时一致,但每次写操作都要访问主存,带来额外的总线压力; 写回(Write Back) 只将修改标记在"脏位"上,等到该 Cache 行被替换时才将数据写回主存,减少了总线流量,但需要额外的逻辑来维护状态。
在多核处理器中,每个核心拥有独立的 Cache,同一块内存数据可能同时存在于多个核心的 Cache 中,一旦某个核心修改了自己的副本,其他核心的 Cache 便成了过时的脏数据——这就是缓存一致性问题。MESI 协议是解决这一问题最经典的方案,它给每个 Cache 行打上四种状态标签:
Modified 表示该行已被本核修改、与主存不一致;Exclusive 表示本核独占且与主存一致;Shared 表示多核共享且与主存一致;Invalid 表示该行已失效、不可使用。某些状态下的读写操作会触发总线事务,通知其他核心更新或失效其副本——这套协议是并发编程中"内存可见性"问题的硬件根基。
CPU 内部由控制器、运算器和寄存器组三部分组成,通过内部总线相连,再经由系统总线与主存和 I/O 设备交互。寄存器组是整个存储层次中距离 CPU 最近的一层,容量极小但速度极快,用于暂存运算的中间结果和操作数——每次运算几乎都从这里取数、将结果写回这里。
取指阶段,控制器读取程序计数器(PC)指向的地址,从主存(或 Cache)中取出指令,同时将 PC 自增指向下一条指令。译码阶段分析操作码与操作数,确定所需的操作类型和数据来源。执行阶段由运算器完成实际的算术或逻辑操作。访存阶段仅在指令需要读写内存时发生,例如 Load/Store 指令;其他指令经过此阶段时不执行任何操作。最后,写回阶段将结果存入目标寄存器或内存地址,完成一条指令的完整生命周期。
这五个阶段的划分,正是流水线(Pipeline)技术的基础——让不同指令的不同阶段在同一时刻并行推进,就像工厂的流水线一样,极大提升了吞吐量。
同构多核中所有计算核心设计相同、地位对等,适合通用并行计算,桌面和服务器处理器多属于此类;异构多核则由主处理核搭配若干结构不同的协处理核,主处理核负责总体调度,协处理核针对特定任务优化,常见于移动芯片的大小核架构——大核负责高性能任务,小核负责后台任务,以此在性能与功耗之间灵活取舍。
操作系统是连接硬件与用户程序的桥梁,向下管理硬件资源,向上为应用程序提供统一的服务接口。没有操作系统,每个程序都需要自己驱动磁盘、管理内存、协调输入输出,这既低效又危险。操作系统将这些职责集中起来,让应用程序专注于自己的业务逻辑。
它所管理的资源归为四类:处理器(CPU 时间)、存储器(内存空间)、I/O 设备,以及文件(持久化存储)。这四类资源的管理方式,构成了操作系统全部复杂性的来源。
理解操作系统的行为,需要先理解它运行时所呈现的四个基本特征。
并发(Concurrency) 是指在同一时间间隔内,多个程序或进程交替推进。需要区分的是,并发不等于并行——并行要求多个程序或任务在同一时刻真正同时执行(需要多个处理器核心,或多个处理器),并发则是单处理器通过快速切换制造出的"同时感"。并发是现代操作系统复杂性的核心来源之一。
共享(Sharing) 是指系统中的软硬件资源可以被多个并发进程共同使用,而非某一进程独占。共享与并发相辅相成——并发需要共享资源,而共享又要求操作系统仲裁访问权限,由此引出同步与互斥等核心机制。
虚拟(Virtual) 是指操作系统将一个物理实体映射为多个逻辑对应物,使每个程序都感觉自己独占资源。虚拟内存是最典型的例子:每个进程都拥有一块连续的"私有"地址空间,但物理内存实际上被多个进程分时共享。
异步(Asynchronous) 是指进程的执行顺序和推进速度是不确定的,不同运行环境下可能产生截然不同的执行轨迹。这是并发编程难以调试的本质原因——同样的程序,因调度顺序不同,可能产生不同的结果。
处理器在任意时刻运行于两种状态之一: 内核态(Kernel Mode) 可执行全部指令,包括操纵硬件的特权指令; 用户态(User Mode) 只能执行非特权指令,无法直接访问底层硬件。这种隔离是操作系统安全性的硬件基础——如果所有程序都能随意读写硬件寄存器,系统的稳定性将无从保证。
用户态切换到内核态只有三种途径。系统调用是程序主动发起的,当程序需要操作系统服务时(如读写文件、分配内存),通过特殊指令陷入内核,操作系统代为完成操作后返回用户态。异常是程序执行过程中出现的错误,如除零、非法内存访问,处理器捕获后交由操作系统处理。外围设备中断则是由设备主动发起的——当磁盘完成了数据读取、网卡收到了数据包,设备向 CPU 发出中断信号,CPU 暂停当前任务转入中断处理程序。
程序是静态的,是存储在磁盘上的代码文本;进程是动态的,是程序的一次具体执行过程。同一个程序用不同数据集运行,可以产生多个并发进程。
进程控制块(PCB)是操作系统为每个进程维护的数据结构,记录进程的状态、优先级、资源占用等信息,是进程存在的唯一标志——进程创建时 PCB 随之建立,进程消亡时 PCB 随之撤销。
进程内部还有更细粒度的执行单位:线程。一个进程可以包含多个线程,这些线程共享进程的地址空间和资源,但各自拥有独立的栈和程序计数器。操作系统调度的最小单位是线程——多线程程序可以让多个任务在同一个进程内并发推进,避免了进程切换的高昂开销。
进程在三种基本状态之间流转:运行态是进程真正占用 CPU 执行的状态;就绪态是进程已获得除 CPU 以外的一切运行所需资源,随时可以运行,只等调度程序分配 CPU;等待态(也称阻塞态)是进程在等待某个外部事件,即使把 CPU 分配给它,它也无法继续执行。
就绪态与等待态之间有一条微妙但重要的边界:就绪的进程缺的只是 CPU,等待的进程缺的是某个尚未发生的事件。从等待态恢复时,进程不能直接跳回运行态,必须经由就绪态中转,等待调度程序的重新调度——这一顺序反映了操作系统对"公平性"的基本承诺。
一个源程序从代码文件变为内存中运行的进程,需要经历三个步骤:
编译将高级语言翻译为目标机器的指令,链接将多个目标文件与库合并成一个可执行文件,装入则将可执行文件载入内存并完成地址绑定。
存储管理的核心任务是完成逻辑地址到物理地址的转换——程序员使用的是逻辑地址,内存中存放的是按物理地址编排的实际数据,中间这层映射由操作系统和硬件协同完成。此外,存储管理还负责内存空间的分配与回收,以及通过虚拟存储扩充可用空间。
虚拟存储的本质是将主存与辅存联合起来,为每个进程提供一个远大于物理内存的连续逻辑地址空间。操作系统在主存与辅存之间调度数据页,使程序感知不到物理内存的限制——这正是第四节中"主存-辅存两级结构"在操作系统层面的具体实现。
文件系统是操作系统对持久化存储的抽象,向用户屏蔽了磁盘扇区、柱面等物理细节,提供以文件名为单位的统一访问接口。文件在磁盘上的物理组织方式有三种主要形态:顺序结构将文件数据连续存储,适合顺序访问但不易动态扩展;链表结构通过指针串联分散的存储块,易于扩展但随机访问性能差;索引结构建立专门的索引表记录各数据块的位置,兼顾随机访问与动态扩展,是 ext4、NTFS 等现代文件系统的常用基础。
设备管理负责协调 CPU 与外围设备之间的数据交互。早期系统中,CPU 必须亲自控制每一次 I/O 操作,大量时间消耗在等待慢速设备上。 通道(I/O Channel) 的引入解决了这一问题:通道是一种专用的 I/O 处理器,可以独立执行 I/O 操作而不占用 CPU,使 CPU 与 I/O 设备能够真正并行工作,CPU 只需在通道完成操作后响应中断即可。
作业是用户提交给系统的一次完整计算任务,从提交到完成经历四个状态:
作业进入执行状态的那一刻,操作系统为其创建进程,此后作业的推进便体现为进程在运行/就绪/等待之间的流转。作业调度负责决定哪个作业从后备队列进入执行,进程调度则负责决定哪个进程在下一时刻获得 CPU——前者关注的是"开始谁的工作",后者关注的是"现在执行谁"。
从这张全景图往回看,可以清晰地看到贯穿全文的一条线索:计算机系统从数字逻辑层一路向上,每一层都在解决前一层留下的新问题——ISA 抽象了硬件细节,操作系统抽象了资源竞争,文件系统抽象了存储物理形态。理解了这些层次之间的关系,Linux 的行为——调度、内存分页、文件描述符、中断处理——便不再是孤立的知识点,而是这套体系在具体实现上的自然展开。