首页 / 资讯中心 / 文章详情

看懂计算机底层:冯·诺依曼结构、二进制与存储金字塔

看懂计算机底层:冯·诺依曼结构、二进制与存储金字塔 ★ FEATURED ARTICLE
1. 先把整台机器看成一幅地图各大件各管哪一段1.1 冯·诺依曼骨架取数、计算、存回的无限循环无论一台电脑多贵多新骨子里仍是冯·诺依曼结构这套框架。CPU 负责取指令、执行指令内存负责临时存放指令和数据硬盘这类外部存储负责把数据持久化下来输入输出设备负责和外界打交道。四个部分通过总线连接在一起构成了所有现代计算机的基本骨架。操作系统、编译器、数据库一切都跑在这副骨架上。我见过很多人学计算机组成原理时把这些当考试知识点背其实它是最高频的排障模型。举个例子你遇到一个系统变慢的问题第一反应应该是慢在哪一层CPU 在满负荷计算还是内存不够在疯狂换页还是磁盘 I/O 在排队把问题定位到具体部件再谈优化手段比盲目加机器、加缓存有效得多。这种先分层、再定位的思路几乎能套用在所有性能问题上。1.2 仓库、工作台和工具袋一个能解释所有性能问题的类比具体到工作方式我习惯用一个车间来做类比这个类比几乎能回答所有为什么加一层就快这么多的问题。硬盘是仓库容量大、价格便宜、断电数据也不丢但搬运极慢内存是工作台CPU 要把处理的零件从仓库搬到手边速度比仓库快好几个数量级但一断电就全部清空高速缓存是系在腰上的工具袋把最常用的东西放在最近的位置抬手就能拿到CPU 本身是那个真正干活的技工所有计算、判断、流转都发生在这里。用这个模型去套各种性能方案思路会非常清晰。内存数据库比传统数据库快不是它有什么魔法而是数据从仓库层搬到了工作台层本地缓存比内存数据库还快是因为它连网络传输都省了数据直接活在进程里CPU 亲和性绑核能提速是减少了线程在核心之间来回跑动的折腾成本。你理解了存储分层就理解了半个性能优化。1.3 程序也是数据一个被忽略却很关键的设定冯·诺依曼结构里还有一个隐藏设定指令和数据在内存里都以二进制形式存放CPU 只是按照程序计数器的指向一段一段地取出来执行。正因为程序也是数据编译器才能把源代码翻译成可执行文件操作系统才能把程序加载进内存调试器才能在运行时打断点、甚至改写指令。这个设定是软件生态的地基。没有它就没有虚拟机没有 JIT 即时编译也没有容器镜像这种把整个运行环境打包的玩法。每次你启动一个进程本质上都是操作系统在帮你完成把仓库里的程序搬到工作台再让 CPU 一段段执行这件事。理解了这条主线后面讲 BIOS 引导、讲操作系统调度你都能找到对应的物理位置。2. 二进制不是选择题是物理世界给的唯一答案2.1 为什么偏偏是 0 和 1晶体管开关与电压电平先回答一个很多人从小就好奇的问题计算机为什么不用十进制答案藏在物理器件里。现代芯片由数十亿个晶体管组成每个晶体管本质上就是一个受电压控制的开关输入电压超过阈值就导通记作 1低于阈值就截止记作 0。用两种状态表达信息判别最可靠、逻辑门最好实现、整体电路最省成本。理论上当然可以用更多电压档位来表示三进制甚至更多进制但工程上几乎没人愿意这么干。电压会漂移、会受温度影响、会被电磁噪声干扰每多一个电平档位判别电路就要更精密误判率就会上升成本几乎是指数级增长。所以在追求可靠性和可制造性的工程世界里二进制不是人类的主观选择而是物理条件约束下的最优解。2.2 从 bit 到文字、图片、声音一切都是数字游戏有了 bit 这个最小单位就可以往上叠编码了。8 个 bit 组成一个字节byte能表示 256 种状态英文和数字用 ASCII 编码一个字节一个字符中文在 UTF-8 编码下通常占 3 个字节整数用补码表示负数表示、溢出行为都由它决定小数按照 IEEE 754 浮点标准存储这就是为什么0.1 0.2不等于0.3一张图片的每个像素拆成 RGB 三个颜色通道每个通道占一个字节一段声音按固定采样率把波形量化成整数序列。这些编码细节平时写业务代码用不太到但调试时它们会突然变得非常重要。乱码问题最典型文件本身没坏字节还是那些字节只是你用 UTF-8 解释了一段本该按 GBK 解释的文本于是全是锟斤拷。文件损坏也同理本质是某些字节被意外改写。有了一切数据归根结底是数字的视角这些看似玄学的问题就都有了明确的排查方向。2.3 为什么底层文档里满屏都是十六进制二进制对人类太不友好了一个字节就要写 8 位十进制虽然好读但看不出位结构。十六进制一位正好对应 4 个 bit是二进制最自然的速记符号。所以看内存转储、读寄存器、解析网络协议、设置权限位时大家几乎都用十六进制。比如常见的文件权限、事件标志位常常写成0x1、0x2、0x4这种形式本质上是给二进制位打了编号。认识0x前缀是你进入底层世界的入门姿势不用背看得多了自然记住。当你发现某个 API 返回的是一串十六进制数时第一反应应该是把它展开成二进制看看是哪几个位被置 1 了——很多所谓诡异的现象拆开位一看就全明白了。3. 从按下电源键到操作系统接管BIOS 引导链路拆解3.1 BIOS 是主板上的一段固件不是操作系统的一部分开机之前内存是空的操作系统还躺在硬盘里CPU 没有任何程序可以执行。这时候就需要一个站在系统之外的引导者——BIOS基本输入输出系统。它存放在主板上的只读存储芯片ROM 或闪存里只要主板供电一上电就会自动运行不需要从任何外部存储加载代码。很多人把 BIOS 和操作系统混为一谈其实它们是两条完全不同的代码路径。BIOS 的职责非常纯粹完成硬件初始化、做基本自检、找到引导设备、把 CPU 控制权交出去。它跟任何操作系统都没有绑定关系这也是为什么同一台机器可以装多个操作系统BIOS 照单全收完全不关心你装的是谁。3.2 POST 自检与启动三连跳BIOS → 引导扇区 → 内核BIOS 上电后的第一件事是加电自检也就是常说的 POST。它要逐个确认 CPU、内存、显卡、键盘等关键硬件是否在线、是否正常。如果内存条没插好你会听到长短不一的报警音那其实不是故障代码而是 POST 失败在用特定频率的滴滴声向你喊话如果你装了诊断卡甚至能看到它把检查到哪一步用十六进制代码显示出来。POST 通过之后BIOS 会按预设的启动顺序去寻找引导设备。找到硬盘后读取硬盘开头的引导扇区——传统模式叫 MBR主引导记录现代 UEFI 模式则直接读取 GPT 分区表里的引导项。引导扇区里的小程序再负责把操作系统内核从硬盘搬进内存。内核接管 CPU 和内存管理的那一刻BIOS 的历史使命就算完成了后面的系统初始化、驱动加载全是内核自己的事。这条链路是我排查开不了机问题的核心地图先听 POST 有没有报警再进 BIOS 界面看硬盘识别没识别到最后检查引导扇区有没有损坏。三个节点逐层排查绝大多数开机故障都能快速定位不用求人。3.3 UEFI换了个名头和界面但干的还是引导的活现代主板几乎都标配 UEFI 取代传统 BIOS。UEFI 支持超大容量磁盘、启动速度更快、自带安全启动校验但它要解决的根本问题始终是同一个谁来负责引导操作系统。很多新装机会在设置界面里看到 UEFI 和 legacy传统模式的选项还会遇到 GPT 和 MBR 分区表的选择。这里有个新手高频踩坑点如果硬盘用 GPT 分区表启动模式必须选 UEFI如果用 MBR 分区表就选 legacy 模式。选错了系统装完了也起不来卡在开机画面。所以每次帮人装机我都会提醒一句——先想清楚引导模式再分区能省掉后面一小时的折腾。4. 操作系统如何用分时复用让一颗 CPU 假装同时运行无数程序4.1 核心动机程序在等 I/O 的时候CPU 是闲着的回到操作系统要解决的根本问题一台电脑只有一颗 CPU早期确实如此但要跑几十上百个程序。如果让程序排队一个个跑完再放下一个体验会非常糟糕——你正在打字后台下载、杀毒扫描就得全部停摆。早期工程师发现了一个关键事实程序大部分时间根本不在算算术而在等 I/O。等用户敲键盘、等磁盘数据、等网络响应一次 I/O 等待动辄几毫秒而 CPU 算一条指令只要纳秒级。让 CPU 在等待时间里干坐着是巨大的浪费。于是分时复用的思路诞生了把 CPU 资源按时间切成小片多个程序轮流使用谁在等 I/O谁就把 CPU 让给下一个。这就是分时操作系统最朴素的起点。我们今天能一边开会一边编译一边听歌全靠这个机制在底层兜底。4.2 时间片与上下文切换分时复用的完整执行链路分时复用不是大概轮着来背后是一套由硬件和操作系统配合完成的严密流程硬件定时器周期性发出时钟中断。这是整个机制的节拍器每隔固定时间通常几毫秒到几十毫秒强制打断当前正在执行的程序。操作系统立刻保存当前进程的现场——寄存器里的值、程序计数器的位置、栈指针等这套快照统称为上下文。调度器根据策略从就绪队列里挑出下一个该运行的进程把它的上下文恢复回去。CPU 跳回该进程上次被打断的位置继续往下执行仿佛从未被打断过。这里的调度策略是真正的学问。时间片设太短上下文切换的开销保存加恢复占比过高系统总吞吐量反而下降设太长交互型程序会明显卡顿。所以现代操作系统普遍采用混合调度交互型进程优先级高、时间片给短尽量快速响应计算型进程优先级低、时间片给长减少无谓切换。你开着一个全核编译任务桌面还能流畅拖鼠标就是因为 GUI 相关进程在调度器眼里优先级更高、总是被优先唤醒。4.3 进程与线程隔离是代价共享是风险讲到分时复用就绕不开进程和线程。一句话概括进程是资源分配的单位线程是 CPU 调度的单位。进程之间内存互相隔离A 进程崩溃不会拖垮 B 进程这是稳定性的基石但隔离也意味着创建进程开销大、切换成本高而且数据交换要经过复杂的进程间通信。线程则共享所属进程的地址空间创建和切换都轻量所以想并发执行多件事时大家优先用线程但共享也带来了风险——两个线程同时读写同一块数据结果可能完全错乱。到这里你再回头看多线程编程里那些经典 bug 就通透了。所谓并发问题本质上是多个线程在时间片上交错执行而读取-修改-写回这个操作不是原子的调度器随时可能在中间插一脚导致另一个线程读到半成品。加锁、用原子操作本质都是把一组操作变成一个不可分割的整体让调度器没有机会在中间打断。理解了分时复用的机制你就理解了为什么并发编程需要那么多奇奇怪怪的江湖规矩。5. 并发与并行两个词背后是两种完全不同的资源博弈5.1 先辨概念并发是结构并行是执行并发和并行是我在面试和日常交流中见过被混淆最多的两个术语。很多人脱口而出并发就是同时干很多事其实只说对了一半而且是最容易误导人的那一半。准确一点说并发讲的是程序结构——你要同时处理多件事代码怎么组织它不承诺这些事情真的在同一时刻执行。单核 CPU 上靠分时复用让多个任务快速交替推进这也是并发。并行讲的是执行方式——多个任务真正在同一时刻各自执行它必须依托多个物理核心或多台机器是硬件资源层面的能力。一个常被引用的类比很能说明问题一间餐馆只有一个厨师他同时接待好几桌客人——给 A 桌的菜切好备用到 B 桌的锅边翻炒两下再转身处理 C 桌的订单这是并发如果店里请了三个厨师各管一灶这才是并行。并发解决的是资源有限但要做的事很多并行解决的是通过增加资源来提速。5.2 为什么多核时代并发结构依然是主流写法有人可能会想现在电脑普遍多核把任务拆开交给多个核心并行跑不就行了为什么还要研究并发结构答案藏在三个现实约束里I/O 等待是常态。网络请求、数据库查询、磁盘读写动辄毫秒级让线程干等就是在浪费 CPU。用异步、事件循环、协程这类并发结构去边等边做别的才能把 CPU 利用率拉满。加核不等于所有任务都能加速。多个步骤之间有严格先后依赖时后面的必须等前面出结果再加一百个核也只能干瞪眼。程序整体加速比受制于串行部分的比例这就是阿姆达尔定律的核心结论——经验值告诉我们串行代码哪怕只占 20%也足以把加速上限卡在 5 倍左右。多线程之间的协作本身就是并发控制问题。锁、信号量、原子操作、无锁队列处理的都是多个执行流同时触碰同一份共享资源的结构问题跟你机器有几个核没有关系。5.3 实战判断先分清 CPU 密集还是 I/O 密集写高性能代码前先问自己一个问题这个任务到底卡在哪如果是 I/O 等待为主调用外部 API、读写数据库、处理大文件优先想异步并发方案比如协程、事件驱动。这时如果你盲目开一堆线程去并行线程创建、上下文切换、锁竞争的成本往往会吃掉大部分收益加人手反而更慢。如果是 CPU 密集计算图像处理、数值模拟、加解密才应该用多线程配合多核并行再配合核绑定减少调度抖动。这个判断直接影响架构选型。我见过不少服务性能差不是代码写得烂而是并发模型选错了该用异步的场景用了重型线程池该并行的场景用了单线程异步结果怎么调都别扭。先诊断瓶颈再选模型比一上来就堆技术栈靠谱得多。6. 存储金字塔从寄存器到硬盘每一层都隔着一个数量级6.1 一张表看清延迟鸿沟很多人知道内存比硬盘快但快多少完全没有概念。我把现代计算机存储层级的关键数据整理成一张表你看完就明白了层级典型容量访问延迟量级断电是否丢失寄存器几十到几百字节约 1ns是L1 高速缓存几十 KB约 1ns是L2 高速缓存几百 KB约 3~10ns是L3 高速缓存几 MB 到几十 MB约 10~40ns是内存 RAM8GB~64GB约 80~100ns是SSD数百 GB 到数 TB约 0.02~0.1ms否机械硬盘数 TB约 5~10ms否从寄存器到机械硬盘延迟大约相差七个数量级。也就是说CPU 如果每次都跑去硬盘拿数据一次访问要等几百万个时钟周期整个处理器几乎都在晒太阳。这就是为什么必须有高速缓存、为什么数据库要有缓冲池、为什么内存数据库能被当成快的代名词——核心思路都是让数据尽量靠近 CPU别去惊动最底下那几层。6.2 局部性原理为什么遍历顺序会影响性能高速缓存的核心思想是拿空间换时间。CPU 访问内存时不是只取一个字节而是以缓存行通常 64 字节为单位整块搬运到缓存里。这个设计背后靠的是两个局部性原理时间局部性刚访问过的数据短时间内很可能再次访问典型例子就是循环里反复用的变量。空间局部性访问过的地址附近的数据很可能紧接着就要被访问典型例子就是数组的顺序遍历。这两个原理能解释大量真实的性能差异。最经典的是二维数组的遍历同一块数据按行遍历可能比按列遍历快数倍。因为二维数组在内存里按行连续存放按行遍历时每次取来的缓存行几乎都被用上按列遍历则是跨行跳地址每次都要重新去内存进货等于频繁往仓库跑。另一个实际经验链表比数组慢不只因为指针跳转损耗更因为链表节点在内存里零散分布缓存命中率天然低。写高频路径代码时优先考虑连续存储的数组或切片往往比抠几条指令都有效。这是数据布局影响性能最直观的体现。6.3 内存的边界操作系统为什么偷偷用内存做磁盘缓存既然内存这么快把所有数据都放内存行不行这里有两个硬约束内存断电丢失做不了持久化内存比磁盘贵一个数量级成本承受不起。所以数据终究要落盘但操作系统做了一件很聪明的事——页缓存你第一次读文件时磁盘数据会顺带留在内存里第二次读同一份数据直接命中内存根本不用再碰磁盘。这就是你反复打开同一个文件越来越快、数据库冷数据变成热数据后查询变快的原因。用free命令看内存那一列 cache 就是操作系统替你暂存的热数据。理解这一层之后设计缓存会更有分寸应用层缓存、操作系统页缓存、存储引擎自身的缓冲池每层都在用空间换时间。很多时候性能问题不是缺缓存而是缓存加得太乱、命中率太低反而增加了维护成本。6.4 多核缓存一致性并发 bug 的物理根源缓存带来速度也带来麻烦。多核 CPU 上每个核心都有自己的 L1/L2 缓存同一个变量可能同时存在于多个核心的缓存里。如果其中一个核心修改了它其他核心还握着旧值程序行为就会错乱。硬件层面靠缓存一致性协议来兜底——以最常见的 MESI 协议为例某个核心写入时会通知其他核心把对应缓存行标记为失效强制它们下次必须重新读取。软件层面还有两个捣乱者编译器的指令重排和 CPU 的乱序执行。为了在优化性能的同时保证程序语义正确才需要内存屏障、原子指令、volatile 关键字这些工具。为什么双重检查锁要加 volatile为什么无锁队列那么难写为什么自旋锁在单核上会死锁追根溯源全都指向缓存一致性和内存模型。你不用亲手实现这些协议但理解了CPU 不会骗你缓存会这个事实再看各类并发工具的文档就能明白它们到底在防什么。最后说一点私人的体会。我见过很多工程师遇到线上问题习惯性先翻日志、再猜原因、实在不行重启大法但真正高效的人开口第一句往往是问题发生在哪一层是 CPU 算不过来还是内存不够在换页还是磁盘 I/O 在排队还是缓存命中率太低这四句话其实就是这篇文章核心思想的一个缩影。计算机的基本组成决定了资源的边界二进制决定了数据长什么样操作系统用分时复用把一颗 CPU 公平地分给所有程序并发与并行决定了你该怎么组织任务存储金字塔决定了数据放哪一层最划算。把这些底层逻辑真正装进脑子里之后你会发现自己看容器、看虚拟化、看分布式、看数据库调优全都有一种原来是在不同层面重复同一套道理的通透感。我个人复盘下来觉得最值得花时间的不是背参数而是把这个工作模型变成自己的直觉。下次再遇到奇怪的性能问题别急着动手先借用这幅地图走一遍数据现在在哪一层为什么慢能不能搬到更快的层这套思考路径比任何具体工具都经得起时间考验。
阅读完成 · 觉得有帮助?
咨询建站