操作系統(tǒng)-進(jìn)程與線程
在進(jìn)程模型中,計(jì)算機(jī)上所有可運(yùn)行的軟件,通常也包括操作系統(tǒng),被組織成若干個(gè)順序進(jìn)程,簡(jiǎn)稱進(jìn)程。一個(gè)進(jìn)程就是一個(gè)正在執(zhí)行程序的實(shí)例,包括程序計(jì)算器,寄存器和變量的當(dāng)前值。從概念上說(shuō),每個(gè)進(jìn)程擁有他自己的虛擬CPU。
守護(hù)進(jìn)程
停留在后臺(tái)處理諸如電子郵件,web頁(yè)面,新聞等之類的活動(dòng)的進(jìn)程稱為守護(hù)進(jìn)程。
進(jìn)程的創(chuàng)建
1)系統(tǒng)初始化
2正在運(yùn)行的程序執(zhí)行了創(chuàng)建進(jìn)程的系統(tǒng)調(diào)用
3)用戶請(qǐng)求創(chuàng)建一個(gè)新進(jìn)程
4)一個(gè)批處理作業(yè)的初始化
進(jìn)程的終止
1)正常退出(自愿)
2)出錯(cuò)退出(自愿)
3)嚴(yán)重錯(cuò)誤(非自愿)
4)被其他進(jìn)程殺死(非自愿)
進(jìn)程的層次結(jié)構(gòu)
某些系統(tǒng)中,當(dāng)進(jìn)程創(chuàng)建了另一個(gè)進(jìn)程后,父進(jìn)程和子進(jìn)程就以某種形式繼續(xù)保持關(guān)聯(lián)。子進(jìn)程自身可以創(chuàng)建更多的進(jìn)程,組成一個(gè)進(jìn)程的層次結(jié)構(gòu)。
進(jìn)程的狀態(tài)
盡管每個(gè)進(jìn)程是一個(gè)獨(dú)立的實(shí)體,有其自己的程序計(jì)數(shù)器和內(nèi)部狀態(tài),但是,進(jìn)程之間經(jīng)常需要相互作用,一個(gè)進(jìn)程的輸出結(jié)果可能作為另一個(gè)進(jìn)程的輸入。
當(dāng)一個(gè)進(jìn)程在邏輯上不能繼續(xù)運(yùn)行時(shí),它就會(huì)被阻塞,典型的例子是它在等待可以使用的輸入,還有這樣的情況,一個(gè)概念上能夠運(yùn)行的進(jìn)程被迫停止,因?yàn)椴僮飨到y(tǒng)調(diào)度另一個(gè)進(jìn)程占用了CPU,第一種情況下是進(jìn)程掛起是程序自身固有的原因,第二種情況則是由系統(tǒng)技術(shù)上的原因引起的,(在鍵入用戶命令行之前,無(wú)法執(zhí)行命令)。這種情況則是由技術(shù)上。
進(jìn)程的狀態(tài):
1.運(yùn)行態(tài)(該時(shí)刻)
2.就緒態(tài)
3.阻塞態(tài)
進(jìn)程的實(shí)現(xiàn)
為了實(shí)現(xiàn)進(jìn)程模型,操作系統(tǒng)維護(hù)著一張表格(一個(gè)結(jié)構(gòu)數(shù)組),即進(jìn)程表。每個(gè)進(jìn)程占用一個(gè)進(jìn)程表項(xiàng)(有些作者稱這些表項(xiàng)為進(jìn)程控制塊),該表項(xiàng)包含了進(jìn)程狀態(tài)的重要信息,包括程序計(jì)數(shù)器,堆棧指針,內(nèi)存分配狀況,所打開文件狀態(tài)、賬號(hào)和調(diào)度信息,以及其他在進(jìn)程由運(yùn)行狀態(tài)轉(zhuǎn)換到就緒態(tài)或阻塞態(tài)必須保存的信息,從而保證該進(jìn)程隨后能再次啟動(dòng),就像從未被中斷過(guò)。
多道程序設(shè)計(jì)模式
好的模型是從概率的角度來(lái)看CPU的利用率,假設(shè)一個(gè)進(jìn)程等待i/o操作的事件與其停留在內(nèi)存的時(shí)間比為p。當(dāng)內(nèi)存中同時(shí)有n個(gè)進(jìn)程是,則所有n個(gè)進(jìn)程都在等待i/o的概率是p的n次方。CPU的利用率 = 1-p的n次方。
從完全精確的角度考慮,應(yīng)該指出此概率模型只是描述了一個(gè)大致情況,它假設(shè)所有n個(gè)進(jìn)程是獨(dú)立的,即內(nèi)存中的5個(gè)進(jìn)程中,3個(gè)運(yùn)行,2個(gè)等待,是完全可以接受的,但在單個(gè)CPU中,不能同時(shí)運(yùn)行3個(gè)進(jìn)程,所以當(dāng)cpu忙時(shí),以及就緒的進(jìn)程也必須等待cpu,因而,進(jìn)程不是獨(dú)立的,更精確的模型應(yīng)該用排隊(duì)論構(gòu)建。但我們的模型(當(dāng)進(jìn)程就緒時(shí),給進(jìn)程分配cpu,否則讓cpu空轉(zhuǎn))仍然是有效的。
線程
在傳統(tǒng)操作系統(tǒng)中,每個(gè)進(jìn)程有一個(gè)地址空間和一個(gè)控制線程,事實(shí)上,這幾乎就是進(jìn)程的定義。不過(guò),經(jīng)常存在在同一個(gè)地址空間中準(zhǔn)并行運(yùn)行多個(gè)控制線程的情形,這些線程就像(差不多)分離的進(jìn)程(共享地址空間除外)。
線程的使用
為什么人們需要在一個(gè)進(jìn)程中再有一類線程?人們需要多線程的主要原因是,在許多應(yīng)用中同時(shí)發(fā)生著多種活動(dòng),其中某些活動(dòng)隨著時(shí)間的推移會(huì)被阻塞。通過(guò)將這些應(yīng)用程序分解成可以準(zhǔn)并行運(yùn)行的多個(gè)順序線程,程序設(shè)計(jì)模型會(huì)變得更簡(jiǎn)單。
有了這樣的抽象,我們才不考慮中斷,定時(shí)器,上下文切換,而只需考察并行進(jìn)程,類似地,只有在有了多線程概念后,我們才加入了一種新元素;并行實(shí)體擁有共享同一個(gè)地址空間和所有課用數(shù)據(jù)的能力,對(duì)于某些應(yīng)用而言,這種能力是必須的,而這盛世多進(jìn)程模型多無(wú)法表達(dá)的。
第二個(gè)關(guān)于需要多線程的理由是,由于線程比進(jìn)程更輕量級(jí),所以他們比進(jìn)程更容易(更快)創(chuàng)建,也更容易撤銷,在許多系統(tǒng)中,創(chuàng)建一個(gè)線程較創(chuàng)建一個(gè)進(jìn)程要快10-100倍。在有大量線程需要?jiǎng)討B(tài)和快速修改時(shí),具有這一特性是很有用的。
需要多線程的第三個(gè)原因設(shè)計(jì)性能方面的討論,若多個(gè)線程是CPU密集型的,那么并不能獲得性能上的增強(qiáng),但是如果存在著大量的計(jì)算和大量的i/o處理,擁有多個(gè)線程允許這些活動(dòng)彼此重疊進(jìn)行。
從而會(huì)加快應(yīng)用程序執(zhí)行的速度。
經(jīng)典的線程模型
理解進(jìn)程的一個(gè)角度是,用某種方法把相關(guān)的資源集中在一起,進(jìn)程與存放程序正文和數(shù)據(jù)以及其他資源的地址空間,這些資源中包括打開的文件,子進(jìn)程,即將發(fā)生的定時(shí)器,信號(hào)處理程序,賬號(hào)信息。把它們都放到進(jìn)程中可以更容易理解。
另一個(gè)概念是,進(jìn)程擁有一個(gè)可執(zhí)行線程,通常簡(jiǎn)寫為線程,在線程中有一個(gè)程序計(jì)數(shù)器,用來(lái)記錄接著要執(zhí)行哪一條指令,線程擁有寄存器,用來(lái)保存線程當(dāng)前的工作變量,線程還擁有一個(gè)堆棧,用來(lái)記錄執(zhí)行歷史。其中每一幀保存了一個(gè)已經(jīng)調(diào)用的但是還沒(méi)有返回的過(guò)程,盡管線程必須在某個(gè)進(jìn)程中執(zhí)行,但是線程和它的進(jìn)程是不同的概念,并且可以分別處理,進(jìn)程用于把資源集中到一起,而線程則是在CPU上被調(diào)度執(zhí)行的實(shí)體。
線程給進(jìn)程模型增加了一項(xiàng)內(nèi)容,即在同一個(gè)進(jìn)程環(huán)境中,允許彼此之間有較大的獨(dú)立性的多個(gè)線程執(zhí)行。在同一個(gè)進(jìn)程中并行運(yùn)行多個(gè)線程,是對(duì)在同一臺(tái)計(jì)算機(jī)上并行運(yùn)行多個(gè)進(jìn)程的模擬,在前一種情形下,多個(gè)線程共享同一個(gè)地址空間和其他資源。而在后一種情形中,多個(gè)進(jìn)程共享物理內(nèi)存,磁盤,打印機(jī)和其他資源,由于線程具有進(jìn)程的某些性質(zhì),所以有時(shí)被稱為輕量級(jí)進(jìn)程。多線程這個(gè)術(shù)語(yǔ),也用來(lái)描述在同一個(gè)進(jìn)程中允許多個(gè)線程的情形,正如在第一章中看到的,一些CPU已經(jīng)有直接硬件支持多線程,并允許線程切換在納秒級(jí)完成。
進(jìn)程中的不同線程不像不同進(jìn)程之間那樣存在很大的獨(dú)立性,所有的線程都有完全一樣的地址空間,這意味著他們也共享同樣的全局變量。由于各個(gè)線程都可以訪問(wèn)進(jìn)程地址空間中的每一個(gè)內(nèi)存地址,這意味著他們也共享同樣的全局變量,由于各個(gè)線程都可以訪問(wèn)進(jìn)程地址空間中的每一個(gè)內(nèi)存地址,所以一個(gè)線程可以讀,寫或設(shè)置清除另一個(gè)線程的堆棧。線程之間是沒(méi)有保護(hù)的,原因是:1)不可能,2)也沒(méi)有必要。這與不用進(jìn)程是有差別的,不同的進(jìn)程會(huì)來(lái)自不同的用戶,它們彼此之間可能有敵意,一個(gè)進(jìn)程總是由某個(gè)用戶所擁有,該用戶創(chuàng)建多個(gè)線程應(yīng)該是為了它們之間的合作,而不是彼此間爭(zhēng)斗,除了共享地址空間之外,所有線程還共享同一個(gè)打開文件集,子進(jìn)程,定時(shí)器,以及相關(guān)信號(hào)等。
和傳統(tǒng)進(jìn)程一樣,線程可以處于若干種狀態(tài)的任何一個(gè),運(yùn)行,阻塞,就緒或終止。正在運(yùn)行的線程擁有CPU并且是活躍的,被阻塞的線程正在等待某個(gè)釋放他的事件,例如,當(dāng)一個(gè)線程執(zhí)行從鍵盤讀入數(shù)據(jù)的系統(tǒng)調(diào)用時(shí),該線程就被阻塞直到鍵入了輸入為止。線程可以被阻塞,以等待某個(gè)外部事件的發(fā)生或者等待其他線程來(lái)釋放他,就緒線程可以被調(diào)度運(yùn)行,并且只有論道它就很快可以運(yùn)行,線程狀態(tài)之間的轉(zhuǎn)換和進(jìn)程狀態(tài)之間的轉(zhuǎn)換是一樣的。
每個(gè)線程都有其自己的堆棧。每個(gè)線程的堆棧有一幀,供各個(gè)被調(diào)用但是還沒(méi)有從中返回的過(guò)程使用,在該棧幀中存放了相應(yīng)過(guò)程的局部變量以及過(guò)程調(diào)用完成之后使用的返回地址,例如,如果過(guò)程x調(diào)用過(guò)程y,y又調(diào)用了z,那么當(dāng)z執(zhí)行時(shí),供x,y和z使用的棧幀會(huì)全部存在堆棧中,通常,每個(gè)線程會(huì)調(diào)用不同的過(guò)程,從而有一個(gè)各自不同的執(zhí)行歷史,這就是為什么每個(gè)線程都需要有自己堆棧的原因。
在多線程的情況下,進(jìn)程通常會(huì)從當(dāng)前的單個(gè)線程開始,這個(gè)線程有能力通過(guò)調(diào)用一個(gè)庫(kù)函數(shù)(thread_creat)創(chuàng)建新的線程,thread_creat的參數(shù)專門指定了新線程要運(yùn)行的過(guò)程名。這里,沒(méi)有必要對(duì)新線程的地址空間加以規(guī)定,因?yàn)樾戮€程會(huì)自動(dòng)在創(chuàng)建線程的地址空間中運(yùn)行。有時(shí),線程是有層次的,它們具有一種父子關(guān)系,但是,通常不存在這樣一種關(guān)系,所有線程都是平等的,不論有無(wú)層次關(guān)系,創(chuàng)建線程通常都返回一個(gè)線程標(biāo)識(shí)符,該標(biāo)識(shí)符就是新線程的名字。
當(dāng)一個(gè)線程完成工作之后,可以通過(guò)調(diào)用一個(gè)庫(kù)的過(guò)程(如thread_exit)退出,該線程接著小時(shí),不再可調(diào)度,在某些線程中,通過(guò)調(diào)用一個(gè)過(guò)程,例如thread_join,一個(gè)線程可以等待一個(gè)(特定)線程退出。這個(gè)過(guò)程阻塞調(diào)用線程,直到那個(gè)(特性)線程退出。在這種情況下,線程的創(chuàng)建和終止非常類似于進(jìn)程的創(chuàng)建和終止,并且也有著相同的選項(xiàng)。
另一個(gè)常見的線程調(diào)用時(shí)thread_yeild,它允許線程自動(dòng)放棄CPU從而讓另一個(gè)線程允許,這樣一個(gè)調(diào)用是很重要的,因?yàn)椴煌谶M(jìn)程,(線程庫(kù))無(wú)法利用始終中斷強(qiáng)制讓線程讓出CPU,所以設(shè)法使線程行為“高尚起來(lái)”,并且隨著時(shí)間的推移自動(dòng)交出cpu,以便讓其他線程有機(jī)會(huì)允許,就變得非常重要。有的調(diào)用允許某個(gè)線程等待另一個(gè)線程完成某些任務(wù),或等待一個(gè)線程宣傳它已經(jīng)完成了有關(guān)的工作。
2.2.4在用戶空間中實(shí)現(xiàn)線程
有兩種主要的方法實(shí)現(xiàn)線程包,在用戶空間中和在內(nèi)核中,這兩種方法忽互有利弊,不過(guò)混合實(shí)現(xiàn)方式也是可能的,我們現(xiàn)在介紹這些方法,并去分析它們的優(yōu)點(diǎn)和缺點(diǎn)。
第一種方法是把整個(gè)線程包放在用戶空間中,內(nèi)核對(duì)線程包一無(wú)所知,從內(nèi)核角度考慮,就是按正常的方式管理,即單線程進(jìn)程,這種方法第一個(gè)也是最明顯的優(yōu)點(diǎn)是,用戶級(jí)線程包可以不在支持線程的操作系統(tǒng)上實(shí)現(xiàn),過(guò)去所有的操作系統(tǒng)都屬于這個(gè)范圍,即使現(xiàn)在也有一些操作系統(tǒng)還是不支持線程。
問(wèn)題:程序猿通常在經(jīng)常發(fā)生線程阻塞的應(yīng)用中才希望使用多線程。例如,在多線程Web服務(wù)器里,這些線程持續(xù)的進(jìn)行系統(tǒng)調(diào)用,而一旦發(fā)生內(nèi)核陷阱進(jìn)行系統(tǒng)調(diào)用,如果原有的線程已經(jīng)阻塞,就很難讓內(nèi)核進(jìn)行線程的切換,如果要讓內(nèi)核消除這種情況,就要持續(xù)進(jìn)行select系統(tǒng)調(diào)用,以便檢查read系統(tǒng)調(diào)用是否安全,對(duì)于那些基本上是CPU密集型而且極少有阻塞的應(yīng)用程序而言,使用多線程的目的又何在呢?由于這樣的做法并不能得到任何益處,所以沒(méi)有人真正會(huì)提出使用多線程來(lái)計(jì)算前n個(gè)素?cái)?shù)這樣的工作。
2.2.5在內(nèi)核中實(shí)現(xiàn)線程
現(xiàn)在考慮內(nèi)核支持和管理線程的情形,此時(shí)不在需要運(yùn)行時(shí)系統(tǒng)了,另外,每個(gè)進(jìn)程中也沒(méi)有線程表,相反,在內(nèi)核中有用來(lái)記錄系統(tǒng)中所有的線程的線程表。當(dāng)某個(gè)線程希望創(chuàng)建一個(gè)新線程或撤銷一個(gè)已有線程時(shí),它進(jìn)行一個(gè)系統(tǒng)調(diào)用,這個(gè)系統(tǒng)調(diào)用通過(guò)對(duì)線程表的更新完成線程創(chuàng)建或者撤銷工作。
內(nèi)核的線程表保存了每個(gè)線程的寄存器,狀態(tài)和其他信息,這些信息和在用戶空間中,(在運(yùn)行時(shí)系統(tǒng)中)的線程是一樣的,但是現(xiàn)在保存在內(nèi)核中,這些信息時(shí)傳統(tǒng)內(nèi)核所維護(hù)的每個(gè)單線程進(jìn)程信息(即進(jìn)程狀態(tài))的子集。另外,內(nèi)核還維護(hù)了傳統(tǒng)的進(jìn)程表,以便跟蹤進(jìn)程的狀態(tài)。
所有能夠阻塞線程的調(diào)用都以系統(tǒng)調(diào)用的形式實(shí)現(xiàn),這與運(yùn)行時(shí)系統(tǒng)過(guò)程相比,代價(jià)是相當(dāng)可觀的,當(dāng)一個(gè)線程阻塞時(shí),內(nèi)核根據(jù)其選擇,可以運(yùn)行同一個(gè)進(jìn)程中的另一個(gè)線程(若有一個(gè)就緒線程)或者另一個(gè)進(jìn)程中的線程,而在用戶級(jí)線程中,運(yùn)行時(shí)系統(tǒng)始終運(yùn)行自己進(jìn)程中的線程,直到內(nèi)核剝奪他的CPU(或者沒(méi)有可運(yùn)行的線程存在了)為止
由于在內(nèi)核中創(chuàng)建或撤銷線程的代價(jià)比較大,某些系統(tǒng)采取“環(huán)?!钡奶幚矸绞?,回收其線程,當(dāng)某個(gè)線程被撤銷時(shí),就把它標(biāo)志為不可運(yùn)行的,但是其內(nèi)核數(shù)據(jù)結(jié)構(gòu)沒(méi)有收到影響,稍后,在必須創(chuàng)建一個(gè)新線程時(shí),就重新啟動(dòng)某個(gè)就線程,從而節(jié)省了一些開心,在用戶級(jí)線程中線程回收也是可能的,但是由于其線程管理的代價(jià)較小,所以沒(méi)有必要進(jìn)行這項(xiàng)工作。
內(nèi)核線程不需要任何新的,非阻塞系統(tǒng)調(diào)用,另外,如果某個(gè)進(jìn)程中的線程引起了頁(yè)面故障,內(nèi)核可以很方便的檢查該進(jìn)程是否有任何其他可運(yùn)行的線程。如果有,在等待所需要的頁(yè)面從磁盤度入市,就選擇一個(gè)可以運(yùn)行的線程運(yùn)行,這樣做的主要缺點(diǎn)是系統(tǒng)調(diào)用的代價(jià)比較大,所以如果線程的操作(創(chuàng)建,終止等)比較多,就會(huì)帶來(lái)很大的開銷
2.3進(jìn)程間通信
進(jìn)程經(jīng)常需要與其他進(jìn)程通信,例如,在一個(gè)shell管道中第一個(gè)進(jìn)程的輸出必須傳送給第二個(gè)進(jìn)程,這樣沿著管道傳遞下去,因此在進(jìn)程之間需要通信,而且最好使用一種結(jié)構(gòu)良好的方式而不要使用中斷,
2.3.1競(jìng)爭(zhēng)條件
在一些操作系統(tǒng)中,協(xié)作的進(jìn)程可能共享一些彼此都能讀寫的公共存儲(chǔ)區(qū),這個(gè)公用存儲(chǔ)區(qū)可能在內(nèi)存中,也可能是一個(gè)共享文件,這里共享存儲(chǔ)區(qū)的位置并不影響通信的本質(zhì)及其帶來(lái)的問(wèn)題,為了理解實(shí)際中進(jìn)程間通信如何工作,我們考慮一個(gè)簡(jiǎn)單但很普遍的例子:一個(gè)假脫機(jī)打印程序,當(dāng)一個(gè)進(jìn)程需要打印一個(gè)文件時(shí),它將文件名放在一個(gè)特殊的假脫機(jī)目錄。另一個(gè)進(jìn)程(打印機(jī)守護(hù)進(jìn)程)則周期性地檢查是否有文件需要打印,若有就打印并將該文件名從目錄下刪掉。