当前位置: 首页 > ops >正文

栈的概念(韦东山学习笔记)

栈核心问题 (针对 LR 覆盖、局部变量分配、RTOS 任务栈 )

一、知识总览

这部分聚焦栈在函数调用、RTOS 任务中的核心逻辑,解决 3 个关键问题:LR 被覆盖如何处理、局部变量在栈里咋分配、RTOS 为啥每个任务要有独立栈。理解这些,才能搞懂程序执行流程、任务切换的本质,尤其是 RTOS 上下文管理的底层逻辑。

二、核心问题分步拆解

(一)问题 1:LR 被覆盖了怎么办?

在函数嵌套调用场景里,内层函数执行 BL 指令(函数调用指令 )会覆盖外层函数的 LR(Link Register,存储返回地址 ),若不处理,函数返回时执行流向会混乱。结合 上图 拆解解决逻辑:

  1. LR 的作用
    LR 存储函数执行完后要返回的地址。比如 main 调用 a_func 时,执行 BL a_func(图 中 0x080001ba: BL a_func ; 0x8000154 ),会把 main 中下一条指令地址存入 LR,保证 a_func 执行完能回到 main 继续运行。

  2. LR 被覆盖的原因
    函数嵌套调用(如 maina_funcb_func )时,内层函数的 BL 指令会覆盖外层函数的 LR。像 a_func 调用 b_func 执行 BL b_func,会把 a_func 里 “返回给 main 的地址” 覆盖成 “返回给 a_func 的地址”,若不处理,b_func 执行完就无法正确回到 main

  3. 解决方法:栈保存与恢复

    • 压栈保存(函数入口 )
      函数开头用 PUSH 指令存 LR 到栈。以 a_func 为例(图 ),PUSH {r0, lr} 会把当前 R0(通用寄存器 )和 LR(返回地址 )压入栈。这样即便内层函数覆盖 LR,栈里仍留存外层函数正确的返回地址。
    • 出栈恢复(函数出口 )
      函数结尾用 POP 指令恢复 LR 到 PC(Program Counter,控制程序执行流向 )。如 a_func 的 POP {r3, pc},从栈中取出之前保存的 LR 值并赋给 PC,让程序回到正确调用点(如 main )。

    总结:通过 “函数入口 PUSH 存 LR + 函数出口 POP 恢复 PC,借助栈的 “后进先出” 特性,解决多层函数调用时 LR 被覆盖问题,确保函数嵌套调用后执行流能正确回归。图直观呈现了 PUSH 保存 LRPOP 恢复执行流的汇编指令与栈操作对应关系,是理解该机制的关键 。

(二)问题 2:局部变量在栈中如何分配?

函数内的局部变量(如 main 里的 char ch = 65; int i = 99; ),依托栈指针(SP )调整和内存布局规则分配,结合 上 解析:

  1. 栈指针调整:预留空间
    函数执行前,编译器生成指令调整 SP(栈指针 )。以 main 函数为例(上图 ),汇编可能有 SP = SP - 20 操作,向 低地址方向扩展栈帧(ARM 栈通常向低地址增长 ),为局部变量、保存的寄存器预留内存。

  2. 变量填充:规则与对齐
    编译器按 “声明顺序 + 内存对齐” 把局部变量填入栈帧。先声明的变量地址更靠近 SP 原位置(地址 “高” ),后声明的更远离(地址 “低” );同时满足内存对齐(如 int 占 4 字节,按 4 字节对齐存储 )。
    比如上图中,char ch = 65(1 字节 )、int i = 99(4 字节 ),编译器会严格安排它们在栈里的位置,保证访问时能通过 SP + 偏移(如 LDR R0, [SP, #4] )正确读取。

    总结:局部变量分配依托 “调整 SP 预留空间 → 按规则填栈帧 → SP + 偏移 访问” 实现。上图清晰展示 main 函数局部变量在栈中的分配逻辑,是理解该过程的核心参考 。

(三)问题 3:为什么 RTOS 任务都有自己的栈?

RTOS 多任务切换时,需保存 / 恢复任务执行现场(寄存器、局部变量等 ),每个任务配置独立栈是实现稳定切换的基础。结合上 解析:

  1. 任务与执行现场
    RTOS 中任务(如 Task_ATask_B )是独立执行流,需随时暂停、恢复。任务执行现场包含 CPU 寄存器(PCLRR0 - R15 )、局部变量、函数返回地址,统称 “上下文”。

  2. 独立栈的必要性

    • 隔离性:任务切换时,不同任务的栈空间独立,避免数据干扰。比如 Task_A 执行到一半被打断,其局部变量、返回地址存在自己的栈里;Task_B 运行时用自己的栈存数据,互不影响。
    • 保存现场:任务切换时(第 2 张图 ),需把当前任务的 “现场” 存到自己的栈,再恢复下一个任务的 “现场”。若共用一个栈,现场会被覆盖,切换后无法恢复执行。

    举例Task_A 执行 b_func 时被切换,栈里存着 PC = 0x0800017aR0 = 2 等现场(第 3、4 张图 );切换到 Task_B 后,Task_B 用自己的栈运行;切回 Task_A 时,从其栈恢复现场继续执行。

    总结:每个任务独立栈是为实现 “执行现场隔离存储与恢复”,保障多任务切换后能正确续跑。上图直观呈现任务切换时栈对现场的保存 / 恢复逻辑,是理解多任务栈设计的关键 。

三、知识串联(从函数调用到 RTOS 任务 )

  • 函数调用:用 LR 存返回地址,嵌套调用时靠 “压栈保存 LR + 出栈恢复”。解决覆盖问题;局部变量依托 SP 调整、栈帧规则分配。
  • RTOS 任务:每个任务用独立栈保存执行现场(寄存器、返回地址、局部变量 ),切换时存 / 恢复现场,保障任务暂停、续跑的正确性。

四、易错点 & 补充说明

(一)易错点

  1. 栈溢出:函数嵌套深、局部变量大,或 RTOS 任务栈配置小,会耗尽栈空间,覆盖代码段、堆,引发程序崩溃。需合理规划栈大小。
  2. 局部变量地址无效:函数返回后,局部变量栈空间释放,若返回其指针(如 return &ch; ),会因访问 “无效地址” 出错。
  3. 任务栈未对齐:RTOS 任务栈需满足 ARM 架构对齐要求(如 4 字节对齐 ),否则存 / 取数据会因硬件不支持非对齐访问出错。

(二)补充拓展

  • 栈帧优化:编译器会优化栈帧(复用空间、局部变量存寄存器 ),调试时需注意优化可能让栈帧 “不直观”。
  • RTOS 上下文切换细节:除栈外,还涉及 PSP(进程栈指针 )和 MSP(主栈指针 )切换(如 Cortex - M 系列 ),复杂 RTOS 会区分内核栈和任务栈,保障系统调用安全。
http://www.xdnf.cn/news/18257.html

相关文章:

  • C#APP.Config配置文件解析
  • Java内功修炼(2)——线程安全三剑客:synchronized、volatile与wait/notify
  • 5.4 4pnpm 使用介绍
  • kotlin 协程笔记
  • AI 创业公司分析报告:RealRoots
  • 0基础安卓逆向原理与实践:第2章:编程基础与工具链
  • 使用PCL读取PCD点云文件
  • Pandas 数据处理核心操作:合并、替换、统计与分组
  • 分贝单位全指南:从 dB 到 dBm、dBc
  • 深入解析EventPoller:Disruptor的轮询式事件处理机制
  • k8s笔记01
  • 服务器硬盘进行分区和挂载
  • SLAM文献之-Globally Consistent and Tightly Coupled 3D LiDAR Inertial Mapping
  • AI +金融 = 七大核心维度+ 落地典型困难
  • 【Golang实战】Go Module 双段 require 配置深度解析
  • Lecture 5 GPUs课程笔记
  • C语言---编译的最小单位---令牌(Token)
  • 认识Node.js及其与 Nginx 前端项目区别
  • KubeBlocks AI:AI时代的云原生数据库运维探索
  • Notepad++批量转UTF-8脚本
  • Flink Stream API - 顶层Operator接口StreamOperator源码超详细讲解
  • 结合SAT-3D,运动+饮食双重养腰新方式
  • Java:将视频上传到腾讯云并通过腾讯云点播播放
  • STM32F407VGT6从零建立一个标准库工程模板+VSCode或Keil5
  • 详解MySQL中的多表查询:多表查询分类讲解、七种JOIN操作的实现
  • 《Linux运维总结:Shell脚本位置参数的具体使用》
  • 【笔记】动手学Ollama 第五章 Ollama 在 LangChain 中的使用 - Python 集成
  • 存储系统中清空日志文件的常用方法总结
  • vue3 el-select 默认选中第一个
  • 链表-24.两两交换链表中的结点-力扣(LeetCode)