
经常有初学的朋友跑来问我计算机系统基础里“基本组成”这一章是不是把输入设备、输出设备、存储器、运算器、控制器这张图背熟就完事了如果只是为了应付期末考试背图也许够了但你要是真的想知道“一段代码写出来之后机器里到底发生了什么”或者想搞清楚“为什么程序有时候快有时候慢”那光背框图远远不够。这篇内容我想把这几个基础概念用一种“能记住、能串起来”的方式讲明白。文章适合两类人一类是正在上计算机系统基础课、第一次接触机器底层的大一同学另一类是写了好几年代码、但发现自己对内存、寄存器、总线这些概念一直停留在模糊印象里的开发者。我会尽量把每个概念都放到具体场景里少堆名词多讲原因。1. 先别急着背框图五大部件真正的分工逻辑1.1 输入输出不是只有键盘鼠标很多人提到输入设备第一反应就是键盘、鼠标、麦克风倒也没错但容易把概念理解窄了。输入设备的准确定义是把外部世界的信息转换成计算机能处理的二进制电信号。所以扫描仪、摄像头、各种传感器、触摸屏里负责捕捉触摸的那部分全都算输入设备。反过来输出设备是把机器内部的二进制结果转换成人能感知的形式显示器、打印机、音箱都属于这一类。这个划分看起来啰嗦但很重要。它强调的是“功能边界”而不是“物理设备”。一块触摸屏物理上是一个整体逻辑上却被分成输入和输出两个功能模块一个带麦克风的耳机同样横跨两个角色。系统课程里的框图从来不按外形来画而是按功能来画你理解了这一点后面分析任何硬件设备都不会被外观带偏。1.2 中间三大件存储器、运算器、控制器再看中间三大件我用做饭来打比方。冰箱是外存里面的菜、肉、调料是数据案板是内存做菜前把需要的食材从冰箱里取出来放到手边厨师是运算器负责切、洗、炒这些实际加工做饭流程的指挥者——告诉你现在该切菜、下一步该开火——是控制器。存储器负责存放“程序”和“数据”并且让CPU能以最快速度取用。这里有个特别多初学者一上来就绕晕的点存储器分主存和外存。主存就是我们说的内存CPU可以直接访问外存是机械硬盘、固态硬盘这类辅助存储CPU不能直接运行外存里的程序必须先由操作系统把程序搬运到内存里CPU才能取指执行。你平时双击打开一个软件感觉上是“从硬盘启动程序”实际上操作系统在背后先把可执行文件加载进内存再把控制权交给程序。整个过程快到你看不见但底层逻辑就是这句话外存里的程序要先进内存CPU才碰得到。1.3 五大部件靠什么连起来五大部件并不是散着摆的它们之间靠总线交换信息。总线本质上就是一组共享的导线分为数据总线、地址总线、控制总线三条逻辑通道。数据总线负责传内容地址总线告诉目标部件“我要访问的是哪个位置”控制总线负责发读写信号、时钟信号这类“指挥命令”。我习惯把总线理解成传菜通道厨师想取案板上的某份食材先通过地址总线报出位置然后控制总线发一个“读取”信号最后食材顺着数据总线送到手上。这一节先记到这个程度总线细节第五章会专门展开。1.4 为什么偏偏是这五个角色教科书把计算机拆成五大部件而不是拆成“主机、显示器、键盘”不是因为老古董而是因为这是功能视角。任何一台通用计算机不管是手机、笔记本还是服务器功能上永远跳不出这几类角色必须能输入信息必须能输出结果必须能保存程序和数据必须能计算必须能指挥这一切按顺序进行。你以后拿到任何新设备都可以试着往这五个框里归类。这是个很好的思维锻炼能让你从“看参数”上升到“看结构”不再被具体的型号、品牌、命名方式带着走。学过这一章之后再去看那些评测视频里说的“CPU性能强”“内存大”你脑子里会自动浮现它们分别为五大部件中的哪个角色服务这感觉比背一百遍框图都踏实。2. 存储程序这个念头决定了所有编程语言的底层逻辑2.1 指令和数据住在一起为什么是一次飞跃早期的计算机不是“写程序”的而是“插线路”的。想让它算一道新题目得重新接插头、改线路机器本身是“专职”的算加法就是加法机算矩阵就是解矩阵方程。这种模式的问题是改一次问题就改一次硬件想通用根本不可能。后来出现了存储程序思想把命令机器干活的指令和指令要处理的数据全都当成普通二进制信息放进同一块存储器里。要干不同的活不需要改任何硬件只要把内存里的指令换掉就行。这个转变看起来只是“程序刚好存在内存里而已”但它直接促成了“通用计算机”成为可能。今天你在同一台电脑上写文档、剪视频、玩游戏、看网页本质上都是同一套硬件只是内存里装载的指令序列不同罢了。没有存储程序就不会有我们理解的“软件”这个词。2.2 一条指令长什么样操作码与地址码既然程序是一堆指令那一堆指令的总清单就是指令集。计算机能理解的最小命令单位是指令一条指令通常包含两个部分操作码和地址码。操作码回答“做什么”。比如做加法、做比较、无条件跳转、停机都属于操作码的范畴。地址码回答“对谁做”。操作数是存在内存里的还是存在某个寄存器里的地址码里会指出来。有些指令不需要地址码比如停机指令有些指令需要两个甚至三个地址比如三地址指令的意思就是从两个源地址取值做运算把结果写到第三个地址。在内存里看一条指令就是一个二进制数。某一套CPU规定“00000001”代表加法指令另一套CPU可能规定的是别的值。这就是为什么同一份可执行文件不能跨平台乱跑不同CPU的指令集编码规则不同机器码不通用。2.3 程序和“数据”之间从不设防既然指令和数据都以二进制形式躺在同一块内存里CPU怎么知道它此刻取出来的内容是“该执行的命令”还是“该计算的数据”答案是靠程序计数器Program Counter缩写PC。PC指向的内存位置CPU取出来之后就当作指令来译码执行而指令里的地址码指向的内存位置取出来的才当作操作数处理。换句话说同一个二进制数字被PC指向就是指令被指令的地址码指向就是数据全靠上下文来区分。这个特性给程序员带来了极大的灵活性但也埋了一个隐患一旦程序出错让CPU跳到了某个本来是数据的内容上CPU就会把一堆乱码当成指令逐条执行。很多计算机安全问题追根溯源都能追溯到“指令和数据共用一池”这个设计上。学到后面你会频繁和这个概念重逢。3. 拆开CPU运算器、控制器与寄存器组各自在忙什么3.1 运算器只做最基础的运算但足够应付一切运算器教科书里叫算术逻辑单元ALU。它干的事情比大部分人想象中要朴素加法、减法、简单的与或非、移位就这些。你可能觉得这也太寒酸了乘法怎么算实际上乘法在底层是被拆成多次加法加移位来完成的只是ALU不在乎它执行得足够快你根本察觉不到。ALU处理的两个数字一般来自寄存器结果也会写回寄存器。举一个具体的过程要算 A BALU先从寄存器R1读A再从R2读B做一次加法把结果送到R3。整个过程没有内存参与所以寄存器之间运算是CPU里最快的那种操作。3.2 控制器指挥方式其实相当机械控制器负责指挥全局但它并不“聪明”本质上是按部就班地执行一套固定流程。它手里握着两个特别关键的寄存器指令寄存器IR和程序计数器PC。PC告诉控制器“下一条指令在内存哪里”IR保存的是“当前正在执行的这条指令”。控制器的工作方式有点像车站调度员从PC指示的地址取指令放进IR然后根据指令里的操作码发信号让对应的部件开工。指令处理完了再把PC加一指向下一条。如果遇到跳转指令控制器干脆把PC改写成跳转目标地址程序就“跳”过去了。整个机制没有任何智能涌现就是机械地取指、解释、执行、走人。3.3 寄存器组CPU手边的临时工作台寄存器组是CPU内部的一批存储单元数量不多通常几十个到几百个每个的宽度等于机器的字长。它最大的特点是速度几乎和CPU同频比内存快一个数量级以上。你可以把寄存器想象成厨师手边一排摆好调料的小碟子现取现用不用每次去案板那边翻。写程序时你可能察觉不到寄存器但编译器每天在背后和寄存器打交道。你在高级语言里写一个临时变量编译器极大概率会把它分配到某个寄存器里因为这样最快。汇编语言里那些看得见的EAX、ECX、RDX之类的名字就是可直接触碰的通用寄存器。备注一下寄存器数目是有限的所以一个函数想同时使用的局部变量太多编译器只能把多出来的东西放到内存的栈里这也会让程序变慢一点点。3.4 一条指令在CPU里跑完一圈的完整旅程把上述角色串起来我们就得到一条指令从开始到结束的标准旅程一般称为指令周期包括取指、译码、执行、访存、写回这几个阶段。假设内存里有一条加法指令“把R1里的数加上R2里的数结果存到R3”完整流程是这样控制器先去PC指向的内存地址把这条指令读进IR这叫取指。接着译码器解析操作码认定这是一条加法指令这叫译码。然后ALU从R1和R2取出两个数做加法这叫执行。如果指令要求从内存取操作数这个阶段才需要访存最后把结果写回R3叫做写回。处理完后PC自动加1指向下一条指令。每个阶段通常需要一到几个时钟周期。CPU主频决定的是“每秒有多少个时钟周期”而不是“每秒执行多少条指令”。有些CPU一个周期能干更多事所以主频高并不等于性能强这一点第五章还会专门说但在这里先记住结论衡量CPU快慢看的是综合效率不是只看主频数字。4. 存储器的金字塔为什么不能只用一块又快又大的存储4.1 从寄存器到硬盘每一层都是取舍看一张典型存储层级表就能明白问题所在寄存器只有几十字节访问速度和CPU一个级别高速缓存Cache有几百KB到几十MB几纳秒级别内存有8GB到几十GB几十纳秒级别固态硬盘几百GB到几TB微秒级别机械硬盘容量更大但延迟到了毫秒级。这些数字背后的规律很直白速度越快成本越高容量就不可能做大容量做大就必然要牺牲速度。做计算机硬件的人没能力发明一块“又大又快又便宜”的存储所以他们采用分层的办法让不同层级各司其职再配合一定的调度策略让CPU大部分时候能在最快的层级里取到数据。级别 |典型容量 |访问速度量级 寄存器 |几十B |CPU内部与CPU同频 高速缓存 |几百KB~几十MB |几ns 内存 |几GB~几十GB |几十ns 固态硬盘 |几百GB~几TB |几十~上百微秒 机械硬盘 |几TB |几毫秒4.2 “易失”到底意味着什么内存不能当U盘用内存依赖电来维持内容断电后里面的数据就没了这就是“易失性”。所以内存只能当临时工作台不能当仓库用。外存非易失断电后数据还在适合长期保存。ROM也属于非易失性存储器但它通常只存固件这类不常改的内容和内存是两个世界。这个特性直接决定了你平时的使用习惯文档如果不保存断电就丢因为它在内存里的副本断电即失但只要保存到硬盘就稳了。理解“内存易失、外存持久”这个区别之后再去学习虚拟内存、操作系统进程管理这些内容你会觉得顺滑很多。4.3 缓存和局部性这部分知识写代码时真的用得上Cache的性能神话来自一条被验证了无数次的规律程序访问内存时倾向于集中访问一小片区域。这就是局部性原理分为时间局部性和空间局部性。比如一个循环反复使用同一个变量这是时间局部性连续遍历数组元素这是空间局部性。Cache正是因为利用了这个规律才高效的它会把最近访问过的数据和它旁边的一小块邻居数据一起搬进高速缓存区下次CPU再访问附近的数据就能直接从缓存命中不必绕道内存。这对写代码有什么启发最典型的例子是遍历二维数组。按行遍历就是连续访问内存空间局部性好缓存命中率高按列遍历则是每访问一个元素就跳到一个很远的内存位置局部性差,程序可能慢好几倍。数据结构课上背过的结论到计算机系统基础这里才真正知道它为什么成立。5. 总线与外设接入多个部件之间怎么不打架5.1 三种总线各管一段总线是连接CPU、内存、外设的通信枢纽按功能分三条数据总线、地址总线、控制总线。数据总线双向传输数据地址总线一般由CPU单方向发出指定访问的内存地址或端口地址控制总线负责传输读写控制、中断请求、时钟等信号。这里有个经典推导值得自己推一遍地址总线的位数决定了CPU能直接访问多少内存空间。如果地址总线是32条那么能表示2^32个不同地址也就是4GB空间如果是15条那只能寻址2^1532768个位置。这个数字不是随便定的它决定了内存上限这也是“32位系统内存上限约4GB”这条传言的底层原因。5.2 外设接入的三种方式查询、中断、DMA外设接入CPU不是插个线就完事还涉及“CPU怎么知道外设准备好了”。朴素做法叫程序查询CPU发出请求后隔一会儿就问一次“数据来没来”。好处是实现简单坏处是CPU被占住什么也别想干。这就好像你等人送材料每过一分钟就问一次对方到了没有期间什么工作都做不了。更聪明的方案是中断CPU先忙自己的其他工作外设准备好之后主动发一个中断信号CPU暂停手头工作去处理这件事处理完再回到刚才的进度。这个设计把CPU从空转等待里解放了出来是现代计算机几乎离不开的机制。第三种是DMA方式适用于高速批量数据比如磁盘读写。CPU只需要发起一个指令后续大块数据的搬运由DMA控制器直接负责干完再通知CPU一声。这样CPU不用一字节一字节地跟着搬效率特别高。5.3 “中断”不是打断一切一个常见误解不少人以为中断就是CPU随时被打断导致程序乱七八糟其实不是。中断是受控的CPU接到中断信号后会先把当前正在执行的指令完整收尾保存当前进度进入中断处理程序忙完再恢复现场继续执行用户几乎感觉不到发生过一次中断。中断也不是无限狂暴地抢占CPU。系统会给不同设备分配不同优先级的信号紧急程度高的才能抢在前面这样才能避免打印机、键盘、网卡之间互相打架。理解了这部分你再去看操作系统里的“系统调用”“异步I/O”就明白那些概念并不是高深魔法它们全是建立在这套硬件协作机制之上的上层设计。6. 纸上跑一遍程序把五个部件串成一条线6.1 练习手写一个加法程序并模拟执行概念听过一遍容易忘最好动手走一遍。你不必真的在电脑上运行汇编拿一张纸就能模拟。假设内存里放着这样一段程序内存地址|内容|说明 100|LOAD 200|从200地址读取数据到寄存器R1 101|ADD 201|把201地址的数据加到R1上 102|STORE 202|把R1存回202地址 200|13|数值13 201|29|数值29 202|0|结果区初始为0现在从PC100开始模拟取指读100地址得到指令LOAD 200PC变101执行把13从200读入R1再取指地址101得到ADD 201PC变102执行R1变成42再取指地址102得到STORE 202执行完把42写入202。整段程序跑完202地址里的值从0变成了42。这个过程中控制器负责取指、解读运算器负责真正的加法存储器的不同位置既保存指令也保存数据输入输出设备并未参与——因为程序不需要它们参与。6.2 三个最容易被初学者误解的结论第一主频高不等于性能强。前面说过现代CPU每个时钟周期能执行的指令数不再等于1架构差异很大所以主频只是众多性能指标之一。第二程序文件双击运行时并不是“从硬盘直接执行”。外存里的程序要先加载到内存PC才会指向它的第一条指令CPU才开始执行。那些“放在SSD上启动特别快”的体验本质上是把加载时间从几十毫秒压到了几毫秒。第三寄存器不是越多越好。寄存器多了一条指令里要表示“用的是哪个寄存器”的位数就得增加机器码变长解码变复杂CPU面积和功耗也上来。所以寄存器数量不仅要快还要在成本和复杂度之间取平衡。6.3 下一步往哪学基本组成这一章是整个计算机系统基础的地基。往下走会有几条明显的主线一条是学更细的指令集和汇编亲手写代码然后反汇编看编译器怎么分配寄存器另一条是学流水线和存储层次优化把Cache、分支预测这些机制纳入性能思维再一条是学中断、异常、系统调用打通应用层到底层的桥梁。无论选哪个方向心里都要始终带着五部件这张功能框架图遇到新知识就挂到对应位置上去。我自己这些年反复体会下来最值钱的能力不是背诵部件名称而是遇到任何一个计算机问题都能下意识地问一句这一步需要哪些部件参与数据在哪里指令又在哪里瓶颈最容易出在哪一层带着这种直觉去学习比死记硬背有用得多。