1. 程式人生 > >Linux 程序棧和執行緒棧的區別

Linux 程序棧和執行緒棧的區別

注:本文所涉及的環境為Linux, 下文討論的棧跟核心棧,沒有任何的關係,關於核心棧,請參考《深入Linux核心架構》中的2.4.1 程序複製

這裡有如下幾個問題,執行緒棧的空間是開闢在那裡的? 執行緒棧之間可以互訪嗎?為什麼在使用pthread_attr_setstack函式時,需要設定棧的大小,而程序task_struct的 mm_struct *mm 成員中卻並沒有卻並沒有stack_size這個成員項,怎麼儲存的棧大小呢?

程序棧:

程序使用者空間的管理在task_struct 的mm_struct *mm成員中體現, mm中的成員定義了使用者空間的佈局情況如圖一。 使用者空間的棧起始於STACK_TOP, 如果設定了PF_RANDOMIZE,則起始點會減少一個小的隨機量,每個體系結構都必須定義STACK_TOP, 大多數都設定為TASK_SIZE, 在32位機上該值為0XC0000000。經過隨機處理後,程序棧的起始地址將存放在mm->start_stack中,可以通過cat /proc/xxx/stat 檢視。

      如圖一,棧從上而下擴充套件,而用於記憶體對映的區域起始於mm->mmap_base, mm->mmap_base通過呼叫mmap_base函式來初始化,為了確保棧不與mmap區域不發生衝突,兩者之間設定了一個安全間隙。mmap_base函式原始碼如下:

#define MIN_GAP (128*1024*1024) 
#define MAX_GAP (TASK_SIZE/6*5)
static inline unsigned long mmap_base(struct mm_struct *mm)
{
  unsigned long gap = current->signal->rlim[RLIMIT_STACK].rlim_cur; // rlim_cur 預設為8388608,及8M, 可以使用 getrlimit(RLIMIT_STACK, &limit) 檢視
  unsigned long random_factor = 0;
  if (current->flags & PF_RANDOMIZE)
    random_factor = get_random_int() % (1024*1024);
  if (gap < MIN_GAP) // 通過MIN_GAP來保證,程序棧的大小至少為128MB
    gap = MIN_GAP;
  else if (gap > MAX_GAP) // 棧的最大空間為TASK_SIZE/6*5, 及2.5G
    gap = MAX_GAP;
  return PAGE_ALIGN(TASK_SIZE - gap - random_factor); // 通過保留random_factor空間大小的間隙來防止棧溢位
}

圖 一 IA-32計算機上虛擬地址空間的佈局

執行緒棧:

執行緒包含了表示程序內執行環境必需的資訊,其中包括程序中標示執行緒的執行緒ID,一組暫存器值,棧,排程優先順序和策略, 訊號遮蔽字,errno變數以及執行緒私有資料。程序的所有資訊對該程序的所有執行緒都是共享的,包括可執行的程式文字,程式的全域性記憶體和堆記憶體,棧以及檔案描述符,所以執行緒的mm_struct *mm指標變數和所屬程序的mm指標變數相同。

       在建立執行緒的時候,可以通過pthread_attr_t來初始化執行緒的屬性,包括執行緒的棧佈局資訊,如棧起始地址stackaddr, 棧大小stacksize。 具體需要通過方法

int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
// 注:stackaddr 指向為該執行緒開闢的空間,該空間可以使用malloc或者mmap來開闢,而不能來自程序的棧區。開闢的stackaddr所指向的動態空間需要自己負責釋放。

當然也可將執行緒棧的空間管理交給系統,如果想改變系統預設的棧大小8MB,可以通過

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
// 注:stacksize最小值為16384,單位為位元組

由上面的API介面,可以得到,執行緒棧的stacksize是儲存在pthread_attr_t中的,可以通過人為的指定,也可以通過在建立執行緒的時候讀取系統的配置檔案來初始化stacksize,當初始化完棧的起始地址,和大小後,便可以通過

int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);

來初始化執行緒棧末尾之後用以避免棧溢位的緩衝區的大小,如果應用程式溢位到此緩衝區中,這個錯誤可能會導致 SIGSEGV 訊號被髮送給該執行緒, 從而造成段錯誤,緩衝區預設設定為PAGESIZE個位元組因為執行緒的mm->start_stack和所屬程序相同,所以執行緒棧的起始地址並沒有存放在task_struct中,應該只是使用attr中的stackaddr,來初始化task_struct->thread-> sp(sp指向struct pt_regs物件,該結構體用於儲存使用者程序或者執行緒的暫存器現場)。

總結:執行緒棧的空間開闢在所屬程序的堆區,執行緒與其所屬的程序共享程序的使用者空間,所以執行緒棧之間可以互訪。執行緒棧的起始地址和大小存放在pthread_attr_t 中,棧的大小並不是用來判斷棧是否越界,而是用來初始化避免棧溢位的緩衝區的大小(或者說安全間隙的大小)

ps: 文中如有錯誤的地方,請各位隨時提出來,我將第一時間更改,謝謝。