0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何实时获取栈的使用情况?

Linux阅码场 来源:IOT物联网小镇 作者:道哥 2021-06-11 14:42 次阅读

对于线程的栈空间,相信各位小伙伴都不陌生。它有下面的这几项特性:

操作系统分配固定的空间;

使用一个栈寄存器来保存实时位置;

后进先出。

poYBAGDDBxmAM_DoAABkwzITDso774.jpg

今天,我们不聊操作系统层面对栈的管理,只从应用程序的角度,来看一下如何实时获取栈的使用情况。

在一般的单片机/嵌入式程序开发过程中,在创建一个线程(或者称作任务)的时候,是可以指定给该线程分配多少栈空间的。

然后在调试的时候呢,周期性的打印出栈区的使用情况:消耗了多少空间,还剩余多少空间。

这样的话,跑完每一个测试用例之后,就能得到一个大致的统计数据,从而最终决定:需要给这个线程分配多少栈空间。

例如:在 ucOS 系统中,提供了函数 NT8U OSTaskStkChk(INT8U prio, OS_STK_DATA *p_stk_data),来获取一个任务的栈使用信息

但是在 Linux 系统中,并没有这样类似的函数,来直接获取栈使用信息。

因此,为了得到此线程的已使用和空闲栈空间,必须通过其他的方式来获取。

下面,就提供 2 种解决方案:正规军方式和杂牌军方式!

正规军方式

在 Linux 系统中,在创建一个线程的时候,是可以通过线程属性来设置:为这个线程分配多少的栈(stack)空间的。

如果应用程序不指定的话,操作系统就设置为一个默认的值。

线程创建完毕之后,操作系统在内核空间,记录了这个线程的一切信息,当然也就包括给它分配的栈空间信息。

为了让应用层能够获取到这个信息,操作系统也提供了相应的系统函数。代码如下:

pthread_attr_t attr; void *stack_addr; int stack_size; 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("statck top = %p ", stack_addr); printf("stack bottom = %p ", stack_addr + stack_size);

从上面这段代码中可以看到,它只能获取栈空间的地址开始以及总的空间大小,仍然不知道当前栈空间的实际使用情况!

我找了一下相关的系统调用,Linux 似乎没有提供相关的函数。

怎么办?只能迂回操作。

我们知道,在 Linux x86 平台上,寄存器 ESP 就是来存储栈指针的。对于一个满递减类型的栈,这个寄存器里的值,就代表了当前栈中最后背使用的、那个栈空间的地址。

因此,只要我们能够获取到 ESP 寄存器里的值,就相当于知道了当前这个栈有多少空间被使用了。

那么怎样来获取 ESP 寄存器的值呢?既然是寄存器,那就肯定是使用汇编代码了。

很简单,就 1 行:

size_t esp_val; asm("movl %%esp, %0" : "=m"(esp_val) :);

对不起,我错了!应该是 2 行代码,忘记变量定义了。

对于汇编代码不熟悉的小伙伴,可以参考之前总结的一篇文章:内联汇编很可怕吗?看完这篇文章,终结它!

找到第 4 个示例,直接抄过来就行。

好了,拿到了以上的所有信息,就可以计算出栈的已使用和空闲空间的大小了:

pYYBAGDDByWAHho6AACNOzmIkjw086.jpg

把以上代码放在一起:

#include #include #include #include #include #include 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 寄存器里的值就可以认为是指向栈区的开始地址!

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

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

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

如下图所示:

poYBAGDDBy2AO4IjAAB0JZhoSaY067.jpg

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

知道了 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 寄存器的值?

poYBAGDDBzSAGqsWAACfTuCL0kY932.jpg

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

责任编辑:lq6

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 线程
    +关注

    关注

    0

    文章

    504

    浏览量

    19674
  • 栈空间
    +关注

    关注

    0

    文章

    5

    浏览量

    5435

原文标题:Linux应用程序设计:用一种讨巧方式,来获取线程栈的使用信息

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    如何获取入口函数不是__iar_program_start的Program entry的stack使用情况

    问题 IAR中默认的入口函数是__iar_program_start,使能stack usage analysis之后: 生成的map文件中STACK USAGE章节里面会包含Program entry的stack使用相关信息: 如果对应启动代码没有使用默认的入口函数__iar_program_start,需要使用--entry链接器选项指定对应的入口函数(以使用入口函数Reset_Handler为例): 但是使能stack usage analysis之后,生成的map文件中STACK USAGE章节里面没有包含Program entry的stack使用相关信息:   原因 正常来说,编译器可以生成每个函数的调用和stack使用
    的头像 发表于 11-25 16:23 151次阅读
    如何<b class='flag-5'>获取</b>入口函数不是__iar_program_start的Program entry的stack<b class='flag-5'>使用情况</b>

    LMX2592正常使用情况下温度大概有多少?

    你好,LMX2592如果将 OUTA_PD = 1;OUTB_PD = 1;为什么芯片还是很烫,正常使用情况下温度大概有多少,对于温度的变化可以提供一下参考吗
    发表于 11-08 08:32

    TMS320C64x在高性能DSP应用中的高速缓存使用情况

    电子发烧友网站提供《TMS320C64x在高性能DSP应用中的高速缓存使用情况.pdf》资料免费下载
    发表于 10-21 09:43 0次下载
    TMS320C64x在高性能DSP应用中的高速缓存<b class='flag-5'>使用情况</b>

    嵌入式设备在远端运行,会有偶发性代码跑飞的情况,无法到现场调试的情况下如何检查出问题?

    运行日志: 1、线程的优先级; 2、线程当前的状态; 3、线程当前的位置; 4、线程的大小; 5、线程历史中使用的最大位置; 6、线程剩余的运行节拍数; 设备内存使用日志: 1、堆栈使用
    发表于 07-22 15:05

    Banana Pi BPI-M7 RK3588开发板在 Linux 上检查 Rockchip NPU 使用情况

    如下介绍如何监控 Rockchip 设备的 NPU 使用情况。 如今,新型设备开始配备内置 AI 芯片。“AI 芯片”的正确技术术语是 NPU,即神经处理单元。 如果您有板载 NPU
    的头像 发表于 07-11 16:47 394次阅读
    Banana Pi BPI-M7 RK3588开发板在 Linux 上检查 Rockchip NPU <b class='flag-5'>使用情况</b>

    鸿蒙开发系统基础能力:ohos.hidebug Debug调试

    使用hidebug,可以获取应用内存的使用情况,包括应用进程的静态堆内存(native heap)信息、应用进程内存占用PSS(Proportional Set Size)信息等;可以完成虚拟机内存切片导出,虚拟机CPU Profiling采集等操作。
    的头像 发表于 06-24 17:35 595次阅读
    鸿蒙开发系统基础能力:ohos.hidebug Debug调试

    如何查看TC397内存的使用情况

    Hi,请问,如何查看 TC397 内存的使用情况?谢谢!
    发表于 05-22 07:50

    可以从下面的图看出我的工程flash和ram的使用情况吗?

    芯片是:dspf28335;可以从下面的图看出我的工程flash和ram的使用情况吗?感觉flash还可以,ram有点不够用了,请求大师指点,如何分析?
    发表于 05-11 10:11

    STM32F303启动文件中的Stack_Size和Stack_Mem如何在C代码中获取其地址或值?

    为了在业务代码中监控系统的堆栈使用情况,想在业务代码中获取Stack_Mem的内存地址和Stack_Size的值。目前定义如下:可实现读取地址但无法读取大小 extern unsi
    发表于 04-01 08:18

    CUBEIDE运行完可以看RAM的使用情况,运行中可以实时查看RAM的使用情况吗?

    CUBEIDE运行完可以看RAM的使用情况,运行中可以实时查看RAM的使用情况吗?以及负载情况? 图片是运行完可以看RAM使用情况,是否可
    发表于 03-12 07:56

    rtthread编译后如何查看堆栈空间使用情况

    rtthread编译后如何查看堆栈空间使用情况,现在只能在编译完成后看到总大小,有没有办法能看到详细的使用情况。 由于RAM只有128K,除去内存池32k,想看看剩余的RAM在哪用了
    发表于 03-05 07:58

    如何知道嵌入式电子控制单元 (ECU) 中的RAM使用情况

    我知道嵌入式软件构建工具会报告程序闪存使用情况。我认为他们也报告 RAM 使用率,但他们是否报告最大 RAM 使用率? 生成工具可能不知道在运行时将使用多少堆。是否有构建工具不知道的其他 RAM 使用情况? 如何准确找出运行时使用了多少RAM?
    发表于 01-22 07:02

    有什么办法可以获取TC397中的实时内存使用情况吗?

    有什么办法可以获取 TC397 中的实时内存使用情况吗? 例如,我能否通过计算当前堆栈指针值和基本堆栈指针值之间的差异来获得使用的内存大小?
    发表于 01-22 06:18

    请问TC397如何查看RAM和pFlash的使用情况

    请问 TC397 如何查看 RAM 和 pFlash 的使用情况
    发表于 01-19 06:37

    正常使用情况下LTC3331的ship引脚接高电平还是低电平?

    正常使用情况下,LTC3331的ship引脚接高电平还是低电平?
    发表于 01-05 11:07