訂閱
糾錯
加入自媒體

Linux應(yīng)用程序設(shè)計:如何獲取線程棧的使用信息?

把以上代碼放在一起:

#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 的當(dāng)前值
   size_t esp_val;
   asm("movl %%esp, %0" : "=m"(esp_val) :);
   // 通過線程屬性,獲取棧區(qū)的起始地址和空間總大小
   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;

雜牌軍方式

上面的正規(guī)軍方法,主要是通過系統(tǒng)函數(shù)獲取了線程的屬性信息,從而獲取了棧區(qū)的開始地址和棧的總空間大小。

為了獲取這兩個值,調(diào)用了 3 個函數(shù),有點笨重!

不知各位小伙伴是否想起:Linux 操作系統(tǒng)會為一個應(yīng)用程序,都提供了一些關(guān)于 limit 的信息,這其中就包括堆棧的相關(guān)信息。

這樣的話,我們就能拿到一個線程的?臻g總大小了。

此時,還剩下最后一個變量不知道:棧區(qū)的開始地址!

我們來分析一下哈:當(dāng)一個線程剛剛開始執(zhí)行的時候,棧區(qū)里可以認(rèn)為是空的,也就是說此時 ESP 寄存器里的值就可以認(rèn)為是指向棧區(qū)的開始地址!

是不是有豁然開朗的感覺?!

但是,這仍然需要調(diào)用匯編代碼來獲取。

再想一步,既然此時棧區(qū)里可以認(rèn)為是空的,那么如果在線程的第一個函數(shù)中,定義一個局部變量,然后通過獲取這個局部變量的地址,不就相當(dāng)于是獲取到了棧區(qū)的開始地址了嗎?

如下圖所示:

我們可以把這個局部變量的地址,記錄在一個全局變量中。然后在應(yīng)用程序的其他代碼處,就可以用它來代表棧的起始地址。

知道了 3 個必需的變量,就可以計算?臻g的使用情況了:

// 用來存儲棧區(qū)的起始地址
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;
   // 記錄棧區(qū)的起始地址(近似值)
   top_stack = (size_t)&x;
   print_stack2();
   return 0;

更討巧的方式

在上面的兩種方法中,獲取棧的當(dāng)前指針位置的方式,都是通過匯編代碼,來獲取寄存器 ESP 中的值。

是否可以繼續(xù)利用剛才的技巧:通過定義一個局部變量的方式,來間接地獲取 ESP 寄存器的值?

void print_stack3()

   int x = 0;
   size_t used, avail;
   // 局部變量的地址,可以近似認(rèn)為是 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;

總結(jié)

以上的幾種方式,各有優(yōu)缺點。

我們把以上 3 個打印堆棧使用情況的函數(shù)放在一起,然后在 main 函數(shù)中,按順序調(diào)用 3 個測試函數(shù),每個函數(shù)中都定義一個整型數(shù)組(消耗 4K 的棧空間),然后看一下這幾種方式的打印輸出信息:

// 測試代碼(3個打印函數(shù)就不貼出來了)
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立場。如有侵權(quán)或其他問題,請聯(lián)系舉報。

發(fā)表評論

0條評論,0人參與

請輸入評論內(nèi)容...

請輸入評論/評論長度6~500個字

您提交的評論過于頻繁,請輸入驗證碼繼續(xù)

暫無評論

暫無評論

人工智能 獵頭職位 更多
掃碼關(guān)注公眾號
OFweek人工智能網(wǎng)
獲取更多精彩內(nèi)容
文章糾錯
x
*文字標(biāo)題:
*糾錯內(nèi)容:
聯(lián)系郵箱:
*驗 證 碼:

粵公網(wǎng)安備 44030502002758號