內核空間和用戶空間,內核態和用戶態

內核空間和用戶空間,內核態和用戶態(轉載)

內核空間和用戶空間
Linux簡化了分段機制,使得虛擬地址與線性地址總是一致,因此,Linux的虛擬地址空間也爲0~4G。Linux內核將這4G字節的空間分爲兩部分。將最高的1G字節(從虛擬地址 0xC0000000到0xFFFFFFFF),供內核使用,稱爲“內核空間”。而將較低的 3G字節(從虛擬地址 0x00000000到0xBFFFFFFF),供各個進程使用,稱爲“用戶空間)。因爲每個進程可以通過系統調用進入內核,因此,Linux內核由系統內的所有進程共享。於是,從具體進程的角度來看,每個進程可以擁有4G字節的虛擬空間。每個進程有各自的私有用戶空間( 0~3G),這個空間對系統中的其他進程是不可見的。最高的1GB字節虛擬內核空間則爲所有進程以及內核所共享。

1.虛擬內核空間到物理空間的映射
內核空間中存放的是內核代碼和數據,而進程的用戶空間中存放的是用戶程序的代碼和數據。不管是內核空間還是用戶空間,它們都處於虛擬空間中。可能有人會問,系統啓動時,內核的代碼和數據不是被裝入到物理內存嗎?它們爲什麼也處於虛擬內存中呢?這和編譯程序有關,後面我們通過具體討論就會明白這一點。
雖然內核空間佔據了每個虛擬空間中的最高1GB字節,但映射到物理內存卻總是從最低地址(0x00000000)開始。對內核空間來說,其地址映射是很簡單的線性映射,0xC0000000就是物理地址與線性地址之間的位移量,在Linux代碼中就叫做PAGE_OFFSET。
內核的虛擬地址空間到物理地址空間的映射我們可以在 include/asm/i386/page.h中看到對內核空間中地址映射的說明及定義: /*
143 /*
144 * This handles the memory map.. We could make this a config
145 * option, but too many people screw it up, and too few need
146 * it.
147 *
148 * A __PAGE_OFFSET of 0xC0000000 means that the kernel has
149 * a virtual address space of one gigabyte, which limits the
150 * amount of physical memory you can use to about 950MB.
151 *
152 * If you want more physical memory than this then see the CONFIG_HIGHMEM4G
153 * and CONFIG_HIGHMEM64G options in the kernel configuration.
154 */
...
173 #define __PAGE_OFFSET CONFIG_PAGE_OFFSET
...
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
源代碼的註釋中說明,如果你的物理內存大於950MB,那麼在編譯內核時就需要加CONFIG_HIGHMEM4G和CONFIG_HIGHMEM64G選項,這種情況我們暫不考慮。如果物理內存小於950MB,則對於內核空間而言,給定一個虛地址x,其物理地址爲 “x-PAGE_OFFSET”,給定一個物理地址 x,其虛地址爲“ x+ PAGE_OFFSET”。
需要注意的是,宏 __pa()僅僅把一個內核空間的虛地址映射到物理地址,而決不適用於用戶空間,用戶空間的地址映射要複雜得多。
2.內核映像
在下面的描述中,把內核的代碼和數據就叫內核映像( kernel image)。當系統啓動時, Linux內核映像被安裝在物理地址 0x00100000開始的地方,即 1MB開始的區間 (第1M留作它用)。然而,在正常運行時,整個內核映像應該在虛擬內核空間中,因此,連接程序在連接內核映像時,在所有的符號地址上加一個偏移量 PAGE_OFFSET,這樣,內核映像在內核空間的起始地址就爲 0xC0100000。
例如,進程的頁目錄 PGD(屬於內核數據結構)就處於內核空間中。在進程切換時,要將寄存器 CR3設置成指向新進程的頁目錄 PGD,而該目錄的起始地址在內核空間中是虛地址,但 CR3所需要的是物理地址,這時候就要用 __pa()進行地址轉換。在 mm_context.h中就有這麼一行語句:
asm volatile(“movl %0,%%cr3”: :”r” (__pa(next->pgd));
這是一行嵌入式彙編代碼,其含義是將下一個進程的頁目錄起始地址 next_pgd,通過__pa()轉換成物理地址,存放在某個寄存器中,然後用 mov指令將其寫入CR3寄存器中。經過這行語句的處理, CR3就指向新進程 next的頁目錄表 PGD了。



內核態和用戶態區別
當一個任務(進程)執行系統調用而陷入內核代碼中執行時,我們就稱進程處於內核運行態(或簡稱爲內核態)。此時處理器處於特權級最高的(0級)內核代碼中執行。當進程處於內核態時,執行的內核代碼會使用當前進程的內核棧。每個進程都有自己的內核棧。當進程在執行用戶自己的代碼時,則稱其處於用戶運行態(用戶態)。即此時處理器在特權級最低的(3級)用戶代碼中運行。當正在執行用戶程序而突然被中斷程序中斷時,此時用戶程序也可以象徵性地稱爲處於進程的內核態。因爲中斷處理程序將使用當前進程的內核棧。這與處於內核態的進程的狀態有些類似。

 


1、用系統調用時進入核心態。Linux對硬件的操作只能在核心態,這可以通過寫驅動程序來控制。在用戶態操作硬件會造成core    dump.   
 2、要注意區分系統調用和一般的函數。系統調用由內核提供,如read()、write()、open()等。而一般的函數由軟件包中的函數庫提供,如sin()、cos()等。在語法上兩者沒有區別。   
 3、一般情況:系統調用運行在覈心態,函數運行在用戶態。但也有一些函數在內部使用了系統調用(如fopen),這樣的函數在調用系統調用是進入核心態,其他時候運行在用戶態。

大概是    當用戶程序調用系統的API時,就產生中斷,進入內核態的API,處理完成後,用中斷再退出,返回用戶態的調用函數。   
   user    api    -->    interrupt    -->    kernel    api    -->    interrupt
---------------------------------------------------------------------

簡單來講一個進程由於執行系統調用而開始執行內核代碼,我們稱該進程處於內核態中. 一個進程執行應用程序自身代碼則稱該進程處於用戶態.


intel x86 架構的 CPU 分爲好幾個運行級別, 0--3 , 0 爲最高級別, 3 爲最低級別


針對不同的級別,有很多的限制,比如說傳統的 in ,out 指令,就是端口的輸入輸出指令, 0 級下是可以用的,但在 3 級下就不能用,你用就產生陷阱,告訴你出錯了,當然限制還有很多了,不只是這一點


操作系統下是利用這個特點,當操作系統自己的代碼運行時, CPU 就切成 0 ,當用戶的程序運行是就只讓它在 3 級運行,這樣如果用戶的程序想做什麼破壞系統的事情的話,也沒辦法做到


當然,低級別的程序是沒法把自己升到高級別的,也就是說用戶程序運行在 3 ,他想把自己變成 0 級自己是做不到的,除非是操作系統幫忙,利用這個特性,操作系統就可以控制所有的程序的運行,確保系統的安全了. 平時把操作系統運行時的級別就叫內核態(因爲是操作系統內核運行時的狀態),而且普通用戶程序運行時的那個級別叫用戶態...
當操作系統剛引導時, CPU 處於實模式,這時就相當於是 0 ,於是操作系統就自動得到最高權限,然後切到保護模式時就是 0 ,這時操作系統就佔了先機,成爲了最高級別的運行者,由於你的程序都是由操作系統來加載的,所以當它把你加載上來後,就把你的運行狀態設爲 3 ,即最低級,然後才讓你運行,所以沒辦法,你只能在最低級運行了,因爲沒辦法把自己從低級上升到高級, 這就是操作系統在內核態可以管理用戶程序,殺死用戶程序的原因.

 


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