FreeRTOS基础--堆栈概念与汇编指令实战解析

🎬 渡水无言个人主页渡水无言

专栏传送门 : 《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 比较指令)

二、堆与栈的核心概念

2.1、堆,栈的概念

2.1、堆和栈的地址生长方向

[三、出栈 / 压栈汇编指令详解(以 STM32 的 ARM 汇编为例)](#三、出栈 / 压栈汇编指令详解(以 STM32 的 ARM 汇编为例))

[3.1 出栈(恢复现场):从低地址→高地址](#3.1 出栈(恢复现场):从低地址→高地址)

3.2、压栈(保存现场):从高地址→低地址

总结


前言

在嵌入式底层开发(尤其是 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、堆和栈的地址生长方向 ![](https://i-blog.csdnimg.cn/direct/4e39afa571f84b958414961c61935558.png) 堆(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 寄存器中,地址方向为**从下往上(低地址→高地址)**。 ![](https://i-blog.csdnimg.cn/direct/dd801ab253e14ec8b5016e4c48112353.png) 核心指令: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 寄存器的值保存到栈中,地址方向为**从上往下(高地址→低地址)**。 ![](https://i-blog.csdnimg.cn/direct/a76e345c81944144bbc736d3226bda73.png) 核心指令: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向低地址移动。 *** ** * ** *** ## 总结 本期博客从堆栈的地址生长方向入手,详解了压栈 / 出栈汇编指令的执行逻辑。

相关推荐
Freak嵌入式14 小时前
LVGL基础知识和概念:视觉样式与资源系统
ide·驱动开发·嵌入式·lvgl·micropython·upypi
独小乐19 小时前
007.GNU C内联汇编杂谈|千篇笔记实现嵌入式全栈/裸机篇
linux·c语言·汇编·单片机·嵌入式硬件·arm·gnu
FreakStudio1 天前
小作坊 GitHub 协作闭环:fork-sync-dev-pr-merge 实战指南
python·单片机·嵌入式·面向对象·电子diy
阿源-2 天前
UEFI Application 如何调用 Protocol
嵌入式·uefi
C^h2 天前
RT thread使用u8g2点亮oled显示屏
linux·单片机·嵌入式硬件·嵌入式
2023自学中2 天前
正点原子 Linux 驱动开发:多点电容触摸屏实验,gt9147 触摸芯片
linux·驱动开发·嵌入式
charlie1145141912 天前
现代Qt开发——0.1——如何在IDE中配置Qt环境?
开发语言·c++·ide·qt·嵌入式
wsoz2 天前
音视频分布传输协议(AVDTP)
音视频·嵌入式·蓝牙br/edr·avdtp
华清远见成都中心2 天前
嵌入式就业岗位有哪些?
嵌入式·华清远见成都中心·成都嵌入式培训机构
无垠的广袤3 天前
【Titan RA8P1 Board】J-Link 调试
单片机·嵌入式·开发板·调试器·jlink