1. 第一章到底在讲什么别急着翻到后面的伪指令手里拿到王爽《汇编语言》这本书大多数人前三天都停在这本薄薄的第一章上。为什么因为这一章没有一条能立刻上手的指令没有寄存器表也没有让你写第一个Hello World满篇都是“地址总线”“数据总线”“存储单元”“内存地址空间”这类听起来像计算机组成原理的概念。不少同学翻了两页就想跳过去直接从第三章的寄存器部分开始“实战”。我的建议是第一章值得你静下心啃完而且要带着后面章节的问题去啃。这一章之所以叫“基础知识”不是它不重要恰恰相反它是整本书所有汇编指令、奇奇怪怪的“段地址偏移地址”计算方式以及DEBUG调试实验的认知底座。你后面遇到的很多困惑比如为什么8086能寻址1MB内存却在段寄存器上那么多弯弯绕绕为什么程序要在内存里跑来跑去为什么显存和内存是一回事根源都能在这一章找到。第一章学扎实了后面几章就是顺着骨架填肉第一章跳过去后面会反复回来补课。这篇文章不是教材的复述而是我站在“已经学完一遍回来再看”的位置上把第一章里真正值钱的思想、最容易懵的地方、以及需要用生活化类比才能想明白的概念拆开讲。配合着原书同步看理解速度会快不少。2. 机器语言是“0和1的世界”汇编语言是“人与机器之间的翻译官”2.1 为什么CPU只认机器码第一章开篇就给了个看起来很绝对的定义机器语言是机器指令的集合机器指令是二进制编码也就是一长串0和1。CPU是纯粹的数字电路它唯一的“工作语言”就是高低电平组合出的二进制信息。比如某条x86指令可能是1000100011011000这样的二进制串CPU通过内部的译码器把它解析成一次“把右边寄存器的数据搬运到左边寄存器”的操作。问题在于这种语言对人不友好。人类读一个十六进制数还能靠查表读二进制串基本等于看天书。你可以试试看下面这段机器码1000100111011000 101110000000000101010101请问它在干什么正常人看不出来。就算我告诉你这是三条指令你也不可能靠眼睛分辨出哪几位是操作码、哪几位是操作数。这就是机器语言的致命弱点机器觉得舒服人受不了。2.2 汇编指令的“一比一翻译”特性汇编语言的出现就是为了解决这个矛盾。它用一个方便记忆的助记符比如MOV、ADD、SUB去对应一条条机器指令。原书举例说汇编指令MOV AX, BX翻译成机器码可以是89D8这里MOV表示“传送”AX、BX是寄存器的名字。要注意汇编指令和机器指令是“一一对应”的关系这一点和其他高级语言有本质区别。C语言里一条printf可能要编译出几十条甚至上百条机器指令但汇编语言里一条指令翻译成机器码就是一条机器码没有中间商赚差价。所以汇编语言又被称为“助记符形式的机器语言”这决定了它天生贴近硬件。这里要澄清一个很多人误解的点把汇编指令翻译成机器码的工具在中文教材里有时被称为“编译器”更准确的说法其实是“汇编器”。王爽这本书为了照顾初学者不纠结术语但你心里要知道这个工具干的事情是“逐条翻译”。后面做实验时用的masm.exe宏汇编器就是干这个的。2.3 汇编语言的三样“成员”第一章给出汇编语言的三类内容很多人扫一眼就过去了我建议你停在这里想清楚汇编指令直接被CPU执行的操作翻译成机器码。这是学习的核心。伪指令由汇编器执行的“指示”不翻译成机器码。比如后面见到的segment、ends、end、assume它们只是告诉汇编器怎么组织代码不会出现在最终的可执行文件里。其他符号比如 - * /、常量、标号、注释等。这些是写给程序员和汇编器看的不是CPU的操作。一句话总结汇编指令是和CPU说话伪指令是和你手里的翻译工具说话。刚开始写程序时经常搞不清end程序结束标记和ends段结束的区别这就是对伪指令性质不熟的表现。吃透这三类东西的边界第三章写第一个程序时会轻松很多。3. 内存、字节级存储单元和“一切皆地址”的底层视角3.1 内存为什么是按“字节”编号的原书接着讲存储单元说存储器被划分成若干个存储单元每个存储单元可以存放一个字节也就是8个二进制位。这是理解所有寻址计算的起点。你可以把内存想象成一个超大型的小区每个存储单元就是小区里的一间储藏室。为了区分每间储藏室门卫给它们编了号0号、1号、2号、3号……一直往上排。这个编号就是存储单元的“地址”。计算机里最小的可寻址单位是字节不是位也不是字。也就是说你无法向CPU请求“把地址100的第3位给我”你只能把地址100的整整8个位一起拿出来然后再用指令去抽取你关心的那一位。这里有个很反直觉的事实内存里既放着指令也放着数据它们只是二进制比特没有天生的身份。在第三章你会学到CPU把某个地址里的内容当作指令取出来执行还是当作普通数据参与计算取决于执行流程落在哪里。同一个二进制数在数据区它就是数值在代码区它就是指令。这个思想第一章已埋下伏笔“指令和数据都是应用中的概念在计算机中指令和数据都用二进制表示”。3.2 B、KB、MB之间的换算关系怎么记第一章要求你记住几个基本容量关系我直接给成一张对照表方便后面算寻址空间时反复查阅单位字节数说明1 B1一个存储单元 8 bit1 KB1024 B2的10次方1 MB1024 KB2的20次方1 GB1024 MB2的30次方换算的关键是“逢1024进一”不是1000。这个细节在计算总线寻址能力时特别容易翻车。比如后面算8086的寻址范围20根地址线能访问2的20次方个字节也就是1024×1024字节正好1MB。你要是错按1000算得到的是97.66KB整个模型就崩了。3.3 指令和数据都能藏在一个地址里原书举了MOV AX, [0]的例子来说明“指令和数据在内存中有时候是同一个东西”我换个更容易理解的场景你在内存地址200H存了一个二进制串10110000如果CPU在取指令阶段走到这里它可能把它解释成一条“把立即数送入AL”的指令如果程序用MOV AL, [200H]来读取同样的二进制串就成了一个数值2这里仅示意具体编码不必纠结。这就是第一章强调的CPU分不清谁是代码谁是数据只有程序逻辑和开发者的约定才能区分。很多初学者学第三章时看到DW定义字数据和DB定义字节数据会发懵内存里只是放了8个位或16个位怎么知道是字还是字节其实指令宽度和操作数宽度是编码里写明的汇编器通过你写的伪指令来生成正确长度的操作。这个理解在这一章建立起来后面就不会再犯“想从内存读一个字节却写了一个字宽度指令”的低级错误。4. CPU读内存的完整过程三条总线是怎么配合的4.1 一个读操作里发生的四件事原书画了一个“CPU读内存”的流程很多同学只是看了一眼图没意识到这是一切汇编程序运行的微观基础。我来一步步拆解。假设CPU要从地址100H的内存单元读取数据CPU通过地址总线把地址编号100H发送出去告诉内存“我要访问哪一个房间”。CPU通过控制总线发出“读内存”的指令信号相当于传达“我要进去取东西不是写东西”。内存收到地址和读信号把100H这个存储单元里保存的字节数据放到数据总线上。CPU从数据总线上拿到数据放到自己的内部寄存器里。整个过程中地址总线负责“报门牌号”控制总线负责“说要干嘛”数据总线负责“运东西”。三者缺一不可而且方向有讲究地址信息和控制信号是从CPU发往内存的数据则可能是从内存流向CPU读也可能是从CPU流向内存写。4.2 地址总线的宽度决定“你到底能看见多大的世界”这是本章含金量最高的公式没有之一CPU寻址能力 2的“地址总线根数”次方单位字节为什么是2的N次方因为每根地址线在某一时刻只能表示0或1两个状态N根地址线合起来就能组成2的N次方个不同的二进制组合每个组合对应一个唯一的内存地址编号。就像你只有两位房间号数字能编出的最大编号就是00到99一共100个。8086 CPU有20根地址线所以它的寻址范围是2^20 1048576 字节 1MB这个1MB是第一章反复出现的数字后面章节里8086的“段地址×16 偏移地址”访问方式本质上就是为了突破16位寄存器只能表示64KB范围的限制用“段偏移”两个16位量拼出20位物理地址。你现在先记死这个1MB后续就不容易迷失。4.3 数据总线的宽度决定“运货的效率”数据总线是另一个重点。它的位数决定CPU一次能从内存中读取/写入多少字节的数据。8086数据总线是16位意味着一次读操作最多可以同时搬运16个二进制位也就是2个字节。8088的数据总线是8位虽然内部结构接近但外部传输能力只有8086的一半这也是两者在早期IBM PC上有差异的重要原因。容易混淆的地方是地址总线根数和数据总线根数是两个独立的参数。地址线决定了你能看的范围有多大数据线决定你每次能拿多少数据。生活中可以这样类比地址总线像快递单上的收货地址位数位数越多能覆盖的地址越多数据总线像货车宽度越宽一趟运的货越多但不影响你能送多少单。4.4 控制总线不只“读”和“写”两个口原书强调控制总线是一些不同控制线的集合包括内存读信号、内存写信号、中断请求信号等。控制总线越多CPU对其他设备的控制能力就越强。这里不用背每种信号的名字但要有层次感地址线和数据线解决“哪家的什么货”控制线解决“这个货是要怎么处理以及什么时候处理”。三条总线合在一起构成了CPU与外部世界打交道的全部通道。我整理了一张表把三者放在一起对比方便你边看书边对号入座总线核心属性对性能/能力的直接影响8086的数值地址总线根数决定最大可寻址内存空间20根寻址1MB数据总线位数决定一次数据传输量16位一次2字节控制总线根数决定对外部设备控制能力视具体型号而定5. 为什么内存地址空间不是“一根内存条”那么简单5.1 芯片的类型很多地址空间却是统一编址的第一章最后讲到内存地址空间这一小节是整个章节思想境界最高的一处。很多零基础的读者以为内存地址空间就是插在主板上的内存条容量但实际上CPU能够通过地址总线访问到的所有存储芯片都被统一纳入到这个“地址空间”里。按原书的说法连接CPU的内存芯片、显卡上的显存、各类接口卡上的ROM/RAM以及外部设备提供的一些存储区域从CPU的角度看都是一段段连续的地址。这就像一个小区的门牌号601室是普通住宅602室是物业办公室603室是锅炉房但门卫的登记册上它们都被排成一个连续的列表。CPU并不需要关心某个地址背后是RAM芯片、ROM芯片还是显存它只要按地址去读写就行。5.2 典型PC的内存地址分配框架虽然在第一章原书没有细给出全部PC机内存布局但为了让你理解“地址空间”这个抽象概念我可以提前给一个简版框架后续章节还会深化地址范围十六进制大体用途00000H ~ 9FFFFH主存储器RAM区应用程序和数据主要待在这里A0000H ~ BFFFFH显存地址空间CPU往这里写数据就等于在屏幕上画像素C0000H ~ FFFFFHROM区比如系统BIOS等固件程序也就是说CPU向B8000H地址写入内容并不只是“往内存某个单元写点东西”而是直接改变了显示器上的字符显示。这个操作在后面章节编写显示程序时会被大量使用。很多人学到那里才恍然大悟为什么汇编里“写显存”就等于“显示输出”因为显存本身就是内存地址空间的一部分。5.3 统一编址带来的两个学习启示第一写汇编程序时你眼里不应该有“内存条”“显卡”“BIOS”这些物理设备的界限你看到的应该是一张从0到1MB的线性地址地图。数据放在哪个物理存储介质上是CPU之外那些硬件协同工作的事情汇编程序员只需要关心地址对不对。第二内存地址空间是有限的资源不是你插了16GB内存条地址空间就有16GB那么大。在8086时代1MB地址空间是全系统共享的既包括RAM又包括显存和ROM所以操作系统和应用程序真正能自由支配的内存远小于1MB。这种“共享地址空间”的观念对后来理解段式管理、保护模式、虚拟内存都有帮助。6. 第一章必须做的小练习用思维实验代替写代码6.1 动手验算给定地址线根数给出寻址范围第一章没有入门实验题但你自己完全可以做几个思维实验来检验理解。第一种是给定地址线根数算寻址上限。比如16根地址线2^16 65536字节 64KB20根地址线2^20 1048576字节 1MB24根地址线2^24 16777216字节 16MB你自己出题自己算算多了就能把“地址空间”和“容量单位”之间的关系固化下来。注意我前面强调的1024换算法则别在这上面栽跟头。6.2 动手画一遍“CPU写内存”的时序原书只画了读的流程你可以自己补一个写的流程CPU先把地址放到地址总线上再把数据放到数据总线上然后通过控制总线发出“写内存”信号内存把数据总线上的内容保存到指定地址单元中。试着用文字或简单的方框把“读”和“写”两个流程对照着描述出来。如果能在不看教材的情况下说清楚每一步这一章最硬的骨头就算啃下来了。6.3 弄清楚“汇编程序员”和“CPU”的分工边界合上书你应当能回答三个问题CPU只认识什么——机器码也就是二进制指令。汇编语言程序最终以什么形式运行——汇编器把汇编指令翻译成机器码CPU执行机器码。汇编程序员直接操作的对象是什么——内存地址空间、寄存器、指令而不是物理器件背后的复杂时序。这三问能答得干脆利落说明你已经在用“汇编语言思维”思考问题了。答不上来的话建议回到原书1.1到1.11小节把每个小标题下的颜色标记再看一遍。7. 我给零基础读者的三条忠告第一条忠告是不要试图靠“死记指令”来过第一章这一章的使命不是让你记住MOV和ADD而是让你建立一套“CPU如何通过总线与内存对话”的心智模型。模型一旦建立后面所有指令和实验都是在往模型里添加细节模型不建立你会把每条指令都当成孤立的语法去背越学越累。第二条忠告是有空就去翻一翻附录或文档里关于8086芯片的逻辑结构图。你不需要现在弄懂每个引脚的含义但去认一认AD0到AD15这种地址/数据复用引脚或者RD、WR控制引脚再看教材里说的“地址总线20根数据总线16根”会有一种“原来这些抽象的线真的长在芯片上”的踏实感。这种把抽象概念落到具体芯片上的联结感是学硬件接近的汇编语言时最大的加速器。第三条忠告是别怕后面的DEBUG调试实验。书里第四章会让你用DEBUG直接观察内存和寄存器你会发现第一章讲的“内存地址空间”在DEBUG里就是一个可以用d命令直接查看的线性区域输入d B800:0屏幕上立刻显示出显存里的数据。当你亲手把一个数字写进显存地址、屏幕上的字符随即改变时第一章那些“地址”“数据”“控制”的抽象名词会在一瞬间全部活过来。我在实际教学里见过不少同学第一章翻得飞快到第六章段寻址就卡住回头补第一章才发现是总线宽度和寻址范围之间的关系没搞清楚。与其后面回头补课不如现在读慢一点把每一个比喻都在脑子里过成画面。这一章慢是后面所有章节快的唯一前提。