订阅
纠错
加入自媒体

Linux应用程序设计:如何获取线程栈的使用信息?

2021-05-27 10:15
道哥分享
关注

把以上代码放在一起:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/resource.h>
void print_stack1()

   size_t used, avail;
   pthread_attr_t attr;
   void *stack_addr;
   int stack_size;
   // 获取栈寄存器 ESP 的当前值
   size_t esp_val;
   asm("movl %%esp, %0" : "=m"(esp_val) :);
   // 通过线程属性,获取栈区的起始地址和空间总大小
   memset(&attr, 0, sizeof(pthread_attr_t));
   pthread_getattr_np(pthread_self(), &attr);
   pthread_attr_getstack(&attr, &stack_addr, &stack_size);
   pthread_attr_destroy(&attr);
   printf("espVal = %p ", esp_val);
   printf("statck top   = %p ", stack_addr);
   printf("stack bottom = %p ", stack_addr + stack_size);
   avail = esp_val - (size_t)stack_addr;
   used = stack_size - avail;
   printf("print_stack1: used = %d, avail = %d, total = %d ",
           used, avail, stack_size);

int main(int argc, char *agv[])

   print_stack1();
   return 0;

杂牌军方式

上面的正规军方法,主要是通过系统函数获取了线程的属性信息,从而获取了栈区的开始地址和栈的总空间大小。

为了获取这两个值,调用了 3 个函数,有点笨重!

不知各位小伙伴是否想起:Linux 操作系统会为一个应用程序,都提供了一些关于 limit 的信息,这其中就包括堆栈的相关信息。

这样的话,我们就能拿到一个线程的栈空间总大小了。

此时,还剩下最后一个变量不知道:栈区的开始地址!

我们来分析一下哈:当一个线程刚刚开始执行的时候,栈区里可以认为是空的,也就是说此时 ESP 寄存器里的值就可以认为是指向栈区的开始地址!

是不是有豁然开朗的感觉?!

但是,这仍然需要调用汇编代码来获取。

再想一步,既然此时栈区里可以认为是空的,那么如果在线程的第一个函数中,定义一个局部变量,然后通过获取这个局部变量的地址,不就相当于是获取到了栈区的开始地址了吗?

如下图所示:

我们可以把这个局部变量的地址,记录在一个全局变量中。然后在应用程序的其他代码处,就可以用它来代表栈的起始地址。

知道了 3 个必需的变量,就可以计算栈空间的使用情况了:

// 用来存储栈区的起始地址
size_t top_stack;
void print_stack2()

   size_t used, avail;
   size_t esp_val;
   asm("movl %%esp, %0" : "=m"(esp_val) :);
   printf("esp_val = %p ", esp_val);
   used = top_stack - esp_val;
   struct rlimit limit;
   getrlimit(RLIMIT_STACK, &limit);
   avail = limit.rlim_cur - used;
   printf("print_stack2: used = %d, avail = %d, total = %d ",
           used, avail, used + avail);

int main(int argc, char *agv[])

   int x = 0;
   // 记录栈区的起始地址(近似值)
   top_stack = (size_t)&x;
   print_stack2();
   return 0;

更讨巧的方式

在上面的两种方法中,获取栈的当前指针位置的方式,都是通过汇编代码,来获取寄存器 ESP 中的值。

是否可以继续利用刚才的技巧:通过定义一个局部变量的方式,来间接地获取 ESP 寄存器的值?

void print_stack3()

   int x = 0;
   size_t used, avail;
   // 局部变量的地址,可以近似认为是 ESP 寄存器的值
   size_t tmp = (size_t)&x;
   used =  top_stack - tmp;
   struct rlimit limit;
   getrlimit(RLIMIT_STACK, &limit);
   avail = limit.rlim_cur - used;
   printf("print_stack3: used = %d, avail = %d, total = %d ",
           used, avail, used + avail);

int main(int argc, char *agv[])

   int x = 0;
   top_stack = (size_t)&x;
   print_stack3();
   return 0;

总结

以上的几种方式,各有优缺点。

我们把以上 3 个打印堆栈使用情况的函数放在一起,然后在 main 函数中,按顺序调用 3 个测试函数,每个函数中都定义一个整型数组(消耗 4K 的栈空间),然后看一下这几种方式的打印输出信息:

// 测试代码(3个打印函数就不贴出来了)
void print_stack1()

   ...

void print_stack2()

   ...

void print_stack3()

   ...

void func3()

   int num[1024];
   print_stack1();
   printf(" ********* ");
   print_stack2();
   printf(" ********* ");
   print_stack3();

void func2()

   int num[1024];
   func3();

void func1()

   int num[1024];
   func2();

int main(int argc, char *agv[])

   int x = 0;
   top_stack = (size_t)&x;
   func1();
   return 0;

打印输出信息:

espVal = 0xffe8c980
statck top   = 0xff693000
stack bottom = 0xffe90000
print_stack1: used = 13952, avail = 8362368, total = 8376320
*********
esp_val = 0xffe8c9a0
print_stack2: used = 12456, avail = 8376152, total = 8388608
*********
print_stack3: used = 12452, avail = 8376156, total = 8388608

<上一页  1  2  
声明: 本文由入驻维科号的作者撰写,观点仅代表作者本人,不代表OFweek立场。如有侵权或其他问题,请联系举报。

发表评论

0条评论,0人参与

请输入评论内容...

请输入评论/评论长度6~500个字

您提交的评论过于频繁,请输入验证码继续

暂无评论

暂无评论

人工智能 猎头职位 更多
扫码关注公众号
OFweek人工智能网
获取更多精彩内容
文章纠错
x
*文字标题:
*纠错内容:
联系邮箱:
*验 证 码:

粤公网安备 44030502002758号