進(jìn)程定義
進(jìn)程是正在運(yùn)行的程序的實(shí)例,是資源擁有的基本單位魔种。
進(jìn)程狀態(tài)
當(dāng)一個進(jìn)程開始運(yùn)行時(shí)析二,它可能會經(jīng)歷下面這幾種狀態(tài)
圖中會涉及三種狀態(tài)
- 運(yùn)行態(tài),運(yùn)行態(tài)指的就是進(jìn)程實(shí)際占用 CPU 時(shí)間片運(yùn)行時(shí)
- 就緒態(tài)务嫡,就緒態(tài)指的是可運(yùn)行甲抖,但因?yàn)槠渌M(jìn)程正在運(yùn)行而處于就緒狀態(tài)
- 阻塞態(tài),除非某種外部事件發(fā)生心铃,否則進(jìn)程不能運(yùn)行
邏輯上來說准谚,運(yùn)行態(tài)和就緒態(tài)是很相似的。這兩種情況下都表示進(jìn)程可運(yùn)行去扣,但是第二種情況沒有獲得 CPU 時(shí)間分片柱衔。第三種狀態(tài)與前兩種狀態(tài)不同的原因是這個進(jìn)程不能運(yùn)行,CPU 空閑時(shí)也不能運(yùn)行愉棱。
三種狀態(tài)會涉及四種狀態(tài)間的切換唆铐,在操作系統(tǒng)發(fā)現(xiàn)進(jìn)程不能繼續(xù)執(zhí)行時(shí)會發(fā)生狀態(tài) 1的輪轉(zhuǎn),在某些系統(tǒng)中進(jìn)程執(zhí)行系統(tǒng)調(diào)用奔滑,例如 pause艾岂,來獲取一個阻塞的狀態(tài)。在其他系統(tǒng)中包括 UNIX朋其,當(dāng)進(jìn)程從管道或特殊文件(例如終端)中讀取沒有可用的輸入時(shí)王浴,該進(jìn)程會被自動終止脆炎。
轉(zhuǎn)換 2 和轉(zhuǎn)換 3 都是由進(jìn)程調(diào)度程序(操作系統(tǒng)的一部分)引起的,進(jìn)程本身不知道調(diào)度程序的存在氓辣。轉(zhuǎn)換 2 的出現(xiàn)說明進(jìn)程調(diào)度器認(rèn)定當(dāng)前進(jìn)程已經(jīng)運(yùn)行了足夠長的時(shí)間秒裕,是時(shí)候讓其他進(jìn)程運(yùn)行 CPU 時(shí)間片了。當(dāng)所有其他進(jìn)程都運(yùn)行過后钞啸,這時(shí)候該是讓第一個進(jìn)程重新獲得 CPU 時(shí)間片的時(shí)候了几蜻,就會發(fā)生轉(zhuǎn)換 3。
程序調(diào)度指的是体斩,決定哪個進(jìn)程優(yōu)先被運(yùn)行和運(yùn)行多久梭稚,這是很重要的一點(diǎn)。已經(jīng)設(shè)計(jì)出許多算法來嘗試平衡系統(tǒng)整體效率與各個流程之間的競爭需求硕勿。
當(dāng)進(jìn)程等待的一個外部事件發(fā)生時(shí)(如從外部輸入一些數(shù)據(jù)后)哨毁,則發(fā)生轉(zhuǎn)換 4。如果此時(shí)沒有其他進(jìn)程在運(yùn)行源武,則立刻觸發(fā)轉(zhuǎn)換 3,該進(jìn)程便開始運(yùn)行想幻,否則該進(jìn)程會處于就緒階段粱栖,等待 CPU 空閑后再輪到它運(yùn)行。
進(jìn)程控制結(jié)構(gòu)
在操作系統(tǒng)中脏毯,是用進(jìn)程控制塊(process control block闹究,PCB)數(shù)據(jù)結(jié)構(gòu)來描述進(jìn)程的,
Linux中的PCB是task_struct結(jié)構(gòu)體食店。
包含
- 進(jìn)程狀態(tài)渣淤;
- 全局變量;
- 虛擬地址空間的信息吉嫩;
- 所打開文件的列表价认;
- 所使用的 I/O 設(shè)備信息。
Linux內(nèi)核其實(shí)是不區(qū)分進(jìn)程和線程的自娩,調(diào)度器實(shí)際是識別task_struct進(jìn)行調(diào)度用踩。
無論進(jìn)程線程,底層都對應(yīng)一個task_struct忙迁,進(jìn)程和線程的區(qū)別是共享資源的多少脐彩,兩個進(jìn)程間完全不共享資源,兩個線程間共享所有資源姊扔。
PCB組織方式(進(jìn)程表)
通常是通過鏈表的方式進(jìn)行組織惠奸,把具有相同狀態(tài)的進(jìn)程鏈在一起,組成各種隊(duì)列恰梢。比如:
將所有處于就緒狀態(tài)的進(jìn)程鏈在一起佛南,稱為就緒隊(duì)列梗掰;
把所有因等待某事件而處于等待狀態(tài)的進(jìn)程鏈在一起就組成各種阻塞隊(duì)列;
另外共虑,對于運(yùn)行隊(duì)列在單核 CPU 系統(tǒng)中則只有一個運(yùn)行指針了愧怜,因?yàn)閱魏?CPU 在某個時(shí)間,只能運(yùn)行一個程序妈拌。
那么拥坛,就緒隊(duì)列和阻塞隊(duì)列鏈表的組織形式如下圖:
進(jìn)程間通信方式
消息傳遞:消息傳遞是進(jìn)程間實(shí)現(xiàn)通信和同步等待的機(jī)制,使用消息傳遞尘分,進(jìn)程間的交流不需要共享變量猜惋,直接就可以進(jìn)行通信;消息傳遞分為發(fā)送方和接收方
先進(jìn)先出隊(duì)列:先進(jìn)先出隊(duì)列指的是兩個不相關(guān)聯(lián)進(jìn)程間的通信培愁,兩個進(jìn)程之間可以彼此相互進(jìn)程通信著摔,這是一種全雙工通信方式
管道:管道用于兩個相關(guān)進(jìn)程之間的通信,這是一種半雙工的通信方式定续,如果需要全雙工谍咆,需要另外一個管道。
直接通信:在這種進(jìn)程通信的方式中私股,進(jìn)程與進(jìn)程之間只存在一條鏈接摹察,進(jìn)程間要明確通信雙方的命名。
間接通信:間接通信是通信雙方不會直接建立連接倡鲸,而是找到一個中介者供嚎,這個中介者可能是個對象等等,進(jìn)程可以在其中放置消息峭状,并且可以從中刪除消息克滴,以此達(dá)到進(jìn)程間通信的目的。
消息隊(duì)列:消息隊(duì)列是內(nèi)核中存儲消息的鏈表优床,它由消息隊(duì)列標(biāo)識符進(jìn)行標(biāo)識劝赔,這種方式能夠在不同的進(jìn)程之間提供全雙工的通信連接。
共享內(nèi)存:共享內(nèi)存是使用所有進(jìn)程之間的內(nèi)存來建立連接羔巢,這種類型需要同步進(jìn)程訪問來相互保護(hù)望忆。
CPU上下文與切換
在每個任務(wù)運(yùn)行前,CPU 都需要知道任務(wù)從哪里加載竿秆、又從哪里開始運(yùn)行启摄,也就是說,需要系統(tǒng)事先幫它設(shè)置好 CPU 寄存器和程序計(jì)數(shù)器(Program Counter幽钢,PC)歉备。
CPU 寄存器,是 CPU 內(nèi)置的容量小匪燕、但速度極快的內(nèi)存蕾羊。而程序計(jì)數(shù)器喧笔,則是用來存儲 CPU 正在執(zhí)行的指令位置、或者即將執(zhí)行的下一條指令位置龟再。它們都是 CPU 在運(yùn)行任何任務(wù)前茶没,必須的依賴環(huán)境瞒窒,因此也被叫做 CPU 上下文姥敛。
CPU 上下文切換砂沛,就是先把前一個任務(wù)的 CPU 上下文(也就是 CPU 寄存器和程序計(jì)數(shù)器)保存起來,然后加載新任務(wù)的上下文到這些寄存器和程序計(jì)數(shù)器哀澈,最后再跳轉(zhuǎn)到程序計(jì)數(shù)器所指的新位置牌借,運(yùn)行新任務(wù)。而這些保存下來的上下文割按,會存儲在系統(tǒng)內(nèi)核中膨报,并在任務(wù)重新調(diào)度執(zhí)行時(shí)再次加載進(jìn)來。
進(jìn)程上下文切換
Linux 按照特權(quán)等級适荣,把進(jìn)程的運(yùn)行空間分為內(nèi)核空間和用戶空間现柠,分別對應(yīng)著下圖中, CPU 特權(quán)等級的 Ring 0 和 Ring 3弛矛。內(nèi)核空間(Ring 0)具有最高權(quán)限晒旅,可以直接訪問所有資源;用戶空間(Ring 3)只能訪問受限資源汪诉,不能直接訪問內(nèi)存等硬件設(shè)備,必須通過系統(tǒng)調(diào)用陷入到內(nèi)核中谈秫,才能訪問這些特權(quán)資源扒寄。
進(jìn)程既可以在用戶空間運(yùn)行,又可以在內(nèi)核空間中運(yùn)行拟烫。
進(jìn)程在用戶空間運(yùn)行時(shí)该编,被稱為進(jìn)程的用戶態(tài),而陷入內(nèi)核空間的時(shí)候硕淑,被稱為進(jìn)程的內(nèi)核態(tài)课竣。
系統(tǒng)調(diào)用
從用戶態(tài)到內(nèi)核態(tài)的轉(zhuǎn)變,需要通過系統(tǒng)調(diào)用來完成置媳。比如于樟,當(dāng)我們查看文件內(nèi)容時(shí),就需要多次系統(tǒng)調(diào)用來完成:首先調(diào)用 open() 打開文件拇囊,然后調(diào)用 read() 讀取文件內(nèi)容迂曲,并調(diào)用 write() 將內(nèi)容寫到標(biāo)準(zhǔn)輸出,最后再調(diào)用 close() 關(guān)閉文件寥袭。
在這個過程中就發(fā)生了 CPU 上下文切換路捧,整個過程是這樣的:
1关霸、保存 CPU 寄存器里原來用戶態(tài)的指令位
2、為了執(zhí)行內(nèi)核態(tài)代碼杰扫,CPU 寄存器需要更新為內(nèi)核態(tài)指令的新位置队寇。
3、跳轉(zhuǎn)到內(nèi)核態(tài)運(yùn)行內(nèi)核任務(wù)章姓。
4佳遣、當(dāng)系統(tǒng)調(diào)用結(jié)束后,CPU 寄存器需要恢復(fù)原來保存的用戶態(tài)啤覆,然后再切換到用戶空間苍日,繼續(xù)運(yùn)行進(jìn)程。
所以窗声,一次系統(tǒng)調(diào)用的過程相恃,其實(shí)是發(fā)生了兩次 CPU 上下文切換。(用戶態(tài)-內(nèi)核態(tài)-用戶態(tài))
不過笨觅,需要注意的是拦耐,系統(tǒng)調(diào)用過程中,并不會涉及到虛擬內(nèi)存等進(jìn)程用戶態(tài)的資源见剩,也不會切換進(jìn)程杀糯。這跟我們通常所說的進(jìn)程上下文切換是不一樣的:進(jìn)程上下文切換,是指從一個進(jìn)程切換到另一個進(jìn)程運(yùn)行苍苞;而系統(tǒng)調(diào)用過程中一直是同一個進(jìn)程在運(yùn)行固翰。
所以,系統(tǒng)調(diào)用過程通常稱為特權(quán)模式切換羹呵,而不是上下文切換骂际。系統(tǒng)調(diào)用屬于同進(jìn)程內(nèi)的 CPU 上下文切換。但實(shí)際上冈欢,系統(tǒng)調(diào)用過程中歉铝,CPU 的上下文切換還是無法避免的。
進(jìn)程上下文切換跟系統(tǒng)調(diào)用區(qū)別
首先凑耻,進(jìn)程是由內(nèi)核來管理和調(diào)度的太示,進(jìn)程的切換只能發(fā)生在內(nèi)核態(tài)。所以香浩,進(jìn)程的上下文不僅包括了虛擬內(nèi)存类缤、棧、全局變量等用戶空間的資源弃衍,還包括了內(nèi)核堆棧呀非、寄存器等內(nèi)核空間的狀態(tài)。
因此,進(jìn)程的上下文切換就比系統(tǒng)調(diào)用時(shí)多了一步:在保存內(nèi)核態(tài)資源(當(dāng)前進(jìn)程的內(nèi)核狀態(tài)和 CPU 寄存器)之前岸裙,需要先把該進(jìn)程的用戶態(tài)資源(虛擬內(nèi)存猖败、棧等)保存下來;而加載了下一進(jìn)程的內(nèi)核態(tài)后降允,還需要刷新進(jìn)程的虛擬內(nèi)存和用戶棧恩闻。
如下圖所示,保存上下文和恢復(fù)上下文的過程并不是“免費(fèi)”的剧董,需要內(nèi)核在 CPU 上運(yùn)行才能完成幢尚。
進(jìn)程上下文切換潛在的性能問題
根據(jù) Tsuna 的測試報(bào)告,每次上下文切換都需要幾十納秒到數(shù)微秒的 CPU 時(shí)間翅楼。這個時(shí)間還是相當(dāng)可觀的尉剩,特別是在進(jìn)程上下文切換次數(shù)較多的情況下,很容易導(dǎo)致 CPU 將大量時(shí)間耗費(fèi)在寄存器毅臊、內(nèi)核棧以及虛擬內(nèi)存等資源的保存和恢復(fù)上理茎,進(jìn)而大大縮短了真正運(yùn)行進(jìn)程的時(shí)間。這也正是導(dǎo)致平均負(fù)載升高的一個重要因素管嬉。
另外皂林,Linux 通過 TLB(Translation Lookaside Buffer)來管理虛擬內(nèi)存到物理內(nèi)存的映射關(guān)系。當(dāng)虛擬內(nèi)存更新后蚯撩,TLB 也需要刷新础倍,內(nèi)存的訪問也會隨之變慢。特別是在多處理器系統(tǒng)上胎挎,緩存是被多個處理器共享的沟启,刷新緩存不僅會影響當(dāng)前處理器的進(jìn)程,還會影響共享緩存的其他處理器的進(jìn)程犹菇。
發(fā)生上下文切換場景
- 為了保證所有進(jìn)程可以得到公平調(diào)度美浦,CPU 時(shí)間被劃分為一段段的時(shí)間片,這些時(shí)間片再被輪流分配給各個進(jìn)程项栏。這樣,當(dāng)某個進(jìn)程的時(shí)間片耗盡了蹬竖,就會被系統(tǒng)掛起沼沈,切換到其它正在等待 CPU 的進(jìn)程運(yùn)行。
- 進(jìn)程在系統(tǒng)資源不足(比如內(nèi)存不足)時(shí)币厕,要等到資源滿足后才可以運(yùn)行列另,這個時(shí)候進(jìn)程也會被掛起,并由系統(tǒng)調(diào)度其他進(jìn)程運(yùn)行旦装。
- 當(dāng)進(jìn)程通過睡眠函數(shù) sleep 這樣的方法將自己主動掛起時(shí)页衙,自然也會重新調(diào)度。
- 當(dāng)有優(yōu)先級更高的進(jìn)程運(yùn)行時(shí),為了保證高優(yōu)先級進(jìn)程的運(yùn)行店乐,當(dāng)前進(jìn)程會被掛起艰躺,由高優(yōu)先級進(jìn)程來運(yùn)行
- 發(fā)生硬件中斷時(shí),CPU 上的進(jìn)程會被中斷掛起眨八,轉(zhuǎn)而執(zhí)行內(nèi)核中的中斷服務(wù)程序腺兴。
線程
線程是調(diào)度的基本單位,是進(jìn)程當(dāng)中的一條執(zhí)行流程廉侧。
同一個進(jìn)程內(nèi)多個線程之間可以共享代碼段页响、數(shù)據(jù)段、打開的文件等資源段誊,但每個線程都有獨(dú)立一套的寄存器和棧闰蚕,這樣可以確保線程的控制流是相對獨(dú)立的。
線程上下文切換
內(nèi)核中的任務(wù)調(diào)度连舍,實(shí)際上的調(diào)度對象是線程没陡;而進(jìn)程只是給線程提供了虛擬內(nèi)存、全局變量等資源烟瞧。
所以诗鸭,對于線程和進(jìn)程,我們可以這么理解:
- 當(dāng)進(jìn)程只有一個線程時(shí)参滴,可以認(rèn)為進(jìn)程就等于線程强岸。
- 當(dāng)進(jìn)程擁有多個線程時(shí),這些線程會共享相同的虛擬內(nèi)存和全局變量等資源砾赔。這些資源在上下文切換時(shí)是不需要修改的蝌箍。
- 另外,線程也有自己的私有數(shù)據(jù)暴心,比如棧和寄存器等妓盲,這些在上下文切換時(shí)也是需要保存的。
這么一來专普,線程的上下文切換其實(shí)就可以分為兩種情況:
- 第一種悯衬, 前后兩個線程屬于不同進(jìn)程。此時(shí)檀夹,因?yàn)橘Y源不共享筋粗,所以切換過程就跟進(jìn)程上下文切換是一樣。
- 第二種炸渡,前后兩個線程屬于同一個進(jìn)程娜亿。此時(shí),因?yàn)樘摂M內(nèi)存是共享的蚌堵,所以在切換時(shí)买决,虛擬內(nèi)存這些資源就保持不動沛婴,只需要切換線程的私有數(shù)據(jù)、寄存器等不共享的數(shù)據(jù)督赤。
雖然同為上下文切換嘁灯,但同進(jìn)程內(nèi)的線程切換,要比多進(jìn)程間的切換消耗更少的資源够挂,而這旁仿,也正是多線程代替多進(jìn)程的一個優(yōu)勢。
線程與進(jìn)程比較
從概念來說孽糖,線程是調(diào)度的基本單位枯冈,而進(jìn)程則是資源擁有的基本單位。
從本質(zhì)上來說办悟,線程與進(jìn)程的實(shí)現(xiàn)方式是一樣的尘奏,底層都是由一個叫task_struct的結(jié)構(gòu)體實(shí)現(xiàn),可以被cpu調(diào)度病蛉,只是資源共享方式不同炫加,進(jìn)程間是不共享資源,而同一個進(jìn)程內(nèi)的線程共享這個進(jìn)程的所有資源铺然。
線程的實(shí)現(xiàn)
- 用戶線程(User Thread):在用戶空間實(shí)現(xiàn)的線程俗孝,不是由內(nèi)核管理的線程,是由用戶態(tài)的線程庫來完成線程的管理魄健,例如go的goroutine赋铝;
- 內(nèi)核線程(Kernel Thread):在內(nèi)核中實(shí)現(xiàn)的線程,是由內(nèi)核管理的線程沽瘦;
- 輕量級進(jìn)程(LightWeight Process):在內(nèi)核中來支持用戶線程革骨;例如Linux的pthread_create。
用戶線程
用戶線程是基于用戶態(tài)的線程管理庫來實(shí)現(xiàn)的析恋,那么線程控制塊(Thread Control Block, TCB) 也是在庫里面來實(shí)現(xiàn)的良哲,對于操作系統(tǒng)而言是看不到這個 TCB 的,它只能看到整個進(jìn)程的 PCB助隧。
所以筑凫,用戶線程的整個線程管理和調(diào)度,操作系統(tǒng)是不直接參與的并村,而是由用戶級線程庫函數(shù)來完成線程的管理漏健,包括線程的創(chuàng)建、終止橘霎、同步和調(diào)度等。
用戶線程的優(yōu)點(diǎn):
- 每個進(jìn)程都需要有它私有的線程控制塊(TCB)列表殖属,用來跟蹤記錄它各個線程狀態(tài)信息(PC姐叁、棧指針、寄存器),TCB 由用戶級線程庫函數(shù)來維護(hù)外潜,可用于不支持線程技術(shù)的操作系統(tǒng)原环;
- 用戶線程的切換也是由線程庫函數(shù)來完成的,無需用戶態(tài)與內(nèi)核態(tài)的切換处窥,所以速度特別快嘱吗;
用戶線程的缺點(diǎn):
- 由于操作系統(tǒng)不參與線程的調(diào)度,如果一個線程發(fā)起了系統(tǒng)調(diào)用而阻塞滔驾,那進(jìn)程所包含的用戶線程都不能執(zhí)行了谒麦。
- 當(dāng)一個線程開始運(yùn)行后,除非它主動地交出 CPU 的使用權(quán)哆致,否則它所在的進(jìn)程當(dāng)中的其他線程無法運(yùn)行绕德,因?yàn)橛脩魬B(tài)的線程沒法打斷當(dāng)前運(yùn)行中的線程,它沒有這個特權(quán)摊阀,只有操作系統(tǒng)才有耻蛇,但是用戶線程不是由操作系統(tǒng)管理的。
- 由于時(shí)間片分配給進(jìn)程(或者輕量級進(jìn)程)胞此,故與其他進(jìn)程比臣咖,在多線程執(zhí)行時(shí),每個線程得到的時(shí)間片較少漱牵,執(zhí)行會比較慢夺蛇;
內(nèi)核線程
內(nèi)核線程是由操作系統(tǒng)管理的,線程對應(yīng)的 TCB 自然是放在操作系統(tǒng)里的布疙,這樣線程的創(chuàng)建蚊惯、終止和管理都是由操作系統(tǒng)負(fù)責(zé)。
內(nèi)核線程的優(yōu)點(diǎn):
- 在一個進(jìn)程當(dāng)中灵临,如果某個內(nèi)核線程發(fā)起系統(tǒng)調(diào)用而被阻塞截型,并不會影響其他內(nèi)核線程的運(yùn)行;
- 分配給線程儒溉,多線程的進(jìn)程獲得更多的 CPU 運(yùn)行時(shí)間宦焦;
內(nèi)核線程的缺點(diǎn):
- 在支持內(nèi)核線程的操作系統(tǒng)中,由內(nèi)核來維護(hù)進(jìn)程和線程的上下文信息顿涣,如 PCB 和 TCB波闹;
- 線程的創(chuàng)建、終止和切換都是通過系統(tǒng)調(diào)用的方式來進(jìn)行涛碑,因此對于系統(tǒng)來說精堕,系統(tǒng)開銷比較大;
輕量級進(jìn)程
是內(nèi)核支持的用戶線程蒲障,一個進(jìn)程可有一個或多個 LWP歹篓,每個 LWP 是跟內(nèi)核線程一對一映射的瘫证,也就是 LWP 都是由一個內(nèi)核線程支持。
另外庄撮,LWP 只能由內(nèi)核管理并像普通進(jìn)程一樣被調(diào)度背捌,Linux 內(nèi)核是支持 LWP 的典型例子。
在大多數(shù)系統(tǒng)中洞斯,LWP與普通進(jìn)程的區(qū)別也在于它只有一個最小的執(zhí)行上下文和調(diào)度程序所需的統(tǒng)計(jì)信息毡庆。一般來說,一個進(jìn)程代表程序的一個實(shí)例烙如,而 LWP 代表程序的執(zhí)行線程么抗,因?yàn)橐粋€執(zhí)行線程不像進(jìn)程那樣需要那么多狀態(tài)信息,所以 LWP 也不帶有這樣的信息厅翔。
在 LWP 之上也是可以使用用戶線程的乖坠,那么 LWP 與用戶線程的對應(yīng)關(guān)系就有三種:
1 : 1,即一個 LWP 對應(yīng) 一個用戶線程刀闷;
N : 1熊泵,即一個 LWP 對應(yīng)多個用戶線程;
N : N甸昏,即多個 LMP 對應(yīng)多個用戶線程顽分;