標籤:

linux為什麼需要內核棧,系統調用時直接使用用戶棧不行嗎?

linux 系統


因為內核沒有權力在任何沒有得到用戶程序允許的情況下修改用戶程序的內存。

棧這種東西,不過就是個約定,你的堆棧指針指向哪裡,哪裡就是堆棧。內核確實知道它所認為的堆棧的位置(代碼位置是fs/exec.c:setup_arg_pages),但不表示內核強制了用戶態程序使用堆棧的方式(比如某個庫故意修改堆棧指針(令為sp)指向一個私有的位置,內核並不能以此為依據認為那個地方就是堆棧。當然更不能修改那個位置的數據。

反過來說,內核是機密之所,比如你把用戶密碼作為參數調一個函數,這個密碼就到堆棧中了,用用戶態的堆棧,回到用戶態的時候要不要清一次呢?——這麼辛苦,那還不如一開始就不用呢。


簡單來說就是cpu不讓

內核不能用RPL&>0的內存作為棧,而用戶代碼不能訪問DPL=0的內存。


題主所說的「內核棧」,嚴謹的說法應該是「進程的內核態堆棧」,因為每個進程都有自己的內核態堆棧。

之前的理解有誤, 特別是原回答中的第四條, 提到內核訪問用戶 空間需要映射到內核的線性空間是錯誤的,內核不需要將用戶空間的頁面映射到內核線性地址 就可以直接訪問用戶空間。

為何使用用戶 內核態堆棧:

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.做個假設,如果執行內核線程,強行不執行各種許可權的檢查,強行使用用戶態堆棧,內核所使用的線性地址和用戶使用的線性地址方法有差異,內核的線性地址是固定的(內存也就一個工作內核),而所有用戶進程可使用的線性地址都一樣(但被映射到不同的物理地址),當內核訪問用戶空間時還需要做頁面映射,將用戶的堆棧相關的物理頁再映射到內核頁表?而每個用戶的系統調用都做一次頁面映射,這個性能開銷應該不小吧,應該不如直接保存用戶寄存器數據,系統調用完了再恢復來的快。


這種純純的技術問題,如何才能獲邀?

避免高特權(比如 內核態)棧內存不被低特權(比如 用戶態)任意修改。

——以上回答完畢——

為了內核數據安全,用戶修改內核數據需要從用戶態切換為內核態,由系統調用完成轉換。

補充:

用戶內存空間,每個用戶進程都有各自獨立的內存空間,保持彼此獨立透明,互不干擾。

內核內存空間,內核線程間無需切換頁表,共享內存空間。

反對某答案里講的 線性地址和虛擬地址,線性地址和虛擬地址(線性上連續地址)其實指代同一件事。

可能要問,那為什麼相同的虛擬地址在不同的進程空間會指向不同的物理地址?那是因為各個進程的映射表並不一樣。

帶 MMU 的CPU處理的數據地址,全都是虛擬地址;

經過 MMU 處理後的地址,到達北橋,為PA地址(linux稱為PA,或者物理地址,但其實還不是真正的物理地址);

經過北橋,到其他IP核或外設,一般還會做地址映射,這才是真正訪問內存硬體或其他外設的地址,稱為dma地址(真正的物理地址);

其他所謂的 「xx地址」 基本都與以上定義重疊。

補充一下:

看到一些答案從硬體的體系結構角度去分析這個問題,這是對問題背景很好的補充。

不過分析的結論肯定是「用戶態不能直接使用內核棧(或內存)」,而不是「為什麼用戶態不能直接使用內核棧(或內存)」。因為無論arm,mips,x86等等體系結構對內核態和用戶態實現flow各不相同,不一而足,但一點無一例外,為了實現高特權(比如 內核態)的內存不被低特權(比如 用戶態)任意修改而服務

btw,了解軟硬體實現目的,瀏覽內核代碼的過程,就無非只是一個又一個不斷驗證我們思路的點。

-------------------2016/12/29 補充-------------------

最近 browse arm architecture 參考手冊(arm v7-A),看到MMU映射的 first-level table entry有關於 supersection,section,large page,small page 4種映射類型:

section 指向 1MB memory block的base address;

supersection 指向 16MB memory block的base address;

large page 指向 64KB page base address;

small page 指向 4KB page base address;

其中large/small page 在short description format下有 2nd-level table,long description format下有3rd-level table。

所以,無論是分段映射方式和分頁映射方式,段地址 or 頁地址還是屬於 mmu 處理後的地址,依舊屬於 虛擬地址 的範疇。(現代os多採用分頁映射)

最後,mmu translation table entry 針對不同arch有不同的格式,對於os kernel development or porting 更只是block box,實在有興趣的可以參考一下我截的幾張圖(可能對於研究arch的人會更有興趣):

short description format 下 4種entry的映射類型:

short description format 下 1st-level table entry 具體格式:

short description format 下 2nd-level table entry 具體格式:

page mapping with long description format

--------------------------------------------------------------


如果內核使用用戶棧,執行到一半的時候上下文切換,另外一個線程破壞了內核使用的用戶棧,整個內核就崩潰了。


不行,內核定址用的是線性地址,線程用的是虛擬地址。


用戶態的棧是可以隨意修改的,如果共用一個棧,系統會極其不安全,假設程序有內存越界,那麼有可能會修改到內核的數據導致內核奔潰。實際上內核地址空間和用戶地址空間是分開的,內核顯然不能和應用程序共用棧,否則操作系統會crash的連win98都不如。


不安全,相當於用戶線程能訪問系統調用的棧數據,隨便改個數據都能使系統崩潰啊。


給你舉個例子:vxworks5.5操作系統是沒有用戶態的,用戶沒有自己的地址空間。所以應用訪問異常的地址空間,很可能導致執行非法指令或定址錯誤;甚至可能會導致CPU異常而reboot。而vxworks6.8則區分用戶態和內核態。


個人理解:

根據內核函數、中斷處理函數的複雜程度和中斷數量(在linux中,每種類型interrupt只能nest一次)可以大概估計出kernel棧需要多大,如果按照題主的思路,在每個user棧的底部預留這個固定空間大小,則我可以把這個空間的頂部地址定義為kernel棧的起始地址。這樣看似一個棧的實現方式也自然分成了兩個棧,最終殊途同歸。

當然如果不預留固定大小的空間則有可能由於user棧使用過多導致運行kernel函數時boom掉.....


其實可以考慮用戶程序和內核共用一個棧空間,但是現代操作系統為了健壯性和安全性,採用了分開的棧結構。


推薦閱讀:

裝載著操作系統的磁碟是默認裝到內存0x8000位置,那ORG 0x7c00又是在做一個什麼事情呢?
linux內核啟動關於先有雞再有蛋的問題?
請問如何從下載的linux內核文件繼續編寫操作系統?
能敘述下面向對象思想在Linux內核編程中的應用嗎?
Linux操作系統關於C library的理解?

TAG:Linux | Linux內核 |