成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux應用程序設計:用一種討巧方式,來獲取線程棧的使用信息

系統 Linux
今天,我們不聊操作系統層面對棧的管理,只從應用程序的角度,來看一下如何實時獲取棧的使用情況。

[[401829]]

面對的問題

對于線程的棧空間,相信各位小伙伴都不陌生。它有下面的這幾項特性:

  1. 由操作系統分配固定的空間;
  2. 使用一個棧寄存器來保存實時位置;
  3. 后進先出。
圖片

今天,我們不聊操作系統層面對棧的管理,只從應用程序的角度,來看一下如何實時獲取棧的使用情況。

在一般的單片機/嵌入式程序開發過程中,在創建一個線程(或者稱作任務)的時候,是可以指定給該線程分配多少棧空間的。

然后在調試的時候呢,周期性的打印出棧區的使用情況:消耗了多少空間,還剩余多少空間。

這樣的話,跑完每一個測試用例之后,就能得到一個大致的統計數據,從而最終決定:需要給這個線程分配多少棧空間。

例如:在 ucOS 系統中,提供了函數 NT8U OSTaskStkChk(INT8U prio, OS_STK_DATA *p_stk_data),來獲取一個任務的棧使用信息。

但是在 Linux 系統中,并沒有這樣類似的函數,來直接獲取棧使用信息。

因此,為了得到此線程的已使用和空閑棧空間,必須通過其他的方式來獲取。

下面,就提供 2 種解決方案:正規軍方式和雜牌軍方式!

正規軍方式

[[401830]]

在 Linux 系統中,在創建一個線程的時候,是可以通過線程屬性來設置:為這個線程分配多少的棧(stack)空間的。

如果應用程序不指定的話,操作系統就設置為一個默認的值。

線程創建完畢之后,操作系統在內核空間,記錄了這個線程的一切信息,當然也就包括給它分配的棧空間信息。

為了讓應用層能夠獲取到這個信息,操作系統也提供了相應的系統函數。代碼如下:

  1. pthread_attr_t attr; 
  2. void *stack_addr; 
  3. int stack_size; 
  4.  
  5. memset(&attr, 0, sizeof(pthread_attr_t)); 
  6. pthread_getattr_np(pthread_self(), &attr); 
  7. pthread_attr_getstack(&attr, &stack_addr, &stack_size); 
  8. pthread_attr_destroy(&attr); 
  9.  
  10. printf("statck top   = %p \n", stack_addr); 
  11. printf("stack bottom = %p \n", stack_addr + stack_size); 

從上面這段代碼中可以看到,它只能獲取棧空間的地址開始以及總的空間大小,仍然不知道當前棧空間的實際使用情況!

我找了一下相關的系統調用,Linux 似乎沒有提供相關的函數。

怎么辦?只能迂回操作。

我們知道,在 Linux x86 平臺上,寄存器 ESP 就是來存儲棧指針的。對于一個滿遞減類型的棧,這個寄存器里的值,就代表了當前棧中最后背使用的、那個棧空間的地址。

因此,只要我們能夠獲取到 ESP 寄存器里的值,就相當于知道了當前這個棧有多少空間被使用了。

那么怎樣來獲取 ESP 寄存器的值呢?既然是寄存器,那就肯定是使用匯編代碼了。

很簡單,就 1 行:

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

對不起,我錯了!應該是 2 行代碼,忘記變量定義了。

對于匯編代碼不熟悉的小伙伴,可以參考之前總結的一篇文章:內聯匯編很可怕嗎?看完這篇文章,終結它!

找到第 4 個示例,直接抄過來就行。

好了,拿到了以上的所有信息,就可以計算出棧的已使用和空閑空間的大小了:

把以上代碼放在一起:

  1. #include <unistd.h> 
  2. #include <stdio.h> 
  3. #include <stdlib.h> 
  4. #include <string.h> 
  5. #include <pthread.h> 
  6. #include <sys/resource.h> 
  7.  
  8. void print_stack1() 
  9.     size_t used, avail; 
  10.     pthread_attr_t attr; 
  11.     void *stack_addr; 
  12.     int stack_size; 
  13.  
  14.     // 獲取棧寄存器 ESP 的當前值 
  15.     size_t esp_val; 
  16.     asm("movl %%esp, %0" : "=m"(esp_val) :); 
  17.  
  18.     // 通過線程屬性,獲取棧區的起始地址和空間總大小 
  19.     memset(&attr, 0, sizeof(pthread_attr_t)); 
  20.     pthread_getattr_np(pthread_self(), &attr); 
  21.     pthread_attr_getstack(&attr, &stack_addr, &stack_size); 
  22.     pthread_attr_destroy(&attr); 
  23.  
  24.     printf("espVal = %p \n", esp_val); 
  25.     printf("statck top   = %p \n", stack_addr); 
  26.     printf("stack bottom = %p \n", stack_addr + stack_size); 
  27.  
  28.     avail = esp_val - (size_t)stack_addr; 
  29.     used = stack_size - avail; 
  30.  
  31.     printf("print_stack1: used = %d, avail = %d, total = %d \n",  
  32.             used, avail, stack_size); 
  33.  
  34. int main(int argc, char *agv[]) 
  35.     print_stack1(); 
  36.     return 0; 

雜牌軍方式

[[401832]]

上面的正規軍方法,主要是通過系統函數獲取了線程的屬性信息,從而獲取了棧區的開始地址和棧的總空間大小。

為了獲取這兩個值,調用了 3 個函數,有點笨重!

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

[[401833]]

這樣的話,我們就能拿到一個線程的棧空間總大小了。

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

我們來分析一下哈:當一個線程剛剛開始執行的時候,棧區里可以認為是空的,也就是說此時 ESP 寄存器里的值就可以認為是指向棧區的開始地址!

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

[[401834]]

但是,這仍然需要調用匯編代碼來獲取。

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

如下圖所示:

[[401835]]

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

知道了 3 個必需的變量,就可以計算棧空間的使用情況了:

  1. // 用來存儲棧區的起始地址 
  2. size_t top_stack; 
  3.  
  4.  
  5. void print_stack2() 
  6.     size_t used, avail; 
  7.      
  8.     size_t esp_val; 
  9.     asm("movl %%esp, %0" : "=m"(esp_val) :); 
  10.     printf("esp_val = %p \n", esp_val); 
  11.  
  12.     used = top_stack - esp_val; 
  13.      
  14.     struct rlimit limit; 
  15.     getrlimit(RLIMIT_STACK, &limit); 
  16.     avail = limit.rlim_cur - used; 
  17.     printf("print_stack2: used = %d, avail = %d, total = %d \n",  
  18.             used, avail, used + avail); 
  19.  
  20. int main(int argc, char *agv[]) 
  21.     int x = 0; 
  22.     // 記錄棧區的起始地址(近似值) 
  23.     top_stack = (size_t)&x;  
  24.     print_stack2(); 
  25.     return 0; 

更討巧的方式

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

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

  1. void print_stack3() 
  2.     int x = 0; 
  3.     size_t used, avail; 
  4.     // 局部變量的地址,可以近似認為是 ESP 寄存器的值 
  5.     size_t tmp = (size_t)&x; 
  6.     used =  top_stack - tmp; 
  7.  
  8.     struct rlimit limit; 
  9.     getrlimit(RLIMIT_STACK, &limit); 
  10.     avail = limit.rlim_cur - used; 
  11.     printf("print_stack3: used = %d, avail = %d, total = %d \n",  
  12.             used, avail, used + avail); 
  13.  
  14. int main(int argc, char *agv[]) 
  15.     int x = 0; 
  16.     top_stack = (size_t)&x; 
  17.     print_stack3(); 
  18.     return 0; 

總結

以上的幾種方式,各有優缺點。

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

  1. // 測試代碼(3個打印函數就不貼出來了) 
  2. void print_stack1() 
  3.     ... 
  4.  
  5. void print_stack2() 
  6.     ... 
  7.  
  8. void print_stack3() 
  9.     ... 
  10.  
  11. void func3() 
  12.     int num[1024]; 
  13.     print_stack1(); 
  14.     printf("\n\n ********* \n"); 
  15.     print_stack2(); 
  16.     printf("\n\n ********* \n"); 
  17.     print_stack3(); 
  18.  
  19. void func2() 
  20.     int num[1024]; 
  21.     func3(); 
  22.  
  23. void func1() 
  24.     int num[1024]; 
  25.     func2(); 
  26.  
  27. int main(int argc, char *agv[]) 
  28.     int x = 0; 
  29.     top_stack = (size_t)&x; 
  30.     func1(); 
  31.     return 0; 

打印輸出信息:

  1. espVal = 0xffe8c980  
  2. statck top   = 0xff693000  
  3. stack bottom = 0xffe90000  
  4. print_stack1: used = 13952, avail = 8362368, total = 8376320  
  5.  
  6.  
  7.  *********  
  8. esp_val = 0xffe8c9a0  
  9. print_stack2: used = 12456, avail = 8376152, total = 8388608  
  10.  
  11.  
  12.  *********  
  13. print_stack3: used = 12452, avail = 8376156, total = 8388608 

 本文轉載自微信公眾號「 IOT物聯網小鎮」,可以通過以下二維碼關注。轉載本文請聯系 IOT物聯網小鎮公眾號。

 

責任編輯:姜華 來源: IOT物聯網小鎮
相關推薦

2018-06-20 16:10:20

WindowsWindows 10應用程序

2010-08-12 15:59:23

Flex應用程序

2009-09-03 08:46:55

UML類圖Java

2012-02-15 14:39:55

GNOME 3

2010-03-04 10:11:17

Android手機系統

2022-05-04 23:08:36

標準Go應用程序

2023-08-16 19:05:59

2010-07-15 17:42:20

SQL Server應

2017-07-05 14:09:04

系統設計與架構java云計算

2011-12-22 10:30:49

2009-02-25 14:51:05

應用程序設計ASP.NET.NET

2010-03-03 15:46:40

Android應用程序

2012-03-20 10:28:43

2012-03-30 15:47:50

ibmdw

2013-12-12 17:58:02

網絡虛擬化疊加SDN

2010-07-20 11:35:41

避免SQL Serve

2018-07-18 08:59:32

Redis存儲模式

2013-03-12 14:07:06

Java編程

2010-08-04 09:34:51

Flex設計

2023-01-01 14:04:51

字節碼接口系統
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 精品亚洲一区二区 | 国产综合在线视频 | 免费观看一级特黄欧美大片 | 一区二区在线看 | 中文字幕一区二区三区四区五区 | 欧美视频在线看 | 日韩视频在线观看中文字幕 | 国产精品视频网站 | 欧美中文在线 | 91视频正在播放 | 懂色中文一区二区三区在线视频 | 午夜欧美a级理论片915影院 | 综合视频在线 | 国产一级影片 | 日本一区二区视频 | 久久精品99国产精品日本 | 亚洲国产一区二区在线 | 国产精品欧美一区喷水 | 在线欧美视频 | 亚洲天堂中文字幕 | 成年精品 | 欧美国产视频一区二区 | 成人免费视频网站在线看 | 久久国产亚洲 | 99在线免费观看视频 | 日韩欧美专区 | 久久曰视频 | 久久久久久美女 | 久久不卡| 日韩福利在线 | 国产成人叼嘿视频在线观看 | 国产亚洲一区二区三区 | 亚洲欧美一区二区三区情侣bbw | 精品国产免费人成在线观看 | 91亚洲一区| 欧美日韩在线成人 | 欧美国产精品一区二区三区 | 欧美精品免费观看二区 | 一级毛片免费视频观看 | 国产精品久久国产精品 | 91精品国产色综合久久 |