1. 切換方式
從用戶態到內核態切換可以通過三種方式,或者說會導致從用戶態切換到內核態的操作:
- 系統調用,這個上面已經講解過了,在我公眾號之前的文章也有講解過。其實系統調用本身就是中斷,但是軟件中斷,跟硬中斷不同。系統調用機制是使用了操作系統為用戶特別開放的一個中斷來實現,如 Linux 的 int 80h 中斷。
- 異常:如果當前進程運行在用戶態,如果這個時候發生了異常事件,會觸發由當前運行進程切換到處理此異常的內核相關進程中
- 外圍設備中斷:外圍設備完成用戶請求的操作之后,會向CPU發出中斷信號,這時CPU會轉去處理對應的中斷處理程序。
2. 代價何在
當發生用戶態到內核態的切換時,會發生如下過程(本質上是從“用戶程序”切換到“內核程序”)
- 設置處理器至內核態。
- 保存當前寄存器(棧指針、程序計數器、通用寄存器)。
- 將棧指針設置指向內核棧地址。
- 將程序計數器設置為一個事先約定的地址上,該地址上存放的是系統調用處理程序的起始地址。
而之后從內核態返回用戶態時,又會進行類似的工作。
3. 如何避免頻繁切換
用戶態和內核態之間的切換有一定的開銷,如果頻繁發生切換勢必會帶來很大的開銷,所以要想盡一切辦法來減少切換。這也是面試常考的問題。
3.1 減少線程切換
因為線程的切換會導致用戶態和內核態之間的切換,所以減少線程切換也會減少用戶態和內核態之間的切換。那么如何減少線程切換呢?
- 無鎖並發編程。多線程競爭鎖時,加鎖、釋放鎖會導致比較多的上下文切換。(為什么加鎖和釋放鎖會導致上下文切換,看文末的補充解釋)
- CAS算法。使用CAS避免加鎖,避免阻塞線程
- 使用最少的線程。避免創建不需要的線程
- 協程。在單線程里實現多任務的調度,並在單線程里維持多個任務間的切換
3.2 一個面試問題
I/O 頻繁發生內核態和用戶態切換,怎么解決。
首先要同意這個說法,即I/O會導致系統調用,從而導致內核態和用戶態之間的切換。因為對I/O設備的操作是發生在內核態。那如何減少因為I/O導致的系統調用呢?答案是:使用戶進程緩沖區。下面解釋一下原因
用戶進程緩沖區
你看一些程序在讀取文件時,會先申請一塊內存數組,稱為buffer,然后每次調用read,讀取設定字節長度的數據,寫入buffer。之后的程序都是從buffer中獲取數據,當buffer使用完后,在進行下一次調用,填充buffer。所以說:用戶緩沖區的目的就是是為了減少系統調用次數,從而降低操作系統在用戶態與核心態切換所耗費的時間。除了在進程中設計緩沖區,內核也有自己的緩沖區。
內核緩存區
當一個用戶進程要從磁盤讀取數據時,內核一般不直接讀磁盤,而是將內核緩沖區中的數據復制到進程緩沖區中。但若是內核緩沖區中沒有數據,內核會把對數據塊的請求,加入到請求隊列,然后把進程掛起,為其它進程提供服務。等到數據已經讀取到內核緩沖區時,把內核緩沖區中的數據讀取到用戶進程中,才會通知進程,當然不同的IO模型,在調度和使用內核緩沖區的方式上有所不同。
小結
圖中的read,write和sync都是系統調用。read是把數據從內核緩沖區復制到進程緩沖區。write是把進程緩沖區復制到內核緩沖區。當然,write並不一定導致內核的緩存同步動作sync,比如OS可能會把內核緩沖區的數據積累到一定量后,再一次性同步到磁盤中。這也就是為什么斷電有時會導致數據丟失。所以說內核緩沖區,可以在OS級別,提高磁盤IO效率,優化磁盤寫操作。
4. 補充解釋
為什么加鎖和釋放鎖會導致上下文切換
Synchronized是通過對象內部的一個叫做監視器鎖(monitor)來實現的。但是監視器鎖本質又是依賴於底層的操作系統的Mutex Lock來實現的。但是由於使用Mutex Lock需要將當前線程掛起並從用戶態切換到內核態來執行,這種切換的代價是非常昂貴的因此,這種依賴於操作系統Mutex Lock所實現的鎖我們稱之為“重量級鎖”。
(上述說法不是很准確,應該不是每種鎖都是切換到內核態,這點我不太確認)
mutex和spin lock的區別
mutex和spin lock的區別和應用(sleep-waiting和busy-waiting的區別)2011-10-19 11:43
而自旋鎖spin lock是busy-waiting。就是說當沒有可用的鎖時,就一直忙等待並不停的進行鎖請求,直到得到這個鎖為止。這個過程中cpu始終處於忙狀態,不能做別的任務。
例如在一個雙核的機器上有兩個線程(線程A和線程B),它們分別運行在Core0 和Core1上。 用spin-lock,coer0上的線程就會始終占用CPU。
另外一個值得注意的細節是spin lock耗費了更多的user time。這就是因為兩個線程分別運行在兩個核上,大部分時間只有一個線程能拿到鎖,所以另一個線程就一直在它運行的core上進行忙等待,CPU占用率一直是100%;而mutex則不同,當對鎖的請求失敗后上下文切換就會發生,這樣就能空出一個核來進行別的運算任務了。(其實這種上下文切換對已經拿着鎖的那個線程性能也是有影響的,因為當該線程釋放該鎖時它需要通知操作系統去喚醒那些被阻塞的線程,這也是額外的開銷)
總結
(1)Mutex適合對鎖操作非常頻繁的場景,並且具有更好的適應性。盡管相比spin lock它會花費更多的開銷(主要是上下文切換),但是它能適合實際開發中復雜的應用場景,在保證一定性能的前提下提供更大的靈活度。