操作系統(tǒng) 虛擬地址空間、用戶空間、內(nèi)核空間、用戶態(tài)與內(nèi)核態(tài)
內(nèi)存分頁
為了節(jié)約內(nèi)存,提高使用效率,操作系統(tǒng)會將內(nèi)存拆成一個個的小塊來使用,在 Linux 中,這每一小塊叫做 page(頁) ,大小為4k
什么是虛擬地址空間
在多任務(wù)操作系統(tǒng)中,每個進(jìn)程都運行在屬于自己的虛擬內(nèi)存中,這塊空間被稱為 Virtual Address Space(虛擬地址空間)
為什么要有虛擬地址空間
為了讓進(jìn)程之間相互隔離。
假如讓進(jìn)程直接操作物理內(nèi)存,很有可能會出現(xiàn),不同進(jìn)程都操作了同一物理內(nèi)存地址,造成相互影響。于是就抽象出來 虛擬地址空間 這樣一個中間層,讓一塊 虛擬地址空間 映射到一塊 物理地址空間 上

如上圖,兩塊虛擬內(nèi)存通過page table(頁表) 將自己映射到物理內(nèi)存上,進(jìn)程只能看到虛擬內(nèi)存(當(dāng)然它自己是不知道內(nèi)存是虛擬的),進(jìn)程只能"運行"在虛擬地址空間,只會操作屬于自己的虛擬內(nèi)存,因此進(jìn)程之間不會相互影響
虛擬地址空間的大小及分配
操作系統(tǒng)需要為每一個進(jìn)程分配屬于自己的虛擬內(nèi)存,那這個虛擬內(nèi)存要分配多大呢?
在沒有虛擬地址空間之前,是根據(jù)進(jìn)程的需要按需分配物理內(nèi)存的。但有了虛擬地址空間,分配策略可以變一下,先把虛擬地址空間分配的大些,但不立馬建立與物理內(nèi)存的映射,而是用到的時候,用多少,建立多少

這樣物理內(nèi)存的大小雖然不變,但是內(nèi)存分配的靈活性大大的提高了,進(jìn)程也不用擔(dān)心地址會跟別的進(jìn)程沖突,盡管用就是
在 32 位操作系統(tǒng)中,操作系統(tǒng)會為每個進(jìn)程分配最大為 4G(2 的 32 次方)的虛擬地址空間
用戶空間與內(nèi)核空間
操作系統(tǒng)雖然為每個進(jìn)程都分配了虛擬地址空間,但 虛擬地址空間中并不是所有的區(qū)域都可以為進(jìn)程所用
操作系統(tǒng)將虛擬地址空間 分為用戶空間和內(nèi)核空間,對于 32 位的操作系統(tǒng),在 Linux 的虛擬地址空間中,用戶空間和內(nèi)核空間的大小比例為 3:1,而在 window 中則為 2:2

為什么會有內(nèi)核空間
為了系統(tǒng)的安全,現(xiàn)代的操作系統(tǒng)一般都強制用戶進(jìn)程不能直接操作內(nèi)核的,所有的系統(tǒng)調(diào)用都要交給內(nèi)核完成(應(yīng)用程序所有的操作,最終都是由內(nèi)核代為完成的)。
但是內(nèi)核也要運行在內(nèi)存中,為了防止用戶進(jìn)程干擾,操作系統(tǒng)為內(nèi)核單獨劃分了一塊內(nèi)存區(qū)域,這塊區(qū)域就是內(nèi)核空間,系統(tǒng)內(nèi)核運行在內(nèi)核空間中,
內(nèi)核空間與用戶空間的映射
在 Linux 中,系統(tǒng)啟動時,就需要將內(nèi)核加載到物理內(nèi)存的內(nèi)核空間上運行。
但對于進(jìn)程,物理內(nèi)存對它是不可見的,但它又需要使用內(nèi)核來完成各種系統(tǒng)調(diào)用,而內(nèi)核實際又在物理內(nèi)存上。怎么解決這個矛盾?
Linux 想了一個辦法,將進(jìn)程的虛擬地址空間中的內(nèi)核空間映射到物理內(nèi)存中的內(nèi)核空間上,內(nèi)核就“搬到”虛擬內(nèi)存中了。而在進(jìn)程看了,自己的內(nèi)存中就有了內(nèi)核了,就可以通過內(nèi)核進(jìn)行各種系統(tǒng)調(diào)用了
在 Linux 中,內(nèi)核空間是持續(xù)的,并且所有進(jìn)程的虛擬地址空間中的內(nèi)核空間都映射到同樣的物理內(nèi)存的內(nèi)核空間

如上圖 進(jìn)程a 和 進(jìn)程b 的內(nèi)核空間都映射到了同一塊物理內(nèi)存區(qū)域,而用戶空間的地址,則被映射到了不同的物理內(nèi)存區(qū)域
用戶態(tài)與內(nèi)核態(tài)
當(dāng)一個進(jìn)程執(zhí)行系統(tǒng)調(diào)用而陷入內(nèi)核代碼中執(zhí)行時,就稱進(jìn)程處于內(nèi)核運行態(tài)(或簡稱為內(nèi)核態(tài))。
比如有一個寫文件的的 python 程序
with open('/test.txt','a+') as fw: # 打開文件
fw.write('內(nèi)容')
復(fù)制代碼運行起來之后就是一個進(jìn)程了,也有了自己的虛擬地址空間,包括用戶空間和內(nèi)核空間。剛開始這個進(jìn)程是運行在用戶空間,但當(dāng)執(zhí)行到 fw.write('內(nèi)容') 時,發(fā)現(xiàn)要往磁盤中寫入一個文件,而讀寫磁盤這種事,只有內(nèi)核才能操作,內(nèi)核提供了一個 write() 系統(tǒng)級函數(shù),fw.write('內(nèi)容') 這段代碼最終是執(zhí)行 write() 系統(tǒng)調(diào)用來實現(xiàn)文件寫入的

剛開始的 python 代碼是加載到用戶空間的內(nèi)存中運行的,當(dāng)執(zhí)行 write() 系統(tǒng)調(diào)用是,write()是在內(nèi)核空間運行的,但他們都屬于同一進(jìn)程,只是在執(zhí)行系統(tǒng)調(diào)用的時候發(fā)生一個狀態(tài)的切換。
程序運行在用戶空間的時候,進(jìn)程處于用戶態(tài),程序進(jìn)入到內(nèi)核運行后,進(jìn)程處于內(nèi)核態(tài),這兩種狀態(tài)的切換就被稱為上行文切換
上行文切換是很消耗資源的,所以要盡量避免上下文切換
用戶態(tài)切換到內(nèi)核態(tài)的 3 種方式
除了上面提到的系統(tǒng)調(diào)用,還有兩張方式會導(dǎo)致用戶態(tài)切換到內(nèi)核態(tài)
系統(tǒng)調(diào)用 : 這是用戶態(tài)進(jìn)程主動要求切換到內(nèi)核態(tài)的一種方式,用戶態(tài)進(jìn)程通過系統(tǒng)調(diào)用申請使用操作系統(tǒng)提供的服務(wù)程序完成工作,而系統(tǒng)調(diào)用的機制其核心還是使用了操作系統(tǒng)為用戶特別開放的一個中斷來實現(xiàn),例如 Linux 的 int 80h 中斷。
異常 : 當(dāng) CPU 在執(zhí)行運行在用戶態(tài)下的程序時,發(fā)生了某些事先不可知的異常,這時會觸發(fā)由當(dāng)前運行進(jìn)程切換到處理此異常的內(nèi)核相關(guān)程序中,也就轉(zhuǎn)到了內(nèi)核態(tài),比如缺頁異常。
外圍設(shè)備的中斷 : 當(dāng)外圍設(shè)備完成用戶請求的操作后,會向 CPU 發(fā)出相應(yīng)的中斷信號,這時 CPU 會暫停執(zhí)行下一條即將要執(zhí)行的指令轉(zhuǎn)而去執(zhí)行與中斷信號對應(yīng)的處理程序,如果先前執(zhí)行的指令是用戶態(tài)下的程序,那么這個轉(zhuǎn)換的過程自然也就發(fā)生了由用戶態(tài)到內(nèi)核態(tài)的切換。
作者:我妻禮彌
鏈接:https://juejin.cn/post/6990237426903957540
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
