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


总结

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

相关推荐
会周易的程序员4 小时前
microLog 的本地日志读取接口 log_reader — 本地日志文件读取工具开发指南
linux·物联网·架构·嵌入式·日志·iot·aiot
charlie1145141919 小时前
Cinux: 加载第一个内核:从 bootloader 跳进 C++
linux·开发语言·c++·嵌入式
dddwjzx15 小时前
嵌入式Linux C应用编程入门——标准IO库
嵌入式
pai同学15 小时前
ESP-IDF+vscode开发ESP32第十二讲——event
嵌入式
凉、介16 小时前
KVM + QEMU 虚拟化
笔记·学习·嵌入式·arm·qemu·虚拟化·kvm
dddwjzx1 天前
嵌入式Linux C应用编程入门——文件IO进阶
嵌入式
2023自学中1 天前
imx6ull 开发板, mame 模拟器,运行游戏 测试
linux·游戏·嵌入式·开发板
dddwjzx2 天前
嵌入式Linux C应用编程入门——文件IO
嵌入式
fzm52982 天前
车载ECU单元测试技术与应用研究
c语言·自动化测试·单元测试·嵌入式·白盒测试
用户120487221613 天前
Linux驱动编译与加载
linux·嵌入式