線程棧和進程棧

1.線程棧和進程棧的區別

  • 線程棧在linux下默認是8M大小,通過mmap開闢線程棧,線程棧的起始地址跟大小保存在pthread_attr_t 中。
    可以配合pthread_attr_setstack使用設置線程棧,由此看來線程棧在所進程的堆區,所以線程與其所屬的進程共享進程的虛擬地址空間.
typedef struct __pthread_attr_s
{
    int __detachstate;  //分離狀態
    int __schedpolicy;//調度策略
    struct __sched_param __schedparam;
    int __inheritsched;
    int __scope;//線程優先級的有效範圍
    size_t __guardsize;//
    int __stackaddr_set;
    void *__stackaddr;//起始地址
    size_t __stacksize;//表示堆棧的大小。

}pthread_attr_t;
  • 進程棧是用戶態的棧吧,位於堆的上方,主要存放棧幀,函數的參數值、局部變量的值等
    在這裏插入圖片描述
  • 進程棧的起始地址存放在task_struct的mm_struct *mm 成員的start_stack中,可以通過cat /proc/xxx/stat 查看
    進程棧區存放的應該是第一個線程的參數上下文,接下來創建的線程的參數上下文存放在struct __pthread_attr_s指向的堆區
struct mm_struct {
    struct vm_area_struct *mmap;           /* 內存區域鏈表 */
    struct rb_root mm_rb;                  /* VMA 形成的紅黑樹 */
    ...
    struct list_head mmlist;               /* 所有 mm_struct 形成的鏈表 */
    ...
    unsigned long start_code, end_code, start_data, end_data;    /* 代碼段、數據段 起始地址和結束地址 */
    unsigned long start_brk, brk, start_stack;                   /* 棧區 的起始地址,堆區 起始地址和結束地址 */
    unsigned long arg_start, arg_end, env_start, env_end;        /* 命令行參數 和 環境變量的 起始地址和結束地址 */

};

在這裏插入圖片描述

  • 另外每個進程都還有一個內核棧,在進行系統調用時,將返回地址跟系統調用參數等上下文信息保存到進程內核棧,其大小INITSTACKSIZE是8k,也就2頁大小,與struct task共享8k內存
Union task_union {
                   Struct task_struct task;//任務描述結構
                   Unsigned long stack[INIT_STACK_SIZE/sizeof(long)];//進程內核棧

 };

在這裏插入圖片描述

2.線程堆棧大小

  • 有大數據量處理的應用中,有時我們有必要在棧空間分配一個大的內存塊或者要分配很多小的內存塊,但是線程的棧空間的最大值在線程創建的時候就已經定下來了,如果棧的大小超過個了該值,系統將訪問未授權的內存塊,毫無疑問,再來的肯定是一個段錯誤。

  • pthread_create()創建線程時,若不指定分配堆棧大小,系統會分配默認值,通過命令查看方法如下:
    在這裏插入圖片描述
    上面的單位爲 Kb,所以,線程默認堆棧大小爲 8M。
    也可以在終端下通過 ulimit -s value 用來重新設置 stack 大小。

  • 一般來說,默認堆棧大小爲 8388608,堆棧最小爲 16384, 單位爲字節。
    若採用默認值的話,會導致出現問題,若內存不足,則 pthread_create() 會返回 12,其定義如下:

#define EAGAIN 11
 
#define ENOMEM 12 /* Out of memory */
  • 我們用 pthread_attr_getstacksize() 和 pthread_attr_setstacksize() 的方法來查看和設置線程的堆棧空間。
1)查看線程堆棧大小示例代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>//線程操作所需頭文件
 
int main(void)
{
	size_t stack_size = 0; //堆棧大小變量
	pthread_attr_t attr; //線程屬性結構體變量
	
	//初始化線程屬性
	int ret = pthread_attr_init(&attr);
	if(ret != 0)
	{
		perror("pthread_attr_init");
		return -1;
	}
	
	//獲取當前的線程棧大小
    ret = pthread_attr_getstacksize(&attr, &stack_size);
    if(ret != 0)
	{
		perror("pthread_attr_getstacksize");
		return -1;
	}
	
	//打印堆棧值
	printf("stack_size = %dB, %dk\n", stack_size, stack_size/1024);
 
    return 0;2)設置線程堆棧大小示例代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>//線程操作所需頭文件
 
int main(void)
{
	size_t stack_size = 0; //堆棧大小變量
	pthread_attr_t attr; //線程屬性結構體變量
	
	//初始化線程屬性
	int ret = pthread_attr_init(&attr);
	if(ret != 0)
	{
		perror("pthread_attr_init");
		return -1;
	}
	
	stack_size = 1024*20; //堆棧大小設置爲20K
    ret = pthread_attr_setstacksize(&attr, stack_size);//設置線程堆棧大小
    if(ret != 0)
	{
		perror("pthread_attr_getstacksize");
		return -1;
	}
	
	stack_size = 0;
    ret = pthread_attr_getstacksize(&attr, &stack_size);//獲取線程堆棧大小
	//打印堆棧值
	printf("stack_size = %dB, %dk\n", stack_size, stack_size/1024);
 
    return 0;
}
  • 結果:
    在這裏插入圖片描述
    在這裏插入圖片描述

3.獲取Linux 內存頁大小的命令

  • 獲取Linux 內存頁(基頁)大小的命令:getconf PAGE_SIZE ,一般的輸出是4096,即 4KB。

4.棧空間和堆空間

  • 一個由C/C++編譯的程序佔用的內存分爲以下幾個部分:
    1、棧區(stack):又編譯器自動分配釋放,存放函數的參數值,局部變量的值等,其操作方式類似於數據結構的棧。
    2、堆區(heap):一般是由程序員分配釋放,若程序員不釋放的話,程序結束時可能由OS回收,值得注意的是他與數據結構的堆是兩回事,分配方式倒是類似於數據結構的鏈表。
    3、全局區(static):也叫靜態數據內存空間,存儲全局變量和靜態變量,全局變量和靜態變量的存儲是放一塊的,初始化的全局變量和靜態變量放一塊區域,沒有初始化的在相鄰的另一塊區域,程序結束後由系統釋放。
    4、文字常量區:常量字符串就是放在這裏,程序結束後由系統釋放。
    5、程序代碼區:存放函數體的二進制代碼。

  • 堆和棧的區別:
    (一)、由以上綜述就可以得知,他們程序的內存分配方式不同。
    (二)、申請和響應不同:
    1、申請方式:stack由系統自動分配,heap需要程序員自己申請,C中用函數malloc分配空間,用free釋放,C++用new分配,用delete釋放。
    2、申請後系統的響應:
    棧:只要棧的剩餘空間大於所申請的空間,系統將爲程序提供內存,否則將報異常提示棧溢出。
    堆:首先應該知道操作系統有一個記錄內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請的空間的堆結點,然後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序。另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣代碼中的delete或free語句就能夠正確的釋放本內存空間。另外,由於找到的堆結點的大小不一定正好等於申請的大小,系統會將多餘的那部分重新放入空閒鏈表中。
    (三)、 申請的大小限制不同:
    棧:在windows下,棧是向低地址擴展的數據結構,是一塊連續的內存區域,棧頂的地址和棧的最大容量是系統預先規定好的,能從棧獲得的空間較小。
    堆:堆是向高地址擴展的數據結構,是不連續的內存區域,這是由於系統是由鏈表在存儲空閒內存地址,自然堆就是不連續的內存區域,且鏈表的遍歷也是從低地址向高地址遍歷的,堆得大小受限於計算機系統的有效虛擬內存空間,由此空間,堆獲得的空間比較靈活,也比較大。
    (四)、申請的效率不同:
    棧:棧由系統自動分配,速度快,但是程序員無法控制。
    堆:堆是有程序員自己分配,速度較慢,容易產生碎片,不過用起來方便。
    (五)、堆和棧的存儲內容不同:
    棧:在函數調用時,第一個進棧的是主函數中函數調用後的下一條指令的地址,然後是函數的各個參數,在大多數的C編譯器中,參數是從右往左入棧的,當本次函數調用結束後,局部變量先出棧,然後是參數。
    堆:一般是在堆得頭部用一個字節存放堆得大小,具體內容由程序員安排。

  • 堆空間和棧空間大小
    我們知道,程序運行時在內存中主要有代碼段、數據段、堆棧段(堆空間和棧空間)、進程頭、動態鏈接庫等區域。 其中數據使用到的:
    (1)數據段:靜態內存空間,其中數據的總大小和初始值在編譯時確定,數據在整個程序運行時一直存在。
    (2)棧空間:自動內存空間,其中數據的大小在編譯時確定,數據的分配和釋放也由編譯器在函數進入和退出時插入指令完成,數據生命週期和函數一樣。
    (3)堆空間:動態(手動)內存空間,其中數據的大小和初始值在運行時確定,數據生命週期不定。

  • 參考:線程棧和進程棧有什麼區別啊?線程堆棧大小的使用介紹棧空間和堆空間大小

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章