从汇编的角度看MCU运行机制

strongerHuang 2026-01-16 08:00

关注+星标公众,不错过精彩内容

素材来源 | 网络


我们平时写的C、C++、JAVA等这些代码属于高级语言,虽然我们能理解,但计算机是需要经过翻译才能理解的,必须通过编译器转成二进制代码,才能运行。

反过来,我们学会高级语言,并不等于理解计算机实际的运行步骤。

计算机真正能够理解的是低级语言,它专门用来控制硬件。汇编语言就是低级语言,直接描述/控制 CPU 的运行。如果你想了解 CPU 到底干了些什么,以及代码的运行步骤,就一定要学习汇编语言。

汇编语言不容易学习,就连简明扼要的介绍都很难找到。下面我尝试写一篇最好懂的汇编语言教程,解释 CPU 如何执行代码。

一、汇编语言是什么?

我们知道,CPU 只负责计算,本身不具备智能。你输入一条指令(instruction),它就运行一次,然后停下来,等待下一条指令。

这些指令都是二进制的,称为操作码(opcode),比如加法指令就是00000011编译器的作用,就是将高级语言写好的程序,翻译成一条条操作码。

对于人类来说,二进制程序是不可读的,根本看不出来机器干了什么。为了解决可读性的问题,以及偶尔的编辑需求,就诞生了汇编语言。

「汇编语言是二进制指令的文本形式」,与指令是一一对应的关系。比如,加法指令00000011写成汇编语言就是 ADD。只要还原成二进制,汇编语言就可以被 CPU 直接执行,所以它是最底层的低级语言。

二、来历

最早的时候,编写程序就是手写二进制指令,然后通过各种开关输入计算机,比如要做加法了,就按一下加法开关。后来,发明了纸带打孔机,通过在纸带上打孔,将二进制指令自动输入计算机。

为了解决二进制指令的可读性问题,工程师将那些指令写成了八进制。二进制转八进制是轻而易举的,但是八进制的可读性也不行。

很自然地,最后还是用文字表达,加法指令写成 ADD。内存地址也不再直接引用,而是用标签表示。

这样的话,就多出一个步骤,要把这些文字指令翻译成二进制,这个步骤就称为 assembling,完成这个步骤的程序就叫做 assembler。它处理的文本,自然就叫做 aseembly code。标准化以后,称为 assembly language,缩写为 asm,中文译为汇编语言。

每一种 CPU 的机器指令都是不一样的,因此对应的汇编语言也不一样。本文介绍的是目前最常见的 x86 汇编语言,即 Intel 公司的 CPU 使用的那一种。

三、寄存器

学习汇编语言,首先必须了解两个知识点:寄存器内存模型

先来看寄存器。CPU 本身只负责运算,不负责储存数据。数据一般都储存在内存之中,CPU 要用的时候就去内存读写数据。

但是,CPU 的运算速度远高于内存的读写速度,为了避免被拖慢,CPU 都自带一级缓存和二级缓存。基本上,CPU 缓存可以看作是读写速度较快的内存。

但是,CPU 缓存还是不够快,另外数据在缓存里面的地址是不固定的,CPU 每次读写都要寻址也会拖慢速度。

因此,除了缓存之外,CPU 还自带了寄存器(register),用来储存最常用的数据。也就是说,那些最频繁读写的数据(比如循环变量),都会放在寄存器里面,CPU 优先读写寄存器,再由寄存器跟内存交换数据。

寄存器不依靠地址区分数据,而依靠名称。每一个寄存器都有自己的名称,我们告诉 CPU 去具体的哪一个寄存器拿数据,这样的速度是最快的。有人比喻寄存器是 CPU 的零级缓存。

四、寄存器的种类

早期的 x86 CPU 只有8个寄存器,而且每个都有不同的用途。现在的寄存器已经有100多个了,都变成通用寄存器,不特别指定用途了,但是早期寄存器的名字都被保存了下来。

  • EAX
  • EBX
  • ECX
  • EDX
  • EDI
  • ESI
  • EBP
  • ESP

上面这8个寄存器之中,前面七个都是通用的。ESP 寄存器有特定用途,保存当前 Stack 的地址(详见下一节)。

我们常常看到 32位 CPU、64位 CPU 这样的名称,其实指的就是寄存器的大小。32 位 CPU 的寄存器大小就是4个字节。

五、内存模型:Heap

寄存器只能存放很少量的数据,大多数时候,CPU 要指挥寄存器,直接跟内存交换数据。所以,除了寄存器,还必须了解内存怎么储存数据。

程序运行的时候,操作系统会给它分配一段内存,用来储存程序和运行产生的数据。这段内存有起始地址和结束地址,比如从0x10000x8000,起始地址是较小的那个地址,结束地址是较大的那个地址。

程序运行过程中,对于动态的内存占用请求(比如新建对象,或者使用malloc命令),系统就会从预先分配好的那段内存之中,划出一部分给用户,具体规则是从起始地址开始划分(实际上,起始地址会有一段静态数据,这里忽略)。

举例来说,用户要求得到10个字节内存,那么从起始地址0x1000开始给他分配,一直分配到地址0x100A,如果再要求得到22个字节,那么就分配到0x1020

这种因为用户主动请求而划分出来的内存区域,叫做 Heap(堆)。它由起始地址开始,从低位(地址)向高位(地址)增长。Heap 的一个重要特点就是不会自动消失,必须手动释放,或者由垃圾回收机制来回收。


六、内存模型:Stack

除了 Heap 以外,其他的内存占用叫做 Stack(栈)。简单说,Stack 是由于函数运行而临时占用的内存区域。

请看下面的例子。

int main() 
{
    int a = 2;
    int b = 3;
}

上面代码中,系统开始执行main函数时,会为它在内存里面建立一个帧(frame),所有main的内部变量(比如ab)都保存在这个帧里面。main函数执行结束后,该帧就会被回收,释放所有的内部变量,不再占用空间。

如果函数内部调用了其他函数,会发生什么情况?

int main() 
{
   int a = 2;
   int b = 3;
   return add_a_and_b(a, b);
}

上面代码中,main函数内部调用了add_a_and_b函数。执行到这一行的时候,系统也会为add_a_and_b新建一个帧,用来储存它的内部变量。也就是说,此时同时存在两个帧:mainadd_a_and_b一般来说,调用栈有多少层,就有多少帧。

等到add_a_and_b运行结束,它的帧就会被回收,系统会回到函数main刚才中断执行的地方,继续往下执行。通过这种机制,就实现了函数的层层调用,并且每一层都能使用自己的本地变量。

所有的帧都存放在 Stack,由于帧是一层层叠加的,所以 Stack 叫做栈。生成新的帧,叫做"入栈",英文是 push;栈的回收叫做"出栈",英文是 pop。Stack 的特点就是,最晚入栈的帧最早出栈(因为最内层的函数调用,最先结束运行),这就叫做"后进先出"的数据结构。

每一次函数执行结束,就自动释放一个帧,所有函数执行结束,整个 Stack 就都释放了。

Stack 是由内存区域的结束地址开始,从高位(地址)向低位(地址)分配。比如,内存区域的结束地址是0x8000,第一帧假定是16字节,那么下一次分配的地址就会从0x7FF0开始;第二帧假定需要64字节,那么地址就会移动到0x7FB0

七、CPU 指令

7.1 一个实例

了解寄存器和内存模型以后,就可以来看汇编语言到底是什么了。下面是一个简单的程序example.c

int add_a_and_b(int a, int b) {
   return a + b;
}

int main() {
   return add_a_and_b(2, 3);
}

gcc 将这个程序转成汇编语言。

$ gcc -S example.c

上面的命令执行以后,会生成一个文本文件example.s,里面就是汇编语言,包含了几十行指令。这么说吧,一个高级语言的简单操作,底层可能由几个,甚至几十个 CPU 指令构成。CPU 依次执行这些指令,完成这一步操作。

example.s经过简化以后,大概是下面的样子。

_add_a_and_b:
   push   %ebx
   mov    %eax, [%esp+8] 
   mov    %ebx, [%esp+12]
   add    %eax, %ebx 
   pop    %ebx 
   ret  

_main:
   push   3
   push   2
   call   _add_a_and_b 
   add    %esp, 8
   ret

可以看到,原程序的两个函数add_a_and_bmain,对应两个标签_add_a_and_b_main。每个标签里面是该函数所转成的 CPU 运行流程。

每一行就是 CPU 执行的一次操作。它又分成两部分,就以其中一行为例。

push   %ebx

这一行里面,push是 CPU 指令,%ebx是该指令要用到的运算子。一个 CPU 指令可以有零个到多个运算子。

下面我就一行一行讲解这个汇编程序,建议读者最好把这个程序,在另一个窗口拷贝一份,省得阅读的时候再把页面滚动上来。

7.2 push 指令

根据约定,程序从_main标签开始执行,这时会在 Stack 上为main建立一个帧,并将 Stack 所指向的地址,写入 ESP 寄存器。后面如果有数据要写入main这个帧,就会写在 ESP 寄存器所保存的地址。

然后,开始执行第一行代码。

push   3

push指令用于将运算子放入 Stack,这里就是将3写入main这个帧。

虽然看上去很简单,push指令其实有一个前置操作。它会先取出 ESP 寄存器里面的地址,将其减去4个字节,然后将新地址写入 ESP 寄存器。

使用减法是因为 Stack 从高位向低位发展,4个字节则是因为3的类型是int,占用4个字节。得到新地址以后, 3 就会写入这个地址开始的四个字节。

push   2

第二行也是一样,push指令将2写入main这个帧,位置紧贴着前面写入的3。这时,ESP 寄存器会再减去 4个字节(累计减去8)。

7.3 call 指令

第三行的call指令用来调用函数。

call   _add_a_and_b

上面的代码表示调用add_a_and_b函数。这时,程序就会去找_add_a_and_b标签,并为该函数建立一个新的帧。

下面就开始执行_add_a_and_b的代码。

push   %ebx

这一行表示将 EBX 寄存器里面的值,写入_add_a_and_b这个帧。这是因为后面要用到这个寄存器,就先把里面的值取出来,用完后再写回去。

这时,push指令会再将 ESP 寄存器里面的地址减去4个字节(累计减去12)。

7.4 mov 指令

mov指令用于将一个值写入某个寄存器。

mov    %eax, [%esp+8]

这一行代码表示,先将 ESP 寄存器里面的地址加上8个字节,得到一个新的地址,然后按照这个地址在 Stack 取出数据。根据前面的步骤,可以推算出这里取出的是2,再将2写入 EAX 寄存器。

下一行代码也是干同样的事情。

mov    %ebx, [%esp+12]

上面的代码将 ESP 寄存器的值加12个字节,再按照这个地址在 Stack 取出数据,这次取出的是3,将其写入 EBX 寄存器。

7.5 add 指令

add指令用于将两个运算子相加,并将结果写入第一个运算子。

add    %eax, %ebx

上面的代码将 EAX 寄存器的值(即2)加上 EBX 寄存器的值(即3),得到结果5,再将这个结果写入第一个运算子 EAX 寄存器。

7.6 pop 指令

pop指令用于取出 Stack 最近一个写入的值(即最低位地址的值),并将这个值写入运算子指定的位置。

pop    %ebx

上面的代码表示,取出 Stack 最近写入的值(即 EBX 寄存器的原始值),再将这个值写回 EBX 寄存器(因为加法已经做完了,EBX 寄存器用不到了)。

注意,pop指令还会将 ESP 寄存器里面的地址加4,即回收4个字节。

7.7 ret 指令

ret指令用于终止当前函数的执行,将运行权交还给上层函数。也就是,当前函数的帧将被回收。

ret

可以看到,该指令没有运算子。

随着add_a_and_b函数终止执行,系统就回到刚才main函数中断的地方,继续往下执行。

add    %esp, 8

上面的代码表示,将 ESP 寄存器里面的地址,手动加上8个字节,再写回 ESP 寄存器。这是因为 ESP 寄存器的是 Stack 的写入开始地址,前面的pop操作已经回收了4个字节,这里再回收8个字节,等于全部回收。

ret

最后,main函数运行结束,ret指令退出程序执行。

八、参考链接

  • Introduction to reverse engineering and Assembly, by Youness Alaoui
  • x86 Assembly Guide, by University of Virginia Computer Science


声明:本文素材来源网络,版权归原作者所有。如涉及作品版权问题,请与我联系删除。

------------ END ------------


FreeRTOS和embOS的区别!


对比cm3.h与cm85.h,其中SysTick用法区别


嵌入式系统为什么需要校验码?



strongerHuang 作者黄工,高级嵌入式软件工程师,分享嵌入式软硬件、物联网、单片机、开发工具、电子等内容。
评论
  • 2026年了,过去的一年工作和家庭事情都比较多,来面包板比较少。2026年事情好了很多,可以多来面包板分享自己的工作。 在新的一年里。工作方面,继续努力,自己的公司把业务都办理完成,能顺利营业就可以,主要也是为了自己工作方便。主业还是多参加比赛。具体的目标:(1)公司工商注册等都完成。(2)参加教学比赛2次。(3)完成论文3篇,一个项目论文,一个会议论文,这个是确定的,必须完成,自己在写一个论文。(4)参加一次技能比赛,视觉的比赛。 有时候运气也很重要,不是自己的也不去强求了
    curton 2026-01-05 09:59 77782浏览
  • ESP32S3小智开发板烧录指南ESP32S3小智开发板烧录核心需做好硬件接线、烧录模式操作与软件配置,步骤如下: 1. 硬件接线:用USB转TTL模块连接,3.3V接板载3.3V(严禁5V)、GND共地,模块TX接开发板RX(GPIO44)、RX接TX(GPIO43),确保接线无松动。 2. 进入烧录模式:按住板载BOOT键不松,快速按EN复位键,先松EN再松BOOT,此时开发板进入烧录模式。  3. 软件配置:IDF环境下先执行`idf.py set-
    丙丁先生 2026-01-10 12:33 69426浏览
  •        面包板社区选品绝对优品!首先感谢面包板及工作人员在这之前策划了很多可以赚取E币的活动(发技术帖、分享项目经验、回答问题、参会、11-12月每天坚持签到领E币),每一个都几乎与了,所以获利不少,这次活动力度之大更是前所未有,买东西返现!!!再次感谢,比心!!!来看看我买了哪些好宝贝:                     
    甜椒的尾巴 2025-12-31 09:37 2572浏览
  • 坚 守2025年于风雨飘摇中逝去。多年来,我如老牛耕垦,在这片希望的田野上持续耕耘。尽管每年收成不一、亦因人而异,但“老骥伏枥,志在千里”的古训,始终激励着我前行。过去一年,职场中虽遇波澜,然心境渐趋平和。恰如苏轼《观潮》诗中所喻:庐山烟雨浙江潮,未至千般恨不消。到得还来别无事,庐山烟雨浙江潮。历事后方知,潮起潮落不过常态,唯有坚守本心、专注所为,方能穿透迷雾、踏实前行。一、专利布局与维护全年围绕核心技术及新产品,累计申报发明专利3项、实用新型专利1项,其中2项发明专利已进入实质审查阶段。系统
    广州铁金刚 2026-01-09 10:40 1565浏览
  • 在智能家居的网络架构中,Wi-Fi、蓝牙、Zigbee与Thread等通信协议是实现设备无线互联与协同控制的底层逻辑。然而,这些协议在标准体系、寻址方式与网络管理机制上却彼此独立,缺乏统一的互操作框架,在进行跨协议组网时需要依赖中心网关作为“翻译桥梁”,这不仅抬高了全屋智能的部署成本,还增加了系统的网络复杂度与不稳定性。在此背景下,行业迫切地需要一种能跨协议、跨生态与跨品牌通信的统一标准来破局,从而在根本上解决智能家居场景中设备难互联、生态难融合与通信不稳定等问题,将智能家居从“平台主导”阶段全
    华普微HOPERF 2026-01-07 11:08 1410浏览
  • 2026年1月6日,美国CES展会上有一则重磅消息,芬兰初创公司Donut Lab正式推出全球首款可量产全固态电池,该电池不仅实现5分钟满电、10万次循环寿命的性能飞跃,更已完成OEM量产适配,搭载该电池的电动摩托车将于今年第一季度交付用户。这一突破标志着长期停留在实验室阶段的全固态电池技术正式迈入商业化落地阶段,有望彻底解决传统锂电池续航短、充电慢、安全隐患三大核心痛点,为新能源产业带来颠覆性变革。这款全固态电池的性能参数堪称“碾压级”超越传统锂电池。据官方披露,其能量密度达到400Wh/kg
    面包超人Tech 2026-01-09 09:23 1586浏览
  •   今天,做清理,想把闲置有点毛病了的一个老收录机看看还能不能用?  拿起电源线,呵呵,这线早就变硬了,拿到插头,准备插到电源插座上,哈哈,不声不响,插头断掉啦!  拿到了工作台,准备行动,拆解、检查、判断可否修复?  这可是一台够老的机器啦!1985年7月3日买的一台三洋牌手提式立体声收录机,那时是很时兴时髦的产品,发票和说明书都在。  前后左右上下都看了看,   SANYO STEREO RADIO CASSETTE RECORDER MODEL NO. M9805F  SANYO ELE
    自做自受 2026-01-06 21:15 1771浏览
  • 在高速数据传输的现代世界中,光模块扮演着至关重要的角色。它们如同信息高速公路上的“快递员”,负责将电信号转换为光信号进行远距离传输,再将光信号转换回电信号供设备使用。然而,在这看似简单的光-电转换过程中,有一个不起眼却不可或缺的元件在默默工作——它就是晶振,或称石英晶体振荡器。晶振:电子设备的“心跳”晶振的核心功能是产生稳定的时钟信号。想象一下,如果一场音乐会没有统一的节拍,演奏会变得混乱不堪。同样,在电子设备中,晶振提供的高精度时钟信号就像乐队的指挥,确保所有部件同步工作。在光模块中,这个“指
    TKD泰晶科技 2026-01-04 15:34 1819浏览
  • 文:郭楚妤编辑:cc孙聪颖在硬核的美国科技展上,看到软萌的国宝大熊猫,是不是有一种反差感?这次,长虹将这份独特的“AI科技+国宝熊猫文化”带到了美国拉斯维加斯的CES展上,通过熊猫主题AI家电,以及全品类AI产品,在全球舞台上讲述着属于“东方智慧”的故事。于是,在此次CES展会上,你可以看到AI科技被赋予了文化的温度和脉络。打开长虹AI TV,智能体伙伴“熊猫小白”上线,化身全天候全场景的陪伴者。打开电视里的“熊猫乐园”,能够实时看到熊猫啃竹子、玩耍的治愈画面,仿佛把四川的竹林生态带到了身边。长
    华尔街科技眼 2026-01-08 15:22 1418浏览
  • 在全球变暖、环境污染与生物多样性下降的生态危机下,发展科技的目的已不仅仅在于为各行各业提质增效,还在于促进人与自然的和谐发展。LoRaWAN作为一种面向大规模部署应用的低功耗IoT通信技术,正凭借着低功耗、远距离、大规模连接和低部署成本等特性,成为连接人类社会与自然生态的重要“沟通桥梁”。这座“沟通桥梁”不仅能将自然环境中原本难以察觉、零散分布的生态变化持续转化为可采集与分析的数据形态,使森林、河流、湿地与野生生物“开口说话”,还能帮助管理者在广袤、偏远、环境恶劣的区域中长期、稳定地获取生态数据
    华普微HOPERF 2026-01-13 15:58 413浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦