進程棧、線程棧、內核棧

進程描述符 task_structnode

線程建立的時候,加上了 CLONE_VM 標記,這樣 線程的內存描述符 將直接指向 父進程的內存描述符。程序員

內存描述符mm_struct數組

mm_struct 內存段

進程棧:stack架構

線程棧:使用mmap系統調用分配的空間,可是mmap分配的系統空間是什麼呢?也就是上圖中的mmap區域或者說共享的內存映射區域是什麼呢?它的方向是向上生長仍是向下生長的?app

mmap其實和堆同樣,實際上能夠說他們都是動態內存分配,可是嚴格來講mmap區域並不屬於堆區,反而和堆區會爭用虛擬地址空間。函數

這裏要提到一個很重要的概念,內存的延遲分配,只有在真正訪問一個地址的時候才創建這個地址的物理映射,這是Linux內存管理的基本思想。Linux內核在用戶申請內存的時候,只是給它分配了一個線性區(也就是虛擬內存),並無分配實際物理內存;只有當用戶使用這塊內存的時候,內核纔會分配具體的物理頁面給用戶,這時候才佔用寶貴的物理內存。內核釋放物理頁面是經過釋放先行區,找到其對應的物理頁面,將其所有釋放的過程。優化

struct mm_struct {
    struct vm_area_struct *mmap;           /* 內存區域鏈表 */
    struct rb_root mm_rb;                  /* VMA 造成的紅黑樹 */
    ...
    struct list_head mmlist;               /* 全部 mm_struct 造成的鏈表 */
    ...
    unsigned long total_vm;                /* 所有頁面數目 */
    unsigned long locked_vm;               /* 上鎖的頁面數據 */
    unsigned long pinned_vm;               /* Refcount permanently increased */
    unsigned long shared_vm;               /* 共享頁面數目 Shared pages (files) */
    unsigned long exec_vm;                 /* 可執行頁面數目 VM_EXEC & ~VM_WRITE */
    unsigned long stack_vm;                /* 棧區頁面數目 VM_GROWSUP/DOWN */
    unsigned long def_flags;
    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;        /* 命令行參數 和 環境變量的 起始地址和結束地址 */
    ...
    /* Architecture-specific MM context */
    mm_context_t context;                  /* 體系結構特殊數據 */

    /* Must use atomic bitops to access the bits */
    unsigned long flags;                   /* 狀態標誌位 */
    ...
    /* Coredumping and NUMA and HugePage 相關結構體 */
};

 

爲何須要區分這些棧,其實都是設計上的問題。這裏就我看到過的一些觀點進行彙總,供你們討論:atom

  1. 爲何須要單獨的進程內核棧?命令行

    • 全部進程運行的時候,均可能經過系統調用陷入內核態繼續執行。假設第一個進程 A 陷入內核態執行的時候,須要等待讀取網卡的數據,主動調用 schedule() 讓出 CPU;此時調度器喚醒了另外一個進程 B,碰巧進程 B 也須要系統調用進入內核態。那問題就來了,若是內核棧只有一個,那進程 B 進入內核態的時候產生的壓棧操做,必然會破壞掉進程 A 已有的內核棧數據;一但進程 A 的內核棧數據被破壞,極可能致使進程 A 的內核態沒法正確返回到對應的用戶態了;
  2. 爲何須要單獨的線程棧?線程

    • Linux 調度程序中並無區分線程和進程,當調度程序須要喚醒」進程」的時候,必然須要恢復進程的上下文環境,也就是進程棧;可是線程和父進程徹底共享一份地址空間,若是棧也用同一個那就會遇到如下問題。假如進程的棧指針初始值爲 0x7ffc80000000;父進程 A 先執行,調用了一些函數後棧指針 esp 爲 0x7ffc8000FF00,此時父進程主動休眠了;接着調度器喚醒子線程 A1: 
      • 此時 A1 的棧指針 esp 若是爲初始值 0x7ffc80000000,則線程 A1 一但出現函數調用,必然會破壞父進程 A 已入棧的數據。
      • 若是此時線程 A1 的棧指針和父進程最後更新的值一致,esp 爲 0x7ffc8000FF00,那線程 A1 進行一些函數調用後,棧指針 esp 增長到 0x7ffc8000FFFF,而後線程 A1 休眠;調度器再次換成父進程 A 執行,那這個時候父進程的棧指針是應該爲 0x7ffc8000FF00 仍是 0x7ffc8000FFFF 呢?不管棧指針被設置到哪一個值,都會有問題不是嗎?
  3. 進程和線程是否共享一個內核棧?

    • No,線程和進程建立的時候都調用 dup_task_struct 來建立 task 相關結構體,而內核棧也是在此函數中 alloc_thread_info_node 出來的。所以雖然線程和進程共享一個地址空間 mm_struct,可是並不共享一個內核棧。
  4. 爲何須要單獨中斷棧?

    • 這個問題其實不對,ARM 架構就沒有獨立的中斷棧。

 

進程空間中堆和棧的區別:

空間大小:棧系統指定大小限制在8M(M 級別),棧是連續空間;堆沒有限定,是不連續存儲空間,靠鏈表連接。

分配方式:堆都是程序員代碼中動態分配和回收的,沒有回收會產生內存泄露;棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,好比局部變量的分配。動態分配由alloca函數進行分配,可是棧的動態分配和堆是不一樣的,他的動態分配是由編譯器進行釋放,無需咱們手工實現。

分配效率:棧分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行;堆是經過調用庫函數

棧:在函數調用時,第一個進棧的是主函數中後的下一條指令(函數調用語句的下一條可執行語句)的地址,而後是函數的各個參數,在大多數的 C 編譯器中,參數是由右往左入棧的,而後是函數中的局部變量。注意靜態變量是不入棧的。
當本次函數調用結束後,局部變量先出棧,而後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。
堆:通常是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。

總之,棧比堆效率高,但沒有堆靈活,優先使用棧,大內存使用堆。

char a[] = "hello"; //字符數組a的容量是6個字符,其內容爲hello。a的內容能夠改變,如a[0]= ‘X’

char *p = "world";//指針p指向常量字符串「world」(位於靜態存儲區,內容爲world),常量字符串的內容是不能夠被修改的。

/**
*內核空間
*棧:grow down,大小系統設置~8M,連續空間,編譯器自動分配
*Memory Mapping Seg:堆棧共享,線程棧
*堆:grow up,大小硬件定,不連續空間,程序員malloc
*BSS:Block Started by Symbol,未初始化的全局變量和靜態變量(靜態data區)
*數據段:存放已初始化的全局變量、靜態變量(全局和局部)、const常量數據(常量data區)
*代碼段:存放CPU執行的機器指令,代碼區是可共享,而且是隻讀的。這部分區域的大小在程序運行前就已經肯定
**/

#include <string>
int a=0;    //數據段:全局初始化變量
char *p1;   //BSS:全局未初始化變量
void main()
{
    int b;//棧
    char s[] = "abc";   //棧
    char *p2;         //棧
    char *p3="123456";   //123456\0在常量區(代碼段??),p3在棧上。
    static int c=0;   //數據段:全局(靜態)初始化區
    *p1 = (char*)malloc(10);  //分配得來的10字節區域在堆上
    *p2 = (char*)malloc(20);  //分配得來的20字節區域在堆上。
    strcpy(p1,"123456");   //123456\0放在常量區,編譯器可能會將它與p3所向"123456\0"優化成一個地方。
}

//const 常量 或右值常量如"123456"放在數據段仍是代碼段??

malloc與free是C++/C語言的標準庫函數,new/delete是C++的運算符。它們均可用於申請動態內存和釋放內存。可是new/delete會調用對象的構造和析構函數。

相關文章
相關標籤/搜索