用戶態線程和內核態線程的區別


用戶級線程

“既然你已經看過線程的基本概念,那我就直接跳過這一部分了。很久很久之前,線程的概念是出現了,但操作系統廠商可不能直接就去修改操作系統的內核,因為對他們來說,穩定性是最重要的。貿然把未經驗證的東西加入內核,出問題了怎么辦?所以想要驗證線程的可用性,得另想辦法。”

“我知道我知道,那些研究人員就編寫了一個關於線程的函數庫,用函數庫來實現線程!”小白得意的說:“這個我剛剛在網上看到了。”

“是的,他們把創建線程、終止線程等功能放在了這個線程庫內,用戶就可以通過調用這些函數來實現所需要的功能。”小明找了張紙,寫上了幾個函數:pthread_creat,pthread_exit ,pthread_join ,pthread_yield ,接着說:“這是幾個重要的功能,我馬上會講到,你應該能大概猜出這些函數的功能吧?”

“emmmm,讓我想想,pthread_creat 是創建一個新線程,pthread_exit 是結束線程,pthread_join 嘛,我猜是准備運行,最后一個,我就不知道了。”

“不知道也沒關系,一會你就清楚了。”小明接着講:“要知道,剛剛我們說的線程庫,是位於用戶空間的,操作系統內核對這個庫一無所知,所以從內核的角度看,它還是按正常的方式管理。”

小白問道:“也就是說操作系統眼里還是只有進程嘍?那我用線程庫寫的多線程進程,只能一次在一個 CPU 核心上運行?” (因為操作系統調度的是進程,每次只能調度不同的進程,所以同一個進程里的線程無法並行)

小明點點頭,說:“你說的沒錯,這其實是用戶級線程的一個缺點,這些線程只能占用一個核,所以做不到並行加速,而且由於用戶線程的透明性,操作系統是不能主動切換線程的,換句話講,如果線程 A 正在運行,線程 B 想要運行的話,只能等待 A 主動放棄 CPU,也就是主動調用 pthread_yield 函數。”

注:對操作系統來說,用戶級線程具有不可見性,也稱透明性。

“停一下,讓我想一想,”小白飛速思考着小明的話,“是不是說,即使有線程庫,用戶級線程也做不到像進程那樣的輪轉調度?”

“非常正確!看來你對進程的概念很清楚嘛。不過呢,雖然不能做到輪轉調度,但用戶級線程也有他自己的好處——你可以為你的應用程序定制調度算法,畢竟什么時候退出線程你自己說了算。剛剛說了,因為操作系統只能看到進程的存在,那如果某一個線程阻塞了,你覺得會發生什么?”

“在操作系統眼里,是進程阻塞了,那么整個進程就會進入阻塞態,在阻塞操作結束前,這個進程都無法得到 CPU 資源。那就相當於,該進程中所有的線程都被阻塞了。”小白得意的回答。

“沒錯,所以如果任由線程進行阻塞操作,進程的效率將受到很大的影響,所以在這個過程中,出現了一個替代方案——jacket。所謂 jacket,就是把一個產生阻塞的系統調用轉化成一個非阻塞的系統調用。”

小白驚訝地問:“這怎么做得到?該阻塞的調用,還能變得不阻塞?”

小明答道:“我來舉個例子吧,不是直接調用一個系統 I/O 例程,而是調用一個應用級別的 I/O jacket 例程,這個 jacket 例程中的代碼會檢查並且確定 I/O 設備是不是正忙,如果忙的話,就在用戶態下將該線程阻塞,然后把控制權交給另一個線程。隔一段時間后再次檢查 I/O 設備。就像你說的,最后還是會執行阻塞調用,但使用 jacket 可以縮短被阻塞的時間。不過有些情況下是可以不被阻塞的,取決於具體的實現。”

小明停頓了一會,說:“用戶級線程的概念大概就這么多,我們接下來講內核級線程吧。”

內核級線程

“有了用戶級線程的鋪墊,內核級線程就好講多了。現在我們知道,許多操作系統都已經支持內核級線程了。為了實現線程,內核里就需要有用來記錄系統里所有線程的線程表。當需要創建一個新線程的時候,就需要進行一個系統調用,然后由操作系統進行線程表的更新。當然了,傳統的進程表也還是有的。你想想看,如果操作系統「看得見」線程,有什么好處?“

小白自信的回答:“操作系統內核如果知道線程的存在,就可以像調度多個進程一樣,把這些線程放在好幾個 CPU 核心上,就能做到實際上的並行了。”

“還有一點你沒有說到,如果線程可見,那么假如線程 A 阻塞了,與他同屬一個進程的線程也不會被阻塞。這是內核級線程的絕對優勢。”

“那內核級線程就沒有什么缺點嗎?”

“缺點當然是有的,你想想看,讓操作系統進行線程調度,那意味着每次切換線程,就需要「陷入」內核態,而操作系統從用戶態到內核態的轉變是有開銷的,所以說內核級線程切換的代價要比用戶級線程大。還有很重要的一點——線程表是存放在操作系統固定的表格空間或者堆棧空間里,所以內核級線程的數量是有限的,擴展性比不上用戶級線程。”

 

用戶級線程(User-Level Threads ULT)

  • 用戶空間運行線程庫,任何應用程序都可以通過使用線程庫被設計成多線程程序。線程庫是用於用戶級線程管理的一個例程包,它提供多線程應用程序的開發和運行支撐環境,包含:用於創建和銷毀線程的代碼、在線程間傳遞數據和消息的代碼、調度線程執行的代碼以及保存和恢復線程上下文的代碼。
  • 所以線程的創建,消息傳遞,調度,保存/恢復上下文都有線程庫來完成。內核感知不到多線程的存在。內核繼續以進程為調度單位,並且給該進程指定一個執行狀態(就緒、運行、阻塞等)。

純用戶級線程的特點:

  1. 線程切換不需要內核模式,能節省模式切換開銷和內核資源。
  2. 允許進程按照特定的需要選擇不同的調度算法來調度線程。調度算法需要自己實現。
  3. 由於其不需要內核進行支持,所以可以跨OS運行。
  4. 不能利用多核處理器的優勢,OS調度進程,每個進程僅有一個ULT能執行
  5. 一個ULT阻塞,將導致整個進程的阻塞。

jacketing技術可以解決ULT一個線程阻塞導致整個進程阻塞。

jacketing的目標是把一個產生阻塞的系統調用轉化成一個非阻塞的系統調用。例如,當進程中的一個線程調用IO中斷前,先調用一個應用級的I/O jacket例程,而不是直接調用一個系統I/O。讓這個jacket例程檢查並確定I/O設備是否忙。如果忙,則jacketing將控制權交給該進程的線程調度程序,決定該線程進入阻塞狀態並將控制權傳送給另一個線程(若無就緒態線程咋可能執行進程切換)。

 

內核級線程(Kernel-Level Threads, KLT 也有叫做內核支持的線程)

  • 線程管理的所有工作(創建和撤銷)由操作系統內核完成
  • 操作系統內核提供一個應用程序設計接口API,供開發者使用KLT

純內核級線程特點:

  1. 進程中的一個線程被阻塞,內核能調度同一進程的其他線程(就緒態)占有處理器運行
  2. 多處理器環境中,內核能同時調度同一進程的多線程,將這些線程映射到不同的處理器核心上,提高進程的執行效率(這樣可以實現並行)。
  3. 應用程序線程在用戶態運行,線程調度和管理在內核實現。線程調度時,控制權從一個線程改變到另一線程,需要模式切換,系統開銷較大。

可以看出,用戶級線程和內核級線程都有各自的優點和缺點,在應用上主要表現為:

  • 用戶級多線程對於處理邏輯並行性問題有很好的效果。不擅長於解決物理並行問題。
  • 內核級多線程適用於解決物理並行性問題。

 

切換的代價

當程序中有系統調用語句,程序執行到系統調用時,首先使用類似int 80H的軟中斷指令,保存現場,去的系統調用號,在內核態執行,然后恢復現場。
每個進程都會有兩個棧,一個內核態棧和一個用戶態棧。當執行int中斷執行時就會由用戶態棧轉向內核棧。系統調用時需要進行棧的切換。(線程上下文切換,保留現場、恢復現場,因此開銷大)
而且內核代碼對用戶不信任,需要進行額外的檢查。
系統調用的返回過程有很多額外工作,比如檢查是否需要調度等。

str = "my string" // 用戶空間
x = x + 2
file.write(str) // 切換到內核空間
 
y = x + 4 // 切換回用戶空間

 

線程實現的組合策略

由操作系統內核支持內核級多線程,由操作系統的程序庫來支持用戶級多線程,線程創建完全在用戶空間創建,線程的調度也在應用程序內部進行,然后把用戶級多線程映射到(或者說是綁定到)一些內核級多線程。編程人員可以針對不同的應用特點調節內核級線程的數目來達到物理並行性和邏輯並行性的最佳方案。

多對一(Many to One)

用戶態進程中的多線程復用一個內核態線程。這樣,極大地減少了創建內核態線程的成本,但是線程不可以並行。因此,這種模型現在基本上用的很少。我再多說一句,這里你可能會有疑問,比如:用戶態線程怎么用內核態線程執行程序?

程序是存儲在內存中的指令,用戶態線程是可以准備好程序讓內核態線程執行的。

一對一(One to One)

該模型為每個用戶態的線程分配一個單獨的內核態線程,在這種情況下,每個用戶態都需要通過系統調用創建一個綁定的內核線程,並附加在上面執行。 這種模型允許所有線程並行執行,能夠充分利用多核優勢,Windows NT 內核采取的就是這種模型。但是因為線程較多,對內核調度的壓力會明顯增加。

多對多(Many To Many)

這種模式下會為 n 個用戶態線程分配 m 個內核態線程。m 通常可以小於 n。一種可行的策略是將 m 設置為核數。這種多對多的關系,減少了內核線程,同時也保證了多核心並行。Linux 目前采用的就是該模型。

兩層設計(Two Level)

這種模型混合了多對多和一對一的特點。多數用戶態線程和內核線程是 n 對 m 的關系,少量用戶線程可以指定成 1 對 1 的關系。

附加知識:內核長什么樣?

AMD Zen3架構:這是Zen3架構的一個CCX模塊,如今也等效於一顆CCD芯片,包含八個CPU核心、4MB二級緩存、32MB三級緩存等。

   

這是一個雙核心的Pentium D的CPU。

 

 

參考文章:

用戶級線程和內核級線程,你分清楚了嗎?

內核級線程(KLT)和用戶級線程(ULT)

《計算機操作系統(第4版)》湯小丹、湯子瀛

用戶態/內核態/線程/協程_牛客博客 (nowcoder.net)

拉勾教育 - 重學操作系統,講師:林䭽  前阿里巴巴高級技術專家(P8)

如何理解處理器、CPU、多處理器、內核、多核? - 木頭龍的回答 - 知乎

顯微鏡下看,芯片內部長啥樣?來,看看AMD Zen3內核

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM