Linux 進程棧和線程棧的區別

注:本文所涉及的環境爲Linux, 下文討論的棧跟內核棧,沒有任何的關係,關於內核棧,請參考《深刻Linux內核架構》中的2.4.1 進程複製 安全

這裏有以下幾個問題,線程棧的空間是開闢在那裏的? 線程棧之間能夠互訪嗎?爲何在使用pthread_attr_setstack函數時,須要設置棧的大小,而進程task_struct的 mm_struct *mm 成員中卻並無卻並無stack_size這個成員項,怎麼保存的棧大小呢? 架構

進程棧: dom

        進程用戶空間的管理在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計算機上虛擬地址空間的佈局 spa

線程棧: 線程

        線程包含了表示進程內執行環境必需的信息,其中包括進程中標示線程的線程ID,一組寄存器值,棧,調度優先級和策略, 信號屏蔽字,errno變量以及線程私有數據。進程的全部信息對該進程的全部線程都是共享的,包括可執行的程序文本,程序的全局內存和堆內存,棧以及文件描述符,因此線程的mm_struct *mm指針變量和所屬進程的mm指針變量相同。 指針

       在建立線程的時候,能夠經過pthread_attr_t來初始化線程的屬性,包括線程的棧佈局信息,如棧起始地址stackaddr, 棧大小stacksize。 具體須要經過方法 code


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 中,棧的大小並非用來判斷棧是否越界,而是用來初始化避免棧溢出的緩衝區的大小(或者說安全間隙的大小)

相關文章
相關標籤/搜索