操作系统随笔(二)
如果你還沒(méi)有讀過(guò)第一篇隨筆,請(qǐng)點(diǎn)擊這里→操作系統(tǒng)隨筆(一)
文章目錄
- @[toc]
- 2 進(jìn)程和線程
- 2.1 進(jìn)程
- 2.1.1 進(jìn)程模型
- 2.1.2 進(jìn)程的創(chuàng)建
- 2.1.3 進(jìn)程的終止
- 2.1.4 進(jìn)程的層次結(jié)構(gòu)
- 2.1.5 進(jìn)程的狀態(tài)
- 2.1.6 進(jìn)程的實(shí)現(xiàn)
- 2.2 線程
- 2.2.1 進(jìn)程的使用
- 2.2.2 經(jīng)典的線程模型
- 2.2.3 POSIX線程
- 2.2.4 在用戶(hù)空間中實(shí)現(xiàn)線程
- 2.2.5 在內(nèi)核中實(shí)現(xiàn)線程
- 2.2.6 混合實(shí)現(xiàn)
- 2.2.7 調(diào)度程序激活機(jī)制
- @[toc]
- 2.1 進(jìn)程
- 2.1.1 進(jìn)程模型
- 2.1.2 進(jìn)程的創(chuàng)建
- 2.1.3 進(jìn)程的終止
- 2.1.4 進(jìn)程的層次結(jié)構(gòu)
- 2.1.5 進(jìn)程的狀態(tài)
- 2.1.6 進(jìn)程的實(shí)現(xiàn)
- 2.2 線程
- 2.2.1 進(jìn)程的使用
- 2.2.2 經(jīng)典的線程模型
- 2.2.3 POSIX線程
- 2.2.4 在用戶(hù)空間中實(shí)現(xiàn)線程
- 2.2.5 在內(nèi)核中實(shí)現(xiàn)線程
- 2.2.6 混合實(shí)現(xiàn)
- 2.2.7 調(diào)度程序激活機(jī)制
2 進(jìn)程和線程
在操作系統(tǒng)中,最核心的概念是進(jìn)程,這是對(duì)正在運(yùn)行程序的一個(gè)抽象。操作系統(tǒng)的其他內(nèi)容都是圍繞進(jìn)程的概念來(lái)展開(kāi)的。
2.1 進(jìn)程
在只有一個(gè)用戶(hù)的PC機(jī)開(kāi)機(jī)的時(shí)候,實(shí)際上會(huì)秘密啟動(dòng)很多進(jìn)程。例如,啟動(dòng)一個(gè)進(jìn)程用來(lái)等待進(jìn)入的電子郵件;或者啟動(dòng)另一個(gè)防病毒進(jìn)程周期性地檢查是否有病毒庫(kù)更新。或者更好笑的是,一開(kāi)機(jī)就是垃圾捆綁軟件,什么2345,什么網(wǎng)頁(yè)游戲,這些都是進(jìn)程。這么多進(jìn)程的活動(dòng)都是需要管理的,于是有一個(gè)支持多進(jìn)程的多道程序系統(tǒng)在這里顯得就很有用了。
在任何多道程序設(shè)計(jì)系統(tǒng)中,CPU能夠很快地切換進(jìn)程,這個(gè)很快是幾百毫秒哦。這也就讓人產(chǎn)生一種并行的錯(cuò)覺(jué),在一秒鐘內(nèi)怎么開(kāi)了這么多進(jìn)程?同時(shí)開(kāi)的嗎?不是,實(shí)際上在一瞬間只能有一個(gè)進(jìn)程讓CPU服務(wù),只是進(jìn)程切換地太快了,這就是偽并行。這和真正意義上的并行是有區(qū)別的,這也導(dǎo)致了此情形可以用來(lái)作為判別是否為多處理器系統(tǒng)的指標(biāo)。
2.1.1 進(jìn)程模型
在進(jìn)程模型中,計(jì)算機(jī)上所有可運(yùn)行的軟件,通常也包括操作系統(tǒng),被組織成若干順序進(jìn)程,簡(jiǎn)稱(chēng)進(jìn)程(process),進(jìn)程是程序的一次執(zhí)行過(guò)程。
每個(gè)進(jìn)程都擁有自己的虛擬CPU,當(dāng)然,實(shí)際上真正的CPU在各進(jìn)程之間來(lái)回切換。我們?cè)谥暗?.1.2 中時(shí)間復(fù)用技術(shù)曾經(jīng)提到過(guò),當(dāng)一個(gè)資源在時(shí)間上復(fù)用時(shí),不同的程序或用戶(hù)輪流使用它。實(shí)際上對(duì)于CPU來(lái)說(shuō)也是如此,在時(shí)間上進(jìn)行復(fù)用的時(shí)候,不同的進(jìn)程輪流使用它。這種快速地切換是需要特定的設(shè)計(jì)的,我們稱(chēng)為多道程序設(shè)計(jì)。
如下圖,在一段時(shí)間內(nèi),CPU為多個(gè)進(jìn)程服務(wù),但是觀察c圖,實(shí)際上在某個(gè)瞬間CPU只服務(wù)一個(gè)進(jìn)程。
當(dāng)然在上述的思考中,我們僅僅討論的是單核CPU,而不是多核。如果是多核CPU,根據(jù)我們之前所說(shuō),多核CPU可以看成一個(gè)大CPU里面裝了多個(gè)小的CPU;甚至于有的電腦還不止一個(gè)CPU,對(duì)于一些并行計(jì)算機(jī),多處理器的情況也是很常見(jiàn)的。
對(duì)于大多數(shù)進(jìn)程來(lái)說(shuō)并不受CPU多道程序設(shè)計(jì)或其他進(jìn)程相對(duì)速度的影響,因?yàn)槊總€(gè)進(jìn)程占用所需CPU的時(shí)間是不同的,所以我們無(wú)法確定在快速切換進(jìn)程的過(guò)程中,需要給每個(gè)進(jìn)程多久的處理時(shí)間處理完才切換。這時(shí)候就迫切的需要干一件事,既然我無(wú)法確定一個(gè)進(jìn)程需要多久的CPU,那我干脆每個(gè)進(jìn)程占有CPU的時(shí)間都相同,但是在對(duì)一個(gè)進(jìn)程處理未完的情況下,我需要有一種物件能夠保存其未處理完的狀態(tài),就像別人玩單機(jī)游戲玩到一半去喝水一樣。在后面的小節(jié)中,我們會(huì)給出這個(gè)物件。
2.1.2 進(jìn)程的創(chuàng)建
有4種主要事件會(huì)導(dǎo)致進(jìn)程的創(chuàng)建:
- 系統(tǒng)初始化
- 正在運(yùn)行的程序執(zhí)行了創(chuàng)建進(jìn)程的系統(tǒng)調(diào)用
- 用戶(hù)請(qǐng)求創(chuàng)建一個(gè)新進(jìn)程
- 一個(gè)批處理作業(yè)的初始化
啟動(dòng)操作系統(tǒng)時(shí),通常會(huì)創(chuàng)建若干個(gè)進(jìn)程,有些事可以同用戶(hù)交互并且替他們工作的前臺(tái)進(jìn)程,其余的為后臺(tái)進(jìn)程。如果想要查看進(jìn)程,在windows操作系統(tǒng)中可以使用任務(wù)管理器,在linux系統(tǒng)中可以用ps指令。
在像windows和ubuntu這樣的交互式系統(tǒng)中,點(diǎn)擊某個(gè)圖標(biāo)都可以啟動(dòng)一個(gè)程序,啟動(dòng)的時(shí)候就相當(dāng)于開(kāi)啟了一個(gè)新的進(jìn)程。
在一個(gè)進(jìn)程開(kāi)始的時(shí)候,可以不打開(kāi)窗口,也可以打開(kāi)一個(gè)或多個(gè)窗口,用戶(hù)可以用鼠標(biāo)和鍵盤(pán)在窗口內(nèi)與進(jìn)程交互,比如打開(kāi)QQ的時(shí)候和別人用鍵盤(pán)打字聊天。
還有一種情況是在大型的批處理系統(tǒng)中,在操作系統(tǒng)認(rèn)為有資源可以運(yùn)行另一個(gè)作業(yè)時(shí),它創(chuàng)建一個(gè)新的進(jìn)程,并運(yùn)行其輸入隊(duì)列中的下一個(gè)作業(yè)。
我們知道系統(tǒng)調(diào)用的作用之一是控制進(jìn)程。在Unix系統(tǒng)中,只有一個(gè)系統(tǒng)調(diào)用可以用來(lái)創(chuàng)建進(jìn)程,即fork。而在Windows中則是用Win32函數(shù)調(diào)用CreateProcess來(lái)負(fù)責(zé)進(jìn)程的創(chuàng)建和程序裝入進(jìn)程的過(guò)程。除了CreateProcess,Win32還有大約100個(gè)其他的函數(shù)用于進(jìn)程的管理。
2.1.3 進(jìn)程的終止
有4種主要事件會(huì)導(dǎo)致進(jìn)程的終止:
- 正常退出
- 出錯(cuò)退出
- 嚴(yán)重錯(cuò)誤
- 被其他進(jìn)程殺死
正常退出就沒(méi)什么好說(shuō)了,Unix用的是exit,Windows調(diào)用的是ExitProcess。
出錯(cuò)退出一般還好說(shuō),如果用戶(hù)在Linux鍵入命令cc foo.c要編譯文件foo.c,但是該文件不存在,那么編譯器就會(huì)退出。
如果是嚴(yán)重錯(cuò)誤,比如分母為0,數(shù)組越界,空指針異常這類(lèi)錯(cuò)誤,那么進(jìn)程會(huì)收到信號(hào)然后中斷。
最后一種在linux很常見(jiàn)的就是kill命令,利用kill 進(jìn)程號(hào)可以殺死一個(gè)進(jìn)程,而在Win32用的則是TerminateProcess函數(shù)。
2.1.4 進(jìn)程的層次結(jié)構(gòu)
在前面,我們?cè)?jīng)提到父子進(jìn)程這個(gè)名詞,那么什么是父子進(jìn)程呢?
在Unix中,通過(guò)fork函數(shù)創(chuàng)建的新進(jìn)程是原進(jìn)程的子進(jìn)程,而調(diào)用fork函數(shù)的進(jìn)程是fork函數(shù)創(chuàng)建出來(lái)的新進(jìn)程的父進(jìn)程。也就是說(shuō),通過(guò)fork函數(shù)創(chuàng)建的新進(jìn)程與原進(jìn)程是父子關(guān)系,fork就相當(dāng)于一個(gè)憑證,有fork,就有父子關(guān)系。
但是這也有一個(gè)問(wèn)題,我們學(xué)過(guò)java的都知道,繼承的父類(lèi)和子類(lèi)共享屬性,那么對(duì)應(yīng)到這里的父子進(jìn)程是否也有共享資源的說(shuō)法呢?
事實(shí)是,父進(jìn)程和子進(jìn)程的共享方式采用的是寫(xiě)時(shí)復(fù)制,即兩個(gè)進(jìn)程在讀資源的時(shí)候的確是共享,但是在寫(xiě)資源的時(shí)候,寫(xiě)資源的那個(gè)進(jìn)程先把資源拷貝一份然后進(jìn)行操作,操作完然后在覆蓋到原來(lái)的資源上,在這個(gè)過(guò)程中我們可以發(fā)現(xiàn),可寫(xiě)的內(nèi)存時(shí)不可以被共享的。
經(jīng)過(guò)上面的說(shuō)明,我們可以大概知道這么個(gè)事,子進(jìn)程是父進(jìn)程創(chuàng)建出來(lái)的。父進(jìn)程可以有多個(gè)子進(jìn)程,但是子進(jìn)程只可以有一個(gè)父進(jìn)程。在Unix中,父進(jìn)程和所有子進(jìn)程組成了一個(gè)進(jìn)程組,當(dāng)一個(gè)信號(hào)傳入進(jìn)程組,進(jìn)程組的每個(gè)進(jìn)程成員皆可以捕獲該信號(hào),并且采取相應(yīng)的動(dòng)作。
但是在Windows則沒(méi)有這些說(shuō)法,所有的進(jìn)程地位都是相同的。
2.1.5 進(jìn)程的狀態(tài)
進(jìn)程之間時(shí)常要相互作用,如Linux命令:cat chapter1 | grep tree,這個(gè)命令啟動(dòng)了兩個(gè)進(jìn)程,一個(gè)是cat,它將chapter文件進(jìn)行輸出;一個(gè)是grep,它在cat輸出的文件中去搜索含有tree的那些單詞。
從這個(gè)過(guò)程我們可以發(fā)現(xiàn)一件事,如果cat進(jìn)程還沒(méi)好,grep進(jìn)程就無(wú)法運(yùn)行。當(dāng)一個(gè)進(jìn)程在邏輯上不能繼續(xù)運(yùn)行時(shí),他就會(huì)被阻塞。
經(jīng)過(guò)上面的敘述,我們引入最簡(jiǎn)單的三種狀態(tài):
| 運(yùn)行態(tài) | 該時(shí)刻進(jìn)程實(shí)際占用CPU |
| 就緒態(tài) | 可運(yùn)行,但是因?yàn)槠渌M(jìn)程正在運(yùn)行而暫時(shí)停止 |
| 阻塞態(tài) | 除非某種外部事件發(fā)生,否則進(jìn)程不能運(yùn)行 |
對(duì)于就緒態(tài)來(lái)說(shuō),很多人可能會(huì)和阻塞態(tài)搞混;實(shí)際上,就緒態(tài)是萬(wàn)事俱備只欠CPU,即資源都準(zhǔn)備好了但是沒(méi)有CPU給它用,而阻塞態(tài)則像我們上面引入的例子,因?yàn)槟硞€(gè)事情還沒(méi)做好而導(dǎo)致其處于一個(gè)等待(阻塞)狀態(tài),這也是為什么有時(shí)候阻塞態(tài)被稱(chēng)為等待態(tài)的原因。
特別典型的例子是C++中我們可以使用
system("pause");來(lái)讓該進(jìn)程處于阻塞狀態(tài)。
轉(zhuǎn)換上圖的2和3是由進(jìn)程調(diào)度程序引起的,進(jìn)程調(diào)度程序是操作系統(tǒng)的一部分。實(shí)際上進(jìn)程調(diào)度也被叫做低級(jí)調(diào)度,當(dāng)系統(tǒng)認(rèn)為一個(gè)運(yùn)行進(jìn)程占用處理器太久了,他就會(huì)讓其他進(jìn)程去占用CPU,此時(shí)發(fā)生轉(zhuǎn)換2;當(dāng)系統(tǒng)已經(jīng)讓所有的程序都占用過(guò)CPU了,公平了,那么這個(gè)時(shí)候就會(huì)“重新洗牌”,第一個(gè)進(jìn)程再次占有CPU,此時(shí)發(fā)生轉(zhuǎn)換3;當(dāng)進(jìn)程等待的一個(gè)外部事件發(fā)生時(shí),則發(fā)生轉(zhuǎn)換4,當(dāng)CPU此時(shí)空閑,則可以發(fā)生轉(zhuǎn)換3,該進(jìn)程立刻運(yùn)行,否則該進(jìn)程將處于就緒態(tài),等待CPU空閑。
2.1.6 進(jìn)程的實(shí)現(xiàn)
為了實(shí)現(xiàn)進(jìn)程模型,操作系統(tǒng)維護(hù)著一張表格,即進(jìn)程表。每個(gè)進(jìn)程都占有一條元組(數(shù)據(jù)庫(kù)的說(shuō)法),每條元組即PCB(進(jìn)程控制塊),該元組中包含了進(jìn)程狀態(tài)的重要信息,包括程序計(jì)數(shù)器、堆棧指針、內(nèi)存分配情況、所打開(kāi)文件的狀態(tài)、賬號(hào)和調(diào)度信息以及其他在進(jìn)程由運(yùn)行態(tài)轉(zhuǎn)換為就緒態(tài)或阻塞態(tài)時(shí)必須保存的信息,從而保證該進(jìn)程隨后能夠再次啟動(dòng)。
一個(gè)進(jìn)程在執(zhí)行過(guò)程中可能被中斷數(shù)千次,但由于PCB的存在,即使中斷,也可以返回到發(fā)生中斷前完全相同的狀態(tài)。
2.2 線程
在很久以前還沒(méi)有引入進(jìn)程之前,系統(tǒng)中的各個(gè)程序只能串行執(zhí)行。比如你想要邊聽(tīng)歌邊開(kāi)QQ,這是不可能做到的,只能先做一件事再做一件事。
后來(lái)引入進(jìn)程后,系統(tǒng)中的各個(gè)程序可以并發(fā)執(zhí)行。也就是說(shuō),可以同時(shí)聽(tīng)歌和開(kāi)QQ。但是,即使引入了進(jìn)程,也不能在QQ中同時(shí)視頻聊天和傳輸文件。這是因?yàn)椴僮飨到y(tǒng)每一次執(zhí)行都是按照進(jìn)程為單位來(lái)執(zhí)行的。
從上面的例子來(lái)看,進(jìn)程是程序的一次執(zhí)行。但是這些功能顯然不可能是由一個(gè)程序順序處理就能實(shí)現(xiàn)的。
有的進(jìn)程可能需要“同時(shí)做很多事”,而傳統(tǒng)的進(jìn)程只能串行地執(zhí)行一系列程序。為此,引入了線程來(lái)提高并發(fā)度。
在傳統(tǒng)中,進(jìn)程是程序執(zhí)行流的最小單位,也就是說(shuō),CPU每次執(zhí)行任務(wù),最少執(zhí)行一個(gè)進(jìn)程。而后在現(xiàn)在,CPU每次執(zhí)行任務(wù),最少執(zhí)行一個(gè)線程,線程是進(jìn)程的子集。也就是說(shuō),引入線程后,線程成為了程序執(zhí)行流的最小單位。
綜上所述,我們可以把線程理解為“輕量級(jí)進(jìn)程”。線程是一個(gè)基本的CPU執(zhí)行單元,也是程序執(zhí)行流的最小單位。引入線程之后,不僅是進(jìn)程之間可以并發(fā),進(jìn)程內(nèi)的各線程之間也可以并發(fā),從而進(jìn)一步提升了系統(tǒng)的并發(fā)度,使得一個(gè)進(jìn)程內(nèi)也可以并發(fā)處理各種任務(wù)(如QQ視頻、文字聊天、傳文件)。引入線程后,進(jìn)程只作為除CPU之外的系統(tǒng)資源的分配單元(如打印機(jī)、內(nèi)存地址空間等都是分配給進(jìn)程的)。
2.2.1 進(jìn)程的使用
如上所說(shuō),為了追求多功能的同時(shí)運(yùn)行,輕量級(jí)進(jìn)程顯得尤為重要,線程比進(jìn)程更輕量級(jí),它們比進(jìn)程更容易創(chuàng)建,也更容易撤銷(xiāo)。
舉一個(gè)例子說(shuō)明為什么線程更容易創(chuàng)建,也更容易撤銷(xiāo)。假如我們用電腦寫(xiě)一本書(shū),通常的做法是創(chuàng)建一個(gè)doc文件直接寫(xiě),這樣的話(huà)如果中間要查詢(xún)某個(gè)東西非常方便,你只需要用WPS自帶的查找或者Office的查找都可以完成這個(gè)工作,這就是一個(gè)進(jìn)程內(nèi)含有多個(gè)線程的現(xiàn)實(shí)模型。
但是如果你采用創(chuàng)建一個(gè)文件夾,一個(gè)文件夾內(nèi)含有多個(gè)章節(jié)的文件,那么你每次要處理某個(gè)章節(jié)的一小段都需要完成打開(kāi)文件,修改內(nèi)容,關(guān)閉文件等一系列操作,十分麻煩。
綜上所述,我們可以總結(jié)如下:
| 傳統(tǒng)進(jìn)程機(jī)制中,進(jìn)程是資源分配、調(diào)度的基本單位 | 傳統(tǒng)進(jìn)程機(jī)制中,只能進(jìn)程間并發(fā) | 傳統(tǒng)的進(jìn)程間并發(fā),需要切換進(jìn)程的運(yùn)行環(huán)境,系統(tǒng)開(kāi)銷(xiāo)很大。 |
| 引入線程后,進(jìn)程是資源分配的基本單位,線程是調(diào)度的基本單位 | 引入線程后,各線程間也能并發(fā),提高了并發(fā)度 | 線程間并發(fā),如果是同一進(jìn)程內(nèi)的線程切換,則不需要切換進(jìn)程環(huán)境,系統(tǒng)開(kāi)銷(xiāo)小,也就是說(shuō)引入線程后,并發(fā)所帶來(lái)的系統(tǒng)開(kāi)銷(xiāo)減小。 |
2.2.2 經(jīng)典的線程模型
進(jìn)程模型基于兩種獨(dú)立的概念:資源分組處理和執(zhí)行。引入線程后,由于一個(gè)進(jìn)程含有多個(gè)線程,所以功能的執(zhí)行依賴(lài)于線程的切換,而不必使用開(kāi)銷(xiāo)更大的進(jìn)程切換,線程的切換即涉及到線程的調(diào)度;而對(duì)于多個(gè)線程來(lái)說(shuō),其使用的是同一個(gè)資源,程序所需的資源分配的基本單位是進(jìn)程而不是線程,多個(gè)線程共享同一個(gè)資源。
在每個(gè)線程中,通常帶有一個(gè)程序計(jì)數(shù)器、寄存器和堆棧指針,這和進(jìn)程是十分類(lèi)似的。線程給進(jìn)程模型增加的內(nèi)容即同一個(gè)進(jìn)程可以有多個(gè)線程,其切換我們?cè)谇懊嬉蔡岬竭^(guò),CPU允許多線程切換納秒級(jí)完成。
和傳統(tǒng)進(jìn)程一樣,線程也有進(jìn)程所擁有的進(jìn)程狀態(tài)。在Windows中線程的創(chuàng)建時(shí)通過(guò)調(diào)用庫(kù)函數(shù)thread_create創(chuàng)建的,調(diào)用庫(kù)函數(shù)thread_exit進(jìn)行退出。
2.2.3 POSIX線程
為了實(shí)現(xiàn)可移植的線程程序,IEEE定義了線程的標(biāo)準(zhǔn)。它定義的線程包叫做pthread,大部分UNIX系統(tǒng)支持該標(biāo)準(zhǔn)。這個(gè)標(biāo)準(zhǔn)定義了超過(guò)60個(gè)函數(shù)調(diào)用。常見(jiàn)的幾個(gè)如下所示:
2.2.4 在用戶(hù)空間中實(shí)現(xiàn)線程
有兩種主要的方法實(shí)現(xiàn)線程包:在用戶(hù)空間中和內(nèi)核中。
第一種方法是把整個(gè)線程包放在用戶(hù)空間中,內(nèi)核對(duì)線程包一無(wú)所知。這樣的話(huà)就會(huì)出現(xiàn)一個(gè)問(wèn)題,即使用戶(hù)開(kāi)多條線程,內(nèi)核還是以為只有一個(gè)進(jìn)程,因?yàn)樗⒉恢肋M(jìn)程內(nèi)發(fā)生了啥,所以這時(shí)候就會(huì)出現(xiàn)單線程進(jìn)程。即使多個(gè)線程處理機(jī)也是分配其中一個(gè)。
以上的情況比較明顯地體現(xiàn)是在Java中利用thread開(kāi)啟多線程,多條線程的執(zhí)行并不是并行地,而是并發(fā)地,你可以在兩條線程中各自打印一點(diǎn)東西,然后同時(shí)啟動(dòng)你就能了解到效果了。
在用戶(hù)空間管理線程時(shí),每個(gè)進(jìn)程需要有其專(zhuān)用的線程表,用拉力跟蹤該進(jìn)程中的線程。這些表和內(nèi)核中的進(jìn)程表類(lèi)似,不過(guò)它們僅僅記錄各個(gè)線程的屬性,如每個(gè)線程的程序計(jì)數(shù)器、堆棧指針、寄存器和狀態(tài)等。
用戶(hù)級(jí)線程有一個(gè)優(yōu)點(diǎn)是,它允許每個(gè)進(jìn)程有自己定制的調(diào)度算法,并且其具有較好的擴(kuò)展性,你可以多開(kāi)幾條線程,但是在內(nèi)核空間中萬(wàn)一開(kāi)多了線程是會(huì)出現(xiàn)問(wèn)題的。
其另外一個(gè)優(yōu)點(diǎn)是,在用戶(hù)級(jí)線程下,線程的切換開(kāi)銷(xiāo)小,其無(wú)需切換為核心態(tài);如果是內(nèi)核級(jí)切換線程,需要陷入內(nèi)核,開(kāi)銷(xiāo)較大。
有一個(gè)問(wèn)題我們前面提到,如果是在用戶(hù)空間下實(shí)行多線程,那么實(shí)際上處理器的占用取決于內(nèi)核中有多少條線程。如果用戶(hù)空間里有6條線程,而內(nèi)核中只有一條線程,那么實(shí)際處理線程時(shí),處理器只會(huì)用到一個(gè)。也就是說(shuō),如果在用戶(hù)空間下實(shí)行多線程,那么一旦有一條線程阻塞,那么其他所有線程都將阻塞。
系統(tǒng)調(diào)用實(shí)際上是可以全部改成非阻塞的,但是這需要修改操作系統(tǒng)。還有一種替代方案是某個(gè)調(diào)用如果阻塞了就提前通知,但是這個(gè)處理方法需要重寫(xiě)部分系統(tǒng)調(diào)用庫(kù),所以也不太好,但是可惜的是,沒(méi)有第三種方法了。
用戶(hù)級(jí)線程包的最后一個(gè)問(wèn)題是,如果一個(gè)線程開(kāi)始運(yùn)行,那么在該進(jìn)程中的其他線程就不能運(yùn)行,除非第一個(gè)線程自動(dòng)放棄CPU,這和我們前面所講的是一樣的。需要知道的是,在一個(gè)單獨(dú)的進(jìn)程內(nèi)部是沒(méi)有時(shí)鐘中斷的,所以也就不可能以輪轉(zhuǎn)調(diào)度的方式調(diào)度線程。所以除非某個(gè)線程能夠按照自己的意志進(jìn)行運(yùn)行時(shí)系統(tǒng),要不然調(diào)度程序是沒(méi)有任何機(jī)會(huì)的。
2.2.5 在內(nèi)核中實(shí)現(xiàn)線程
如圖所示,此時(shí)如果是在內(nèi)核中實(shí)現(xiàn)線程,那么不需要運(yùn)行時(shí)系統(tǒng)了。內(nèi)核實(shí)現(xiàn)線程的情況下,進(jìn)程表和線程表都由內(nèi)核控制。根據(jù)我們上一小節(jié)所說(shuō),內(nèi)核控制的線程可以按照線程數(shù)來(lái)分配處理器,當(dāng)一個(gè)線程阻塞時(shí),內(nèi)核會(huì)自動(dòng)切換另外一個(gè)線程。
雖然使用內(nèi)核線程可以解決阻塞等諸多問(wèn)題,但也不是一勞永逸,內(nèi)核級(jí)線程的管理工作由操作系統(tǒng)內(nèi)核完成。線程調(diào)度、切換等工作都由內(nèi)核負(fù)責(zé),因此內(nèi)核級(jí)線程的切換必然需要在核心態(tài)下才能完成,線程管理的成本高,開(kāi)銷(xiāo)大。
2.2.6 混合實(shí)現(xiàn)
在前面,我們說(shuō)的兩種情況如圖所示:
這兩個(gè)圖在有的書(shū)中也被叫做多對(duì)一模型和一對(duì)多模型。
但是既然這兩種模型各有各的缺點(diǎn),為什么不聯(lián)合起來(lái)呢?將用戶(hù)級(jí)線程和某些內(nèi)核線程多路復(fù)用起來(lái),這在一些書(shū)上叫做多對(duì)多模型,如圖所示:
采用這種方法的特點(diǎn)是:內(nèi)核還是只能識(shí)別內(nèi)核級(jí)線程,這也就導(dǎo)致了即使用戶(hù)級(jí)線程再多,處理器的分配數(shù)量還是依照內(nèi)核級(jí)線程來(lái)確定,但是不會(huì)再有阻塞問(wèn)題,也不會(huì)再有內(nèi)核線程開(kāi)多出毛病的問(wèn)題。
2.2.7 調(diào)度程序激活機(jī)制
盡管內(nèi)核級(jí)線程在一些關(guān)鍵點(diǎn)上優(yōu)于用戶(hù)級(jí)線程,但是內(nèi)核級(jí)線程速度慢是硬傷。為了保持其優(yōu)良特性并且改進(jìn)其速度,研究人員研究出了調(diào)度程序激活機(jī)制。
調(diào)度程序激活機(jī)制的本質(zhì)即:既然能在用戶(hù)空間中有這么大的便利,那我把內(nèi)核的權(quán)限給你不就行了,用戶(hù)線程如果發(fā)出的系統(tǒng)調(diào)用是安全的,那么就行使內(nèi)核賦予的權(quán)限去處理即可,如果實(shí)在不能處理,再去陷入內(nèi)核交給內(nèi)核去處理。這樣的話(huà),由于避免了在用戶(hù)空間和內(nèi)核空間之間的不必要轉(zhuǎn)換,從而提高了效率。
在2.2.4前面我們說(shuō)到過(guò),多對(duì)一模型中,由于只有一個(gè)內(nèi)核級(jí)線程,所以一旦線程堵塞就完蛋了。這時(shí)候如果堵塞,內(nèi)核就會(huì)通知該進(jìn)程的運(yùn)行時(shí)系統(tǒng),并且在堆棧中以參數(shù)形式傳遞有問(wèn)題的線程編號(hào)和所發(fā)生事件的一個(gè)描述。內(nèi)核通過(guò)在一個(gè)已知的其實(shí)地址啟動(dòng)運(yùn)行時(shí)系統(tǒng),從而發(fā)出了通知,這種機(jī)制被叫做上行調(diào)用。一旦如此激活,運(yùn)行時(shí)系統(tǒng)就重新調(diào)度其線程。
在某個(gè)用戶(hù)線程運(yùn)行的同時(shí)發(fā)生一個(gè)硬件中斷時(shí),被中斷的CPU切換進(jìn)內(nèi)核態(tài)。如果該線程沒(méi)有什么大問(wèn)題,在相關(guān)的事件發(fā)生后并處理完成,那么線程會(huì)通過(guò)與PCB同樣功能的TCB(線程控制塊)去重新啟動(dòng)自己所在的線程。
總結(jié)
- 上一篇: ducker桌面版更改安装位置_Ubun
- 下一篇: PYHON中的切片