講講用戶空間和內核空間
本文轉載自微信公眾號「安琪拉的博客」,作者安琪拉 。轉載本文請聯系安琪拉的博客公眾號。
最近在重新梳理Java 并發的知識,這篇文章是為了后面講ReentrantLock做準備的,先熱個身,隨著研究的深入,就來到了這里,要把一件事情講清楚,可以無限下鉆,就像物體->分子->原子->夸克,直到超出自己能理解的知識范疇。
前言
我們知道操作系統采用的是虛擬地址空間,以32位操作系統舉例,它的尋址空間為4G(2的32次方),這里解釋二個概念:
- 尋址: 是指操作系統能找到的地址范圍,32位指的是地址總線的位數,你就想象32位的二進制數,每一位可以是0,可以是1,是不是有2的32次方種可能,2^32次方就是可以訪問到的最大內存空間,也就是4G。
- 虛擬地址空間:為什么叫虛擬,因為我們內存一共就4G,但操作系統為每一個進程都分配了4G的內存空間,這個內存空間實際是虛擬的,虛擬內存到真實內存有個映射關系。例如X86 cpu采用的段頁式地址映射模型。
操作系統將這4G可訪問的內存空間分為二部分,一部分是內核空間,一部分是用戶空間。
內核空間是操作系統內核訪問的區域,獨立于普通的應用程序,是受保護的內存空間。
用戶空間是普通應用程序可訪問的內存區域。
以linux操作系統為例,將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱為內核空間,而將較低的3G字節(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱為用戶空間。空間分配如下圖所示:
每個進程可以通過系統調用進入內核,因此,Linux內核由系統內的所有進程共享。于是,從具體進程的角度來看,每個進程可以擁有4G字節的虛擬空間。
區分內核空間和用戶空間原因
其實早期操作系統是不區分內核空間和用戶空間的,但是應用程序能訪問任意內存空間,如果程序不穩定常常把系統搞崩潰,比如清除操作系統的內存數據。后來覺得讓應用程序隨便訪問內存太危險了,就按照CPU 指令的重要程度對指令進行了分級,指令分為四個級別:Ring0~Ring3 (和電影分級有點像),linux 只使用了 Ring0 和 Ring3 兩個運行級別,進程運行在 Ring3 級別時運行在用戶態,指令只訪問用戶空間,而運行在 Ring0 級別時被稱為運行在內核態,可以訪問任意內存空間。
用戶態的程序不能隨意操作內核地址空間,這樣對操作系統具有一定的安全保護作用。
說說內核態和用戶態
其實很清晰:當進程/線程運行在內核空間時就處于內核態,而進程/線程運行在用戶空間時則處于用戶態。
在內核態下,進程運行在內核地址空間中,此時 CPU 可以執行任何指令。運行的代碼也不受任何的限制,可以自由地訪問任何有效地址,也可以直接進行端口的訪問。
在用戶態下,進程運行在用戶地址空間中,被執行的代碼要受到 CPU 的很多檢查,比如:進程只能訪問映射其地址空間的頁表項中規定的在用戶態下可訪問頁面的虛擬地址。
我們來看下linux系統的整體結構:下面是內核空間(寫成了內存空間)
用戶態到內核態的切換
如上圖所示,所有系統資源的管理都是在內存空間進行的,也就是在內核態去做的,那我們應用程序需要訪問磁盤,讀取網卡的數據,新建一個線程都需要通過系統調用接口,完成從用戶態到內存態的切換。
比如我們 Java 中需要新建一個線程,new Thread( Runnable ...) 之后調用start() 方法時, 看Hotspot Linux 的JVM 源碼實現,最終是調pthread_create 系統方法來創建的線程,這里會從用戶態切換到內核態完成系統資源的分配,線程的創建。
當一個任務(進程)執行系統調用而陷入內核代碼中執行時,稱進程處于內核運行態(內核態)
除了系統調用可以實現用戶態到內核態的切換,還有別的方式嗎?有,軟中斷和硬中斷。
軟中斷是指進程發生了異常事件;硬中斷就有很多種,例如時鐘周期、IO等。涉及的操作系統知識比較多,這里不展開講。