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

freertos入门---栈的概念

freertos入门—栈的概念

1 栈的概念

  栈也是一块内存空间,CPU的SP寄存器指向它,它可以用于函数调用、局部变量、多任务系统里保存现场。栈对于多任务系统非常的重要,每个任务都有自己的栈,我们只有深入理解栈的作用,才能深入理解多任务系统。

2 函数调用

  我们首先编写一个程序,程序流程为:在主函数中调用函数a_func(),在a_func()中调用b_func()与c_func()。

#include "stm32f10x.h" 

int g_cnt;

int b_func(volatile int a)
{
	a += 2;
	return a;
}

int c_func(volatile int a)
{
	a += 3;
	return a;
}

void a_func(volatile int a)
{
	g_cnt = b_func(a);
	g_cnt = c_func(g_cnt);
}

int main(void)
{
	char ch = 65;
	volatile int i = 99;
	a_func(i);
	
	return 0;
}

  接着利用下述方法生成反汇编码:
在这里插入图片描述
在这里插入图片描述

fromelf --text -a -c --output=test.dis  .\Objects\点亮一个LED灯.axf

  打开生成的反汇编码,将反汇编码显示如下:
在这里插入图片描述
  函数调用的本质就是使用 BL 指令,但是我们main函数调用 a_func 函数时,我们设置了LR。然后我们在 a_func 函数里调用 b_func 函数时,又使用同样的 BL 指令设置 LR 等于另外一个值,那么我们的 LR 不就是被覆盖了吗?由此我们引出以下问题:

  1. LR被覆盖了,怎么办?
  2. 局部变量在栈中如何分配?
  3. 为何每个RTOS任务中都有自己的栈?

  在函数调用过程中,我们调用函数a,我们设置了 LR 等于函数 a 执行完后的返回地址,在函数 a 中我们马上去调用函数 b,BL 指令同时会去设置 LR,会把之前的 LR 覆盖掉,那么显然在 LR 被覆盖前会去保存 LR。
在这里插入图片描述
  同样在函数 b 中,我们不管三七二十一,也是先把 LR 保存下来,后面我们可能不会去修改 LR ,但是为了以防万一,或者说编译器为了统一处理,在函数入口处会保存 LR。
在这里插入图片描述
  接着我们把函数调用时栈的分配具体讲解:

  1. 假设一开始,寄存器指向某一位置,我们调用 main 函数,保存 r3 以及 lr 。
    在这里插入图片描述

  2. main 函数调用函数 a,会去保存 lr 和 r0。
    在这里插入图片描述

  3. 函数 a 调用函数 b ,函数 b 里也去保存两个寄存器。
    在这里插入图片描述

  4. 接着我们调用函数 c ,那么函数 c 的栈是在 函数 b 的栈下面吗?答案是否定的,我们调用完函数 b 后,函数 b 的栈就被回收了,所以当函数 b 的栈被回收后,我们才开始分配函数 c 的栈。
    在这里插入图片描述

  所以,在每一个c函数的入口,会划分出自己的栈,保存 LR 进栈里,保存局部变量。

2 局部变量

  对于局部变量的讲解,我们从下面 main 函数中的局部变量开始分析:
在这里插入图片描述
  一进入 main 函数时,就入栈了若干个寄存器:
在这里插入图片描述
  我们分析 MOVS r2,#0x41 ,0x41是65,那么我们的 r2 寄存器其实就是局部变量 ch ,所以说局部变量有可能保存寄存器中,不一定保存在内存里,当我们加上 volatile 后它就保存在内存里了,或者说寄存器不够用时也保存在内存里。
在这里插入图片描述
  继续分析 MOVS r0,#0x63 , 0X63 是99,那么 r0 是 i 变量,接着将 r0 写入[sp,#0]中,所以变量i是保存在栈中。
在这里插入图片描述
  所以 main 函数一进来的时候他就划分了栈,在栈中保存了一些寄存器,它使用保持寄存器的方式给我们划分出了一块空间,这块空间在后面代码我们分析了知道 [sp,#0] 对应的是 i 。
  我们继续分析 MOVS r4,#0xc8 ,0xc8 是 200 ,那么 r4 是 uch 变量。该变量保存在寄存器中而不是内存中。
在这里插入图片描述

3 RTOS如何使用栈

  为什么每一个 RTOS 任务都要有一个属于自己的栈?我们以下面函数 b 为例,在函数中它会将变量 a 加 2 ,然后返回这个新的值。
在这里插入图片描述
  现在假设有两个任务,Task_A 中定义一个变量 cnt ,然后定义一个死循环,在这个死循环中不断的去累加这个 cnt ,任务 b 也是一样。
在这里插入图片描述
  那么这两个任务如何运行呢?任务A运行一阵子发生切换,然后任务B运行一阵子再发生切换,接着再发生切换,这个切换在哪发生我们根本无法控制。
在这里插入图片描述
  我们来看下面一个场景,函数 a 调用函数 b ,他可能辛辛苦苦才执行到 ADDS r0,r0,#2 ,这个数值还未返回就发生了切换:
在这里插入图片描述
  在任务A发生切换时会先保存A的现场然后恢复B,任务B发生切换时会先保存B的现场然后恢复A。
在这里插入图片描述
&emsps; 以人为例,这个人要看两本书,一本是语文一本是数学。他的脑子只有一个,只能先读语文再读数学,学腻之后再看语文再看数学。一开始的时候语文读到了100页,读累了想看数学了,那得在100页加入书签。看了200页数学也看累了,那得在200页处加入书签,然后从前面语文100页处继续看。这加书签就是保存现场,从书签那里继续看就是恢复现场。
在这里插入图片描述
  任务A运行到黑线这个位置,辛辛苦苦把 r0 加上了2,但是很不幸在这里我们还没有使用这个新结果呢,我就被切换出去了,那怎么办?我们辛辛苦苦算出的结果不能够浪费,在这个场景里是 r0 ,在其他的场景里可能是 r1 或者 r2 ,所以干脆把所有的寄存器都保存下来。那么这些寄存器保存在什么位置,我们分析下处理器架构。
在这里插入图片描述
  保存的寄存器只能保存在内存里。假设任务A一开始的时候我们给它分配了一块内存,我们让sp寄存器指向addr a,分配 Task_A 函数的栈,接着 Task_A 函数调用 b 函数,分配 b 函数的栈,任务 A 发生切换时是被内核的定时器中断函数来切换,定时器中断函数需要保存任务 A 的现场,保存切换瞬间所有的寄存器到任务A的栈里面。
在这里插入图片描述
  当保存现场完成后sp指向最后,并且将sp记录到任务A的结构体中。为什么需要在任务A的结构体中记录sp,因为以后切换的时候我们需要先找到任务A的结构体,从这结构体中我们才可以找到sp,把sp赋给硬件的寄存器。
  接着我们应该讲解任务 B 的恢复,但是任务 B 的恢复与任务 A 的恢复一样,因此我们以任务 A 的恢复进行讲解。恢复任务 A 首先找到任务 A 的结构体得到 A 的sp。得到任务 A 的sp后我们就可以把保存的寄存器值全部恢复到硬件上,恢复到 cpu 里。这样虽然在 ADDS r0,r0,#2 发生了切换,但是恢复现场后寄存器的值都能被恢复到cpu中,因此可以继续执行下面的程序。
  所以RTOS中每个任务有不同的栈是因为:1.有不同的调用关系  2.有自己的局部变量  3.保存现场

相关文章:

  • 保山网站制作东莞seo项目优化方法
  • 网站建设选青岛的公司好不好安徽网络推广和优化
  • 动感技术网站建设腾讯企业邮箱
  • 企业网站建设报价郑州新闻发布
  • 大朗镇网站建设百度搜索引擎优化怎么做
  • 重庆南坪网站建设咨询400网站优化外包顾问
  • 6.过拟合处理:确保模型泛化能力的实践指南——大模型开发深度学习理论基础
  • 【Python编程】高性能Python Web服务部署架构解析
  • Gateway Timeout504 网关超时的完美解决方法
  • 离线地图显示
  • 加密算法学习与SpringBoot实践
  • Kubernetes(K8S)部署 Redis Cluster 集群
  • Web3的技术挑战:去中心化的可扩展性与性能问题
  • 《基于WebGL的matplotlib三维可视化性能调优》——让大规模3D数据流畅运行在浏览器端!
  • PE文件安全分析实战指南:从结构解析到高级威胁狩猎
  • Golang的代码生成工具实践
  • 【AIGC】通义万相 2.1 与蓝耘智算:共绘 AIGC 未来绚丽蓝图
  • Java实战:Spring Boot application.yml配置文件详解
  • 5.训练策略:优化深度学习训练过程的实践指南——大模型开发深度学习理论基础
  • 【C++】list容器的入门及其模拟实现
  • c++为什么支持simd,而java不支持
  • valgrind 检测多线程 bug,检测 并发 bug concurrent bug parallel bug
  • 【gc】家电行业研发部门的阿米巴经营方案
  • DeepSeek 开源周回顾「GitHub 热点速览」
  • 在虚拟机上安装 Hadoop 全攻略
  • LeetCode:1328. 破坏回文串(贪心 Java)