RTOS中如何确定使用栈的大小

举报
yd_274589494 发表于 2024/05/26 08:22:00 2024/05/26
【摘要】 @TOC 前言本篇文章将带大家学习在FreeRTOS中怎么样去确认栈的大小,在确认栈的大小后就可以根据实际情况来分配栈空间,防止栈空间被浪费。 一、生成反汇编文件首先我们来看一下什么是汇编文件,什么是反汇编文件:汇编文件 (.s 文件):定义:在嵌入式系统中,汇编文件通常是指包含汇编语言代码的文本文件,其中的指令和数据使用特定的汇编语法表示。这些文件以 .s 作为文件扩展名。作用:汇编文件用...

@TOC


前言

本篇文章将带大家学习在FreeRTOS中怎么样去确认栈的大小,在确认栈的大小后就可以根据实际情况来分配栈空间,防止栈空间被浪费。

一、生成反汇编文件

首先我们来看一下什么是汇编文件,什么是反汇编文件:

汇编文件 (.s 文件):

定义:在嵌入式系统中,汇编文件通常是指包含汇编语言代码的文本文件,其中的指令和数据使用特定的汇编语法表示。这些文件以 .s 作为文件扩展名。
作用:汇编文件用于编写底层的系统级代码,通常包括中断服务程序、启动代码、设备驱动程序等。它们直接与硬件交互,并对系统资源进行管理。
例如,一个嵌入式系统的汇编文件可能包含了与硬件设备通信的指令,如对寄存器的读写、对外设的控制等。

反汇编文件 (.dis 文件):

定义:反汇编文件是从已编译的二进制文件(如可执行文件、固件文件等)生成的汇编语言代码文件。这些文件以 .dis 作为文件扩展名。
作用:反汇编文件用于分析已编译程序的底层运行机制,通常用于调试、优化和逆向工程等任务。

我们会通过keil5来生成反汇编文件来查看如何估算栈的大小:

使用下面这句话可以将.axf文件生成.dis文件:

fromelf --text -a -c --output=xxx.dis xxx.axf

让我们逐步解释每个参数的含义:

–text:这个选项告诉 fromelf 工具将输出的内容以文本形式显示。这意味着输出将以可读的文本格式呈现,而不是二进制格式。

-a:这个选项告诉 fromelf 工具生成汇编伪指令(assembly pseudo-instructions)。这样生成的反汇编文件将包含一些更易读的汇编指令,而不是完全的机器码。

-c:这个选项告诉 fromelf 工具在输出时显示注释。注释通常包含有关代码和数据的信息,有助于理解反汇编输出。

–output=xxx.dis:这个选项指定了输出文件的名称,其中 xxx.dis 是你希望生成的反汇编文件的文件名。反汇编文件将被写入到这个文件中。

xxx.axf:这是输入的 ARM 可执行文件(.axf 文件)的名称。fromelf 将会对这个文件执行反汇编操作,并根据指定的参数生成相应的输出文件。
在这里插入图片描述

二、粗略估计栈大小

首先在任务中使用一个数组,任务相当于就是一个函数,那么在任务中定义出来的数组就是保存在栈中的。

void vTask1( void *pvParameters )
{
	volatile int buff[100];//定义出数组
	strcpy((char*)buff, "Hello");
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务1的信息 */
		printf( "T1 run\r\n" );
		
		vTaskDelay(10);
	}
}

编译生成反汇编文件查看对应的函数:

进入函数一开始就可以看到SUB sp,sp,#0x190这段汇编指令。

这段指令的含义如下:

将当前栈指针的值减去 400,并将结果存储回栈指针寄存器中。
在这里插入图片描述
定义出局部的数组后SP指针会向下移动。

这里可以知道定义的局部变量越多那么所需要的栈空间也是越大的。

在这里插入图片描述
根据函数调用链来判断所需要栈的大小:

分析函数调用:

从主函数开始,沿着函数调用链分析程序的执行路径。
记录每个函数调用时,当前函数的局部变量、参数、返回地址等需要存储在栈上的数据。

计算局部变量大小:

对于每个函数,计算所有局部变量所需的总大小。
局部变量的大小是其数据类型大小的总和。

考虑函数调用的深度:

对于递归函数或者多层嵌套的函数调用,需要考虑函数调用的深度。
每次函数调用都会在栈上增加一层新的栈帧,包含函数的局部变量、参数、返回地址等。

计算其他需要存储的信息:

除了局部变量,还需要考虑参数、返回地址、临时寄存器等信息。
一些编译器可能会为函数调用保存一些额外的状态信息,如寄存器的保存。

计算总的栈大小:

将以上计算得到的每个函数所需的栈大小累加起来,得到整个函数调用链所需的栈大小。

现在在vTask1任务中调用了stack函数,stack函数中也会使用到局部数组,那么这个时候就需要把所stack函数中使用到的栈和vTask1中定义的栈都计算出来。

void stack(void)
{
	char stack_buff[100];
	strcpy(stack_buff, "Hello");
}

void vTask1( void *pvParameters )
{
	volatile int buff[100];//定义出数组
	strcpy((char*)buff, "Hello");
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务1的信息 */
		printf( "T1 run\r\n" );
		
		vTaskDelay(10);
	}
}

三、在任务中调用多个函数这些函数的栈是怎么样的?

在vTask1中调用了stack1和stack两个函数,下面我们来分析一下他们的栈分布。

void stack(void)
{
	char stack_buff[100];
	strcpy(stack_buff, "Hello");
}

void stack1(void)
{
	char stack_buff[100];
	strcpy(stack_buff, "Hello");
}

void vTask1( void *pvParameters )
{
	volatile int buff[100];//定义出数组
	strcpy((char*)buff, "Hello");

	stack();
	
	stack1();	
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务1的信息 */
		printf( "T1 run\r\n" );
		
		vTaskDelay(10);
	}
}

很多同学会认为这三个数组会保存在同一个栈中的,但是结果确是下面这样的。

那么下面来讲解一下这个原理:

函数调用过程:

当一个函数被调用时,系统会执行一系列操作来为该函数创建一个新的执行环境,这个执行环境被称为栈帧(stack frame)或活动记录(activation record)。
这个栈帧用于存储函数调用时所需的各种信息,如参数、局部变量、返回地址等。

栈的创建:

当函数被调用时,系统会在当前栈顶分配一块新的内存空间作为新的栈帧。
这个新的栈帧将用于存储函数的局部变量、参数和其他必要的信息。
函数的参数会被复制到栈帧中的指定位置,局部变量也会在栈帧中分配空间。

函数执行:

在函数执行过程中,系统可以通过栈帧来访问函数的参数和局部变量。
函数执行期间,栈帧中的数据可以被读取、修改和使用。

函数调用的完成:

当函数执行完成时,系统会销毁该函数的栈帧。
这意味着栈顶会恢复到调用该函数之前的状态,原来的栈帧将被释放。
释放栈帧时,系统会回收该栈帧所占用的内存空间,以便后续的函数调用可以重用该空间。

返回值传递:

在函数调用完成时,系统会将函数的返回值(如果有的话)传递给调用者。
返回值通常会存储在调用者的栈帧中的指定位置,以便调用者可以访问和使用它。
在这里插入图片描述

四、栈大小的准确计算

在使用rthread创建任务的时候首先会将栈里面的数据全部设置成#。

在这里插入图片描述
当使用了栈后#就会被设置为其他的内容,此时就可以根据#的位置来判断栈使用了多少。
在这里插入图片描述

总结

本篇文章主要讲解了如何确定栈的大小,大家也可以尝试去查看一下栈有什么作用,以及如何计算栈的大小。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。