linux用戶棧和內核棧解析

進程是程序的一次執行過程。用劇本和演出來類比,程序相當於劇本,而進程則相當於劇本的一次演出,舞臺、

燈光則相當於進程的運行環境。

進程的堆棧

每個進程都有自己的堆棧,內核在創建一個新的進程時,在創建進程控制塊task_struct的同時,也爲進程創建

自己堆棧。一個進程 有2個堆棧,用戶堆棧和系統堆棧用戶堆棧的空間指向用戶地址空間,內核堆棧的空間

指向內核地址空間。當進程在用戶態運行時,CPU堆棧指針寄存器指向的 用戶堆棧地址,使用用戶堆棧,當進

程運行在內核態時,CPU堆棧指針寄存器指向的是內核棧空間地址,使用的是內核棧;

進程用戶棧和內核棧之間的切換

進程由於中斷或系統調用從用戶態轉換到內核態時,進程所使用的棧也要從用戶棧切換到內核棧。系統調用

實質就是通過指令產生中斷,稱爲軟中斷進程因爲中斷(軟中斷或硬件產生中斷),使得CPU切換到特權工

作模式,此時進程陷入內核態,進程進入內核態後,首先把用戶態的堆棧地址保存在內核堆棧中,然後設置堆

棧指針寄存器的地址爲內核棧地址,這樣就完成了用戶棧向內核棧的切換。

當進程從內核態切換到用戶態時,最後把保存在內核棧中的用戶棧地址恢復到CPU棧指針寄存器即可,這樣就

完成了內核棧向用戶棧的切換。

這裏要理解一下內核堆棧。前面我們講到,進程從用戶態進入內核態時,需要在內核棧中保存用戶棧的地址。

那麼進入內核態時,從哪裏獲得內核棧的棧指針呢?

要解決這個問題,先要理解從用戶態剛切換到內核態以後,進程的內核棧總是空的這點很好理解,當進程在

用戶空間運行時,使用的是用戶 棧;當進程在內核態運行時,內核棧中保存進程在內核態運行的相關信息,但

是當進程完成了內核態的運行,重新回到用戶態時,此時內核棧中保存的信息全部恢 復,也就是說,進程在內

核態中的代碼執行完成回到用戶態時,內核棧是空的

理解了從用戶態剛切換到內核態以後,進程的內核棧總是空的,那剛纔這個問題就很好理解了,因爲內核棧是

空的,那當進程從用戶態切換到內核態後,把內核棧的棧頂地址設置給CPU的棧指針寄存器就可以了。

X86 Linux內核棧定義如下(可能現在的版本有所改變,但不妨礙我們對內核棧的理解):

在/include/linux/sched.h中定義瞭如下一個聯合結構:

union task_union {

       struct task_struct task;

       unsigned long stack[2408];

};      有這個聯合結構可知,每個進程有自己的內核棧

從這個結構可以看出,內核棧佔8kb的內存區。實際上,進程的task_struct結構所佔的內存是由內核動態分配

的,更確切地說,內核根本不給task_struct分配內存,而僅僅給內核棧分配8K的內存,並把其中的一部分給task_struct使用。

這樣內核棧的起始地址就是union task_union變量的地址+8K 字節的長度。例如:我們動態分配一個union task_union類型的變量如下:

unsigned char *gtaskkernelstack

gtaskkernelstack = kmalloc(sizeof(union task_union));

 

那麼該進程每次進入內核態時,內核棧的起始地址均爲:(unsigned char *)gtaskkernelstack + 8096

 

知乎某大神回答:

 

作者:唐浩然
鏈接:https://www.zhihu.com/question/43699081/answer/124798606
來源:知乎
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
 

爲何使用用戶 內核態堆棧:
1,多是安全原因 ,進入系統調用需要 保存用戶 態運行時的 寄存器信息,(包括控制寄存器信息 EFLAGS, 以及 cs/ip,)這些 保存在低權限 的用戶態堆棧,實在不安全。
2: 任務切換必須發生在內核態,在任務切換時,也需要使用用戶的內核態堆棧,來保存 用戶 態時的 寄存器信息(包換用戶態堆棧指針)。 比如,最後將 新任務的 內核態堆棧中 的 保存的上次切換時保存 的用戶 態寄存器 彈出,然後恢復新任務用戶 態執行。
3: 還有 注意到一個特點, 每當用戶 程序 從用戶 態進入內核態時, 用戶程序的內核態 堆棧總是空的, 什麼內容也沒有,或許 這可能也是從安全層面的考慮?



以下是原答案
權限檢查的原因
拿x86,2.6內核舉例:
1.內核的分頁管理機制中,對頁表及頁全局目錄的描述中,分系統頁表/頁全局目錄用戶頁表/全局目錄(即u/s位),爲0時只有內核可以訪問(cpu特權級爲0,或者說cs寄存器的權限標誌位爲0,這個權限也適用於對“段”的訪問,因爲被訪問的“段”也有權限級別,intel設計了四個段級別,由兩個bit來表示,但linux只使用了00和11,即0和3特權級,表示內核態和用戶態)
2.系統調用實際上是調用內核提供給用戶的函數接口(其產生就是軟中斷的產生,此時內核會保存用戶態時的寄存器內容到內核堆棧,調用執行完在將保存的寄存器回覆出去給用戶),而函數在內核態執行,即這些函數所在內存的頁面以及運行過程使用的堆棧地址佔用的頁面都落在在 頁表 的u/s位標誌爲0的頁上,內核的棧指針可以訪問到這些頁,用戶態的進程對這些頁面沒有權限讀寫(頁沒有標誌‘’執行‘權限’),用戶態的堆棧指針指不到這些地址。
3. 再者,執行內核函數(系統調用)進入內核態,cs寄存器的cpl發生了切換,相應的ds也必須含有內核數據段的段選擇符,ss段也同樣要有內核數據段的段選擇符。
4. 4.做個假設,如果執行內核線程,強行不執行各種權限的檢查,強行使用用戶態堆棧,內核所使用的線性地址和用戶使用的線性地址方法有差異,內核的線性地址是固定的(內存也就一個工作內核),而所有用戶進程可使用的線性地址都一樣(但被映射到不同的物理地址),當內核訪問用戶空間時還需要做頁面映射,將用戶的堆棧相關的物理頁再映射到內核頁表?而每個用戶的系統調用都做一次頁面映射,這個性能開銷應該不小吧,應該不如直接保存用戶寄存器數據,系統調用完了再恢復來的快。

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