Linux应用程序设计:如何获取线程栈的使用信息?
把以上代码放在一起:
#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
最新活动更多
-
即日-11.13立即报名>>> 【在线会议】多物理场仿真助跑新能源汽车
-
11月20日火热报名中>> 2024 智能家居出海论坛
-
11月28日立即报名>>> 2024工程师系列—工业电子技术在线会议
-
12月19日立即报名>> 【线下会议】OFweek 2024(第九届)物联网产业大会
-
即日-12.26火热报名中>> OFweek2024中国智造CIO在线峰会
-
即日-2025.8.1立即下载>> 《2024智能制造产业高端化、智能化、绿色化发展蓝皮书》
推荐专题
发表评论
请输入评论内容...
请输入评论/评论长度6~500个字
暂无评论
暂无评论