由於操做系統的不少操做會消耗系統的物理資源,例如建立一個新進程時,要作不少底層的細緻工做,如分配物理內存,從父進程拷貝相關信息,拷貝設置頁目錄、頁表等,這些操做顯然不能隨便讓任何程序均可以作,因而就產生了特權級別的概念,與系統相關的一些特別關鍵性的操做必須由高級別的程序來完成,這樣能夠作到集中管理,減小有限資源的訪問和使用衝突。Intel的X86架構的CPU提供了0到3四個特權級,而在咱們Linux操做系統中則主要採用了0和3兩個特權級,也就是咱們一般所說的內核態和用戶態。html
運行於用戶態的進程能夠執行的操做和訪問的資源都受到極大的限制,而運行於內核態的進程則能夠執行任何操做而且在資源的使用上也沒有限制。不少程序開始時運行於用戶態,但在執行的過程當中,一些操做須要在內核權限下才能執行,這就涉及到一個從用戶態切換到內核態的過程。本文主要要介紹的就是這個過程。linux
這裏再明確一個概念,每一個進程都有一個4G大小的虛擬地址空間,在這個4G大小的虛擬地址空間中,前0~3G爲用戶空間,每一個進程的用戶空間之間是相互獨立的,互不相干。而3G~4G爲內核空間,由於每一個進程均可以從用戶態切換到內核態,所以,內核空間對於全部進程來講,能夠說是共享的,不過這麼說有些不太嚴謹,應該說內核空間中大部分區域對於全部的進程來講都是共享的,這不共享的小部分區域是存儲全部進程內核棧的區域,爲何這麼說,由於每一個進程都存在一個內核棧,而各個進程的內核棧之間必定是不共享的。關於內核空間的詳細描述,參見架構
二、Linux用戶空間與內核空間 post
瞭解了上面所說的這些以後,相信對於內核態和用戶態的概念已經有了必定的瞭解,下面正式開始進入由用戶態向內核態切換的過程。url
首先須要瞭解,什麼狀況下會發生從用戶態向內核態切換。這裏細分爲3種狀況。spa
一、發生系統調用時操作系統
這是處於用戶態的進程主動請求切換到內核態的一種方式。用戶態的進程經過系統調用申請使用操做系統提供的系統調用服務例程來處理任務。而系統調用的機制,其核心還是使用了操做系統爲用戶特別開發的一箇中斷機制來實現的,即軟中斷。.net
二、產生異常時指針
當CPU執行運行在用戶態下的程序時,發生了某些事先不可知的異常,這時會觸發由當前運行的進程切換處處理此異常的內核相關的程序中,也就是轉到了內核態,如缺頁異常。
三、外設產生中斷時
當外圍設備完成用戶請求的操做後,會向CPU發出相應的中斷信號,這時CPU會暫停執行下一條即將要執行的指令轉而去執行與中斷信號對應的處理程序,若是先前執行的指令是用戶態下的程序,那麼這個轉換的過程天然也就發生了由用戶態到內核態的切換。好比硬盤讀寫操做的完成,系統會切換到硬盤讀寫的中斷處理程序中執行後續操做等。
能夠看到上述三種由用戶態切換到內核態的狀況中,只有系統調用是進程主動請求發生切換的,中斷和異常都是被動的。
因爲系統調用、中斷和異常由用戶態切換到內核態的機制大同小異,因此這裏僅就係統調用的切換過程進行具體說明。
若是一個用戶程序須要調用底層的系統接口,如fork等諸如libc裏面的系統調用函數,就牽涉到用戶態與內核態的切換問題,由於系統調用處理程序都是運行在內核態下。
在系統調用時因爲用戶態和內核態是運行於兩個獨立的棧上面,即分別爲內核棧和用戶棧,所以,不能僅簡單的傳遞函數指針,由於對於內核態堆棧在用戶態下是不可見的,因此對於系統調用函數的處理程序對於用戶態是不可見的;同時,由於內核棧和用戶棧是相互獨立的,因此在參數傳遞的過程當中不能使用普通的壓棧出棧的方式來進行參數傳遞。
每個系統調用函數在內核當中都存在對應的句柄處理函數,通常以sys_開頭,這些句柄處理函數做爲一個系統調用表形式存在:linux-3.9.4/arch/x86/syscalls/syscall_32.tbl
PS:在3.9.4內核中系統調用初始爲350個,系統調用的最大個數是動態變化的,即不用如2.6內核中,在添加系統調用時需先查看MAX是否知足,若不知足則須要進行修改。在3.9.4內核中則不須要這個過程,如今編譯出的內核其syscall_MAX爲351,若添加一個系統調用,則編譯出內核以後,該值爲352。
每個系統調用的函數對應着內核裏的一個具體實現,每個系統函數都有一個相應的數字對應,即系統調用號,這個數字事實上是系統調用函數指針的偏移。
當咱們運行一個系統調用時,運行時庫經過查找這個表來決定對應的函數代碼,即系統調用號,而後存入到寄存器中,一般爲eax寄存器,而後當切換到到內核態後,內核根據系統調用號來查找到對應的系統調用處理例程的函數名,從而找到對應的代碼入口地址。系統調用切換過程如圖所示:
由於在前面已經說過,內核棧和用戶棧分別處於內核空間和用戶空間兩個不一樣的空間中,所以,這兩個棧是相互獨立的,因此參數傳遞不能只是簡單的壓棧出棧,所以,Linux內核中主要是才用寄存器的方式來完成這個任務。
能夠看到,在發生系統調用時,先是RING0_INT_FRAME,
能夠看到這個過程是對esp和eip進行處理,使其指向內核棧。而後把寄存器eax中的系統調用號入棧,而後SAVE_ALL,
而SAVE_ALL中首先是各個寄存器的入棧操做,即將傳遞的參數壓到內核棧中。到此,完成了由用戶態向內核態的切換過程。因爲此次時間有限,沒有細緻的由源碼角度去研究這個具體的過程,過段時間有空了,好好研究一下,再進行修改補充。
歡迎你們提出問題,進行交流指導~