1. 程式人生 > 實用技巧 >Linux系統使用者態和核心態

Linux系統使用者態和核心態

Unix/Linux的體系架構

如上圖所示,從巨集觀上來看,Linux作業系統的體系架構分為使用者態和核心態(或者使用者空間和核心空間)。核心從本質上看是一種軟體-----控制計算機的硬體資源,並提供上層應用程式執行的環境。 使用者態即上層應用程式的活動空間,應用程式的執行必須依託於核心提供的資源,包括CPU資源、儲存資源、I/O資源等。為了使上層應用能夠訪問到這些資源,核心必須為上層應用提供訪問的介面:`系統呼叫`。

簡單來說:
核心態:執行在核心空間的程序的狀態
使用者態:執行在使用者空間的程序的狀態

系統呼叫
系統呼叫是作業系統的最小功能單位,這些系統呼叫根據不同的應用場景可以進行擴充套件和裁剪,現在各種版本的Unix實現都提供了不同數量的系統呼叫,如Linux的不同版本提供了240-260個系統呼叫,FreeBSD大約提供了320個。
我們可以把系統呼叫看成是一種不能再化簡的操作(類似於原子操作,但是不同概念),有人把它比作一個漢字的一個“筆畫”,而一個“漢字”就代表一個上層應用,我覺得這個比喻非常貼切。一個漢字有很多筆畫組成,因此有時候如果要實現一個完整的漢字就必須呼叫很多的系統呼叫。這有時是一件很崩潰的事情。

系統庫函式:系統呼叫的封裝
應用程式直接使用系統呼叫,這勢必會加重程式設計師的負擔,良好的程式設計方法是:重視上層的業務邏輯操作,而儘可能避免底層複雜的實現細節。那麼有沒有優化空間呢?庫函式正是為了將程式設計師從複雜的細節中解脫出來而提出的一種有效方法。它實現對系統呼叫的封裝,將簡單的業務邏輯介面呈現給使用者,方便使用者呼叫,從這個角度上看,庫函式就像是組成漢字的“偏旁”。這樣的一種組成方式極大增強了程式設計的靈活性,
對於簡單的操作,我們可以直接呼叫系統呼叫來訪問資源,如“人”;對於複雜操作,我們藉助於庫函式來實現,如“仁”。庫函式依據不同的標準也可以有不同的實現版本,如ISOC 標準庫,POSIX標準庫等。

shell:系統呼叫的封裝
Shell是一個特殊的應用程式,俗稱命令列,本質上是一個命令直譯器,它下通系統呼叫,上通各種應用,通常充當著一種“膠水”的角色,來連線各個小功能程式,讓不同程式能夠以一個清晰的介面協同工作,從而增強各個程式的功能。同時,Shell是可程式設計的,它可以執行符合Shell語法的文字,這樣的文字稱為Shell指令碼,通常短短的幾行Shell指令碼就可以實現一個非常大的功能,原因就是這些Shell語句通常都對系統呼叫做了一層封裝。為了方便使用者和系統互動,一般,一個Shell對應一個終端,終端是一個硬體裝置,呈現給使用者的是一個圖形化視窗。我們可以通過這個視窗輸入或者輸出文字。這個文字直接傳遞給shell進行分析解釋,然後執行。

總結一下,使用者態的應用程式可以通過三種方式來訪問核心態的資源:
1)系統呼叫
2)庫函式
3)Shell指令碼

下圖是對上圖的一個細分結構,從這個圖上可以更進一步對核心所做的事有一個“全景式”的印象。主要表現為:向下控制硬體資源,向內管理作業系統資源:包括程序的排程和管理、記憶體的管理、檔案系統的管理、裝置驅動程式的管理以及網路資源的管理,向上則嚮應用程式提供系統呼叫的介面。
從整體上來看,整個作業系統分為兩層:使用者態和核心態,這種分層的架構極大地提高了資源管理的可擴充套件性和靈活性,而且方便使用者對資源的呼叫和集中式的管理,帶來一定的安全性。

使用者態和核心態的切換

因為作業系統的資源是有限的,如果訪問資源的操作過多,必然會消耗過多的資源,而且如果不對這些操作加以區分,很可能造成資源訪問的衝突。所以,為了減少有限資源的訪問和使用衝突,Unix/Linux的設計哲學之一就是:對不同的操作賦予不同的執行等級,就是所謂特權的概念。簡單說就是有多大能力做多大的事,與系統相關的一些特別關鍵的操作必須由最高特權的程式來完成。

Intel的X86架構的CPU提供了0到3四個特權級,數字越小,特權越高,Linux作業系統中主要採用了0和3兩個特權級,分別對應的就是核心態和使用者態。
運行於使用者態的程序可以執行的操作和訪問的資源都會受到極大的限制,而執行在核心態的程序則可以執行任何操作並且在資源的使用上沒有限制。很多程式開始時運行於使用者態,但在執行的過程中,一些操作需要在核心許可權下才能執行,這就涉及到一個從使用者態切換到核心態的過程。比如C函式庫中的記憶體分配函式malloc(),它具體是使用sbrk()系統呼叫來分配記憶體,當malloc呼叫sbrk()的時候就涉及一次從使用者態到核心態的切換,類似的函式還有printf(),呼叫的是wirte()系統呼叫來輸出字串,等等。

到底在什麼情況下會發生從使用者態到核心態的切換,一般存在以下三種情況:

1)當然就是系統呼叫:原因如上的分析。
2)異常事件: 當CPU正在執行執行在使用者態的程式時,突然發生某些預先不可知的異常事件,這個時候就會觸發從當前使用者態執行的程序轉向核心態執行相關的異常事件,典型的如缺頁異常。
3)外圍裝置的中斷:當外圍裝置完成使用者的請求操作後,會像CPU發出中斷訊號,此時,CPU就會暫停執行下一條即將要執行的指令,轉而去執行中斷訊號對應的處理程式,如果先前執行的指令是在使用者態下,則自然就發生從使用者態到核心態的轉換。

注意:系統呼叫的本質其實也是中斷,相對於外圍裝置的硬中斷,這種中斷稱為軟中斷,這是作業系統為使用者特別開放的一種中斷,如Linux int 80h中斷。所以,從觸發方式和效果上來看,這三種切換方式是完全一樣的,都相當於是執行了一箇中斷響應的過程。但是從觸發的物件來看,系統呼叫是程序主動請求切換的,而異常和硬中斷則是被動的。

三、總結

本文僅是從巨集觀的角度去理解Linux使用者態和核心態的設計,並沒有去深究它們的具體實現方式。從實現上來看,必須要考慮到的一點我想就是效能問題,因為使用者態和核心態之間的切換也會消耗大量資源。關於實現的細節,目前學藝不精不敢亂說,等日後補上。但知道了這一點,我相信對很多問題也就很容易理解了,比如說基於緩衝區的IO和無緩衝的IO,使用者程序和核心程序之間的切換,IO複用中的讀寫核心事件表,等等,這些知識之後會一一補上。


核心空間和使用者空間

對 32 位作業系統而言,它的定址空間(虛擬地址空間,或叫線性地址空間)為 4G(2的32次方)。也就是說一個程序的最大地址空間為 4G。作業系統的核心是核心(kernel),它獨立於普通的應用程式,可以訪問受保護的記憶體空間,也有訪問底層硬體裝置的所有許可權。為了保證核心的安全,現在的作業系統一般都強制使用者程序不能直接操作核心。具體的實現方式基本都是由作業系統將虛擬地址空間劃分為兩部分,一部分為核心空間,另一部分為使用者空間。

針對 Linux 作業系統而言,最高的 1G 位元組(從虛擬地址 0xC0000000 到 0xFFFFFFFF)由核心使用,稱為核心空間。而較低的 3G 位元組(從虛擬地址 0x00000000 到 0xBFFFFFFF)由各個程序使用,稱為使用者空間

對上面這段內容我們可以這樣理解:
每個程序的4G地址空間中,最高1G都是一樣的,即核心空間。只有剩餘的3G才歸程序自己使用。
換句話說就是, 最高 1G 的核心空間是被所有程序共享的!
下圖描述了每個程序 4G 地址空間的分配情況(此圖來自網際網路):

為什麼需要區分核心空間與使用者空間

在 CPU 的所有指令中,有些指令是非常危險的,如果錯用,將導致系統崩潰,比如清記憶體、設定時鐘等。如果允許所有的程式都可以使用這些指令,那麼系統崩潰的概率將大大增加。
所以,CPU 將指令分為特權指令和非特權指令,對於那些危險的指令,只允許作業系統及其相關模組使用,普通應用程式只能使用那些不會造成災難的指令。比如 Intel 的 CPU 將特權等級分為 4 個級別:Ring0~Ring3。
其實 Linux 系統只使用了 Ring0 和 Ring3 兩個執行級別(Windows 系統也是一樣的)。當程序執行在 Ring3 級別時被稱為執行在使用者態,而執行在 Ring0 級別時被稱為執行在核心態。

核心態與使用者態

好了我們現在需要再解釋一下什麼是核心態、使用者態:
當程序執行在核心空間時就處於核心態,而程序執行在使用者空間時則處於使用者態。
在核心態下,程序執行在核心地址空間中,此時 CPU 可以執行任何指令。執行的程式碼也不受任何的限制,可以自由地訪問任何有效地址,也可以直接進行埠的訪問。
在使用者態下,程序執行在使用者地址空間中,被執行的程式碼要受到 CPU 的諸多檢查,它們只能訪問對映其地址空間的頁表項中規定的在使用者態下可訪問頁面的虛擬地址,且只能對任務狀態段(TSS)中 I/O 許可點陣圖(I/O Permission Bitmap)中規定的可訪問埠進行直接訪問。

對於以前的 DOS 作業系統來說,是沒有核心空間、使用者空間以及核心態、使用者態這些概念的。可以認為所有的程式碼都是執行在核心態的,因而使用者編寫的應用程式程式碼可以很容易的讓作業系統崩潰掉。
對於 Linux 來說,通過區分核心空間和使用者空間的設計,隔離了作業系統程式碼(作業系統的程式碼要比應用程式的程式碼健壯很多)與應用程式程式碼。即便是單個應用程式出現錯誤也不會影響到作業系統的穩定性,這樣其它的程式還可以正常的執行(Linux 可是個多工系統啊!)。

所以,區分核心空間和使用者空間本質上是要提高作業系統的穩定性及可用性。

如何從使用者空間進入核心空間

其實所有的系統資源管理都是在核心空間中完成的。比如讀寫磁碟檔案,分配回收記憶體,從網路介面讀寫資料等等。我們的應用程式是無法直接進行這樣的操作的。但是我們可以通過核心提供的介面來完成這樣的任務。
比如應用程式要讀取磁碟上的一個檔案,它可以向核心發起一個 "系統呼叫" 告訴核心:"我要讀取磁碟上的某某檔案"。其實就是通過一個特殊的指令讓程序從使用者態進入到核心態(到了核心空間),在核心空間中,CPU 可以執行任何的指令,當然也包括從磁碟上讀取資料。具體過程是先把資料讀取到核心空間中,然後再把資料拷貝到使用者空間並從核心態切換到使用者態。此時應用程式已經從系統呼叫中返回並且拿到了想要的資料,可以開開心心的往下執行了。
簡單說就是應用程式把高科技的事情(從磁碟讀取檔案)外包給了系統核心,系統核心做這些事情既專業又高效。

對於一個程序來講,從使用者空間進入核心空間並最終返回到使用者空間,這個過程是十分複雜的。舉個例子,比如我們經常接觸的概念 "堆疊",其實程序在核心態和使用者態各有一個堆疊。執行在使用者空間時程序使用的是使用者空間中的堆疊,而執行在核心空間時,程序使用的是核心空間中的堆疊。所以說,Linux 中每個程序有兩個棧,分別用於使用者態和核心態。

下圖簡明的描述了使用者態與核心態之間的轉換:

既然使用者態的程序必須切換成核心態才能使用系統的資源,那麼我們接下來就看看程序一共有多少種方式可以從使用者態進入到核心態。概括的說,有三種方式:系統呼叫、軟中斷和硬體中斷。這三種方式每一種都涉及到大量的作業系統知識,所以這裡不做展開。

整體結構

接下來我們從核心空間和使用者空間的角度看一看整個 Linux 系統的結構。它大體可以分為三個部分,從下往上依次為:硬體 -> 核心空間 -> 使用者空間。如下圖所示(此圖來自網際網路):

在硬體之上,核心空間中的程式碼控制了硬體資源的使用權,使用者空間中的程式碼只有通過核心暴露的系統呼叫介面(System Call Interface)才能使用到系統中的硬體資源。其實,不光是 Linux,Windows 作業系統的設計也是大同小異。

總結
現代的作業系統大都通過核心空間和使用者空間的設計來保護作業系統自身的安全性和穩定性。所以在我們閱讀有關作業系統的資料時經常遇到核心空間、使用者空間和核心態、使用者態等概念,希望本文能夠幫助您理解這些基本的概念。


轉載文章,轉載至:
https://www.cnblogs.com/bakari/p/5520860.html
https://www.jianshu.com/p/a77613045601