
🎬 渡水无言 :个人主页渡水无言
❄专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《freertos专栏》
⭐️流水不争先,争的是滔滔不绝
📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生
| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生
在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录
[1.1 读内存:Load](#1.1 读内存:Load)
[1.2 写内存:Store](#1.2 写内存:Store)
[1.3 加减运算](#1.3 加减运算)
[1.4 比较指令](#1.4 比较指令)
[三、出栈 / 压栈汇编指令详解(以 STM32 的 ARM 汇编为例)](#三、出栈 / 压栈汇编指令详解(以 STM32 的 ARM 汇编为例))
[3.1 出栈(恢复现场):从低地址→高地址](#3.1 出栈(恢复现场):从低地址→高地址)
前言
在嵌入式底层开发(尤其是 RTOS 移植、中断处理)中,堆和栈的内存管理、汇编级的压栈 / 出栈操作是核心基础。本文从堆栈的地址生长方向入手,详解压栈 / 出栈汇编指令的执行逻辑。
一、常用汇编指令速览
ARM 汇编指令是操作硬件的 "最小单元",在 FreeRTOS 任务切换、中断现场保护中随处可见。以下是嵌入式开发中最常用的几类指令:
1.1 读内存:Load
用于从内存中读取数据到寄存器。
LDR R0, [R1, #4] ; 读取地址 R1+4 处的4字节数据,存入 R0
LDR:Load Register,加载寄存器。
R1, #4\]:基址 + 偏移寻址,计算有效地址为 R1 + 4。 ### 1.2 写内存:Store 用于将寄存器中的数据写入内存。 STR R0, [R1, #4] ; 把 R0 的4字节数据写入地址 R1+4 STR:Store Register,存储寄存器。 与 LDR 相反,实现寄存器到内存的数据回写。 ### 1.3 加减运算 用于寄存器间的算术操作,是任务调度、地址计算的基础。 ADD R0, R1, R2 ; R0 = R1 + R2 ADD R0, R0, #1 ; R0 = R0 + 1 SUB R0, R1, R2 ; R0 = R1 - R2 SUB R0, R0, #1 ; R0 = R0 - 1 ### 1.4 比较指令 用于比较两个寄存器的值,结果保存在 PSR(程序状态寄存器)中,为后续条件跳转提供依据。 CMP R0, R1 ; 比较 R0 和 R1,结果更新到PSR 1.5 跳转指令 用于程序流控制,是函数调用、任务切换的核心。 B main ; Branch,直接跳转到 main 标签处 BL main ; Branch and Link,先将返回地址存入LR寄存器,再跳转到 main ## 二、堆与栈的核心概念 堆(Heap)和栈(Stack)是处理器中两种核心的内存分配方式,二者的地址生长方向、操作逻辑差异显著,直接影响程序的内存布局和执行效率。 ### 2.1、堆,栈的概念 注意:我们经常 "堆栈" 混合着说,其实它们不是同一个东西: 堆(heap)就是一块空闲的内存,需要提供管理函数。 malloc:从堆里划出一块空间给程序使用。 free:用完后,再把它标记为 "空闲" 的,可以再次使用。 栈(stack)函数调用时局部变量保存在栈中,当前程序的环境也是保存在栈中。 可以从堆中分配一块空间用作栈。 ### 2.1、堆和栈的地址生长方向  堆(Heap):生长方向向上,即内存地址从低到高扩展。 堆是开发者通过malloc()/free()手动管理的内存区域,分配时从低地址开始,每次申请新内存都会向高地址方向延伸。 栈(Stack):生长方向向下,即内存地址从高到低收缩。 栈是系统自动管理的内存区域(用于存储函数参数、局部变量、寄存器现场),每次压栈操作都会占用更低的内存地址。 不同处理器的堆栈生长方向差异 | 处理器 / 架构 | 堆栈生长方向 | 核心逻辑(以 PUSH 指令为例) | |------------|----------|---------------------------------------------------------| | 51 单片机 | 正向生长(向上) | 执行`PUSH`时,先将栈指针`SP+1`,再将数据入栈 | | 80x86 微机 | 逆向生长(向下) | 执行`PUSH`时,先将栈指针`SP-2`,再将 16 位数据入栈(小端模式:高字节存高地址,低字节存低地址) | | STM32(ARM) | 逆向生长(向下) | 执行压栈指令时,栈指针`SP`先递减,再存储数据;出栈时先读取数据,再递增`SP` | > 补充知识点:C 语言函数参数传递遵循 "从右向左入栈" 规则,因此函数最左侧的参数会最后入栈、最先出栈。 ## 三、出栈 / 压栈汇编指令详解(以 STM32 的 ARM 汇编为例) STM32 基于 ARM 架构,常用`ldmia`(出栈 / 加载)、`stmdb`(压栈 / 存储)指令完成寄存器现场的保存与恢复,以下结合流程图和具体地址示例拆解执行逻辑。 ### 3.1 出栈(恢复现场):从低地址→高地址 出栈指令用于将栈中保存的寄存器值恢复到 CPU 寄存器中,地址方向为**从下往上(低地址→高地址)**。  核心指令:ldmia r0!, {r4-r6} 执行示例(假设初始r0=0x04): 将r0指向的地址0x04中的内容加载到r4,随后r0 = r0 + 4 = 0x08; 将r0指向的地址0x08中的内容加载到r5,随后r0 = r0 + 4 = 0x0C; 将r0指向的地址0x0C中的内容加载到r6,随后r0 = r0 + 4 = 0x10; 最终效果:栈中低地址的数据依次恢复到r4→r5→r6,栈指针r0向高地址移动。 ### 3.2、压栈(保存现场):从高地址→低地址 压栈指令用于将 CPU 寄存器的值保存到栈中,地址方向为**从上往下(高地址→低地址)**。  核心指令:stmdb r0!, {r4-r6} 执行示例(假设初始r0=0x10): 先将r0 = r0 - 4 = 0x0C,再将r6的内容存储到r0指向的地址0x0C; 再将r0 = r0 - 4 = 0x08,再将r5的内容存储到r0指向的地址0x08; 最后将r0 = r0 - 4 = 0x04,再将r4的内容存储到r0指向的地址0x04; 最终效果:r4→r5→r6的内容依次保存到栈的低地址区域,栈指针r0向低地址移动。 *** ** * ** *** ## 总结 本期博客从堆栈的地址生长方向入手,详解了压栈 / 出栈汇编指令的执行逻辑。