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、堆和栈的地址生长方向

堆(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向低地址移动。


总结

本期博客从堆栈的地址生长方向入手,详解了压栈 / 出栈汇编指令的执行逻辑。

相关推荐
济6172 小时前
BMS系统专栏:BQ76920 锂电 AFE 芯片深度解析
嵌入式硬件·嵌入式·bms电池管理
iCxhust2 小时前
C# 生成命令行程序 将hex格式烧录程序转换成bin烧录格式
开发语言·汇编·单片机·嵌入式硬件·c#·微机原理
不脱发的程序猿2 小时前
DLL文件缺失怎么办?
单片机·嵌入式硬件·嵌入式
一路往蓝-Anbo11 小时前
第三篇:ADC 与模拟前端
stm32·嵌入式硬件·嵌入式·硬件设计
iCxhust17 小时前
C#进程管理程序
开发语言·汇编·stm32·单片机·c#·微机原理
hhcgchpspk19 小时前
汇编语言传递数据和地址的误区
汇编·笔记·nasm·masm
iCxhust20 小时前
MTK8088单板机制作(一)时钟电路
汇编·单片机·嵌入式硬件·微机原理·8088单板机
iCxhust1 天前
8086 汇编位测试使用方法
汇编·单片机·嵌入式硬件·微机原理·8088单板机
iCxhust1 天前
用汇编在8088单板机上创建一个进程
汇编·微机原理
IAR Systems1 天前
在IAR工具链中使用overlay命令进行SMP多核工程TCM配置
arm开发·嵌入式·嵌入式开发·iar