我正在为 Linux 嵌入式平台开发多线程应用程序。
目前我正在将每个线程的堆栈大小(通过
pthread_set_attr
)设置为相当大的默认值。我想将每个线程的值微调为较小的值,以减少应用程序的内存使用量。我可以尝试将每个线程的堆栈大小设置为逐渐减小的值,直到程序崩溃,但应用程序使用约 15 个线程,每个线程具有完全不同的功能/属性,因此这种方法将非常耗时。
我更愿意能够直接测量每个线程的堆栈使用情况。人们可以推荐一些实用程序来执行此操作吗? (例如,我来自 vxWorks 背景,并使用 vxWorks shell 中的“ti”命令直接提供有关堆栈使用情况的统计信息以及有关任务状态的其他有用信息。)
以下是测量 Linux 应用程序中(本机 pthreads)堆栈使用情况的两个工具:
瓦尔格林德
用途:
valgrind --tool=drd --show-stack-usage=yes PROG
Valgrind 是一个稳定而强大的工具,不仅可用于测量堆栈使用情况。但它可能不支持所有嵌入式 CPU 型号。
堆栈使用
用途:
stackusage PROG
Stackusage 是一个轻量级工具,专门用于测量线程堆栈使用情况,对于大多数配备 glibc 的嵌入式 Linux 平台来说应该是可移植的。目前它可能不像 Valgrind/drd 那样经过充分测试或成熟。
完全披露:我是 Stackusage 的作者。
我不知道有什么好的工具,但作为最后的手段,您可以在应用程序中包含一些代码来检查它,类似于以下内容:
__thread void* stack_start;
__thread long stack_max_size = 0L;
void check_stack_size() {
// address of 'nowhere' approximates end of stack
char nowhere;
void* stack_end = (void*)&nowhere;
// may want to double check stack grows downward on your platform
long stack_size = (long)stack_start - (long)stack_end;
// update max_stack_size for this thread
if (stack_size > stack_max_size)
stack_max_size = stack_size;
}
必须在某些嵌套最深的函数中调用 check_stack_size() 函数。
然后作为线程中的最后一条语句,您可以将 stack_max_size 输出到某个地方。
stack_start 变量必须在线程启动时初始化:
void thread_proc() {
char nowhere;
stack_start = (void*)&nowhere;
// do stuff including calls to check_stack_size()
// in deeply nested functions
// output stack_max_size here
}
参考Tobi的答案:如果在线程初始化时设置变量很困难,您可以随时使用
pthread_attr_getstackaddr
获取堆栈的基数。然后,您可以在自己的函数中获取自动变量的地址,以确定此时堆栈的深度。
在Linux/GLIB/多线程环境中,线程的默认堆栈大小由pthread库从getrlimit()和
RLIMIT_STACK
参数获取。在 shell 中,您可以使用如下命令获取该值:
$ ulimit -s
8192
以上结果以兆字节为单位。因此,我的系统上的默认线程堆栈大小是 8 MB 虚拟内存。
让我们考虑以下创建一个线程的程序:
#include <pthread.h>
#include <unistd.h>
static void *thd(void *p)
{
pause();
return NULL;
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, thd, NULL);
pthread_join(tid, NULL);
return 0;
}
让我们编译并运行它:
$ gcc pg.c -o pg -lpthread
$ ./pg &
由于默认情况下堆栈前面有一个红色区域页面(即没有读/写访问权限的页面)来检测堆栈溢出,因此可以在/proc/
$ cat /proc/`pidof pg`/smaps
[...]
7fd503787000-7fd503788000 ---p 00000000 00:00 0
Size: 4 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 0 kB
Pss: 0 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 0 kB
Anonymous: 0 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: mr mw me sd
7fd503788000-7fd503f88000 rw-p 00000000 00:00 0
Size: 8192 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 8 kB
Pss: 8 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 8 kB
Referenced: 8 kB
Anonymous: 8 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: rd wr mr mw me ac sd
[...]
上面的输出片段首先显示了 4KB 长的红色页面和 8MB 长的线程堆栈。 RSS 字段显示线程实际消耗的 RAM。这里是 8 KB。这个消耗是线程的内部pthread的任务控制块(TCB)+杂项内部信息。
让我们杀死前面的程序:
$ kill `pidof pg`
[2]+ Terminated ./pg
让我们在线程中添加一些局部变量。我们写入它们以触发实际的 RAM 分配:
#include <pthread.h>
#include <unistd.h>
#include <string.h>
static void *thd(void *p)
{
char buffer[8192];
// Force the physical allocation of the corresponding stack space
memset(buffer, 0, sizeof(buffer));
pause();
return NULL;
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, thd, NULL);
pthread_join(tid, NULL);
return 0;
}
编译并运行:
$ gcc pg.c -o pg -lpthread
$ ./pg &
[2] 38167
内存映射显示更大的 RSS 等于 16 KB:
$ cat /proc/`pidof pg`/smaps
[...]
7f7e61244000-7f7e61245000 ---p 00000000 00:00 0
Size: 4 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 0 kB
Pss: 0 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 0 kB
Anonymous: 0 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: mr mw me sd
7f7e61245000-7f7e61a45000 rw-p 00000000 00:00 0
Size: 8192 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 16 kB
Pss: 16 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 16 kB
Referenced: 16 kB
Anonymous: 16 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: rd wr mr mw me ac sd
[...]
这 16 KB 实际上是上面看到的内部 pthread 信息的 8 KB 加上线程的
buffer
局部变量的 8 KB。
因此我们看到了一种捕获进程线程实际堆栈消耗的方法:查看进程内存映射中相应内存区域的RSS。
PS:调整线程堆栈大小时,不要忘记为信号处理分配空间,因为处理程序是在接收线程的堆栈上执行的。值 MINSIGSTKSZ 被定义为信号处理程序的最小堆栈大小(参见