在像 vxworks 这样的 RTO 中,每当我们创建任务时,都会指定堆栈大小。我们可以在 C 中编写一个例程来检查堆栈是否溢出来执行任务吗?
7 回答
查看您的编译器,他们经常让您添加前奏函数来执行此操作,或者他们甚至可能自己检查它,除非您操作堆栈指针寄存器。
并检查操作系统是否允许您安装“保护页面”。将线程堆栈中的最后一页标记为非读/非写并捕获 SIGSEGV 信号并使用 OS/CPU 特定的方式来确定它是否是失败的保护页。为此,您必须确保函数的堆栈帧(堆栈传递的参数、局部变量和分配的空间)始终小于页面大小,否则您可以跳过“保护页面”这是最好的方法处理它,因为它在正常处理期间没有运行时开销。
您会看到这高度依赖于 OS/CPU/编译器。但我很确定谷歌会为所有系统找到适用于该技术的可用代码和助手,因为它对于低级程序员(例如运行时或解释器实现者)来说是一种非常常见的技术。
如果您知道堆栈有多大,并且您很小心,那么可以(但不是可移植的)。如果没有其他方法获取栈的基地址,则需要在线程的main函数中记录一个栈变量的地址;这为您提供了堆栈顶部的近似值。然后,在您的检查功能中,您获取局部变量的地址;这给了你堆栈的底部。如果顶部和底部之间的差异与您的筹码量有关,那么该担心了;如果差异大于堆栈大小,那么担心为时已晚 - 损坏已经造成(但现在您需要考虑如何清理)。
仅供参考,您可以使用 checkStack() 从 VxWorks 的 shell 中执行类似的操作。
您可以使用一些技术 - 通常您有一个低优先级任务,它每秒左右嗅探所有其他任务的堆栈状态。
a: 在你的任务开始之前,确保堆栈空间被一个已知的模式填充。然后,您可以通过检查模式找出剩余多少“未损坏”堆栈。
- 优点:让您检查堆栈使用的“高水位线”。
- 缺点:如果您分配堆栈内存,但由于某种原因不写入,此技术可能无法检测到溢出。
b:您可以简单地嗅探所有其他线程的堆栈指针。
- 缺点:这只是对堆栈指针进行“采样”,因此可能不会注意到短暂的溢出
- 优点:快速简单。
我建议两者结合。因为您正在使用诸如 VxWorks TaskInfoGet() 函数之类的东西来做低级的事情,所以很难使它甚至可以远程移植。
我不了解 VxWorks,但我记得 Green Hill 的 Velosity/uVelosity 内核提供了执行此操作的代码。即使他们没有,因为他们提供了用户可以修改的源代码,并且基础设施在那里,所以添加起来真的很容易。
编辑:为了披露,我和他们一起做了暑期实习,将 uVelosity 移植到一个新的架构中。这就是我如何熟悉它处理线程堆栈的方式。
如果您的特定应用程序静态分配其线程,则可以将它们的堆栈放置在静态定义的区域中,并使用链接器映射将符号放置在这些区域的末尾。然后,您只需要获取当前堆栈指针(如其他答案中所述)并将“堆栈段结束”指针与该地址进行比较。这也适用于动态分配,如果每个线程都有一些地方来存储提供给它的地址作为其堆栈的末尾。
C 没有为您提供任何这样做的方法,但是您仍然可以通过对底层系统进行一些假设来做到这一点。
在 Windows 上,堆栈大小默认为 1MB,除非在编译器上另有指定,并且始终与循环内存地址对齐。根据此信息,您可以使用如下函数估算剩余堆栈:
unsigned long remaining_stack_size() {
char dummy;
return 0x000fffff & (unsigned long)&dummy;
// 0x000fffff is 1MB -1 (1048576 -1)
}
请注意,它实际上返回了当前堆栈地址,这与它的剩余大小相同,因为堆栈指针随着它的填充而减小。
概念证明:
#include <stdio.h>
#include <windows.h>
unsigned long remaining_stack_size() {
char dummy;
return 0x001fffff & (unsigned long)&dummy + 1; // okay, some minor adjusts
}
void recurse_to_death(unsigned long used, char *p) {
char buf[32*1024];
used += 32*1024;
printf("Used: 0x%08x Remaining: 0x%08x\n", used, remaining_stack_size());
recurse_to_death(used, buf);
}
DWORD WINAPI my_thread(void *p) {
printf("Total stack size of this Thread: 0x%08x bytes\n", remaining_stack_size() + 72);
recurse_to_death(0, NULL);
return 0;
}
int main(int argc, char *argv) {
DWORD tid;
// CreateThread's stack size actually defaults to 1MB+64KB and does not honor lower values
CreateThread(NULL, NULL, my_thread, NULL, NULL, NULL);
Sleep(30000);
return 0;
}
remaining_stack_size()
将在剩余大小收敛到 时预测堆栈溢出0
。