深入探討程式間通訊的重要性:理解不同的通訊機制(下)

努力的小雨發表於2023-09-01

前言

在上一篇文章中,我們探討了程式間通訊的三種常見機制:管道、訊息佇列和共享記憶體。我們瞭解到,這些機制各有其特點和適用場景,可以根據實際需求選擇合適的機制進行程式間通訊。然而,程式間通訊並不僅限於這三種方式。

在本文中,我們將繼續探索程式間通訊的知識點,重點關注訊號量、訊號和套接字。訊號量是一種用於程式同步的機制,它可以用於控制對共享資源的訪問。訊號是一種用於程式間通知的機制,可以用於處理非同步事件。而套接字則是一種用於網路通訊的介面,它可以實現不同主機之間的程式間通訊。

訊號量

共享記憶體通訊方式雖然提供了高效的資料交換,但也引發了新的問題。如果多個程式同時修改同一個共享記憶體區域,很可能會導致資料衝突。舉個例子,如果兩個程式同時寫入同一個地址,先寫入的程式可能會發現自己的內容被後寫入的程式覆蓋。

在程式間共享資源時,使用訊號量可以避免多個程式同時訪問共享資源而導致資料衝突的問題。訊號量是一個整型計數器,用來表示資源的可用數量。透過P操作和V操作來控制訊號量的值。

  • P操作會將訊號量減1,如果結果小於0,則表示資源已被佔用,程式需要阻塞等待。如果結果大於等於0,則表示資源仍然可用,程式可以繼續執行。
  • V操作會將訊號量加1,如果結果小於等於0,則表示有其他程式正在等待資源,需要喚醒其中一個程式。如果結果大於0,則表示沒有程式在等待資源。

透過使用P操作和V操作,可以實現對共享資源的互斥訪問和同步執行。例如,可以初始化一個訊號量為1,使得只有一個程式可以訪問共享資源,從而避免資料錯亂。另外,可以初始化一個訊號量為0,使得程式按照特定的順序執行,實現多程式的同步。
接下來,我們先看下互斥訪問,如果要使得兩個程式互斥訪問共享記憶體,我們可以初始化訊號量為 1。

image

具體的過程如下:

  • 程式 A 在訪問共享記憶體之前,先執行了 P 操作。由於訊號量的初始值為 1,所以程式 A 執行 P 操作後,訊號量減為 0,表示共享資源可用,程式 A 可以訪問共享記憶體。
  • 如果此時程式 B 也想訪問共享記憶體,它執行了 P 操作。結果訊號量變為 -1,表示臨界資源已被佔用,因此程式 B 被阻塞。
  • 直到程式 A 訪問完共享記憶體,才會執行 V 操作,使得訊號量恢復為 0。接著,程式 A 喚醒被阻塞的程式 B,使其可以訪問共享記憶體。
  • 最後,程式 B 完成共享記憶體的訪問後,執行 V 操作,將訊號量恢復到初始值 1。

將訊號量初始化為 1,代表著它是一個互斥訊號量。這種設定可以確保在任何時刻只有一個程式可以訪問共享記憶體,從而有效保護了共享記憶體的完整性。有人可能會發現如果多執行緒都來訪問資源全部阻塞了喚醒誰呢?這不就是我們之前講到的程式排程演算法了嗎?程式阻塞後會進入阻塞佇列,而喚醒哪個程式則由系統的排程演算法決定。

在多程式環境中,每個程式並不一定按照順序執行,它們以各自獨立且不可預測的速度向前推進。然而,在某些情況下,我們希望多個程式能夠密切合作,以實現一個共同的任務。

比如生產者消費者模式,假設程式A負責生產資料,而程式B負責讀取資料,這兩個程式是相互合作、相互依賴的。程式A必須先生產資料,然後程式B才能讀取到資料,因此它們之間存在執行順序。

接下來,我們來討論同步執行。我們可以透過初始化訊號量為0來實現。

image

具體過程如下:

  • 如果程式B比程式A先執行,那麼當它執行P操作時,由於訊號量的初始值為0,所以訊號量會變為-1,表示程式A還沒有生產資料,程式B會被阻塞等待。
  • 接著,當程式A生產完資料後,執行V操作,訊號量會變為0,這會喚醒被阻塞在P操作的程式B。
  • 最後,程式B被喚醒後,意味著程式A已經生產了資料,程式B就可以正常讀取資料了。

可以看出,將訊號量初始化為0,代表著這是一個同步訊號量,它可以保證程式A在程式B之前執行。

訊號

訊號是一種在異常情況下進行程式間通訊的機制,它是一種非同步通訊方式,其資料結構一般為一個數字。

在Linux作業系統中,為了響應各種事件,提供了幾十種訊號,每個訊號代表著不同的含義。可以透過執行"kill -l"命令來檢視所有的訊號列表。

image

對於在Shell終端中執行的程式,我們可以透過鍵盤輸入某些組合鍵向程式傳送訊號。例如,按下Ctrl+C會產生SIGINT訊號,表示終止該程式;按下Ctrl+Z會產生SIGTSTP訊號,表示暫停該程式,但程式並未結束。需要注意的是,Ctrl+Z命令在某些情況下可能會導致記憶體飆升等問題(比如你看一個全量伺服器日誌),因此需要謹慎使用。

如果程式在後臺執行,可以使用kill命令向程式傳送訊號,但前提是需要知道正在執行的程式的PID(程式ID)。例如,執行"kill -9 ###"命令會向PID為"###"的程式傳送SIGKILL訊號,用於立即終止該程式。

因此,訊號的事件來源主要有硬體來源(如鍵盤的Ctrl+C)和軟體來源(如kill命令)。訊號是程式間通訊機制中唯一的非同步通訊機制,程式需要為訊號設定相應的監聽處理程式,當收到特定訊號時,執行相應的操作,類似於其他程式語言中的通知機制。

Socket

Socket通訊是一種常用的程式間通訊機制,可以用於跨網路與不同主機上的程式之間通訊,也可以在同一臺主機上的程式之間進行通訊。

Socket通訊是透過網路協議進行資料傳輸的一種方式。在使用Socket通訊時,一個程式可以作為伺服器端建立一個Socket,並指定一個IP地址和埠號來監聽連線請求;另一個程式則可以作為客戶端建立一個Socket,指定伺服器的IP地址和埠號來發起連線。一旦連線建立,伺服器和客戶端就可以透過Socket進行資料的傳送和接收。

在同一臺主機上,程式可以使用特殊的IP地址(如本地迴環地址127.0.0.1)和不同的埠號來建立Socket連線,實現程式間的通訊。這種方式被稱為本地迴環通訊,可以用於程式之間的協作和資料交換。

後期我將詳細講解計算機基礎的網路篇,敬請期待!

總結

IPC 機制 資料抽象 參與者 方向 核心實現
管道 位元組流 兩個程式 單向 通常以 FIFO 的緩衝區來管理資料。
有匿名管道和命名管道兩類主要實現
訊息佇列 訊息 多程式 單向
雙向
佇列的組織方式。
透過檔案的許可權來管理對佇列的訪間
訊號量 計數器 多程式 單向
雙向
核心餘護共享計數器。
透過檔案的許可權來管理劉計數器的訪問
共享記憶體 記憶體區問 多程式 單向
雙向
核心維護共享的記憶體區可。
透過檔案的許可權來管理對共享記憶體的訪間
訊號 事件編號 多程式 單向 為執行緒/程式維護訊號等待佇列。
透過使用者了組等的許可權來管理訊號的操作
套接字 資料包文 兩個程式 單向
雙向
有基於IP/埠和基於檔案路輕的定址方式。
利用網路棧來管理通訊

程式間通訊是作業系統中的重要概念,它允許不同的程式之間進行資料交換、訊息傳遞和協作。在Linux系統中,提供了多種程式間通訊的機制,包括管道、訊息佇列、共享記憶體、訊號量、訊號和套接字。每種通訊機制都有不同的特點和適用場景。需要根據具體需求選擇合適的方式。程式間通訊涉及到資源的共享和競爭,需要合理地使用同步和互斥機制來保證資料的一致性和正確性。同時,程式的喚醒順序也會受到系統的排程演算法的影響。

相關文章