测量 Linux 多线程应用程序的堆栈使用情况

Measuring stack usage for Linux multi-threaded application

提问人:mtabz 提问时间:9/12/2008 最后编辑:Rachid K.mtabz 更新时间:11/19/2023 访问量:11840

问:

我正在为 Linux 嵌入式平台开发一个多线程应用程序。

目前,我正在将每个线程的堆栈大小(通过)设置为相当大的默认值。我想将每个线程的该值微调为更小的值,以减少应用程序的内存使用量。我可以通过反复试验,将每个线程的堆栈大小设置为逐渐变小的值,直到程序崩溃,但应用程序使用 ~15 个线程,每个线程具有完全不同的功能/属性,因此这种方法将非常耗时。pthread_set_attr

我更希望能够直接测量每个线程的堆栈使用情况。有没有一些实用程序可以推荐这样做?(例如,我来自vxWorks后台,使用vxWorks shell中的“ti”命令直接提供有关堆栈使用情况的统计信息以及有关任务状态的其他有用信息。

Linux 多线程 堆栈 pthreads

评论


答:

4赞 Tobi 9/12/2008 #1

我不知道有什么好的工具,但作为最后的手段,您可以在应用程序中包含一些代码来检查它,类似于以下内容:

__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
}
3赞 DGentry 9/12/2008 #2

参考 Tobi 的回答:如果在线程初始化时设置变量很困难,您可以随时使用来获取堆栈的基础。然后,您可以在自己的函数中获取自动变量的地址,以确定当时堆栈的深度。pthread_attr_getstackaddr

8赞 d99kris 3/4/2015 #3

以下是两个用于测量 Linux 应用程序中(原生 pthreads)堆栈使用情况的工具:

瓦尔格林德

用法:

valgrind --tool=drd --show-stack-usage=yes PROG

Valgrind 是一种稳定而强大的工具,不仅可用于测量烟囱使用情况。不过,它可能不支持所有嵌入式 CPU 型号。

堆栈用法

用法:

stackusage PROG

Stackusage 是一个轻量级工具,专门设计用于测量线程堆栈使用情况,对于大多数配备 glibc 的嵌入式 Linux 平台来说,它应该是可移植的。在这一点上,它可能不像 Valgrind/drd 那样经过充分测试或成熟。

完全披露:我是 Stackusage 的作者。

0赞 Rachid K. 11/18/2023 #4

在 Linux/GLIB/多线程环境中,线程的默认堆栈大小由 getrlimit() 中的 pthread 库和参数获取。在 shell 中,您可以使用如下命令获取此值:RLIMIT_STACK

$ 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/<pid>/smaps 中查看线程堆栈:

$ 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
[...]
7fd503788000-7fd503f88000 rw-p 00000000 00:00 0 
Size:               8192 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                   8 kB
Pss:                   8 kB
[...]

上面的输出片段首先显示 4 KB 长的红色区域,紧随 8 MB 长线程的堆栈之后。RSS 字段根据 Linux 的延迟分配功能显示线程消耗的实际 RAM:只有虚拟内存中的触摸页才会触发实际的 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
[...]
7f7e61245000-7f7e61a45000 rw-p 00000000 00:00 0 
Size:               8192 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                  16 kB
Pss:                  16 kB
[...]

这 16 KB 实际上是上面看到的 8 KB 内部 pthread 信息加上线程局部变量的 8 KB。buffer

因此,我们看到了一种捕获进程线程实际堆栈消耗的方法:查看进程内存映射中相应内存区域的 RSS。

PS:在调整线程堆栈的大小时,不要忘记为信号处理分配空间,因为处理程序是在接收线程的堆栈上执行的。值 MINSIGSTKSZ 定义为信号处理程序的最小堆栈大小(参见 <signal.h>)。否则,为信号定义一个备用堆栈:参见 sigalstack()