計算機操作系統(第四版)之進程要點梳理

進程的描述與控制



前驅圖和程序執行

前驅圖:

一種DAG,描述進程之間執行的前後關係。

程序的順序執行:

順序性、封閉性、可再現性。

程序的併發執行:

間斷性、失去封閉性、不可再現性。

進程的描述

進程的定義:

  1. 系統利用 PCB(進程控制塊)來描述進程的基本情況和活動過程,進而控制和管理進程。
  2. 由程序段、相關的數據段和 PCB 三部分構成進程實體。一般將進程實體簡稱爲進程。
  3. 所謂創建進程,實質上是創建進程實體中的 PCB;而撤消進程,實質上是撤消進程的 PCB。

進程的特徵:

  1. 動態性。
    由創建而產生,由調度而執行,由撤消而消亡。
  2. 併發性。
  3. 獨立性。
    進程實體是一個能獨立運行、獨立分配資源和獨立接受調度的基本單位。
  4. 異步性。

進程的三種基本狀態:

  1. 就緒狀態。
    進程已分配到除 CPU 以外的所有必要資源。
  2. 執行狀態。
  3. 阻塞狀態。
    正在執行的進程由於發生某事件而暫時無法繼續執行時,便放棄處理機而處於暫停狀態。
    通常將處於阻塞狀態的進程也排成一個隊列,稱爲阻塞隊列。在較大的系統中則根據阻塞原因的不同會設置多個阻塞隊列。
    三種基本狀態及其轉換

進程的創建狀態和終止狀態:

  1. 創建狀態。
    進程創建的步驟:1)由進程申請一個空白PCB。2)向PCB中填寫用於控制和管理進程的信息。3)爲進程分配運行時所必須的資源。4)把進程轉入就緒狀態並插入到就緒隊列中。
  2. 終止狀態。
    進程終止的步驟:1)等待操作系統進行善後處理。2)將其 PCB 清零,並將 PCB 空間返還系統。
    在系統進行善後處理後,進程仍在系統中保留一個記錄,其中保存狀態碼和一些計時統計數據,供其它進程收集。一旦其它進程完成了對其信息提取之後,操作系統將刪除該進程,即將其 PCB 清零,並將 PCB 空間返還系統。
    這裏寫圖片描述

掛起操作的引入:

基於如下需要:1)終端用戶的請求。2)父進程請求。3)負荷調節的需要。4)操作系統的需要。
在引入掛起原語Suspend和激活原語Active後導致的幾種狀態轉換:
1)活動就緒(Readya) –> 靜止就緒(Readys)。2)活動阻塞(Blockeda) –> 靜止阻塞(Blockeds)。
3)靜止阻塞(Blockeds) –> 靜止就緒(Readys)。 [ 處於該狀態的進程在其所期待的事件出現後發生狀態轉換 ]
4)靜止就緒(Readys) –> 活動就緒(Readya)。5)靜止阻塞(Blockeds) –> 活動阻塞(Blockeda)。
這裏寫圖片描述

進程管理中的數據結構:

  1. 操作系統中用於管理控制的數據結構。
    內存表、設備表、文件表和用於進程管理的進程表(又被稱爲進程控制塊 PCB)。
  2. 進程控制塊 PCB 的作用(操作系統中最重要的記錄型數據結構)。
    1)作爲獨立運行基本單位的標誌。
    PCB 是進程存在於系統中的唯一標誌。
    2)能實現間斷性運行方式。
    可以將 CPU 現場信息保存在中斷進程的 PCB 中,以在再次被調度時恢復。
    3)提供進程管理所需要的信息。
    4)提供進程調度所需要的信息。
    5)實現與其它進程的同步與通信。
  3. 進程控制塊中的信息。
    1)進程標識符。
    外部標識符、內部標識符(唯一的數字標識符)。
    2)處理機狀態。
    即處理機的上下文,由處理機的各種寄存器中的內容組成。寄存器包括:通用寄存器、指令計數器、程序狀態字 PSW、用戶棧指針。
    3)進程調度信息。
    進程狀態、進程優先級、進程調度所需要的其它信息(例如已執行的時間總和)、事件(阻塞原因)。
    4)進程控制信息。
    程序和數據的地址、進程同步和通信機制、資源清單(列出了除 CPU 外的全部資源;還有一張已分配到的資源清單)、鏈接指針(本進程所在隊列中的下一進程的 PCB 的首地址)。
  4. 進程控制塊的組織方式。
    1)線性方式。2)鏈接方式。3)索引方式。

進程控制

進程控制一般是由 OS 的內核中的原語來實現的。

操作系統內核:

通常將一些與硬件緊密相關的模塊、各種常用設備的驅動程序以及運行頻率較高的模塊,都安排在緊靠硬件的軟件層次中,將它們常駐內存,即通常被稱爲的 OS 內核。目的在於兩方面:一是便於對這些軟件進行保護,防止遭受其他應用程序的破壞;二是可以提高 OS 的運行效率。
爲防止 OS 本身及關鍵數據遭受應用程序破壞,通常將處理機的執行狀態分成:
1)系統態:又稱管態、內核態,能執行一切指令,訪問所有的寄存器和存儲區;
2)用戶態:又稱目態,僅能執行規定的指令,訪問指定的寄存器和存儲區。
大多數 OS 內核包含以下兩大方面的功能:
1. 支撐功能。
1)中斷處理。2)時鐘管理。3)原語操作。
2. 資源管理功能。
1)進程管理。2)存儲器管理。3)設備管理。

進程的創建:

  1. 進程的層次結構。
    當子進程被撤銷時,應將從父進程獲得的資源歸還給父進程;當父進程被撤銷時,也必須同時撤銷其所有的子進程。
  2. 進程圖。
  3. 引起創建進程的事件。
    1)用戶登錄。2)作業調度。3)提供服務。4)應用請求。
  4. 進程的創建。
    OS 調用進程創建原語Creat進行創建新進程:
    1)申請空白 PCB,爲新進程申請獲得唯一的數字標識符,並從 PCB 集合中索取一個空白 PCB。
    2)爲新進程分配其運行所需的資源。
    3)初始化進程控制塊:初始化標識信息、初始化處理機狀態信息、初始化處理機控制信息
    4)如果進程就緒隊列能夠接納新進程,便將新進程插入就緒隊列。

進程的終止:

  1. 引起進程終止的事件。
    1)正常結束。3)異常結束。3)外界干預。
  2. 進程的終止過程(下述過程即之前所謂的操作系統進行善後處理)。
    1)根據被終止進程的標識符,從 PCB 集合中檢索出該進程的 PCB。
    2)若被終止進程正處於執行狀態,應立即終止該進程的執行,並置調度標誌爲真,用於指示該進程被終止後應重新進行調度。
    3)還應將其所有子孫進程予以終止,以防它們成爲不可控的進程。
    4)將被終止進程所擁有的全部資源,或者歸還給其父進程,或者歸還給系統。
    5)將被終止進程(PCB)從所在隊列(或鏈表)中移出,等待其他程序來蒐集信息。

進程的阻塞與喚醒:

  1. 引起進程阻塞和喚醒的事件。
    1)向系統請求共享資源失敗。
    2)等待某種操作的完成。
    3)新數據尚未到達。
    4)等待新任務的到達。
  2. 進程阻塞過程。
    進程通過調用阻塞原語 block 把自己阻塞。阻塞是進程自身的一種主動行爲。
  3. 進程喚醒過程。
    由有關進程(比如用完並釋放了該 I/O 設備的進程)調用喚醒原語 wakeup。

進程的掛起與激活:

  1. 進程的掛起。
    利用掛起原語 suspend。
  2. 進程的激活過程。
    利用激活原語 active。
    假如採用的是搶佔調度策略,則每當有靜止就緒進程被激活而插入就緒隊列時,便應檢查是否要進行重新調度。

進程同步

進程同步的基本概念

1 . 兩種形式的制約關係。
1)間接相互制約關係。
共享系統資源。特別地,對於臨界資源只能是互斥訪問。
2)直接相互制約關係。
爲完成同一項任務而相互合作。
2. 臨界資源。
通過經典問題生產者-消費者問題來說明爲什麼要對互斥資源互斥的訪問:

int in=0,out=0,count=0; //共享變量:生產、消費指針,產品數目
item buffer[n];         //共享數組變量:包含n個緩衝區的緩衝池

void producer(){        //生產者進程
    while(1){
        produce an item in nextp;  //生產一個產品nextp
        ...
        while(count==n)           //判斷緩衝池是否已滿           
        ;
        buffer[in]=nextp;   //產品nextp投放到in指針所指的緩衝區     
        in=(in+1)%n;        // in指針向後移動一下
        count++;    //產品數目加1
    }                
};
void consumer(){      //消費者進程
    while(1){
        while(count==0)          //產品數不等於0時,可以消費        
        ;
        nextc=buffer[out];    //out所指緩衝區即爲要消費的產品
        out=(out+1)%n;       //out指針向後移動
        count--;                  //產品數目減1
        consume an item in nextc; //開始消費產品nextc
        ... 
    }
};

如果生產者程序和消費者程序併發執行就會出現差錯,問題就在於這兩個進程共享變量 count。
count–,count++這兩個操作在用機器語言實現時, 常可用下面的形式描述:
register1=count;——————– register2=count;
register1=register1+1;———— register2=register2-1;
count=register1;——————– count=register2;
倘若兩段程序中各語句交叉執行的順序不是一段執行後再執行另一段,則count會得到錯誤值。
解決此問題的關鍵是應把變量 count 作爲臨界資源處理,亦即,令生產者進程和消費者進程互斥地訪問變量 count。
3. 臨界區。
在每個進程中訪問臨界資源的那段代碼稱爲臨界區。
在臨界區前面要加上一段稱爲進入區的代碼,用於對欲訪問的臨界資源進行檢查,看它是否正被訪問。
在臨界區後面也要加上一段稱爲退出區的代碼,用於將臨界區正被訪問的標誌恢復爲未被訪問的標誌。
除進入區、臨界區和退出區之外的其它部分的代碼,都稱爲剩餘區。
4. 同步機制應遵循的規則。
1)空閒讓進。 2)忙則等待。 3)有限等待。 4)讓權等待。

硬件同步機制

1 . 關中斷。
在進入鎖測試之前關閉中斷,直到完成鎖測試並上鎖之後才能打開中斷。在此過程中不響應中斷,從而不會引發調度。關中斷會影響系統效率,且不適用於多 CPU 系統。
2. 利用 Test-and-Set 指令實現互斥。

// 指令描述
boolean TS(boolean *lock)
{
    boolean old;
    old=*lock;
    *lock=TRUE;
    return old;
}
// 實現互斥的循環進程結構
do{
    ...
    while TS(&lock);
    critical section;
    lock=False;
    remainder section;
}while(TRUE);

3 . 利用 Swap 指令實現進程互斥。

// 指令描述
void swap(boolean *a, boolean *b)
{
    boolean temp;
    temp=*a;
    *a=*b;
    *b=temp;
}
// 實現互斥的循環進程結構
do{
    key=True;
    do{
        swap(&lock,&key);
        }while(key!=False);
    critical section;
    lock=False;
    remainder section;
}while(True);

上述硬件指令能有效地實現進程互斥,但當臨界資源忙碌時,其它訪問進程會一直處於“忙等”狀態,不符合“讓權等待”的原則,造成處理機資源的浪費。

信號量機制
——Dijkstra

1 . 整型信號量
定義爲一個用於表示資源數目的整型量 S。
僅能通過兩個標準的原子操作 wait(S) 和 signal(S) 來訪問。這兩個操作也被分別稱爲 P、V 操作。

Pwait(S){ 
    while(S<=0);
    S--;
}
V: 
signal(S){
    S++;
}

在整型信號量機制中,並未遵循“讓權等待”的準則,而是使進程處於“忙等”的狀態。
2. 記錄型信號量
記錄型信號量機制則是一種不存在“忙等”現象的進程同步機制。但在採取了“讓權等待”的策略後,又會出現多個進程等待訪問同一臨界資源的情況。爲此,在信號量機制中,除了需要一個用於代表資源數目的整型變量 value 外,還應增加一個進程鏈表指針 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 的初值表示系統中某類資源的數目,因而又稱爲資源信號量。
wait(S) 中執行 block(S->list) 後的 S->value 的絕對值表示在該信號量鏈表中已阻塞進程的數目。
如果 S->value 的初值爲 1,此時的信號量轉化爲互斥信號量,用於進程互斥。
3. AND 型信號量
假定有兩個進程都要訪問兩個不同的臨界資源。而又由於某些原因兩進程對這兩個臨界資源的P操作次序不同,那麼則可能會出現兩進程各自獲得其中一個臨界資源,並分別在等待對方釋放另一個臨界資源。此時已進入死鎖狀態。
AND 同步機制的基本思想是:將進程在整個運行過程中需要的所有資源,一次性全部地分配給進程,待進程使用完後再一起釋放。只要尚有一個資源未能分配給進程,其它所有可能爲之分配的資源也不分配給它。亦即,對若干個臨界資源的分配,採取原子操作方式:要麼全部分配,要麼一個也不分配。
AND 同步也稱爲同時 wait 操作。

Swait(S1,S2,…,Sn) {
    while(TRUE) {
        if(Si>=1 &&&& Sn>=1) {
            for(i=1; i<=n; i++) Si--;
            break;
        else {
            place the process in the waiting queue associated with the first Si found with Si<1and set the program count of this process to the beginning of Swait operation.
            // 上述英文解釋不是很清楚,所以這裏僅需知道將進程阻塞在第一個<1的臨界資源Si的list上。
        }
    }
}

Ssignal(S1,S2,…,Sn) {
    while(TRUE) {
        for(i=1; i<=n; i++) {
            Si++;
            Remove all the process waiting in the queue associated with Si into the ready queue.
            // 喚醒臨界資源Si的list上的某個進程。
        }
    }
}

4 . 信號量集
一次需要 N 個某類臨界資源。
每次分配之前,都必須測試該資源的數量Si,看其是否大於其分配下限值ti,允許則分配相應的需求值di。

Swait(S1,t1,d1,…,Sn,tn,dn)Ssignal(S1,d1,…,Sn,dn)

特殊情況:
Swait(S,1,1)。蛻化爲一般的記錄型信號量(S>1 時)或互斥信號量(S=1 時)。
Swait(S,1,0)。當 S≥1 時,允許任意多個進程進入某特定區;當 S 變爲 0 後,將阻止任何進程進入特定區。換言之,它相當於一個可控開關。

信號量的應用

  1. 利用信號量實現進程互斥。
    wait(mutex)和 signal(mutex)必須成對地出現。
  2. 利用信號量實現前趨關係。
    將 signal(S) 操作放在進程P1語句的後面;而在進程P2語句的前面插入 wait(S) 操作。將 S 被初始化爲 0,這樣,若 P2 先執行必定阻塞,只有在 P1 執行後 P2 才能執行。

管程機制

  1. 管程的定義。
    代表共享資源的數據結構,以及由對該共享數據結構實施操作的一組過程所組成的資源管理程序,共同構成了一個操作系統的資源管理模塊,我們稱之爲管程。
    對於請求訪問共享資源的諸多併發進程,可以根據資源的情況接收或阻塞,確保每次僅有一個進程進入管程,執行這組過程,使用共享資源,達到對共享資源所有訪問的統一管理,有效地實現進程互斥。
  2. 條件變量。
    爲了解決進程在管程中時被阻塞或掛起所造成的問題,引入條件變量 condition。
    需要對每個條件變量都予以說明,形式爲:condition x1,x2;對條件變量的操作僅僅是 wait 和 signal。含義如下:
    x.wait:正在調用管程的進程因 x 條件需要被阻塞或掛起,則調用 x.wait 將自己插入到 x 條件的等待隊列上,並釋放管程,直到 x 條件變化。
    x.signal:正在調用管程的進程發現 x 條件發生了變化,則調用 x.signal,重新啓動(喚醒)一個因 x 條件而阻塞或掛起的進程。如果存在多個這樣的進程,則選擇其中的一個,如果沒有,則繼續執行原進程,而不產生任何結果。

經典的進程同步問題

生產者-消費者問題

1 . 利用記錄型信號量解決。

int in = 0, out = 0; //對緩衝區進行循環使用
item buffer[n];
seamphere mutex = 1, empty = n, full = 0;
//互斥信號量,空緩衝池數量資源信號量,滿緩衝池數量資源信號量
void producer()     //生產者
{
    do
    {
        produce an item 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);
        ...
        consume the item in nextc;
    }while(true);
}

在每個程序中的多個 wait 操作順序不能顛倒,應先執行對資源信號量的wait 操作,然後再執行對互斥信號量的 wait 操作,否則可能引起進程死鎖。反例:加入順序顛倒。當緩衝池滿了的時候,生產者進程嘗試進行生產,則先執行互斥信號量的 wait 會成功,然後執行資源信號量的 wait 會被阻塞,並且導致互斥信號量沒有被釋放,消費者進程無法進行消費,所以生產者進程也一直不會被喚醒,從而造成死鎖。
2. 利用 AND 信號量解決。
在記錄型信號量代碼中用 Swait(S1,S2) 代替兩個 wait,用 Ssignal(S1,S2) 代替兩個 signal。
3. 利用管程解決。
建立一個管程,並實現兩個過程、兩個條件變量。生產者和消費者進入臨界區通過這調用兩個過程即可。

// 創建管程
Monitor producerconsumer{
    item buffer[N];
    int in,out;
    int count;
    condition notfull,notempty;
    public:
    void put(item x){
        if(count>=N) cwait(notfull);    //wait不滿條件變量
        buffer(in)=x;
        in=(in+1) % N;
        count++;
        csignal(notempty);
    }
    void get (item x){
        if(count<=0) cwait(notempty);   //wait非空條件變量
        x=buffer(out);
        out=(out+1) % N;
        count--;
        csignal(notfull);
    }
    { in=0; out=0; count=0; }    //初始化
} PC;

哲學家進餐問題

五個哲學家共用一個圓桌,且每人兩旁都有一個筷子,即共享5個筷子,進餐需要拿到兩旁的兩個筷子。
1. 利用記錄型信號量解決。
爲了實現對筷子的互斥使用,可以用一個信號量表示一隻筷子,由這五個信號量構成信號量數組。有一種情況會造成死鎖,即哲學家同時先拿起左邊(或右邊)的筷子,然後去請求另一個筷子,導致無限等待。可以規定至多隻允許有四位哲學家同時去拿左邊(或右邊)的筷子等方法來預防死鎖問題。
2. 利用 AND 信號量機制解決。

讀者-寫者問題

允許多個同時讀,但只允許一個寫。
1. 利用記錄型信號量解決。

semaphore rmutex = 1, wmutex = 1;
//互斥訪問readcount; 實現讀-寫、寫-寫互斥
int readcount = 0;
void reader() {
    do {
        wait(rmutex);
        if(readcount == 0) wait(wmutex);
        readcount++;
        signal(rmutex);
        ...
        perform read operation;
        ...
        wait(rmutex);
        readcount--;
        if(readcount == 0) signal(wmutex);
        signal(rmutex);
    } while(true);
}

void writer() {
    do {
        wait(wmutex);
        perform write operation;
        signal(wmutex);
    } while(true);
}

對於 wait(rmutex); if(readcount == 0) wait(wmutex); 。當寫進程正在執行時,第一個讀進程會執行成功第一個 wait 操作且阻塞在第二個 wait 操作隊列上,之後的所有讀進程都會阻塞在第一個 wait 操作隊列上。因此,當寫進程執行完之後如果喚醒了第一個讀進程,然後讀進程會不斷喚醒其它讀進程。
2. 利用信號量集機制解決。

int RN; //增加限制:最多允許RN個讀者同時讀
semaphore L = RN, mx = 1;
//讀者數量資源信號量; 實現讀-寫、寫-寫互斥
int readcount = 0;
void reader() {
    do {
        Swait(L, 1, 1);
        Swait(mx, 1, 0);
        ...
        perform read operation;
        ...
        Ssignal(L, 1);
    } while(true);
}

void writer() {
    do {
        Swait(mx, 1, 1; L, RN, 0);
        perform write operation;
        signal(mx, 1);
    } while(true);
}

進程通信

進程通信的類型

  1. 共享存儲器系統。
    1)基於共享數據結構的通信方式。
    僅適於傳遞相對少量的數據,屬於低級通信。
    2)基於共享存儲區的通信方式。
    屬於高級通信。
  2. 管道通信系統。
    所謂“管道”,是指用於連接一個讀進程和一個寫進程以實現它們之間通信的一個共享文件,又名 pipe 文件。管道機制必須提供以下三方面的協調能力:1)互斥。2)同步。3)確定對方是否存在。
  3. 信息傳遞系統。
    進程不必藉助任何共享存儲區或數據結構,而是以格式化的消息爲單位,將通信的數據封裝在消息中,並利用操作系統提供的一組通信命令(原語),在進程間進行消息傳遞,完成進程間的數據交換。
    1)直接通信方式。
    2)間接通信方式。
  4. 客戶機-服務器系統。
    是網絡環境中主流的通信實現機制。實現方法:
    1)套接字。
    一個套接字就是一個通信標識類型的數據結構,是進程通信和網絡通信的基本構件。
    基於文件型的套接字:原理類似於管道。
    基於網絡型的套接字:該類型通常採用的是非對稱方式通信,即發送者需要提供接收者命名。在通信結束時通過關閉進程(或服務器端)的套接字撤銷連接。
    2)遠程過程調用和遠程方法調用。
    遠程過程調用 RPC 是一個通信協議,允許運行於一臺主機系統上的進程調用另一臺主機系統上的進程。如果涉及的軟件採用面向對象編程,那麼遠程過程調用亦可稱作遠程方法調用。
    在本地客戶端,每個能夠獨立運行的遠程過程都擁有一個客戶存根,本地進程調用遠程過程實際是調用該過程關聯的存根。同樣在服務器端,其所對應的實際可執行進程也存在一個服務器存根與其關聯。
    遠程過程調用的步驟作用在於將客戶過程的本地調用轉化爲客戶存根,然後發送給服務器存根,再轉化爲服務器過程的本地調用,對客戶與服務器來說,它們的中間步驟是不可見的。

消息傳遞通信的實現方式

  1. 直接消息傳遞系統(直接通信方式)。
    1)直接通信原語。
    對稱尋址方式:要求發送進程和接收進程都必須以顯式方式提供對方的標識符。即一個通信命令只能發送給(或接受)自一個進程。
    非對稱尋址方式:接收進程不同於對稱尋址方式,不需要命名發送進程,只需填寫表示源進程的參數即可。即可以接受來自任何進程的消息。
    2)消息的格式:定長和變長。
    3)進程的同步方式:進程在發送或接受消息後,存在兩種可能性:或者繼續發送(或接受)或者阻塞。於是得到三種同步方式:發送 接收進程都阻塞、發送進程不阻塞 接收進程阻塞、發送 接收進程都不阻塞。
    4)通信鏈路:兩進程通信必須建立一條通信鏈路。第一種方式:通過顯式的“建立連接”命令請求系統建立;第二種方式:無須明確提出請求,只須利用系統提供的發送命令,系統會自動爲之建立一條鏈路。
  2. 信箱通信(間接通信方式)。
    通過某種中間實體來暫存消息。該實體建立在隨機存儲器的公用緩衝區上,稱爲郵箱或信箱。
    1)信箱的結構。
    信箱頭:存放有關信箱的描述信息。
    信箱體:若干個存放消息的信箱格組成。
    2)信箱通信原語:創建、撤銷;發送、接收。
    3)信箱的類型。
    私用郵箱:只有擁有者有權從信箱中讀取消息,其他用戶則只能將自己構成的消息發送到該信箱中。
    公用郵箱:由操作系統創建,並提供給系統中的所有核準進程使用(發送和接收)。
    共享郵箱:它由某進程創建,在創建時或創建後指明它是可共享的,同時須指出共享進程(用戶)的名字。信箱的擁有者和共享者都有權從信箱中取走發送給自己的消息。

直接消息傳遞系統實例——消息緩衝隊列通信機制

1 . 消息緩衝隊列通信機制中的數據結構。
1)消息緩衝區。

typedef struct message_buffer {
    int sender; //發送者進程標識符
    int size;   //消息長度
    char *text; //消息正文
    struct message_buffer *next;    //指向下一個消息緩衝區指針
}

2)PCB 中有關通信的數據項。

typedef struct processcontrol_block {
    ...
    struct message_buffer *mq;  //消息隊列隊首指針
    semaphore mutex;    //消息隊列互斥信號量
    semaphore sm;   //消息隊列資源信號量
    ...
} PCB;

2 . 發送原語。
發送進程在利用發送原語發送消息之前,應先在自己的內存空間設置一發送區 a,把待發送的消息正文、發送進程標識符、消息長度等信息填入其中,然後調用發送原語,把消息發送給目標(接收)進程。發送原語首先根據發送區 a 中所設置的消息長度a.size 來申請一消息緩衝區 i,接着把發送區 a 中的信息複製到緩衝區 i 中。爲了能將 i 掛在接收進程的消息隊列 mq 上,應先獲得接收進程的內部標識符 j,然後將 i 掛在 j.mq 上。且對隊列需要互斥訪問。

void send(receiver, a) {
    getbuf(a.size, i);
    i.sender = a.sender;
    i.size = a.size;
    copy(i.text, a.text);
    i.next = 0;
    getid(PCBset, receiver.j);  //獲得接收進程內部的標識符
    wait(j.mutex);
    insert(&j.mq, i);
    signal(j.mutex)l
    siganl(j.sm);
}

3 . 接收原語。
從自己的消息緩衝隊列 mq 中摘下第一個消息緩衝區 i,並將其中的數據複製到以 b 爲首址的指定消息接收區內。

void receive(b) {
    j = internal name;
    wait(j.sm);
    wait(j.mutex);
    remove(j.mq, i);
    signal(j.mutex);
    b.sender = i.sender;
    b.size = i.size;
    copy(b.text, i.text);
    releasebuf(i);
}

線程的基本概念

線程的引入

引入進程,是爲了使多個程序能併發執行,以提高資源利用率和系統吞吐量。引入線程,則是爲了減少程序在併發執行時所付出的時空開銷,使 OS 具有更好的併發性。

  1. 進程的兩個基本屬性。
    1)進程是一個可擁有資源的獨立單位。
    2)進程同時又是一個可獨立調度和分派的基本單位。
  2. 程序併發執行所需付出的時空開銷。
    由於進程是一個資源的擁有者,因而在創建、撤消和切換中,系統必須爲之付出較大的時空開銷,因此限制了系統中設置進程的數量。
  3. 線程。
    設法將進程的上述兩個屬性分開,由 OS 分開處理,亦即對於作爲調度和分派的基本單位,不同時作爲擁有資源的單位;而對於擁有資源的基本單位,又不對之進行頻繁的切換。正是在這種思想的指導下,形成了線程的概念。

線程和進程的比較

線程又稱爲輕型進程或進程元,傳統進程稱爲重型進程。

  1. 調度的基本單位。
    在傳統的 OS 中,作爲擁有資源的基本單位和獨立調度、分派的基本單位都是進程。而在引入線程的操作系統中,則把線程作爲調度和分派的基本單位,而進程作爲資源擁有的基本單位。
    在同一進程中,線程的切換不會引起進程的切換,但從一個進程中的線程切換到另一個進程中的線程時,將會引起進程的切換。
  2. 併發性。
    在引入線程的操作系統中,不僅進程之間可以併發執行,而且在一個進程中的多個線程之間亦可併發執行。
  3. 擁有資源。
    不論是傳統的 OS,還是引入了線程的 OS,進程都可以擁有資源,並作爲系統中擁有資源的一個基本單位。線程本身並不擁有系統資源,而是僅有一點必不可少的、能保證獨立運行的資源,例如線程控制塊等。
    允許一個進程中的多個線程貢獻該進程所擁有的資源,這主要表現在:屬於同一進程的所有線程都具有相同的地址空間。
  4. 獨立性。
    在同一進程中的不同線程之間的獨立性要比不同進程之間的獨立性低得多。因爲進程之間是爲了防止彼此干擾和破壞;而同一進程中的不同線程往往是爲了提高併發性以及進行相互之間的合作而創建的。
  5. 系統開銷。
    進程的創建、撤銷和切換所付出的開銷都明顯大於線程的創建、撤銷和切換的開銷。
  6. 支持多處理機系統。
    對於單線程進程,不管多少處理機,同一時刻該進程只能運行在一個處理機上。對於多線程進程,就可以將一個進程中的多個線程分配到多個處理機上並行執行。

線程的狀態和線程控制塊

  1. 線程運行的三種狀態。
    1)執行狀態。2)就緒狀態。3)阻塞狀態。
  2. 線程控制塊 TCB
    所有用於控制和管理線程的信息都記錄在線程控制塊 TCB 中。線程控制塊 TCB 中的信息:
    1)線程標識符。2)一組寄存器。3)線程運行狀態。4)優先級。5)線程專有存儲器。6)信號屏蔽。7)堆棧指針,在指向的堆棧中通常保存有局部變量和返回地址。
  3. 多線程 OS 中的進程屬性。
    1)進程是一個可擁有資源的基本單位。
    2)通常一個進程中都含有若干個(至少一個)相對獨立的線程。由進程爲這些(個)線程提供資源及運行環境。多個線程可併發執行。
    3)進程已不再是一個可執行的實體,而把線程作爲獨立運行的基本單位。雖然如此,進程仍具有與執行相關的狀態。例如,所謂進程處於“執行”狀態,實際上是指該進程中的某線程正在執行。此外,對進程所施加的與進程狀態有關的操作,也對其線程起作用。例如,在把某個進程掛起時,該進程中的所有線程也都將被掛起;又如,在把某進程激活時,屬於該進程的所有線程也都將被激活。

線程的實現

線程的實現方式

  1. 內核支持線程 KST。
    線程的創建、撤消和切換等都是在內核空間實現的。爲每一個內核支持線程設置一個線程控制塊以對內核線程進行控制和管理。
    缺點:對於用戶的線程切換而言,其模式切換的開銷較大。因爲用戶進程的線程在用戶態運行,而線程調度和管理是在內核實現的,系統開銷較大。
  2. 用戶級線程 ULT。
    用戶級線程是在用戶空間中實現的。對線程的創建、撤消、線程之間的同步與通信等功能,都無須內核的支持,即用戶級線程是與內核無關的。
    值得說明的是,對於設置了用戶級線程的系統,其調度仍是以進程爲單位進行的。
    缺點:一個線程被阻塞,它所在的進程中的所有線程都會被阻塞。且多線程應用不能利用多處理機進行多重處理的優點,因爲內核每次分配給一個進程的僅有一個 CPU,而進程中僅有一個線程能執行。
  3. 組合方式。
    把用戶級線程和內核支持線程兩種方式進行組合,提供了組合方式 ULT/KST 線程。一些內核支持線程對應多個用戶級線程,這是用戶級線程通過時分多路複用內核支持線程來實現的。
    由於連接方式的不同,有三種不同的模型:
    1)多對一模型。將用戶級線程映射到一個內核控制線程,這些用戶級線程一般屬於一個進程。在任一時刻,只有一個線程能夠訪問內核,類似於前面的單純的方式 2。
    2)一對一模型。每一個用戶級線程映射到一個內核支持線程。每創建一個用戶線程,相應地就需要創建一個內核支持線程,開銷較大。
    3)多對多模型。許多用戶級線程映射到同樣數量或更少數量的內核支持線程上。克服了上述兩種模型的缺點。

線程的實現

  1. 內核支持線程的實現。
    系統在創建一個新進程時,便爲它分配一個任務數據區 PTDA,其中包括若干個線程控制塊 TCB 空間。
    每當進程要創建一個線程時,便爲新線程分配一個 TCB,將有關信息填入該 TCB 中,併爲之分配必要的資源。
    有的系統中爲了減少創建和撤消一個線程時的開銷,在撤消一個線程時,並不立即回收該線程的資源和 TCB,當以後再要創建一個新線程時,便可直接利用已被撤消但仍保持有資源和 TCB 的線程作爲新線程。
  2. 用戶級線程的實現。
    用戶級線程是在用戶空間實現的。所有的用戶級線程都運行在一箇中間系統上。兩種方式實現中間系統:
    1)運行時系統。
    所謂“運行時系統”,實質上是用於管理和控制線程的函數(過程)的集合,運行時系統中的所有函數都駐留在用戶空間,並作爲用戶級線程與內核之間的接口。
    不論在傳統的 OS 中,還是在多線程 OS 中,系統資源都是由內核管理的。在傳統的 OS 中,進程是利用 OS 提供的系統調用來請求系統資源的,系統調用通過軟中斷(如 trap)機制進入 OS 內核,由內核來完成相應資源的分配。用戶級線程是不能利用系統調用的。當線程需要系統資源時,是將該要求傳送給運行時系統,由後者通過相應的系統調用來獲得系統資源的。
    2)內核控制線程。
    這種線程又稱爲輕型進程 LWP。LWP 可通過系統調用來獲得內核提供的服務,這樣,當一個用戶級線程運行時,只要將它連接到一個 LWP 上,此時它便具有了內核支持線程的所有屬性。這種線程實現方式就是組合方式。
    每一個 LWP 都要連接到一個內核級線程上,這樣,通過 LWP 可把用戶級線程與內核線程連接起來,用戶級線程可通過 LWP 來訪問內核,但內核所看到的總是多個 LWP 而看不到用戶級線程。
    在內核級線程執行操作時,如果發生阻塞,則與之相連接的多個 LWP 也將隨之阻塞,進而使連接到 LWP 上的用戶級線程也被阻塞。

線程的創建和終止

在 OS 中有用於創建線程的函數(或系統調用)和用於終止線程的函數(或系統調用)。

  1. 線程的創建。
    應用程序在啓動時,通常僅有一個“初始化線程”,它的主要功能是用於創建新線程。
  2. 線程的終止。
    有些線程(主要是系統線程),在它們一旦被建立起來之後,便一直運行下去而不再被終止。

繼續加油~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章