淺談用戶態和內核態以及用戶空間和內核空間

要了解什麼是用戶態,什麼是內核態,我們需要先了解什麼是進程的用戶空間和內核空間:

Linux虛擬內存的大小爲2^32(在32位的x86機器上),內核將這4G字節的空間分爲兩部分。最高的1G字節(從虛地址0xC0000000到0xFFFFFFFF)供內核使用,稱爲“內核空間”。而較低的3G字節(從虛地址0x00000000到0xBFFFFFFF),供各個進程使用,稱爲“用戶空間”。也就是說,在這4G的內存中,0-3G是給用戶留下的用戶空間,這段空間是各個進程獨立,無法互相訪問的,3-4G是進程的內核空間,每個進程可以通過系統調用進入內核,因此,Linux內核空間由系統內的所有進程共享。於是,從具體進程的角度來看,每個進程可以擁有4G字節的虛擬地址空間(也叫虛擬內存)。

爲什麼要叫他虛擬內存呢?這要對比物理地址來看,在這個虛擬內存中分配的地址並不是其真實的物理地址,一個可執行文件在被編譯後,會被分配成很多頁,在這個在文件執行的過程中,它往內存中裝載的單位就是頁。當一個文件被執行時,操作系統會先爲該程序創建一個 4GB 的進程虛擬地址空間。前面介紹過,虛擬地址空間只是一箇中間層而已,它的功能是利用一種映射機制將虛擬地址空間映射到物理地址空間,所以,創建4GB虛擬地址空間其實並不是要真的創建空間,只是要創建那種映射機制所需要的數據結構而已,這種數據結構就是頁目和頁表。

當創建完虛擬地址空間所需要的數據結構後,進程開始讀取文件的第一頁。在文件的第一頁包含了文件頭和段表等信息,進程根據文件頭和段表等信息,將文件中所有的段一一映射到虛擬地址空間中相應的頁 (文件中的段的長度都是頁長的整數倍 ) 。這時文件的真正指令和數據還沒有被裝入內存中,操作系統只是根據文件的頭部等信息建立了文件和進程虛擬地址空間中頁的映射關係而已。當CPU 要訪問程序中用到的某個虛擬地址時,當CPU發現該地址並沒有相相關聯的物理地址時,CPU認爲該虛擬地址所在的頁面是個空頁面,CPU會認爲這是個頁錯誤 (Page Fault) ,CPU也就知道了操作系統還未給該頁面分配內存,CPU會將控制權交還給操作系統。操作系統於是爲該頁面在物理空間中分配一個頁面,然後再將這個物理頁面與虛擬空間中的虛擬頁面映射起來,然後將控制權再還給進程,進程從剛纔發生頁錯誤的位置重新開始執行。由於此時已爲文件的那個頁面分配了內存,所以就不會發生頁錯誤了。隨着程序的執行,頁錯誤會不斷地產生,操作系統也會爲進程分配相應的物理頁面來滿足進程執行的需求。

由此,我們可以看到虛擬內存的好處:

首先,它避免了多個進程訪問到同一用戶地址空間,避免有些程序惡意修改其它程序。通過虛擬內存的管理也可以控制物理內存的訪問權限。

然後,通過這種映射機制,給分配和釋放內存帶來了方便,通過頁面調度避免了大量數據的裝入裝出,提高了內存效率。

現在我們就可以再來說下用戶態和內核態的概念了,用戶態和內核態粗略的說就是進程工作在內核空間下就叫用戶態,進程工作在內核空間下就叫內核態。具體的說一下就是當一個任務(進程)執行系統調用而陷入內核代碼中執行時,我們就稱進程處於內核狀態。此時處理器處於特權級最高的(0級)內核代碼。當進程處於內核態時,執行的內核代碼會使用當前的內核棧。每個進程都有自己的內核棧。當進程在執行用戶自己的代碼時,則稱其處於用戶態。即此時處理器在特權級最低的用戶代碼中運行。當正在執行用戶程序而突然中斷時,此時用戶程序也可以象徵性地處於進程的內核態。因爲中斷處理程序將使用當前進程的內核態。

然後我們細細的說一下用戶態和內核態的區別和聯繫,說道這裏,就不得不提一下CPU的三種運行級別了,工作在內核態下的進程擁有最高級別Ring0,工作在用戶態下的進程擁有最低級別Ring3,在Ring3狀態下是不能訪問Ring0狀態下的地址和數據的。也就是說,進程在用戶態下是沒法訪問到內核空間中的數據的,那麼我們就看出這樣做的好處了,通過內核態和用戶態就產生了一個保護機制,用戶無法隨意的進入所有進程共享的內核空間。

爲了讓用戶安全的訪問內核空間,操作系統提供了以下幾種方式:

1.系統調用進入內核態:如調用write(),read(),send()等IO函數等操作,進程就會進入內核態使用內核代碼去完成操作。

2.異常:當CPU在執行運行在用戶態的程序時,發現了某些事件不可知的異常,這是會觸發由當前運行進程切換到處理此異常的內核相關程序中,也就到了內核態,比如缺頁異常。

3.外圍設備的中斷:當外圍設備完成用戶請求的操作之後,會向CPU發出相應的中斷信號,這時CPU會暫停執行下一條將要執行的指令轉而去執行中斷信號的處理程序,如果先執行的指令是用戶態下的程序,那麼這個轉換的過程自然也就發生了有用戶態到內核態的切換。比如硬盤讀寫操作完成,系統會切換到硬盤讀寫的中斷處理程序中執行後續操作等。

那麼進程是如何完成用戶態到內核態的切換的呢?具體的步驟大致如下:

(1)從當前進程的描述符中提取其內核棧的ss0及esp0信息。
(2)使用ss0和esp0指向的內核棧將當前進程的cs,eip,eflags,ss,esp信息保存起來,這個過程也完成了由用戶棧找到內核棧的切換過程,同時保存了被暫停執行的程序的下一條指令。
(3)將先前由中斷向量檢索得到的中斷處理程序的cs,eip信息裝入相應的寄存器,開始執行中斷處理程序,這時就轉到了內核態的程序執行了。

發佈了52 篇原創文章 · 獲贊 46 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章