操做系統知識回顧(3)--進程的同步與通訊

OS 中引入進程後,系統中的多道程序能夠併發執行,但系統卻變得更加複雜,爲使進程有序運行,引入了同步機制。在進程之間傳送大量數據,也須要利用進程通訊工具。這篇文章總結了進程的幾種同步方式和進程之間的通訊方式。數組

1. 進程間同步

1.1 基本概念

爲避免競爭條件,操做系統須要利用同步機制在併發執行時,保證對臨界區的互斥訪問。進程同步的解決方案主要有:信號量和管程。bash

對於同步機制,須要遵循如下四個規則:數據結構

  • 空閒則入:沒有進程在臨界區時,任何進程能夠進入;
  • 忙則等待:有進程在臨界區時,其餘進程均不能進入臨界區;
  • 有限等待:等待進入臨界區的進程不能無限期等待;
  • 讓權等待(可選):不能進入臨界區的進程,應該釋放 CPU,如轉換到阻塞態;

1.2 信號量

信號量機制(semaphore)是一種協調共享資源訪問的方法。信號量由一個變量 semaphore 和兩個原子操做組成,信號量只能經過 PV 操做來完成,並且 PV 操做都是原子操做。併發

將信號量表示以下:工具

typedef struct {
    int value;
    struct process_control_block *list;
} semaphore;
複製代碼

相應的 P(wait) 操做和 V(signal) 操做以下實現:ui

wait(semaphore *S) {
    S->value--;
    if(S->value < 0) {
        block(S->list);
    }
}
signal(semaphore *S) {
    S->value++;
    if(S->value <= 0) {
        wakeup(S->list);
    }
}
複製代碼

信號量可分爲兩類:互斥信號量,信號量大小爲爲 01,用來實現進程的互斥訪問;資源信號量,信號量大小爲資源數,用來表示系統資源數目。spa

資源信號量操作系統

表明資源信號量時,S->value 初值表示系統資源的數目,P 操做意味着進程請求一個資源,因而系統中可分配的資源數減一,若是 S->value < 0,表示該類資源已分配完畢,所以阻塞該進程,並插入信號量鏈表 S->list 中。小於 0 時,S->value 的絕對值表示該信號量鏈表中阻塞的進程數。線程

V 操做表示進程釋放一個資源,因而系統中可分配的資源數加一,若是增長一後仍然 S->value <= 0,表示該信號量鏈表中仍然有阻塞的進程,所以調用 wakeup,將 S->list 中的第一個進程喚醒。code

互斥信號量

表明互斥信號量時,S->value 初值爲 1,表示只容許一個進程訪問該資源。

利用信號量實現兩個進程互斥描述以下:

semaphore mutex = 1;
P() {
    wait(mutex);
    臨界區;
    signal(mutex);
}
複製代碼

mutex = 1 時,表示兩個進程都沒有進入臨界區,當 mutex = 0 時,表示一個進程進入臨界區運行;當 mutex = -1 時,表示一個進程進入臨界區運行,另外一個進程被阻塞在信號量隊列中。

1.3 管程

管程採用面向對象思想,將表示共享資源的數據結構及相關的操做,包括同步機制,都集中並封裝到一塊兒。全部進程都只能經過管程間接訪問臨界資源,而管程只容許一個進程進入並執行操做,從而實現進程互斥。

Monitor monitor_name {
    share variable declarations;
    condition declarations;
    
    public:
    void P1(···) {
        ···
    }
    
    {
        initialization code;
    }
}
複製代碼

管程中設置了多個條件變量,表示多個進程被阻塞或掛起的條件,條件變量的形式爲 condition x, y;,它也是一種抽象數據類型,每一個變量保存了一條鏈表,記錄因該條件而阻塞的進程,與條件變量相關的兩個操做:condition.cwaitcondition.csignal

  • condition.cwait:正在調用管程的進程因 condition 條件須要被阻塞,則調用 condition.cwait 將本身插入到 condition 的等待隊列中,並釋放管程。此時其餘進程能夠使用該管程。
  • condition.csignal:正在調用管程的進程發現 condition 條件發生變化,則調用 condition.csignal 喚醒一個因 condition 條件而阻塞的進程。若是沒有阻塞的進程,則不產生任何結果。

2. 經典同步問題

2.1 生產者-消費者問題

生產者-消費者問題描述的是:生產者和消費者兩個線程共享一個公共的固定大小的緩衝區,生產者在生成產品後將產品放入緩衝區;而消費者從緩衝區取出產品進行處理。

它須要保證如下三個問題:

  • 在任什麼時候刻只能有一個生產者或消費者訪問緩衝區(互斥訪問);
  • 當緩衝區已滿時,生產者不能再放入數據,必須等待消費者取出一個數據(條件同步);
  • 而當緩衝區爲空時,消費者不能讀數據,必須等待生產者放入一個數據(條件同步)。

利用信號量解決

用信號量解決生產者-消費者問題,使用了三個信號量:

  • 互斥信號量 mutex:用來保證生產者和消費者對緩衝區的互斥訪問;
  • 資源信號量 full:記錄已填充的緩衝槽數目;
  • 資源信號量 empty:記錄空的緩衝槽數目。
#define N 10
int in = 0, out = 0;
item buffer[N];
semaphere mutex = 1, full = 0, empty = N;

void producer(void) {
    while(TRUE) {
        item nextp = produce_item();
        wait(empty);          
        wait(mutex);                 
        buffer[in] = nextp;
        in = (in + 1) % N;
        signal(mutex);              
        signal(full);
    }
}

void consumer(void) {
    while(TRUE) {
        wait(full);
        wait(mutex);
        item nextc = buffer[out];
        out = (out + 1) % N;
        signal(mutex);
        signal(empty);
        consume_item(nextc);
    }
}
複製代碼

須要注意的是進程中的多個 wait 操做順序不能顛倒,不然可能形成死鎖。例如在生產者中,當系統中沒有空的緩衝槽時,生產者進程的 wait(mutex) 獲取了緩衝區的訪問權,但 wait(empty) 會阻塞,這樣消費者也沒法執行。

利用管程解決

利用管程解決時,須要爲它們創建一個管程,其中 count 表示緩衝區中已有的產品數目,條件變量 fullemptycwaitcsignal 兩個操做,另外還包括兩個過程:

  • put(x):生產者將本身生產的產品放入到緩衝區中,而若是 count >= N,表示緩衝區已滿,生產者須要等待;
  • get(x):消費者從緩衝區中取出一個產品,若是 count <= 0,表示緩衝區爲空,消費者應該等待;
Monitor producerconsumer {
    item buffer[N];
    int in, out;
    condition full, emtpy;
    int count;
    
    public:
    void put(item x) {
        if(count >= N) { 
            cwait(full);
        }
        buffer[in] = x;
        in = (in + 1) % N;
        count++;
        csignal(emtpy);
    }
    item get() {
        if(count <= 0) {
            cwait(emtpy);
        }
        x = buffer[out];
        out = (out + 1) % N;
        count--;
        csignal(full);
    }
    
    { in = 0; out = 0; count = 0; }
}
複製代碼

因而生產者和消費者可描述爲:

void producer() {
    while(TRUE) {
        item nextp = produce_item();
        producerconsumer.put(nextp);
    }
}
void consumer() {
    while(TRUE) {
        item nextc = producerconsumer.get();
        consume_item(nextc);
    }
}
複製代碼

2.2 哲學家就餐問題

哲學家就餐問題描述的是:有五個哲學家共用一個圓桌,分別坐在周圍的五張椅子上,在圓桌上有五個碗和五隻筷子,他們交替地進行思考和進餐。哲學家在平時進行思考,在飢餓時試圖獲取左右兩隻筷子,拿到兩隻筷子才能進餐,進餐完後放下筷子繼續思考。

爲實現筷子的互斥使用,能夠用一個信號量表示一隻筷子,五個信號量構成信號量數組,也都被初始化爲 1

semaphore chopstick[5] = {1, 1, 1, 1, 1};
複製代碼

i 位哲學家的活動可描述爲:

void philosopher(int i) {
    while(TRUE) {
        wait(chopstick[i]);
        wait(chopstick[(i + 1) % 5]);
        // eat
        signal(chopstick[i]);
        signal(chopstick[(i + 1) % 5]);
        // think
    }
}
複製代碼

上述解法中,若是五位哲學家同時飢餓而都拿起左邊的筷子,再試圖去拿右邊的筷子時,會出現無限期等待而引發死鎖。

2.3 讀者-寫者問題

讀者-寫者問題描繪的是:一個文件能夠被多個進程共享,容許多個 Reader 進程同時讀這個文件,但不容許 Wirter 進程和其餘 Reader 進程或 Writer 進程同時訪問這個文件。因此讀者-寫者須要保證一個 Writer 進程必須與其餘進程互斥地訪問共享對象。

解決這個問題須要設置兩個互斥信號量和一個整形變量:

  • 互斥信號量 wmutext:實現 Reader 進程和 Writer 進程在讀或寫時的互斥;
  • 整形變量 readcount:正在讀的進程數目;
  • 互斥信號量 rmutext:實現多個 Reader 進程對 readcount 變量的互斥訪問;
semaphore rmutex = 1, wmutex = 1;
int readcount = 0;

void Reader() {
    while(TRUE) {
        wait(rmutex);
        if(readcount == 0) {
            wait(wmutex);
        }
        readcount++;
        signal(rmutex);
        // perform read opertaion
        wait(rmutex);
        readcount--;
        if(readcount == 0) {
            signal(wmutex);
        }
        signal(rmutex);
    }
}
void Writer() {
    while(TRUE) {
        wait(wmutex);
        // perform wirte opertaion
        signal(wmutex);
    }    
}
複製代碼

只要有一個 Reader 進程在讀,便不容許 Writer 進程去寫。因此,僅當 readcount = 0,表示沒有 Reader 進程在讀時,Reader 進程才須要執行 wait(wmutex) 操做,而 readcount != 0 時,表示有其餘 Reader 進程在讀,也就確定沒有 Writer 在寫。同理,僅當 readcount = 0 時,才執行 signal(wmutex) 相似。

3. 進程通訊

進程通訊是指進程之間的信息交換。在進程間要傳送大量數據時,應利用高級通訊方法。

3.1 共享內存

在共享內存系統中,多個通訊的進程共享某些數據結構或存儲區,進程之間可以經過這些空間進行通訊。

可分爲兩種類型:

  • 基於共享數據結構的通訊方式。多個進程共用某些數據結構,實現進程之間的信息交換,例如生產者-消費者問題中的緩衝區。這種方式僅適用於少許的數據,通訊效率低下。
  • 基於共享存儲區的通訊方式。在內存中分配一塊共享存儲區,多個進程可經過對該共享區域的讀或寫交換信息。通訊的進程在通訊前,須要先向系統申請共享存儲區的一個分區,以便對其中的數據進行讀寫。

3.2 管道

管道(Pipe)是指用於鏈接一個讀進程和一個寫進程以實現進程間通訊的一個共享文件。發送進程以字符形式將數據送入管道,而接收進程則從管道中接收數據。

管道機制提供了三方面的協調能力:

  • 互斥:當一個進程對管道執行讀或寫操做時,其餘進程必須等待;
  • 同步:當寫進程把必定數量的數據寫入管道,便睡眠等待,直到讀進程取走數據後再把它喚醒;
  • 肯定對方是否存在,只有肯定對方存在才能通訊。

3.3 消息傳遞

消息傳遞機制中,進程以格式化的消息爲單位,將通訊的數據封裝在消息中,並利用操做系統提供的原語,在進程之間進行消息傳遞,完成進程間數據交換。

按照實現方式,可分爲兩類:

  • 直接通訊方式:發送進程利用操做系統提供的發送原語,直接把消息發送給進程,接收進程則利用接收原語來接收消息;
  • 間接通訊方式:發送和接收進程,經過共享中間實體方式進行消息的發送和接收,完成進程間的通訊。
相關文章
相關標籤/搜索