第二章 進程管理
2.1 進程的基本概念
程序的順序執行特征:
- 順序性
- 封閉性
- 可再現性
前驅圖,有向無循環圖(DGA)
程序的並發執行特征:
- 間斷性
- 失去封閉性
- 不可再現性
進程的特征和狀態:
-
因為程序執行的結果是不可再現的,程序是不能參與並發執行的。
-
為使程序能並發執行,且為了對並發執行的程序進行描述和控制,引入“進程”概念。
-
定義:進程是進程實體的運行過程,是系統進行資源分配和調度的一個基本單位。
-
結構:由程序段、相關的數據段和PCB三部分構成進程實體。總稱“進程映像”。
-
特征:
- 動態性(它由創建而產生,由調度而執行,由撤消而消亡)(程序則只是一組有序指令的集合,因而程序是靜態的)
- 並發性
- 獨立性
- 異步性
-
進程的三種基本狀態:
- 就緒狀態,就緒隊列 :就緒進程排成隊列。
- 執行狀態
- 阻塞狀態
-
三種狀態的轉換
-
掛起狀態
- 引入原因:
- 終端用戶的請求
- 父進程請求
- 負荷調節的需要
- 操作系統的需要
- 具有掛起狀態的進程狀態圖
- 引入原因:
-
創建狀態和終止狀態
- 創建狀態:
- 創建一個新進程兩個步驟:
- 創建PCB,分配資源,向PCB填寫管理信息
- 把該進程轉入就緒狀態並插入就緒隊列之中
- 創建一個新進程兩個步驟:
- 終止狀態:
- 兩個步驟:
- 等待操作系統進行善后處理
- 將其PCB清零,並將PCB返還系統
- 兩個步驟:
- 五種狀態轉換圖
- 創建狀態:
-
六種狀態轉換圖
-
進程控制塊(PCB):是操作系統中最重要的記錄型數據結構
- 作用:使一個在多道程序環境下不能獨立運行的程序,成為一個能獨立運行的基本單位,一個能與其它進程並發執行的進程。
- OS根據進程的PCB感知到該進程的存在,PCB是進程存在的惟一標志,OS根據PCB來對並發執行的進程進行控制和管理的。
- 因為PCB經常被系統訪問,尤其是被運行頻率很高的進程及分派程序訪問,故PCB應常駐內存。
- 進程控制塊中的信息
- 進程標識符:
- 內部標識符:在所有的操作系統中都為每一個進程賦予了一個惟一的數字標識符。
- 外部標識符:它由創建者提供,通常是由字母、數字組成,往往是由用戶(進程)在訪問該進程時使用。
- 處理機狀態:處理機狀態信息由處理機的各種寄存器中的內容組成的。
- 進程調度信息:PCB中存放與進程調度和進程對換有關的信息
- 進程狀態
- 進程優先級
- 進程調度所需的其他信息
- 事件:指進程由執行狀態轉變為阻塞狀態所等待發生的事件,即阻塞原因。
- 進程控制信息:
- 程序和數據的地址
- 進程同步和通信機制
- 資源清單
- 鏈接指針
- 進程標識符:
- 進程控制塊的組織方式:
- 鏈接方式
- 索引方式
- 作用:使一個在多道程序環境下不能獨立運行的程序,成為一個能獨立運行的基本單位,一個能與其它進程並發執行的進程。
2.2 進程控制
進程控制一般是由OS內核中的原語來實現的
- 原語(Primitive)是由若干條指令組成的,用於完成一定功能的一個過程。
- 執行過程中不允許被中斷
- 原子操作在管態下執行,常駐內存。
進程的創建
- 進程圖
- 引起創建進程的事件:
- 用戶登錄
- 作業調度:作業由外存進入內存
- 提供服務
- 上述三種情況下,都是由系統內核來創建一個新進程。
- 應用請求
- 進程的創建過程:一旦操作系統發現了要求創建新進程的事件后,便調用進程創建原語Create( )按下述步驟創建一個新進程。
- 申請空白PCB
- 為新進程分配資源(需要知道新進程所需內存大小)
- 初始化進程控制塊
- 將新進程插入就緒隊列
進程的終止
- 引起進程終止的事件:
- 正常結束
- 異常結束
- 外界干預
- 進程的終止過程:如果系統中發生了上述要求終止進程的某事件,OS便調用進程終止原語Terminate(),按下述過程去終止指定的進程:
- 根據被終止進程的標識符,從PCB集合中檢索出該進程的PCB,從中讀出該進程的狀態。
- 若被終止進程正處於執行狀態,應立即終止該進程的執行,並置調度標志為真,用於指示該進程被終止后應重新進行調度其他進程執行。
- 若該進程還有子孫進程,還應將其所有子孫進程予以終止,以防它們成為不可控的進程。
- 將被終止進程所擁有的全部資源,或者歸還給其父進程,或者歸還給系統。
- 將被終止進程(PCB)從所在隊列(或鏈表)中移出,等待其他程序來搜集信息。
進程的阻塞與喚醒
- 引起進程阻塞和喚醒的事件:
- 請求系統的服務
- 啟動某種操作
- 新數據尚未到達
- 無新工作可做
- 進程阻塞過程:正在執行的進程,當發現上述某事件時,無法繼續執行,於是進程便主動調用阻塞原語block把自己阻塞。
- 進入block過程后,首先立即停止執行,把進程控制塊中的現行狀態由“執行”改為“阻塞”
- 並將PCB插入阻塞隊列。如果系統中設置了基於不同事件的阻塞隊列,則將本進程插入到具有相同事件的阻塞(等待)隊列。
- 轉調度程序進行重新調度。
- 進程喚醒過程:當被阻塞進程所期待的事件出現時,則由有關進程(如:用完並釋放了該I/O設備的進程)調用喚醒原語wakeup( ),將等待該事件的進程喚醒。
- 首先把被阻塞的進程從等待該事件的阻塞隊列中移出
- 將其PCB中的現行狀態由阻塞改為就緒,
- 然后再將該PCB插入到就緒隊列中。
- 注意:block原語和wakeup原語是一對作用剛好相反的原語。因此必須成對使用;否則長久地處於阻塞狀態再無機會繼續運行。
進程的掛起與激活
- 進程的掛起:當出現引起進程掛起的事件時,系統將利用掛起原語suspend()將指定進程掛起。
- 首先檢查被掛起進程的狀態,活動就緒狀態改為靜止就緒;活動阻塞則改為靜止阻塞。
- 為了方便用戶或父進程考查該進程的運行情況,把該進程的PCB復制到某指定的內存區域。若被掛起的進程正在執行,則轉向調度程序重新調度。
- 進程的激活:系統將利用激活原語active( )將指定進程激活。
- 激活原語先將進程從外存調入內存,靜止就緒改為活動就緒;靜止阻塞改為活動阻塞。
- 假如采用的是搶占調度策略,則每當有新進程進入就緒隊列時,應檢查是否要進行重新調度。
2.3 進程同步
進程同步的基本概念
- 使並發進程有效共享資源與合作,程序結果可再現
- 兩種形式的制約關系
- 間接互相制約關系
- 直接相互制約關系
- 臨界資源
- 如打印機、磁帶機,進程間應采取互斥方式實現這種資源的共享
- 生產者消費者問題
- 輸入指針+1:in=(in+1)%n
- 輸出指針+1:out=(out+1)%n
- (in+1)%n=out表示緩沖池滿,(out+1)%n=in表示緩沖池空
- 臨界區:人們把在每個進程中訪問臨界資源的那段代碼稱為臨界區(critical section)。
- 在臨界區前面增加一段用於進行上述檢查的代碼,把這段代碼稱為進入區(entry section)。相應地,在臨界區后面也要加上一段稱為退出區(exit section)的代碼,用於將臨界區正被訪問的標志恢復為未被訪問的標志。
- 進程中除上述進入區、臨界區及退出區之外的其它部分的代碼,在這里都稱為剩余區。
- 同步機制應遵循的規則
- 空閑讓進
- 忙則等待
- 有限等待
- 讓權等待
信號量機制
-
整型信號量,通過wait(s)和signal(s)來訪問,稱為P、V操作,不遵循“讓權等待”
-
S=+n:n個可用的空閑資源
-
S=0:資源全被占用
-
wait(S){ while(S<=0); S--; } signal(S){ s++; }
-
-
記錄型信號量:增加一個進程鏈表指針,用於鏈接上述所有等待進程
-
value=+n:n個可用空閑資源
-
value=0:資源全被占用,沒有空閑,沒有等待進程
-
value=-n:n個進程在等待空閑資源
-
typeof struct{ int value; struct process_control_block *list; }semaphore; wait(semaphore *S){ S->value--; if(S->value < 0) block(S->list); } signal(semaphore *S){ S->value++; if(S->value <= 0) wakeup(S->list); }
-
value初值為1表示只允許一個進程訪問資源, 此時信號量為互斥信號量,用於進程互斥
-
-
AND型信號量:若干個臨界資源的分配,采取原子操作方式:要么把它所請求的資源全部分配到進程,要么一個也不分配。在wait操作中,增加了一個“AND”條件,故稱為AND同步,或稱為同時wait操作,即Swait (Simultaneous wait)
-
信號量集:有些情況當資源數量低於某一下限值時,便不予以分配,在每次分配前測試該資源的數量,看其是否大於下限值
- S為信號量,d為需求值,t為下限值
- Swait(S, d, d):此時在信號量集中只有一個信號量S,但允許它每次申請d個資源,當現有資源數少於d時,不予分配。
- Swait(S, 1, 1):此時的信號量集已蛻化為一般的記錄型信號量(S>1時)或互斥信號量(S=1時)。
- Swait(S, 1, 0):當S≥1時,允許多個進程進入某特定區;當S變為0后,將阻止任何進程進入特定區。換言之,它相當於一個可控開關。
信號量的應用
- 進程互斥:設置互斥信號量mutex,設其初始值為1
- 前趨關系:
- 例如:設有兩個並發執行的進程P1和P2。P1中有語句S1;P2中有語句S2。我們希望在S1執行后再執行S2。
- P1和P2共享一個公用信號量S,並賦予其初值為S=0
- 在進程P1中用: S1; signal(S);
- 在進程P2中用: wait(S); S2;
- 例如:設有兩個並發執行的進程P1和P2。P1中有語句S1;P2中有語句S2。我們希望在S1執行后再執行S2。
管程機制
- 定義:代表共享資源的數據結構,以及由對該共享數據結構實施操作的一組過程所組成的資源管理程序,共同構成了一個操作系統的資源管理模塊,稱之為管程。
- 管程被請求和釋放資源的進程所調用。
- 管程由四部分組成:
- 管程的名稱
- 局部於管程的共享數據結構說明
- 對該數據結構進行操作的一組過程
- 對局部於管程的共享數據設置初始值的語句
- 管程的特性:
- 模塊化
- 抽象的數據類型
- 信息掩蔽
- 管程和進程的差別:
- 進程定義的時私有的數據結構,管程定義的時公共的數據結構
- 進程是與順序程序有關的操作,管程主要是進行同步操作和初始化操作
- 設置進程的目的是在於實現系統的並發性,管程則是解決共享資源的互斥使用問題
- 進程為主動工作方式,管程為被動工作方式(被進程調用)
- 進程之間能並發執行,管程不能與其調用者並發
- 進程具有動態性,管程僅是操作系統中的一個資源管理模塊,供進程調用
- 條件變量:
- 每個條件變量保存了一個鏈表,用於記錄因該條件變量而阻塞的所有進程,同時提供的兩個操作,表示為x.wait和x.signal,或者cwait(x)和csignal(x)。
經典進程同步問題
生產者—消費者問題
-
記錄型信號量解決:
semaphore mutex = 1, empty = n, full = 0;//初始化 item buffer[n]; int in = 0, out = 0;//指針 void producer(){ do{ ... 產生一個元素nextp ... wait(empty);//先檢查是否由空位 wait(mutex);//檢查是否有消費者訪問臨界資源 buffer[in] = nextp;//裝入元素 in = (in + 1) % n;//指針后移 signal(mutex); signal(full);//空間滿變量加一,表示有一個元素在緩沖池 }while(true); } void consumer(){ do{ wait(full);//檢查是否為空 wait(mutex); nextc = buffer[out]; out = (out + 1) % n; signal(mutex); signal(empty); ... 消費nextc中的元素 ... }while(true); } void main(){ cobegin producer(); consumer(); coend }
-
AND信號量解決:
semaphore mutex = 1, empty = n, full = 0;//初始化 item buffer[n]; int in = 0, out = 0;//指針 void producer(){ do{ ... 產生一個元素nextp ... //替換為 Swait(empty, mutex);//-------- buffer[in] = nextp;//裝入元素 in = (in + 1) % n;//指針后移 //替換為 Ssignal(mutex, full);//--------- }while(true); } void consumer(){ do{ Swait(full, mutex);//--------- nextc = buffer[out]; out = (out + 1) % n; Ssignal(mutex, empty);//---------- ... 消費nextc中的元素 ... }while(true); } void main(){ cobegin producer(); consumer(); coend }
-
管程解決:
//管程 Monitor producer_consumer{ item buffer[n]; int in, out, count;//count表示緩沖池中已有的產品數目 condition notfull, notempty;//條件變量 void put(item x){ if(count >= n) notfull.wait; buffer[in] = nextp; in = (in + 1) % n; count++; notempty.signal; } void get(item x){ if(count <= 0) notempty.wait; nextc = buffer[out]; out = (out + 1) % n; count--; notfull.signal; } in = 0; out = 0; count = 0; }PC; //生產者-消費者 void producer(){ item x; while(true){ ... 產生一個元素nextp ... PC.put(x); } } void consumer(){ item x; while(true){ PC.get(x); ... 消費元素nextc ... } }
哲學家進餐問題
-
記錄型信號量:
semaphore chopstick[5] = {1, 1, 1, 1, 1}; while(true){ wait(chopstick[i]); wait(chopstick[(i+1)%5]); ... eat ... signal(chopstick[i]); signal(chopstick[(i+1)%5]); ... think }
-
AND型信號量:
semaphore chopstick[5] = {1, 1, 1, 1, 1}; while(true){ Swait(chopstick[(i+1)%5], chopstick[i]); ... eat ... Ssignal(chopstick[(i+1)%5], chopstick[i]); ... think }
-
最多允許
semaphore chopstick[5] = {1, 1, 1, 1, 1}; int count = 4; while(true){ wait(count); wait(chopstick[i]); wait(chopstick[(i+1)%5]); ... eat ... signal(chopstick[i]); signal(count); signal(chopstick[(i+1)%5]); ... think }
讀者寫者問題
-
記錄型信號量解決:
semaphore rmutex = 1, wmutex = 1; int readcount = 0; void reader(){ while(true){ wait(rmutex);//控制對readcount的互斥操作 if(readecount == 0) wait(wmutex);//沒有讀者時(當前為第一個讀者),檢查是否有寫者,上鎖 readcount++; signal(rmutex);//對readcount操作完成 ... read ... wait(rmutex); readcount--; if(readcount == 0) signal(wmutex);//最后一個讀者釋放寫者鎖 signal(rmutex); } } void writer(){ while(true){ wait(wmutex); ... write ... signal(wmutex); } }
-
信號量集解決:增加了一個限制,即最多只允許 RN 個讀者同時讀
int RN; semaphore L = RN, mx = 1; void reader(){ while(true){ Swait(L, 1, 1);//L:資源值,1:下限值,1:需求值,進入讀者數小於RN時才允許進入 Swait(mx, 1, 0); /* 讀者不修改mx 只有mx=1時,讀者才可進入 當mx>=1時,允許多個線程進入 當mx=0,阻止任何進程進入 */ ... read ... Ssignal(L, 1); } } void writer(){ while(true){ Swait(mx, 1, 1, L, RN, 0)//僅當既無寫者在寫,也無讀者在讀時,writer才能寫,這里不修改L ... write ... Ssignal(mx, 1); } }
2.5 進程通信
進程通信的類型
- 共享存儲器系統
- 基於共享數據結構的通信方式(低效,少量數據)
- 基於共享存儲區的通信方式
- 消息傳遞系統:進程間的數據交換是以格式化的消息(message)為單位。在計算機網絡中,把消息稱為報文。
- 直接通信:利用OS提供的發送命令直接發送給目標進程Send(Receiver, message), Receive(Sender, message)
- 間接通信:間接通信方式指進程之間的通信需要通過作為共享數據結構的實體。該實體用來暫存發送進程發送給目標進程的消息,接收進程則從該實體中取出對方發送給自己的消息。通常把這種中間實體稱為信箱。
- 管道(pipe)通信系統:
- 管道是指用於連接一個讀進程和一個寫進程以實現它們之間通信的一個共享文件,又名pipe文件。
- 發送進程(即寫進程) 以字符流形式將大量的數據送入管道,接收進程(即讀進程) 則從管道中接收(讀)數據。
- 管道機制需要提供的協調能力:
- 互斥
- 同步
- 確定對方是否存在
消息緩沖隊列通信機制
線程的基本該概念
線程的引入
- 進程的兩個基本屬性:
- 進程是一個可擁有資源的獨立單位
- 進程同時又是一個可獨立調度和分派的基本單位
- 將進程的兩個屬性分開:
- 作為調度和分派的基本單位——線程
- 擁有資源的基本單位,不進行頻繁的切換——進程
線程與進程的比較
- 調度:在同一進程中,線程的切換不會引起進程的切換,但從一個進程中的線程切換到另一個進程中的線程時,將會引起進程的切換
- 並發性:在引入線程的操作系統中,不僅進程之間可以並發執行,而且同一個進程中的多個線程之間亦可並發執行
- 擁有資源:線程自己基本不擁有系統資源(僅有一點必不可少的資源),但它可以訪問其隸屬進程的資源,即一個進程的代碼段、數據段及所擁有的系統資源。
- 系統開銷:對進程創建或撤消以及切換時,開銷(處理資源)明顯大於對線程操作的開銷。此外,由於一個進程中多個線程具有相同的地址空間,在同步和通信的實現方面線程也比進程容易,甚至都無須OS內核的干預。
- 支持多處理機:多線程可以在多CPU上並行