天天看點

linux網絡:使用者态以及核心态

學習linux網絡程式設計的時候,經常看到使用者态以及核心态,但是不是很了解,是以寫個部落格認真研究一番:

多數計算機有兩種運作模式:核心态和使用者态。

軟體中最基礎的部分是作業系統,它運作在核心态(也稱管态、核心态)。在這個模式中,作業系統具有對所有硬體的完全通路權,可以執行機器能夠運作的任何指令。軟體的其餘部分運作在使用者态下。在使用者态下,隻使用了機器指令中的一個子集。特别地,那些會影響機器的控制或可進行IO操作的指令,在使用者态中的程式裡是禁止的。

——整理自《現代作業系統》

從網上找了一個圖

linux網絡:使用者态以及核心态

從圖上我們可以看出來通過系統調用将Linux整個體系分為使用者态和核心态(或者說核心空間和使用者空間)。那核心态到底是什麼呢?其實從本質上說就是我們所說的核心,它是一種特殊的軟體程式,特殊在哪兒呢?控制計算機的硬體資源,例如協調CPU資源,配置設定記憶體資源,并且提供穩定的環境供應用程式運作。

核心态:cpu可以通路記憶體的所有資料,包括外圍裝置,例如硬碟,網卡,cpu也可以将自己從一個程式切換到另一個程式。

使用者态:隻能受限的通路記憶體,且不允許通路外圍裝置,占用cpu的能力被剝奪,cpu資源可以被其他程式擷取。

為什麼要有使用者态和核心态?

由于需要限制不同的程式之間的通路能力, 防止他們擷取别的程式的記憶體資料, 或者擷取外圍裝置的資料, 并發送到網絡, CPU劃分出兩個權限等級 -- 使用者态和核心态。

使用者态到核心态怎樣切換?

往往我們的系統的資源是固定的,例如記憶體2G,CPU固定,磁盤2TB,網絡接口固定。是以就需要作業系統對資源進行有效的利用。假設某個應用程式過分的通路這些資源,就會導緻整個系統的資源被占用,如果不對這種行為進行限制和區分,就會導緻資源通路的沖突。是以,Linux的設計的初衷:給不同的操作給與不同的“權限”。Linux作業系統就将權限等級分為了2個等級,分别就是核心态和使用者态。

各位有沒有發現,前面講了這麼多核心态和使用者态什麼不同,其實用一句話就能概括:它們權限不同。使用者态的程序能夠通路的資源受到了極大的控制,而運作在核心态的程序可以“為所欲為”。一個程序可以運作在使用者态也可以運作在核心态,那它們之間肯定存在使用者态和核心态切換的過程。打一個比方:C庫接口malloc申請動态記憶體,malloc的實作内部最終還是會調用brk()或者mmap()系統調用來配置設定記憶體。

那為問題又來了,從使用者态到核心态到底怎麼進入?隻能通過系統調用嗎?還有其他方式嗎?

從使用者态到核心态切換可以通過三種方式:

  1. 系統調用,這個上面已經講解過了,在我公衆号之前的文章也有講解過。其實系統調用本身就是中斷,但是軟體中斷,跟硬中斷不同。
  2. 異常:如果目前程序運作在使用者态,如果這個時候發生了異常事件,就會觸發切換。例如:缺頁異常。
  3. 外設中斷:當外設完成使用者的請求時,會向CPU發送中斷信号。

1. 使用者态和核心态的概念差別

究竟什麼是使用者态,什麼是核心态,這兩個基本概念以前一直了解得不是很清楚,根本原因個人覺得是在于因為大部分時候我們在寫程式時關注的重點和着眼的角度放在了實作的功能和代碼的邏輯性上,先看一個例子:

1)例子

void testfork(){
if(0 = = fork()){
printf(“create new process success!\n”);
}
printf(“testfork ok\n”);
}
           
void testfork(){  
if(0 = = fork()){  
printf(“create new process success!\n”);  
}  
printf(“testfork ok\n”);  
}
           

這段代碼很簡單,從功能的角度來看,就是實際執行了一個fork(),生成一個新的程序,從邏輯的角度看,就是判斷了如果fork()傳回的是0則列印相關語句,然後函數最後再列印一句表示執行完整個testfork()函數。代碼的執行邏輯和功能上看就是如此簡單,一共四行代碼,從上到下一句一句執行而已,完全看不出來哪裡有展現出使用者态和程序态的概念。

如果說前面兩種是靜态觀察的角度看的話,我們還可以從動态的角度來看這段代碼,即它被轉換成CPU執行的指令後加載執行的過程,這時這段程式就是一個動态執行的指令序列。而究竟加載了哪些代碼,如何加載就是和作業系統密切相關了。

2)特權級

熟悉Unix/Linux系統的人都知道,fork的工作實際上是以系統調用的方式完成相應功能的,具體的工作是由sys_fork負責實施。其實無論是不是Unix或者Linux,對于任何作業系統來說,建立一個新的程序都是屬于核心功能,因為它要做很多底層細緻地工作,消耗系統的實體資源,比如配置設定實體記憶體,從父程序拷貝相關資訊,拷貝設定頁目錄頁表等等,這些顯然不能随便讓哪個程式就能去做,于是就自然引出特權級别的概念,顯然,最關鍵性的權力必須由高特權級的程式來執行,這樣才可以做到集中管理,減少有限資源的通路和使用沖突。

特權級顯然是非常有效的管理和控制程式執行的手段,是以在硬體上對特權級做了很多支援,就Intel x86架構的CPU來說一共有0~3四個特權級,0級最高,3級最低,硬體上在執行每條指令時都會對指令所具有的特權級做相應的檢查,相關的概念有CPL、DPL和RPL,這裡不再過多闡述。硬體已經提供了一套特權級使用的相關機制,軟體自然就是好好利用的問題,這屬于作業系統要做的事情,對于Unix/Linux來說,隻使用了0級特權級和3級特權級。也就是說在Unix/Linux系統中,一條工作在0級特權級的指令具有了CPU能提供的最高權力,而一條工作在3級特權級的指令具有CPU提供的最低或者說最基本權力。

3)使用者态和核心态

現在我們從特權級的排程來了解使用者态和核心态就比較好了解了,當程式運作在3級特權級上時,就可以稱之為運作在使用者态,因為這是最低特權級,是普通的使用者程序運作的特權級,大部分使用者直接面對的程式都是運作在使用者态;反之,當程式運作在0級特權級上時,就可以稱之為運作在核心态。

雖然使用者态下和核心态下工作的程式有很多差别,但最重要的差别就在于特權級的不同,即權力的不同。運作在使用者态下的程式不能直接通路作業系統核心資料結構和程式,比如上面例子中的testfork()就不能直接調用sys_fork(),因為前者是工作在使用者态,屬于使用者态程式,而sys_fork()是工作在核心态,屬于核心态程式。

當我們在系統中執行一個程式時,大部分時間是運作在使用者态下的,在其需要作業系統幫助完成某些它沒有權力和能力完成的工作時就會切換到核心态,比如testfork()最初運作在使用者态程序下,當它調用fork()最終觸發sys_fork()的執行時,就切換到了核心态。

線程等待

說到線程等待,很快就會想到阻塞。但是,其實線程等待不一定是阻塞,還有可能是自旋。

要阻塞或喚醒一個線程,就會消耗較多的系統資源,因為這種操作是需要作業系統介入的,自然就會發生使用者态和核心态之間的切換,就會消耗大量系統資源。當這種操作高頻發生時,就會消耗大量的CPU處理時間。那麼,有什麼方案可以解決這種問題嗎?有。方案是:讓子彈飛一會兒。

假設A線程有對資源Z加鎖,但此時發現資源Z已經被線程B鎖定,此時,一種方案是A進入阻塞狀态,等待B釋放鎖。但是,我們如果事先知道,線程B對資源Z的加鎖狀态持續時間很短,那麼,A其實沒必要阻塞,等一等就好了。類似于執行一段空循環。這樣,就避免了線程的阻塞和喚醒,也就避免了使用者态和核心态的切換。這就是自旋。當然,自旋也需要消耗一定的計算資源,但是比較阻塞來說,就要好太多了。當然,這種方案是基于B的加鎖狀态不會持續太久,且不會有太多線程同時競争同一資源的場景下的。換句話說,是基于樂觀鎖而設計的。

參考:https://www.cnblogs.com/maxigang/p/9041080.html

繼續閱讀