linux網絡:用戶態以及內核態

學習linux網絡編程的時候,經常看到用戶態以及內核態,但是不是很理解,所以寫個博客認真研究一番:

多數計算機有兩種運行模式:內核態和用戶態。

軟件中最基礎的部分是操作系統,它運行在內核態(也稱管態、核心態)。在這個模式中,操作系統具有對所有硬件的完全訪問權,可以執行機器能夠運行的任何指令。軟件的其餘部分運行在用戶態下。在用戶態下,只使用了機器指令中的一個子集。特別地,那些會影響機器的控制或可進行IO操作的指令,在用戶態中的程序裏是禁止的。
——整理自《現代操作系統》

從網上找了一個圖

從圖上我們可以看出來通過系統調用將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

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