在多线程嵌入式软件(用 C 或 C++ 编写)中,必须为线程提供足够的堆栈空间,以允许其完成操作而不溢出。在某些实时嵌入式环境中,正确调整堆栈大小至关重要,因为(至少在我使用过的某些系统中),操作系统不会为您检测到这一点。

通常,新线程(主线程除外)的堆栈大小是在创建线程时指定的(即在 pthread_create() 等的参数中)。通常,这些堆栈大小被硬编码为在最初编写或测试代码时已知的良好值。

然而,未来对代码的更改通常会破坏硬编码堆栈大小所基于的假设,并且在命运重大的一天,您的线程进入其调用图的更深层次分支之一并溢出堆栈 - 导致整个系统瘫痪或默默地破坏记忆。

我个人在线程中执行的代码在堆栈上声明结构实例的情况下看到过这个问题。当结构体被扩充以容纳额外的数据时,堆栈大小相应地膨胀,可能导致堆栈溢出的发生。我想这对于已建立的代码库来说可能是一个巨大的问题,因为无法立即知道向结构添加字段的全部效果(太多线程/函数无法找到使用该结构的所有位置)。

由于对“堆栈大小”问题的通常回答是“它们不可移植”,因此我们假设编译器、操作系统和处理器都是本次调查的已知数量。我们还假设不使用递归,因此我们不处理“无限递归”场景的可能性。

有哪些可靠的方法来估计线程所需的堆栈大小?我更喜欢离线(静态分析)和自动的方法,但欢迎所有想法。

有帮助吗?

其他提示

您可以使用一个静态分析工具,如 StackAnalyzer ,如果你的目标符合要求。

如果您想花很多钱,您可以使用商业静态分析工具,例如 Klocwork。虽然Klocwork的主要目的是检测软件缺陷和安全漏洞。但是,它还有一个名为“kwstackoverflow”的工具,可用于检测任务或线程内的堆栈溢出。我正在将其用于我从事的嵌入式项目,并且取得了积极的成果。我不认为任何这样的工具都是完美的,但我相信这些商业工具非常好。我遇到的大多数工具都与函数指针作斗争。我还知道,像 Green Hills 这样的许多编译器供应商现在都在他们的编译器中构建了类似的功能。这可能是最好的解决方案,因为编译器非常了解做出有关堆栈大小的准确决策所需的所有细节。

如果你有时间,我相信你可以使用脚本语言来制作你自己的堆栈溢出分析工具。该脚本需要识别任务或线程的入口点,生成完整的函数调用树,然后计算每个函数使用的堆栈空间量。我怀疑可能有免费的工具可以生成完整的函数调用树,这样应该会更容易。如果您知道平台的具体情况,则生成每个函数使用的堆栈空间会非常容易。例如,PowerPC 函数的第一条汇编指令通常是带有更新指令的存储字,该更新指令根据函数所需的量调整堆栈指针。您可以从第一条指令开始获取大小(以字节为单位),这使得确定使用的总堆栈空间相对容易。

这些类型的分析都会为您提供堆栈使用的最坏情况上限的近似值,这正是您想知道的。当然,专家(比如与我一起工作的专家)可能会抱怨您分配了太多的堆栈空间,但他们是不关心良好软件质量的恐龙:)

另一种可能性是使用处理器(如果有的话)的内存管理单元(MMU)来检测堆栈溢出,尽管它不计算堆栈使用情况。我已经使用 PowerPC 在 VxWorks 5.4 上完成了此操作。这个想法很简单,只需将一页写保护内存放在堆栈的最顶部即可。如果溢出,将会发生处理器异常,并且您很快就会收到堆栈溢出问题的警报。当然,它不会告诉您需要增加多少堆栈大小,但如果您擅长调试异常/核心文件,您至少可以找出溢出堆栈的调用序列。然后,您可以使用此信息来适当增加堆栈大小。

-djhaus

不是免费的,但 Coverity的确实堆的静态分析。

静态(离线)堆栈检查是不是看起来一样困难。我已经实现了它为我们的嵌入式IDE( RapidiTTy ) - 它目前适用于ARM7(NXP LPC2xxx ),皮质-M3(STM32和NXP LPC17xx),x86和我们的内部MIPS ISA兼容FPGA软核。

本质上讲,我们使用的可执行代码的一个简单的解析,以确定每个函数的堆栈用法。最显著堆栈分配在每个函数的开始进行;只是一定要看看它是如何与不同的优化级别改变,如果适用,ARM / Thumb指令集等,也请记住,任务通常有自己的堆栈,中断服务程序和经常(但不总是)共用一个独立的堆栈区<! / p>

一旦你有各功能的使用方法,这也很容易建立从剖析一个调用树和计算每个功能的最大使用量。我们的IDE为您生成调度器(有效薄的RTOS),所以我们准确地知道哪些功能被指定为“任务”,哪些是ISR的,所以我们可以说每个堆栈区域中的最坏情况下的使用。

当然,这些数字是几乎总是过的实际最大。这样想sprintf一个功能,可以使用的很多的堆栈空间,但变化极大取决于格式字符串和您提供的参数。对于这些情况,你也可以使用动态分析 - 填补堆在你启动一个已知的值,然后在调试器中运行一段时间,暂停,看看还有多少每个堆栈的充满了你的价值(高水印样式测试)

这两种方法都不是完美的,但结合两种会给你什么是真实世界的使用将是像一个相当不错的图片。

如在回答这个问题所讨论的,一个常用的方法是用公知的值,然后,以初始化所述堆运行的代码为一段时间,看看其中模式停止。

这是不是一个脱机方法,但对我的工作,我们的项目有一个调试命令读取上的所有应用程序中的任务堆栈的高水位标记。此输出为每个任务和可用净空的量堆栈使用的表。 24小时运行,有很多的用户交互后检查这些数据给了我们一些信心,定义堆栈分配是“安全的”。

这适用于使用具有已知模式填充所述堆栈和假设,这是可以重新编写的唯一方式是通过正常的堆栈使用,但如果它是由任何其它书面的阱试图技术装置的堆叠溢出是你最担心的!

我们试图在我的工作在嵌入式系统上解决这个问题。它疯了,也只是太多的代码(包括我们自己和第三方框架)来得到任何可靠的答案。幸运的是,我们的设备是基于Linux的,所以我们回落到让每一个线程2MB,让虚拟内存管理器优化使用标准的行为。

我们的一个问题,这个解决方案是的第三方工具之一,在其整个存储空间进行的mlock(理想情况下提高性能)。这导致栈的所有2MB它的线程(其中75-150)的每个线程在被寻呼。我们失去了我们的存储空间的一半,直到我们理解了它,并且注释掉的那一行。

旁注:linux的虚拟内存管理器(VMM)中4K块分配RAM。当一个新的线程请求为它的堆栈地址空间2MB,在VMM分配假内存页所有,但最上面的一页。当堆栈增长到假页面中的内核检测到某个页面错误,并用一个真实的,(其实际消耗的RAM另一4K)交换的假网页。这样一个线程的堆栈可以长到它所需要的任何尺寸(只要它是小于2MB)和VMM将确保的内存只有最小量被使用。

除了一些已经做了,我想指出的是,往往在嵌入式系统中,你必须严格控制堆栈的使用,因为你必须保持堆栈大小大小合理的建议。

在某种意义上,使用堆栈空间有点像分配内存,但没有一个(简单)的方式来确定您的分配成功了所以没有控制堆栈的使用将导致永远奋斗弄清楚为什么你的系统再次崩溃。所以,举例来说,如果你的系统内存分配从堆栈中的局部变量,无论是分配使用malloc内存()或者,如果你不能使用malloc()写自己的存储处理器(这是一个很简单的任务)。

没有-NO:

void func(myMassiveStruct_t par)
{
  myMassiveStruct_t tmpVar;
}

是 - 是:

void func (myMassiveStruct_t *par)
{
  myMassiveStruct_t *tmpVar;
  tmpVar = (myMassiveStruct_t*) malloc (sizeof(myMassicveStruct_t));
}

似乎很明显,但往往是不 - 特别是当你不能使用malloc()

当然,你仍然会出现问题,所以这只是一些帮助,但并没有解决你的问题。它将,但是,帮助您估计在未来的堆栈大小,因为一旦你找到了你的堆栈大小适中,如果你的话,一些代码的修改后,再次运行的堆栈空间,您可以检测到一些bug或其他问题(太深调用栈为一个)。

不是100%肯定,但我认为这也可以完成。如果你有一个JTAG端口暴露,您可以连接到TRACE32并检查最大堆栈用量。虽然对于这一点,你将不得不放弃最初的相当大的任意堆栈大小。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top