如何为pthread线程预先分配堆栈空间是最佳的方式?

7
我正在为嵌入式Linux系统中运行的实时程序编写代码。由于我们不能在页面故障时不可预测地停顿,因此我想在堆栈中预先执行前驱以确保我们使用的区域被 mlockall() 调用所覆盖。
对于主线程来说,这很简单;只需执行一些大的 alloca() 并确保每几页都进行一次写操作。这有效是因为在程序启动时,堆栈限制比我们需要的量大得多;最终我们正好分配了我们预先提示的数量。
但是,对于 pthread 堆栈,它们是否也将使用 MAP_GROWSDOWN 进行分配? 如果是这样,那么考虑以下事项,最佳的预先提示方式是什么:
  1. 我们不知道 libc 启动会消耗多少已知的堆栈大小
  2. 我们不想为堆栈分配比必要的内存更多的内存
我知道我可以使用 pthread_attr_setstack 来传递手动分配的堆栈,但如果可能的话,这会使清理线程变得复杂,因此我倾向于避免这种情况。
因此,执行这个预先提示的最佳方法是什么?如果有一种简单的方法可以找到堆栈的下限(刚好在保护页面之上)那就足够了,在这个点上我可以从那里写到当前的堆栈指针。
请注意,可移植性不是问题;我们很乐意拥有仅适用于 x86-32 和 Linux 的解决方案。
2个回答

3
如果您使用pthread_attr_setstacksize,则仍然可以自动分配已知大小的内存。
glibc nptl在堆栈之间留有保护页,所以您也可以设置SEGV处理程序并简单地涂鸦,直到出现错误,然后从循环中longjmp。这会很丑陋!
编辑:一个非常不可移植的方法是打开/proc/self/maps找到您的堆栈!

API合同中是否有保证pthread_attr_setstacksize不会使用类似于GROWSDOWN的东西,即在分配时mlockall()将立即应用于整个堆栈而无需额外的预取? - bdonlan
线程堆栈在glibc nptl中都是有界的。我不确定它是否是规范的一部分,但我认为它是。快速查看显示nptl还使用堆栈池,因此如果您正在创建/销毁线程,则代码不应在获取“热”堆栈时失败。 - Ben Jackson
好的,我想现在这样就足够了 - 希望我们不会遇到页面阻塞的情况 :) - bdonlan

1

是的。如果在pthread_create之前调用了mlockall(MCL_CURRENT | MCL_FUTURE),则在启动线程时会发生线程堆栈的页面错误。之后,在访问线程中的堆栈时将不再出现页面错误。 因此,人们总是为新创建的线程设置适当的线程大小,以避免为未来到来的线程锁定太多内存。 请查看: https://rt.wiki.kernel.org/index.php/Threaded_RT-application_with_memory_locking_and_stack_handling_example

如果将线程堆栈大小更改为7MB,则会看到: 初始计数:Pagefaults,Major:0(允许>=0),Minor:190(允许>=0) mlockall()生成:Pagefaults,Major:0(允许>=0),Minor:393(允许>=0) malloc()和touch生成:Pagefaults,Major:0(允许>=0),Minor:25633(允许>=0) 第二个malloc()和使用生成:Pagefaults,Major:0(允许0),Minor:0(允许0)

查看ps -leyf的输出,可以看到RSS现在约为100 [MB] 按Enter键退出 我是一个RT-thread,具有在使用过程中不生成页面故障的堆栈,堆栈大小为7340032 由创建线程引起:Pagefaults,Major:0(允许>=0),Minor:1797(允许>=0) 由使用线程堆栈引起:Pagefaults,Major:0(允许0),Minor:0(允许0)

创建线程时发生了1797个页面故障,大约为7MB。 -barry


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接