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

努力的小雨發表於2023-08-31

程式間通訊

在作業系統中,程式間通訊是指不同程式之間進行資訊共享、資料傳輸和訊息通知等互動的過程。每個程式在建立時都有自己獨立的虛擬地址空間,但它們共享核心空間。因此,要實現程式間的通訊,必須透過核心來進行中介,如下圖所示:

image

在Linux系統中,提供了多種程式間通訊的機制,包括管道、訊息佇列、共享記憶體、訊號量、訊號、套接字等。這些機制允許程式之間共享資料、傳輸訊息以及進行程式間的同步與通訊。下面我們詳細講解下。

管道

管道是一種程式間通訊機制,它可以將一個程式的輸出直接作為另一個程式的輸入。在Linux系統中,管道可以用於將命令的輸出傳遞給另一個命令進行處理。

ps -ef |grep java

使用Linux系統練手的時候,想必大家都是用這樣的一種命令檢視java程式,命令中的 | 就是管道命令,但是這個是匿名管道,用完了就銷燬,匿名管道只能在有父子關係的程式之間進行通訊。他的功能也很好理解,一個程式的輸出直接作為另一個程式的輸入,所以才能只展現java程式,所以他的傳輸方式是單向傳輸。

那麼既然有匿名管道,就有命名管道,被叫做 FIFO,因為資料是先進先出的傳輸方式。命名管道具有讀寫兩個埠,程式可以透過開啟管道的檔案來進行讀取或寫入。當一個程式寫入資料到管道時,另一個程式可以從管道中讀取資料。

在使用命名管道前,先需要透過 mkfifo 命令來建立,並且指定管道名字:

$ mkfifo myPipe

myPipe 是管道的名稱,在 Linux 中一切皆檔案的原則下,管道也以檔案的形式存在。我們可以使用 ll 命令檢視一下,該檔案的型別是 p,表示為管道(pipe)。

image

接下來,我們將資料寫入名為 myPipe 的管道中:

image

在執行完寫入操作後,你可能會發現命令執行後一直停留在那裡。這是因為管道中的資料沒有被讀取,只有當管道中的資料被完全讀取後,命令才能正常退出。因此,我們需要執行另一個命令來讀取管道中的資料:

image

可以觀察到,管道中的內容已經被成功讀取並列印在終端上,另外,echo命令也正常退出了。

從中我們可以得知,匿名管道的通訊範圍限定在具有父子關係的程式之間。由於管道本身沒有實體,也就是沒有管道檔案,所以只能透過fork來複制父程式的檔案描述符,以實現程式間的通訊。(fork是一個作業系統呼叫,用於建立一個新的程式。當呼叫fork時,作業系統會複製當前程式的副本)

image

在shell中執行A | B命令時,A程式和B程式都是由shell建立的子程式。A和B之間不存在父子關係,它們的父程式都是shell。

image

此外,對於命名管道,它可以在不相關的程式之間進行通訊。這是因為命名管道事先建立了一個特定型別的裝置檔案,在程式中只需要使用該裝置檔案,就可以實現程式之間的通訊。

訊息佇列

訊息佇列是一種程式間通訊的機制,它相比於管道具有更高的效率和靈活性。訊息佇列是透過在核心中建立一個訊息連結串列來實現的,程式可以將資料放入訊息佇列中,然後其他程式可以從佇列中讀取這些資料。

例如,當程式A需要向程式B傳送訊息時,程式A將資料放入B程式對應的訊息佇列後即可正常返回。而程式B可以在需要時去讀取資料。同樣地,當程式B需要向程式A傳送訊息時,也可以按照相同的方式進行操作。

與管道不同的是,訊息佇列是有格式的,每個訊息體都是固定大小的儲存塊,程式在讀取資料時需要約定好訊息體的資料型別。訊息佇列的優勢在於可以支援程式間的非同步通訊,傳送方和接收方不需要同時執行,訊息可以在佇列中等待對方讀取。不像管道是無格式的位元組流資料。如果程式從訊息佇列中讀取了訊息體,核心就會把這個訊息體刪除。

image

訊息佇列的生命週期與核心相關,如果沒有顯式地釋放訊息佇列或關閉作業系統,訊息佇列將一直存在。而管道的生命週期是隨著程式的建立和結束而動態建立和銷燬。

然而,訊息佇列也存在一些缺點。由於資料在使用者態和核心態之間進行複製,訊息佇列通訊過程中存在一定的開銷。當程式將資料寫入訊息佇列時,需要將資料從使用者態複製到核心態;而另一個程式從訊息佇列中讀取資料時,需要將資料從核心態複製到使用者態。這種資料複製開銷會影響通訊的效率。

共享記憶體

共享記憶體是一種高效的程式間通訊機制,它允許多個程式共享同一塊記憶體區域,避免了資料的複製過程,提高了通訊速度。

在共享記憶體機制中,作業系統將一塊共享記憶體區域對映到多個程式的虛擬地址空間中,使得它們可以直接訪問同一塊實體記憶體。這樣,一個程式對共享記憶體的寫入操作,其他程式可以立即看到更新後的資料,而不需要進行資料的複製傳輸。

由於共享記憶體不進行資料複製,因此在程式間通訊的過程中,它具有較低的開銷和較高的傳輸速度。然而,共享記憶體機制需要透過同步機制來保證多個程式之間的資料一致性,以免出現競爭條件和資料不一致的問題。

image

總結

本篇文章總結了程式間通訊的三種常見機制:管道、訊息佇列和共享記憶體。它介紹了每種機制的特點、優缺點以及適用場景。管道適用於父子程式之間的通訊,但只能在有親緣關係的程式之間使用。訊息佇列可以用於非同步通訊,並且支援多個程式之間的通訊,但是訊息的格式需要事先定義。共享記憶體是一種高效的通訊方式,可以實現多個程式共享同一塊記憶體區域,但需要處理程式間的同步和互斥。根據實際需求,可以選擇合適的機制進行程式間通訊。

下一篇文章將繼續探討訊號量、訊號和套接字的知識點!

相關文章