拓冰建站拓冰建站
首页 / 资讯中心 / 正文

用笨办法啃透《编码》:从手电筒到CPU的底层学习指南

1. 为什么我选择用“笨办法”啃下《编码》这本书第一次翻开《编码隐匿在计算机软硬件背后的语言》的时候我的反应和大多数人一样——这书是不是拿错了封面写着计算机翻开全是手电筒、继电器、黑白电线、莫尔斯电码讲得跟初中物理似的。那时候我刚工作两年自认为对计算机体系结构还算有点概念结果看了三章就发现自己连“为什么一个字节是8位”这种问题都答不上来。这本书的副标题是“隐匿在计算机软硬件背后的语言”作者Charles Petzold用了一种极其反直觉的写法他从两个人用手电筒隔着街道聊天开始讲起一路讲到继电器、逻辑门、加法器、存储器、CPU最后讲到操作系统和高级语言。整本书没有一行真正的代码却把计算机从零到一的构建过程讲得清清楚楚。问题在于这本书太“厚”了。不是页数厚是信息密度厚。每一章都在前一章的基础上加一层抽象你如果只是用眼睛扫过去很容易产生“我懂了”的幻觉但合上书就会发现脑子里全是碎片。我试过快速通读一遍结果只记住了“手电筒”和“继电器”两个词。后来我换了个策略也就是标题里说的“笨办法”每一章都动手做笔记、画图、甚至用纸笔模拟书中的电路。比如讲到逻辑门的时候我真的拿了两节电池、几个开关和灯泡在桌上搭了一个AND门和OR门。讲到加法器的时候我用Excel把真值表全部列了一遍手动验证了半加器和全加器的进位逻辑。讲到存储器的时候我用Python写了一个极简的触发器模拟器虽然只有几十行代码但跑起来的那一刻我对“锁存”这个概念的理解彻底不一样了。这个“笨办法”的核心逻辑其实很简单计算机科学里很多概念是层层抽象的如果你跳过底层直接看上层你只能记住结论无法理解原因。而《编码》这本书的价值恰恰在于它把每一层抽象都拆开了给你看如果你不用“笨办法”去亲手验证每一层你就浪费了这本书最大的价值。这篇文章适合两类人一类是正在读或者准备读《编码》但觉得“读不进去”的朋友另一类是虽然没读过这本书但想真正搞明白“计算机到底是怎么从一堆开关变成能跑程序的机器”的从业者。我会把我在读这本书过程中用到的所有“笨办法”拆开讲包括每一章的核心逻辑、我做的笔记和实验、以及那些让我卡住很久的坑。2. 从手电筒到继电器底层逻辑的“笨”拆解2.1 手电筒通信为什么是整本书的基石《编码》第一章讲的是两个小孩用手电筒隔着街道聊天。一个手电筒亮一下代表“1”灭着代表“0”通过约定好的编码规则他们可以传递任意信息。这个场景看起来简单到幼稚但它包含了计算机科学里最核心的几个概念二进制、编码、协议、以及物理层与逻辑层的分离。我在这里用的“笨办法”是真的拿一个手电筒和一个朋友做了一次实验。我们约定用莫尔斯电码的简化版亮1秒代表“划”亮0.3秒代表“点”间隔0.3秒。结果发现人在接收端根本跟不上节奏因为手电筒的开关速度、人的反应速度、以及环境光干扰都会导致误码。这个实验让我深刻理解了一件事为什么计算机需要时钟信号。人的手速大概在毫秒级而现代CPU的时钟周期是纳秒级差了六个数量级。如果没有一个统一的时钟来同步收发双方通信根本不可能可靠。书里接下来引入了“编码”的概念同样的物理信号亮/灭用不同的编码规则可以表示不同的信息。比如你可以约定“亮1灭0”也可以约定“亮0灭1”甚至可以用“亮-灭-亮”代表一个字母。这其实就是字符编码的雏形。我在笔记里画了一张表把书里提到的几种编码方式莫尔斯电码、布莱叶盲文、二进制编码做了对比发现它们本质上都是“用有限符号表示无限信息”的映射规则。提示很多读者会跳过这一章觉得“手电筒太简单了”。但如果你不理解“物理信号如何通过编码变成逻辑信息”后面讲继电器和逻辑门的时候你会更晕。建议在这一章至少花半小时亲手画一遍编码表。2.2 继电器从机械开关到逻辑门的跨越继电器是《编码》里第一个真正意义上的“硬件”组件。书里用了一个非常巧妙的比喻继电器就像一个由电流控制的水阀小电流控制大电流或者更准确地说一个电路的通断可以控制另一个电路的通断。这个特性让继电器从单纯的“开关”变成了“逻辑元件”。我在这里的“笨办法”是用纸笔画出继电器的四种基本连接方式。书里讲了串联、并联、常开、常闭四种组合我每一种都画了电路图并且标注了输入输出关系。画完之后我发现这四种组合其实就是后来逻辑门的物理实现继电器连接方式逻辑等价输入输出关系两个常开串联AND门两个都通电才输出两个常开并联OR门任意一个通电就输出常开常闭串联异或门雏形一个通一个断才输出常闭单独使用NOT门输入通电则输出断电这个表格是我自己总结的书里并没有直接列出来。但画完这张表之后我再看后面讲逻辑门的时候脑子里自动就能把继电器和逻辑符号对应起来。很多读者觉得逻辑门是“凭空出现”的抽象概念其实它就是从继电器的物理连接方式中抽象出来的。这里有一个我踩过的坑继电器是有延迟的。书里在讲振荡器的时候提到了这一点但我在做实验的时候才真正体会到。我用一个继电器搭了一个简单的振荡电路结果发现输出频率远低于理论值因为继电器的机械触点需要几毫秒才能吸合或释放。这个延迟在现代计算机里是致命的所以后来才有了真空管和晶体管。但理解这个延迟很重要因为它解释了为什么计算机需要时钟信号来同步——如果每个逻辑门的延迟不一样你就需要一个统一的节拍来确保信号稳定后再进行下一步。2.3 从逻辑门到加法器手动验证每一个真值表《编码》从第11章开始进入真正的“计算”部分。书里先讲了二进制加法然后讲如何用逻辑门实现半加器和全加器。这一部分是整本书的第一个大坎因为你需要同时理解二进制运算、逻辑门电路、以及进位传递。我的“笨办法”是用Excel把半加器和全加器的真值表全部列出来然后手动推导每一位的进位。半加器很简单两个输入A和B输出和S以及进位C。真值表只有四行ABSC0000011010101101这个表我看了三遍就记住了但全加器就没那么简单了。全加器有三个输入A、B、进位输入Cin输出和S以及进位输出Cout。真值表有八行我一开始怎么都记不住。后来我换了个方法把全加器拆成两个半加器加一个OR门。第一个半加器处理A和B输出S1和C1第二个半加器处理S1和Cin输出S和C2最后C1和C2通过OR门输出Cout。拆解之后逻辑就清晰了。然后我用Python写了一个模拟器把8个全加器串联起来模拟了一个8位加法器。代码很简单但跑起来的时候我输入了两个8位二进制数看着每一位的进位像波浪一样从低位传到高位那种感觉比看一百遍书都管用。这个模拟器我后来还扩展了一下加了溢出检测和标志位输出基本上就是一个极简的ALU了。注意书里在讲加法器的时候进位传递是“串行”的也就是每一位的进位要等前一位算完才能算。这种设计叫“行波进位加法器”速度很慢。现代CPU用的是“超前进位加法器”但原理是一样的只是把进位提前算出来了。如果你不理解行波进位超前进位就更看不懂了。3. 存储器、CPU与指令集用模拟器打通任督二脉3.1 触发器与锁存器为什么“记住”这么难《编码》第14章讲存储器的时候我卡了整整一周。书里从最简单的RS触发器讲起然后讲D触发器、JK触发器最后讲如何用触发器组成寄存器。问题在于触发器是一个“反馈回路”输出会反过来影响输入这种自引用结构在直觉上很难理解。我的“笨办法”是用Python写了一个RS触发器的模拟器然后手动输入各种组合观察输出变化。RS触发器有两个输入S和R两个输出Q和Q。规则很简单S1时Q1R1时Q0SR0时保持SR1时禁止。但问题在于如果你在SR1之后同时把两个输入都变成0输出是不确定的。这个“不确定状态”在书里只是一笔带过但我在模拟器里反复测试了几十次才真正理解为什么这个状态是“禁止”的。后来我画了一张状态转移图把RS触发器的四种输入组合和对应的输出状态全部标出来然后用箭头表示状态之间的转移。画完之后我发现触发器的本质是一个“双稳态电路”它有两个稳定状态0和1输入信号只是用来触发状态切换而不是直接决定输出。这个理解对我后面看CPU的寄存器设计帮助极大。D触发器是在RS触发器基础上加了门控电路把两个输入合并成一个数据输入和一个时钟输入。书里讲D触发器的时候用了一个“锁存”的比喻时钟信号就像一扇门门打开的时候数据可以通过门关上的时候数据被锁住。这个比喻很形象但我在模拟器里发现D触发器在时钟高电平期间是“透明”的也就是说如果时钟一直保持高电平输出会跟着输入变化。这就是为什么后来有了“边沿触发”的触发器——只在时钟的上升沿或下降沿采样数据。3.2 从寄存器到RAM地址译码的笨办法理解了触发器之后寄存器就很好理解了一组触发器并联共用同一个时钟信号就可以存储多位数据。书里讲寄存器的时候我画了一个8位寄存器的结构图每个触发器对应一位时钟信号统一控制。然后讲RAM的时候引入了“地址译码”的概念。地址译码是另一个让我卡住的地方。书里讲的是如果你有8个寄存器每个寄存器有8位那么你需要一个3-8译码器来选择哪个寄存器被读写。3-8译码器的输入是3位地址输出是8条选择线每条线对应一个寄存器。我一开始不理解为什么需要译码器直接用地址线去控制寄存器的使能端不行吗后来我用了一个“笨办法”画了一张8个寄存器的内存地图然后手动模拟了读写过程。假设我要往地址3写入数据地址线是011译码器输出第3条线为高电平其他7条线为低电平。第3条线连接到第3个寄存器的使能端所以只有第3个寄存器被激活数据写入。如果不用译码器你就需要8条独立的使能线地址线也要8条这样地址空间和寄存器数量就是一对一的关系无法扩展。这个理解让我后来看现代内存的“行地址”和“列地址”复用技术时轻松了很多。本质上地址译码就是用更少的线控制更多的设备这是计算机体系结构里一个反复出现的主题。3.3 亲手实现一个极简CPU《编码》最后几章讲的是CPU和指令集。书里用一个非常简化的模型CPU只有几个寄存器、几条指令来演示“取指-译码-执行”的循环。这一部分我用了最“笨”的办法用Python写了一个完整的CPU模拟器包括寄存器、内存、指令译码器和执行单元。我定义的指令集只有8条指令LOAD、STORE、ADD、SUB、JUMP、JZ、HALT、NOP。每条指令有操作码和操作数操作码占4位操作数占4位。内存是256字节寄存器有4个A、B、C、D。程序计数器PC指向下一条指令的地址。写这个模拟器花了大概三个晚上但收获巨大。我第一次真正理解了“程序就是数据”这句话的含义指令存储在内存里和普通数据没有区别CPU只是按照PC指向的地址去取指令、译码、执行。我还实现了一个简单的汇编器把汇编代码翻译成机器码。当我写了一个计算斐波那契数列的小程序看着它在我的模拟器上跑出正确结果的时候那种成就感比跑通任何框架都强。提示如果你也想写CPU模拟器建议从4位或8位开始不要一上来就搞32位。指令集越简单越好能跑通“取指-译码-执行”循环就行。重点不是模拟器的功能有多强而是你亲手走了一遍CPU的工作流程。4. 那些让我卡住很久的坑与笨办法的意外收获4.1 二进制补码为什么负数要用补码表示《编码》在讲加法器的时候提到了二进制补码但书里只是简单带过没有详细解释为什么需要补码。我一开始觉得补码很反直觉为什么-1要用11111111表示直接用一个符号位不就行了吗后来我用“笨办法”手动推导了一遍如果用一个符号位表示正负那么0会有两种表示0和-0而且加法器需要额外的电路来处理符号位。而补码的好处是减法和加法可以用同一套电路。比如计算5-3可以转换成5(-3)而-3的补码是3的按位取反加1。这样加法器不需要知道是在做加法还是减法统一按加法处理就行。我画了一张表把4位二进制数的所有补码表示列出来然后手动验证了每一对加减法。验证完之后我对补码的理解从“记住规则”变成了“理解原因”。这个理解在我后来看浮点数表示IEEE 754的时候帮助很大因为浮点数的指数部分也用了类似的偏移表示法。4.2 时钟信号为什么计算机需要“心跳”书里在讲触发器的时候提到了时钟信号但直到我写CPU模拟器的时候才真正理解时钟信号的作用。在我的模拟器里我一开始没有加时钟结果发现指令执行顺序完全乱了有的指令还没执行完下一条指令就开始取指了。后来我加了一个“时钟周期”的概念每个时钟周期只做一件事要么取指要么译码要么执行要么写回。这样虽然效率低但逻辑清晰。现代CPU用了流水线技术可以同时执行多条指令的不同阶段但本质上还是靠时钟信号来同步。这个理解让我后来看CPU的“主频”参数时有了新的认识主频不是“运算速度”而是“心跳频率”。每个时钟周期CPU只能完成一个最基本的操作所以主频越高单位时间内完成的操作越多。但不同CPU的“一个操作”可能不一样所以不能单纯用主频来比较性能。4.3 笨办法的意外收获从“知道”到“理解”用“笨办法”读《编码》最大的收获不是记住了多少知识点而是建立了一套从物理层到应用层的完整心智模型。以前我看计算机的时候看到的是一个个独立的模块CPU、内存、硬盘、操作系统、编译器。现在我看计算机的时候看到的是一个层层抽象的系统从继电器到逻辑门从逻辑门到加法器从加法器到CPU从CPU到操作系统每一层都是在前一层的基础上加了一层抽象。这个心智模型让我在后来学习新技术的时候轻松了很多。比如看AJAX请求设置编码格式的时候我会想到“编码”本质上就是字符到字节的映射规则不同的编码格式UTF-8、GBK只是映射表不同。看哈夫曼编码的时候我会想到“变长编码”的本质是用短码表示高频符号用长码表示低频符号这和莫尔斯电码的原理是一样的。看Base64编码的时候我会想到“用64个可打印字符表示二进制数据”本质上是一种“编码转换”把任意字节映射到可打印字符集。甚至看URL编码、HTML特殊字符编码的时候我也会想到《编码》里讲的“转义”概念当某个字符在特定上下文中有特殊含义时需要用一种“转义序列”来表示它。比如URL里的空格要写成%20HTML里的要写成本质上都是“用安全字符表示不安全字符”。5. 给不同基础读者的笨办法适配建议5.1 零基础读者从手电筒实验开始如果你完全没有计算机基础我建议你不要一上来就啃《编码》的后面几章。先从第一章的手电筒实验开始真的拿一个手电筒和一个朋友做一次通信实验。然后按照书里的顺序一章一章地做笔记、画图。遇到不懂的概念不要跳过用搜索引擎查一下或者找一些视频教程辅助理解。对于零基础读者我特别推荐用“纸笔模拟”的方法。比如讲逻辑门的时候用纸笔画电路图讲加法器的时候用纸笔列真值表讲存储器的时候用纸笔画触发器的状态转移图。这些“笨办法”看起来效率低但能帮你建立扎实的底层直觉。5.2 有编程基础的读者直接写模拟器如果你已经会写代码我建议你跳过纸笔模拟直接用代码实现书里的每一个组件。从逻辑门开始写一个简单的逻辑门模拟器然后写加法器、触发器、寄存器、RAM最后写一个完整的CPU模拟器。这个过程可能需要几周时间但收获会比单纯看书大十倍。写模拟器的时候不要追求功能完整重点是理解每个组件的工作原理。比如写加法器的时候你不需要实现32位加法8位就够了写CPU的时候你不需要实现完整的指令集8条指令就够了。关键是走一遍“取指-译码-执行”的循环理解程序是如何被执行的。5.3 有工作经验的读者用《编码》补全知识体系如果你已经工作几年对计算机体系结构有一定了解但总觉得知识是“碎片化”的我建议你用《编码》来补全知识体系。具体方法是每读一章就把它和你工作中遇到的技术问题对应起来。比如你工作中用过数据库那么读到存储器那一章的时候可以想一想数据库的索引结构B树、B树和内存的地址译码有什么关系。你工作中用过编译器那么读到指令集那一章的时候可以想一想编译器是如何把高级语言翻译成机器码的。你工作中用过网络协议那么读到编码那一章的时候可以想一想网络协议里的编码格式如TLV、JSON、Protobuf和书里讲的编码规则有什么异同。这种“对应式阅读”能帮你把书里的底层原理和上层应用连接起来形成完整的知识网络。5.4 一个通用的笨办法费曼学习法不管你是什么基础我都推荐你用费曼学习法来读《编码》。具体做法是每读完一章就假装你要给一个完全不懂计算机的朋友讲这一章的内容。如果你能用自己的话把这一章的核心概念讲清楚说明你真的理解了如果你讲着讲着卡住了说明你还有没理解的地方回去重新读。我在读《编码》的时候每读完一章都会写一篇“讲解稿”发在我的个人笔记里。这些讲解稿后来成了我写这篇文章的素材。写讲解稿的过程其实就是把书里的知识“内化”成自己的知识的过程。6. 笨办法读《编码》的长期价值用“笨办法”读《编码》最大的长期价值不是让你成为硬件专家而是让你建立一种“从底层理解问题”的思维方式。这种思维方式在解决复杂技术问题的时候特别有用。比如你遇到一个性能问题普通人的做法是“加缓存、加索引、加机器”但如果你有底层思维你会先问瓶颈到底在哪里是CPU计算、内存访问、磁盘IO还是网络延迟然后你会用工具去测量而不是盲目优化。这种“先理解再优化”的思维方式就是《编码》教给我的。再比如你遇到一个编码问题普通人的做法是“试各种编码格式直到不乱码为止”但如果你有底层思维你会先问这个数据的原始编码是什么中间经过了哪些转换每一步转换是否指定了正确的编码格式然后你会用工具去检测编码而不是盲目尝试。我后来在工作中遇到过一个数据库同步的问题两个数据库的字符集不一样导致同步后出现乱码。同事试了好几种编码格式都没解决我用《编码》里学到的知识分析了一下源数据库是UTF-8目标数据库是GBK同步工具默认用UTF-8解码源数据然后用GBK编码写入目标数据库但有些字符在GBK里不存在所以出现了乱码。解决方案是在同步工具里指定“源编码UTF-8目标编码GBK”并且对不支持的字符做转义处理。这个问题解决之后我对“编码”的理解又深了一层。《编码》这本书我读了三年前后读了五遍。第一遍是通读第二遍是精读第三遍是带着问题读第四遍是写模拟器的时候参考第五遍是写这篇文章的时候重新翻阅。每一遍都有新的收获。如果你也在读这本书或者准备读这本书我建议你不要着急一章一章地啃用“笨办法”把每一个概念都亲手验证一遍。这个过程可能很慢但慢就是快。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门