進程管理
進程
進程是程序的一次執行
是一個程序及其數據在處理機上順序執行時所發生的活動
是具有獨立功能的程序在一個數據集合上的一次運行過程
是系統進行資源分配和調度的一個基本單位
是PCB結構、程序和數據的集合
設備分配只針對現有進程,不會創建進程
進程的特征:
- 動態性:進程的實質是程序的一次執行過程,因此,動態特征是進程最重要的特征
- 並發性:沒有為之建立進程的程序是不能並發執行的,僅當為之建立一個進程后才能參加
並發執行 - 獨立性:進程是一個能獨立運行的基本單位,同時也是系統分配資源和調度的獨立單位
- 異步性:由於進程間的相互制約,使進程具有執行的間斷性,即進程按各自獨立的、不可
預知的速度向前推進 - 結構特征:為了控制和管理進程,系統為每個進程設立一個進程控制塊--PCB
進程與程序的區別:
- 程序是進程的靜態文本,進程是執行程序的動態過程
- 進程與程序之間不是一一對應的,同一程序同時運行於若干不同的數據集合上,它將屬於若干個不同的進程;
一個進程可以執行多個程序 - 程序可作為軟件資源長期保存,進程只是一次執行過程,是暫時的
- 進程是系統分配調度的獨立單位,能與其他進程並發執行
進程狀態及其演變
進程執行的間斷性,決定了進程可能具有多種狀態
基本狀態
運行的進程可能具有就緒、執行、阻塞三種基本狀態
當進程已分配到除CPU以外的所有必要資源時,它便處於就緒狀態,一旦獲得CPU,便立即執行,進入執行狀態
正在執行的進程,由於發生某個事件而暫時無法執行時,便放棄處理機而進入阻塞狀態
由於執行的進程變為阻塞狀態后,調度程序立即把處理機分配給另一個就緒進程(因此,阻塞進程的事件消失后,進程不會立即恢復到執行狀態,而轉變為就緒狀態,重新等待處理機)
創建和終止
為了管理的需要,還存在着兩種比較常見的進程狀態,即創建狀態和終止狀態
創建狀態:
引起創建的事件:
- 用戶登錄
- 作業調度:為被調度的作業創建進程
- 提供服務:要求打印
- 應用請求
創建一個進程一般要通過兩個步驟:
- 首先,為一個新進程創建PCB,並填寫必要的管理信息;
- 其次,把該進程轉入就緒狀態並插入就緒隊列之中。
當一個新進程被創建時,系統已為其分配了PCB,填寫了進程標識等信息,但由於該進程所必需的資源或其它信息,如主存資源尚未分配等,一般而言,此時的進程已擁有了自己的PCB,但進程自身還未進入主存,即創建工作尚未完成,進程還不能被調度運行,其所處的狀態就是創建狀態。
引入創建狀態,是為了保證進程的調度必須在創建工作完成后進行,以確保對進程控制塊操作的完整性。同時,創建狀態的引入,也增加了管理的靈活性,操作系統可以根據系統性能或主存容量的限制,推遲創建狀態進程的提交。對於處於創建狀態的進程,獲得了其所必需的資源,以及對其PCB初始化工作完成后,進程狀態便可由創建狀態轉入就緒狀態。
終止狀態:
引起終止的事件:
- 正常結束
- 異常結束
- 外界干預
- 系統管理員kill
- 父進程終止
- 父進程請求
進程的終止也要通過兩個步驟:
- 首先等待操作系統進行善后處理
- 然后將其PCB清零,並將PCB 空間返還系統
當一個進程到達了自然結束點,或是出現了無法克服的錯誤,或是被操作系統所終結,或是被其他有終止權的進程所終結,它將進入終止狀態
進入終止態的進程以后不能再執行,但在操作系統中依然保留一個記錄,其中保存狀態碼和一些計時統計數據,供其它進程收集。一旦其它進程完成了對終止狀態進程的信息提取之后,操作系統將刪除該進程。
阻塞和喚醒
阻塞是進程自身的一種主動行為
a. 調用block原語
b. 停止執行,修改PCB進入阻塞隊列(一個或多個
喚醒由其他相關進程完成
a. wakeup原語
b. 修改PCB進入就緒隊列
掛起
為了系統和用戶觀察分析的需要,還引入了掛起操作,與掛起對應的是激活操作
當進程被掛起,便會進入靜止狀態:正在執行,便會暫停執行,處於就緒狀態則不接受調度
引入掛起狀態的原因有:
- 終端用戶的請求:當終端用戶在自己的程序運行期間發現有可疑問題時,希望暫時使自己的程序靜止下來。亦即,使正在執行的進程暫停執行;若此時用戶進程正處於就緒狀態而未執行,則該進程暫不接受調度,以便用戶研究其執行情況或對程序進行修改。我們把這種靜止狀態稱為掛起狀態。
- 父進程請求:有時父進程希望掛起自己的某個子進程,以便考查和修改該子進程,或者協調各子進程間的活動。
- 負荷調節的需要:當實時系統中的工作負荷較重,已可能影響到對實時任務的控制時,可由系統把一些不重要的進程掛起,以保證系統能正常運行。
- 操作系統的需要:操作系統有時希望掛起某些進程,以便檢查運行中的資源使用情況或進行記賬。
在引入掛起狀態后:
- 活動就緒→靜止就緒:當進程處於未被掛起的就緒狀態時,稱此為活動就緒狀態,表示為Readya
當用掛起原語Suspend 將該進程掛起后,該進程便轉變為靜止就緒狀態,表示為Readys,處於Readys狀態的進程不再被調度執行 - 活動阻塞→靜止阻塞:當進程處於未被掛起的阻塞狀態時,稱它是處於活動阻塞狀態,表示為Blockeda。當用Suspend原語將它掛起后,進程便轉變為靜止阻塞狀態,表示為Blockeds。處於該狀態的進程在其所期待的事件出現后,將從靜止阻塞變為靜止就緒
- 靜止就緒→活動就緒:處於Readys 狀態的進程,若用激活原語Active 激活后,該進程將轉變為Readya 狀態
- 靜止阻塞→活動阻塞:處於Blockeds 狀態的進程,若用激活原語Active 激活后,該進程將轉變為Blockeda 狀態
五個進程狀態的轉換
- NULL→創建:一個新進程產生時,該進程處於創建狀態
- 創建→活動就緒:在當前系統的性能和內存的容量均允許的情況下,完成對進程創建的必要操作后,相應的系統進程將進程的狀態轉換為活動就緒狀態
- 創建→靜止就緒:考慮到系統當前資源狀況和性能要求,並不分配給新建進程所需資源,主要是主存資源,相應的系統進程將進程狀態轉為靜止就緒狀態,對換到外存,不再參與調度,此時進程創建工作尚未完成
- 執行→終止:當一個進程到達了自然結束點,或是出現了無法克服的錯誤,或是被操作系統所終結,或是被其他有終止權的進程所終結,進程即進終止狀態
PCB
為了描述和控制進程的運行,系統為每個進程定義了一個數據結構——進程控制塊PCB(Process Control Block),它是進程實體的一部分,是操作系統中最重要的記錄型數據結構
PCB 的作用是使一個在多道程序環境下不能獨立運行的程序(含數據),成為一個能獨立運行的基本單位,一個能與其它進程並發執行的進程
- 作為獨立運行基本單位的標志
- 能實現間斷性的運行方式
- 提供進程管理所需要的信息
- 提供進程調度所需要的信息
- 實現與其他進程的同步與通信
PCB中的信息:
- 進程標識符:用於惟一地標識一個進程,一個進程通常有兩種標識符:
- 內部標識符,在所有的操作系統中,都為每一個進程賦予了一個惟一的數字標識符,
它通常是一個進程的序號。設置內部標識符主要是為了方便系統使用。 - 外部標識符,它由創建者提供,通常是由字母、數字組成,往往是由用戶(進程)在訪問該進程時使用。為了描述進程的家族關系,還應設置父進程標識及子進程標識。此外,還可設置用戶標識,以指示擁有該進程的用戶。
- 內部標識符,在所有的操作系統中,都為每一個進程賦予了一個惟一的數字標識符,
- 處理機狀態:主要是由處理機的各種寄存器中的內容組成的。處理機在運行時,許多信息都放在寄存器中。當處理機被中斷時,所有這些信息都必須保存在PCB 中,以便在該進程重新執行時,能從斷點繼續執行。這些寄存器包括
- 通用寄存器,又稱為用戶可視寄存器,它們是用戶程序可以訪問的,用於暫存信息,在大多數處理機中,有 8~32 個通用寄存器,在RISC 結構的計算機中可超過100 個
- 指令計數器,其中存放了要訪問的下一條指令的地址
- 程序狀態字PSW,其中含有狀態信息,如條件碼、執行方式、中斷屏蔽標志等
- 用戶棧指針,指每個用戶進程都有一個或若干個與之相關的系統棧,用於存放過程和系統調用參數及調用地址,棧指針指向該棧的棧頂。
- 進程調度信息:在 PCB中還存放一些與進程調度和進程對換有關的信息,包括
- 進程狀態,指明進程的當前狀態,作為進程調度和對換時的依據
- 進程優先級,用於描述進程使用處理機的優先級別的一個整數,優先級高的進程應優先獲得處理機
- 進程調度所需的其它信息,它們與所采用的進程調度算法有關,比如,進程已等待CPU的時間總和、進程已執行的時間總和等
- 事件,指進程由執行狀態轉變為阻塞狀態所等待發生的事件,即阻塞原因。
- 進程控制信息:
- 程序和數據的地址,指進程的程序和數據所在的內存或外存地(首)址,以便再調度到該進程執行時,能從PCB中找到其程序和數據
- 進程同步和通信機制,指實現進程同步和進程通信時必需的機制,如消息隊列指針、信號量等,它們可能全部或部分地放在PCB 中
- 資源清單,即一張列出了除CPU 以外的、進程所需的全部資源及已經分配到該進程的資源的清單
- 鏈接指針,它給出了本進程(PCB)所在隊列中的下一個進程的PCB的首地址。
PCB的組織方式
在一個系統中,通常可擁有數十個、數百個乃至數千個PCB。為了能對它們加以有效的管理,應該用適當的方式將這些PCB組織起來。目前常用的組織方式有以下兩種。
- 線性方式:將系統種所有PCB都組織在一張線性表中,將該表首地址存在內存的一個專用區域
實現簡單,開銷小,但是每次都需要掃描整張表,適合進程數目不多的系統 - 鏈接方式:把同一狀態的PCB鏈接成一個隊列,形成就緒隊列、若干個阻塞隊列和空白隊列等
對其中的就緒隊列常按進程優先級的高低排列,優先級高排在隊前,此外,也可根據阻塞原因的不同而把處於阻塞狀態的進程的PCB排成等待I/O 操作完成的隊列和等待分配內存的隊列等 - 索引方式:系統根據所有進程的狀態建立幾張索引表,例如就緒索引表、阻塞索引表等,並把各索引表在內存的首地址記錄在內存的一些專用單元中,在每個索引表的表目中,記錄具有相應狀態的某個PCB在PCB址
進程的控制
進程控制是進程管理最基本的功能,主要包括創建新進程,終止已完成的進程,將發生異常的進程置於阻塞狀態,進程運行中的狀態轉換等
進程創建
參數:進程標識、優先級、進程起始地址、CPU初始狀態、資源需求等等
創建進程的過程:
- 創建一個空白PCB
- 為新進程分配所需資源
- 初始化PCB
- 標識信息,將系統分配的標識符和父進程標識符填入新PCB
- 處理機狀態信息,使程序計數器指向程序入口地址,使棧指針指向棧頂
- 處理機控制信息,將進程設為就緒/靜止狀態,通常設為最低優先級
- 如果就緒隊列能接納,則插入
進程終止
進程終止的時機/時間:
- 正常結束
- 異常結束
- 越界錯,訪問的存儲區越出該進程的區域
- 保護錯,試圖訪問不允許訪問的資源,或以不適當的方式訪問(寫只讀)
- 非法指令,試圖執行不存在的指令(可能是程序錯誤地轉移到數據區,數據當成了指令)
- 特權指令出錯,用戶進程試圖執行一條只允許OS執行的指令
- 運行超時,執行時間超過指定的最大值
- 等待超時,進程等待某件事超過指定的最大值
- 算數運算錯,試圖執行被禁止的運算(被0除)
- I/O故障
- 外界干預
- 操作員或OS干預(死鎖)
- 父進程請求,子進程完成父進程指定的任務時
- 父進程終止,所有子進程都應該結束
終止過程:
- 根據被終止進程的標識符,從PCB集合中檢索出該PCB,讀取進程狀態
- 若處於執行狀態:立即終止執行,置調度標志為true,指示該進程被終止后重新調度
- 若進程有子孫進程:將其所有子孫進程終止
- 全部資源還給父進程/OS
- PCB從所在隊列/鏈表中移出
進程阻塞
阻塞的時機/事件
- 請求共享資源失敗,系統無足夠資源分配
- 等待某種操作完成
- 新數據尚未到達(相互合作的進程)
- 等待新任務
阻塞過程:進程通過block
進程喚醒
原語wakeup,和阻塞成對使用
喚醒過程:先把被阻塞的進程從該事件阻塞隊列移出,將其PSB狀態改為就緒,再插入就緒隊列
進程同步
制約關系
- 資源共享關系(間接制約
- 需要互斥的訪問臨界資源
- 相互合作關系(直接制約
臨界資源:一次只允許一個進程訪問的資源
- 引起不可再現性是因為臨界資源沒有互斥的訪問
臨界區
while(1){
entry; //進入區
critical; //臨界區
exit; //退出區
}
各進程應互斥進入相關臨界區,所謂臨界區是指一段代碼,一段程序
同步機制應該遵循:
- 空閑讓進
- 忙則等待
- 有限等待
- 讓權等待:不能進入臨界區的執行進程放棄cpu執行權
整形信號量
信號量機制是一種進程間的低級通信方式
s是一個整形量,除了初始化外,僅通過兩個原子操作wait(s)和signal(s)訪問(也叫P,V操作)
wait(s){
while(s<=0);
s--;
}
signal(s){
s++;
}
互斥關系:A,B共享一個緩沖區,互斥
PA(){
while(1){
wait(s);
// 臨界區
signal(s);
// 剩余區
}
}
PB(){
while(1){
wait(s);
// 臨界區
signal(s);
// 剩余區
}
}
main(){
s=1; //init
// begin
PA();
PB();
// end
}
前驅關系:P1,P2同步,P2→P1
PA(){
P(s);
a1;
}
PB(){
a2;
V(s);
}
總結:
- 互斥的模式:PV總是成對出現在同一進程中;
- 同步的模式:PV總是成對出現在不同進程中;
- 前驅關系:有多少前驅關系設置多少個信號量,初值為0;有多少前驅做多少P操作,有多少后繼結點做多少V操作,無前驅不做P操作。
信號量表示的是臨界資源數
初值為2,表示初始時有2個可用的資源,若現在為-1,說明這兩個可用資源已經被占用了,而且有一個進程在等待資源
初值為3,表示初始時有3個可用的資源,若現在為1,表示有兩個資源被進程訪問了,可用資源變為1,沒有進程會等待
為了使兩個進程同步運行,至少2個信號量
例題
1.桌上有一空盤,允許存放一只水果,爸爸可向盤內放蘋果或桔子,兒子專等吃桔子,女兒專等吃蘋果
semaphore s,so,sa=1,0,0;
father(){
while(1) {
wait(s);
// 將水果放入盤中;
if(放入的是桔子)
then signal(so);
else signal(sa);
}
}
son(){
while(1){
wait(so);
// 從盤中取出桔子
signal(s);
// 吃桔子
}
}
daughter() {
while(1){
wait(sa);
// 從盤中取出蘋果
signal(s);
// 吃蘋果
}
}
main()
{
// cobegin
father();
son();
daughter();
// coend
}
2.某寺廟,有小、老和尚若干,由小和尚提水入缸供老和尚飲用,水缸可容10桶水,水取自同一個井中,水井窄,每次只能容一個桶取水,水桶總數3個,每次入缸取水桶僅為1桶,且不可同時進行,試給出有關取水,入水的算法
mutexj = 1;
mutexg = 1;
empty = 10;
full = 0;
count = 3;
old(){
while(1) {
wait(full); //缸中有無水
wait(count); //有無桶
wait(mutexg); //取水前
Fetch from gang;
signal(mutexg);
signal(count);
signal(empty); //通知小
}
}
little(){
while(1) {
wait(empty); //缸中有無空
wait(count); //有無桶
wait(mutexj); //取水前
Fetch from well;
signal(mutexj);
wait(mutexg); //倒水前
pour;
signal(mutexg);
signal(count);
signal(full); //通知老
}
}
記錄型信號量
整形信號量中S<=0就會不斷地測試,未遵循讓權等待,而是處於忙等
解決方案:建立一個進程鏈表list,連結所有等待該類資源的進程
typedef 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);
}
- S->value>0時,表示系統中可用資源的數目
- 當S->value<0時,S->value的絕對值表示阻塞進程的數目
- 如果S->value的初值為1,表示只允許一個進程訪問臨界資源,此時的信號量轉化為互斥信號量
AND信號量
要么全分配,要么一個也不分配
不用時有可能發生死鎖
Swait(s1,s2,…,sn){
while(1){
if (s1>=1& …&sn >=1){
for(i=1;i<=n;i++) si--;
break;
}
else{
//將進程放入與找到的第一個si<1的si相關的阻塞隊列中
//並將該進程的程序計數設置為swait操作的開始
}
}
}
Ssignal(s1,s2,…,sn){
while(1){
for (i=1;i<=n;i++){
si++;
//將與si關聯的隊列中等待的所有進程都移動到就緒隊列中
}
}
}
信號量集
為提高效率而對AND信號量的擴充
允許一次申請多種資源多個
ti為分配下限值,Si>=ti則不分配,di為該進程需求值
Swait(S1, t1, d1, …, Sn, tn, dn){
while(1){
if(Si>=ti& … &Sn>=tn)
for (i=1;i<=n;i++)
Si=Si-di;
else{
//將進程放在Si<ti的第一個Si的阻塞隊列中
//並將該進程的程序計數設置為swait操作的開始
}
}
}
Ssignal(S1, d1, …, Sn, dn){
while(1){
for (i=1;i<=n;i++) {
Si =Si+di;
//將與si關聯的隊列中等待的所有進程都移動到就緒隊列中
}
}
}
Swait(S,d,d):允許每次申請d個資源,少於d不分配
Swait(S,1,1):S>1記錄型信號量,S=1互斥形信號量
Swait(S,1,0):可控開關,S>=1時允許同時進入,S<1時不允許
經典進程同步問題
生產者-消費者*
定義數據結構
int n;
typedef item = xxx; //產品類型
item buffer [n]; //緩沖池
int in = 0,out = 0;
int counter = 0; //產品數
緩沖池滿時,生產者等待;為空時,消費者等待
記錄型信號量
semaphore mutex=1,empty=n,full=0
item buffer[n]; //緩沖池
int in = 0,out = 0;
int main()
{
cobegin:
producer();
consumer();
coend
}
producer(){
while(1){
…
produce an item in nextp;
…
wait(empty);
wait(mutex);
buffer[in]=nextp;
in=(in+1)%n;
signal(mutex);
signal(full);
}
}
consumer(){
while(1) {
wait(full);
wait(mutex);
nextc=buffer[out];
out=(out+1)%n;
signal(mutex);
signal(empty);
consumer the item in nextc;
}
}
P操作的順序至關重要,順序不當可能導致死鎖(有緩沖池使用權,無緩沖)
V操作的順序無關緊要
當緩沖區只有一個時,mutex可省略
AND信號量
semaphore mutex=1,empty=n,full=0;
item buffer[n];
int in=0,out=0;
main(){
cobegin
producer();
consumer();
coend
}
producer()
{
while(1)
{
...
produce......;
...
Swait(empty,mutex);
buffer[int]=nextp;
in = (in+1)mod n;
Ssignal(mutex,full);
}
}
consumer(){
while(1){
Swait(full,mutex);
nextc=buffer[out];
out=(out+1)%n;
Ssignal(mutex,empty);
consumer......;
}
}
哲學家進餐
5個哲學家圍坐,用5只筷子吃面,筷子交替擺放
記錄型信號量
設5個信號量表示5只筷子
AND信號量
同上
讀-寫*
讀進程可共享對象,寫進程不可
設整形變量readcount讀者數,wmutex讀寫互斥,rmutex互斥訪問
記錄型信號量:
semaphore rmutex=1,wmutex=1;
int readcount=0;
int main(){
cobegin:
reader();
writer();
coend;
}
reader(){
while(1){
wait(rmutex);
if(readcount==0) wait(wmutex);//無人讀才能寫
readcount++;
signal(rmutex);
...
read......
...
wait(rmutex);
readcount--;
if(readcount==0) signal(wmutex);
signal(rmutex);
}
}
writer(){
while(1){
wait(wmutex);
...
write......
...
signal(wmutex);
}
}
寫優先
semaphore rmutex=1,wmutex=1,s=1;
int readcount=0;
int main(){
cobegin:
reader();
writer();
coend;
}
reader(){
while(1){
wait(s);//!
wait(rmutex);
if(readcount==0) wait(wmutex);//無人讀
readcount++;
signal(rmutex);
signal(s);//!
...
read......
...
wait(rmutex);
readcount--;
if(readcount==0) signal(wmutex);
signal(rmutex);
}
}
writer(){
while(1){
wait(s);//!
wait(wmutex);
...
write......
...
signal(wmutex);
signal(s);//!
}
}
信號量集
#define RN 20//最大讀者數
semaphore L=RN,mx=1;
int main(){
cobegin:
reader();
writer();
coend;
}
reader(){
while(1){
swait(L,1,1);
swait(mx,1,0);
...
read......
...
wait(rmutex);
ssignal(L,1);
}
}
writer(){
while(1){
swait(mx,1,1);
swait(L,RN,0);
...
write......
...
ssignal(mx,1);
}
}
管程
將同步操作的機制和臨界資源結合到一起,避免了要使用臨界資源的進程自備同步操作
管程:一個數據結構和能為並發進程所執行的一組操作
包括:1. 局部對於管程的共享變量,2. 對該數據結構操作的一組過程,3. 對局部管程數據設初值
Monitor m_name{ //管程名
variable declarations; //共享變量說明
cond declarations; //條件變量說明
public: //能被進程調用的過程
void P1(…); //對數據結構的操作過程
{}
void P2(…);
{}
...
void Pn(…);
{}
...
{
//管程主體
//初始化代碼
}
}
生產者-消費者
建立管程PC,包括:
- 二過程:put(item)過程;get(item)過程;
- 一變量:count>=n時滿,<=0時空
- 初始值:in=out=count=0
Monitor PC{
item buffer[N];
int in out;
condition notfull,notempty;
int count;
public:
void put(item x){
if(count>=N)cwait(notfull);
buffer[in]=x;
in=(in+1)%N;
count++;
csignal(notempty);
}
void get(item x){
if(count<=0)cwait(notempty);
x=buffer[out];
out=(out+1)%N;
count--;
csignal(notfull);
}
}
void producer(){
item x;
while(1){
//produce
PC.put(x);
}
}
void consumer(){
item x;
while(1){
PC.get(x);
//consume
}
}