操作系統用戶級線程能夠調用內核嗎?

最近在學習操作系統的進程和線程

然後發現書上在講純用戶級線程提到

進程b有兩個線程1和2 當線程2阻塞導致控制權轉移內核 內核實行i/o操作把進程b置於阻塞狀態

這裡是純用戶進程 不是內部線程無法調用內核嗎?

那為什麼線程2阻塞是調用i o?

這種情況下進程b處於阻塞,而線程2處於運行態(書上的寫明),是不是因為進程b還沒調用線程庫將線程2設為阻塞態?


至少在Windows裡面並沒有什麼「進程處於阻塞狀態」的這個概念,不知道書里講的是什麼內容。在Windows裡面,你調用一個函數結果進了內核態,雖然發生了很多事情,但總的來說其實是,系統把你的線程停了,弄了個新的棧,復用你的線程,把東西跑完了,結果給你。

Windows的一個線程也不一定只有一個函數在跑,你調用IO函數的話,就會有很多APC一樣的東西進來,雖然都在同一個線程里,但是跑的是不同的函數,就像堆棧本身也有一個堆棧一樣(只是概念上的)。棧底的函數正在WaitForSingleObjectEx,系統又push了一個函數(譬如說APC)進去這個進程裡面跑。

見:Asynchronous Procedure Calls


用戶級線程在內核眼中是不存在的,內核能看到的只有進程

所以某個用戶級線程調用了會阻塞的系統調用,在內核看來就是進程調用了這個系統調用,然後內核會阻塞整個進程以及它下面的所有線程


用戶級線程可以調用內核,阻塞的時候全部用戶線程阻塞。那麼用戶線程如何調度呢,答案是非同步io,所有io操作使用非同步模式,然後切換到其他用戶線程。


如果學習操作系統只看教科書,等於是紙上談兵,只要你有C的基礎,可以直接看Unix的源碼,不到10000行。

推薦我現在正在翻譯的MIT操作系統工程課程的書籍,主要分析的是xv6教學操作系統,是Unix v6在X86架構上的ANSI標準C實現,還添加了多核的支持。非常適合學習操作系統的基礎知識。xv6-book是仿照萊昂氏unix源碼分析(至於這本書有多經典,看看萊昂氏UNIX源代碼分析 (豆瓣))寫的。

項目地址:deyuhua/xv6-book

有興趣可以去看看

洗洗睡了。。。


更:推薦閱讀 Modern Operating Systems (Amazon.com)

的用戶態線程一節: 2.2.4: Implementing thread in user space

------

這裡純用戶進程 不是內部線程無法調用內核嗎
那為什麼線程2阻塞是調用i o?

如果單純看這個問題,答案應該是可以。

操作系統的功能本來就是提供各種服務,如果用戶態的程序無法調用內核,那還要內核有什麼用呢。當然,這裡的調用不是直接進行函數調用,而是通過系統調用等手段的間接的、受控制的調用。

具體到你的情況,即用戶態線程能不能這樣做,答案當然還是可以。線程庫沒有能力阻止用戶線程直接進行系統調用請求內核服務。舉個例子,比如某線程2完全可以直接調用read()請求進行讀文件操作。即使C庫或者線程庫對read進行了包裝/重定向,線程2完全可以直接以trap指令(例如x86的int x80或者sysenter指令)來調用read,所以沒有不能調用內核一說。

由於read()是blocking調用(可能導致用戶程序被阻塞),而一旦這種阻塞發生,內核將直接阻塞進程b,因此即使線程1本來可以運行,在內核的作用下線程1、2都將無法運行,直到線程2的讀文件請求不再被阻塞。這其實就是純用戶態線程最大的痛點——blocking的系統調用會導致整個進程被阻塞。

還有這種情況是不是導致了進程b處於阻塞而線程2處於運行因為進程b還沒調用線程庫將線程2設為阻塞態?

這麼長的一個句子,連個逗號都沒有。。。你真的覺得我們能看懂嗎

你這種說法雖然有一定道理,但未免顯得有些業餘。

為什麼有道理:

  1. 由於內核不知道線程1、2的存在(用戶態線程),因此線程2調用read調用的時候,內核只知道這是進程b的一次系統調用,因此阻塞發生時,內核把進程b在內核的狀態設為阻塞態。
  2. 由於線程2對read的調用可能繞過了線程庫,因此線程庫的代碼(比如說某個管理線程)還沒來得及執行,因此線程2在進程b的視角下(即在線程庫的線程狀態表中),確實是非阻塞態,即運行態的

為什麼說有點業餘:通過上面的分析我們已經看到,一個說的是進程在內核的狀態,一個說的是線程在線程庫中的狀態,是兩個層面的東西,實際上它們之間不是充要條件的關係,不需要完全保持同步。實際上,進程在內核態可運行是線程可以真正運行的必要條件。

進一步說,這樣看似「矛盾」的情況會不會出問題呢?其實沒關係,雖然進程b還認為線程1、2都能運行,但實際上整個進程都在掛起,進程b這樣這時候想什麼都沒有任何意義。只有進程b恢復了在內核的可運行狀態,進程b對線程的控制才會奏效。


這裡有幾個概念你可能弄混了:用戶態、內核態、調用內核、訪問內核服務、調用與調度、函數調用

操作系統運行時分為用戶態和內核態這兩種狀態,又稱用戶空間與內核空間,首先明確下用戶態是沒辦法直接調用內核的代碼的,用戶態(包括你提到的用戶線程)要想調用內核必須通過操作系統提供的系統調用,這個系統調用可以簡單理解為API(比如read、write、ioctl、malloc等),實際上呢我們日常編程訪問的API是庫封裝後的函數,並不是系統調用本身,但是對於一般的編程不用嚴格區分;

嚴格講沒有調用內核這個概念,我的理解你是指訪問內核,讓內核提供服務,訪問內核服務我們前面解釋過通過調用系統調用即可實現;另外你可能把調用跟調度弄混了:調用的英文是call,一個函數調用另一個函數即調用(比如main 函數調用myfun函數);調度的英文單詞是schedule,在操作系統這個語境下調度永遠只能由內核來完成。

下面解釋下調度(schedule):這裡為了簡單假設計算機只有一個單核CPU,調度是由操作系統內核實現,調度是為了選擇一個進程或者線程在CPU上運行,系統上運行的所有線程和進程(Linux操作系統不區分進程跟線程)在操作系統內核里都有登記,內核以隊列(queue)數據結構登記管理這些進程/線程,內核從這個隊列數據結構中挑選一個進程執行就是一次進程調度。

這裡明白了調度的概念,接下來問題是什麼時候會觸發一次進程調度呢:假設隊列中有兩個進程A、B,假設現在A正在執行,那麼什麼時候會觸發調度使得放棄A的執行呢 1)A主動放棄執行,比如A執行wait系統調用(比如執行sleep 10,級睡眠10秒) ;2)A調用read系統調用讀取一個文件,即執行I/O操作; 3)A進程執行完畢,即A退出執行; 4)給A分配的時間片用完了,schedule會查看下隊列是否有其他進程等待執行,假設B剛好處於ready可執行狀態,則切換到B;5)用戶/或系統創建了一個新進程C,並且C的優先順序比A的更高。

看了上面對調度的解釋(schedule),我們知道極端情況下系統可能處於空閑狀態,比如A、B都在等待I/O完成,或者A、B都執行完畢退出了,沒有創建C進程,這種情況下系統處於空轉狀態,即idle任務出來執行。


用戶態與內核態都有自己對應的線程函數。。只不過是內核態線程運行在內核態而已。。


系統調用,就是用戶態進城使用內核功能的介面


你想問協同式多任務(非搶佔式多任務)的實現原理?

用戶線程本質也是一個系統線程來,時間片一旦交出或者被搶走,只能等系統再次把時間片分回來才能執行了。題主所謂純用戶線程是pthead的意思么?


進程和線程都是被內核調度的,進程和線程對內核來講本質是一樣的,進程和線程本身就是代碼,如果調用了系統調用,也許就算調用內核吧,其實說進入更準確一點。


推薦閱讀:

TAG:操作系統 | 線程 | 操作系統內核 | 進程 | 多線程編程 |