我正在使用RTOS为RAM有限的嵌入式微控制器编写C程序。
我经常将代码拆分成短函数,但每次调用函数都需要更多的堆栈内存。每个任务都需要它自己的堆栈,这是项目中占用内存最多的部分之一。
是否有替代方案可以保持代码良好组织和可读性,并仍然保留内存?
a()
调用 b()
再调用 c()
然后再调用 d()
,而是直接让a()
调用 b()
、c()
和 d()
本身。inline
。您的堆栈使用有三个组成部分:
最小化堆栈使用的关键是尽量减少参数传递和自动变量。实际函数调用本身的空间消耗相对较小。
参数
解决参数问题的一种方法是通过指针传递结构体而不是大量参数。
foo(int a, int b, int c, int d)
{
...
bar(int a, int b);
}
改为这样做:
struct my_params {
int a;
int b;
int c;
int d;
};
foo(struct my_params* p)
{
...
bar(p);
};
如果传递了许多参数,则此策略是不错的选择。如果参数都不同,则可能不适用于您。您最终将传递一个包含许多不同参数的大型结构。
自动变量(局部变量)
这往往是占用栈空间最多的部分。
请记住,如果您只是将所有局部变量从局部范围移动到模块范围,则没有节省任何空间。您用数据段空间交换了堆栈空间。
一些RTOS支持线程本地存储器,以基于每个线程的方式分配“全局”存储器。这可能允许您在每个任务的基础上拥有多个独立的全局变量,但这会使您的代码不那么直观。
如果你有大量的主存空间但只有很少的堆栈空间,我建议考虑静态分配。
在C语言中,所有在函数内声明的变量都是“自动管理”的,也就是说它们被分配在堆栈上。
将声明标记为“静态”会将它们存储在主存中而不是堆栈上。它们基本上像全局变量一样运作,但仍然可以避免过度使用全局变量所带来的坏习惯。你可以为声明大型、长期存在的缓冲区/变量为静态变量来减少对堆栈的压力。
请注意,如果你的应用程序是多线程的或者使用递归,这种方法可能无法正常工作。
开启优化,特别是激进的内联。编译器应该能够内联方法以最小化调用。根据使用的编译器和优化开关,将一些方法标记为inline
可能会有所帮助(或被忽略)。
使用GCC时,请尝试添加“-finline-functions”(或-O3)标志,可能还可以添加“-finline-limit=n”标志。
我曾在某处读到的一个技巧,用于评估嵌入式设置中代码的堆栈需求,即在开始时使用已知模式(十六进制中的DEAD是我的最爱)填充堆栈空间,然后让系统运行一段时间。
正常运行后,读取堆栈空间并查看在操作过程中有多少堆栈空间未被替换。设计时应至少留下150%的空间,以处理可能未被执行的所有模糊代码路径。
是的,RTOS确实可以通过任务堆栈使用来占用RAM。我的经验是,作为一个RTOS的新用户,往往会倾向于使用比必要更多的任务。
对于使用RTOS的嵌入式系统而言,RAM可能是一种宝贵的资源。为了保留RAM,在简单功能中,仍然可以通过在循环轮询方式下运行的协作多任务设计中实现多个功能,从而减少总任务数。
我认为你可能在想象这里不存在的问题。大多数编译器在“分配”栈上的自动变量时实际上并不会做任何事情。
在执行“main()”之前,栈已经被分配好了。当你从函数a()调用函数b()时,a使用的最后一个变量的存储区域地址将传递给b()。如果b()然后调用函数c(),那么c的栈就从b()定义的最后一个自动变量之后开始。
请注意,栈内存已经存在并分配好了,没有进行初始化,唯一涉及的处理是传递堆栈指针。
唯一成为问题的时候是三个函数都使用大量存储空间时,栈必须容纳所有三个函数的内存。尽量将分配大量存储空间的函数保持在调用栈的底部,即不要从它们调用另一个函数。
对于内存受限的系统,另一个技巧是将函数中占用内存较多的部分拆分成单独的自包含函数。
你能通过全局变量替换一些本地变量吗? 特别是数组,它们可能会占用堆栈。
如果情况允许在函数之间共享某些全局变量, 那么你可以减少内存占用。
换取的成本是增加了复杂性,以及更大的风险,在函数之间产生意外的副作用,而获得了可能更小的内存占用。
你现在的函数中有什么样的变量? 我们在谈论什么样的大小和限制?
根据您的编译器和优化选项的程度,每次函数调用都会使用堆栈。因此,首先您可能需要限制函数调用的深度。
有些编译器对于简单函数使用跳转而不是分支,这将减少堆栈使用。显然,您可以通过使用汇编宏跳转到函数而不是直接函数调用来做同样的事情。
如其他回答中所述,内联是一种可用的选项,尽管这会增加代码大小的成本。
另一个消耗堆栈的区域是局部参数。这个区域您可以控制。使用(文件级别的)静态变量将避免堆栈分配,但会增加静态RAM分配的成本。全局变量也是一样。
在(真正的)极端情况下,您可以想出一种约定函数的方式,该函数使用固定数量的全局变量作为临时存储,以代替堆栈上的局部变量。麻烦的是确保使用相同全局变量的所有函数永远不会同时被调用。(因此需要制定约定)
如果你需要开始保留堆栈空间,那么你应该要么获取更好的编译器,要么增加内存。
你的软件通常会不断增长(新功能等),因此如果你必须从一开始就考虑如何保留堆栈空间来启动项目,那么它注定会失败。