進程間通訊的方式(IPC)

1、常見的通信方式

  1. 管道pipe:管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關係的進程間使用。進程的親緣關係通常是指父子進程關係。
  2. 命名管道FIFO:有名管道也是半雙工的通信方式,但是它允許無親緣關係進程間的通信。
  3. 消息隊列MessageQueue:消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。
  4. 共享存儲SharedMemory:共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號量,配合使用,來實現進程間的同步和通信。
  5. 信號量Semaphore:信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段。
  6. 套接字Socket:套解口也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同及其間的進程通信。
  7. 信號 ( sinal ) : 信號是一種比較複雜的通信方式,用於通知接收進程某個事件已經發生。

2、按通信類型區分

  1. 共享存儲器系統
    1.基於共享數據結構的通信方式
    (僅適用於傳遞相對少量的數據,通信效率低,屬於低級通信)
    2.基於共享存儲區的通信方式
  2. 管道通信系統
    管道是指用於連接一個讀進程和一個寫進程以實現它們之間通信的一個共享文件(pipe文件)
    管道機制需要提供一下幾點的協調能力
    1.互斥,即當一個進程正在對pipe執行讀/寫操作時,其它進程必須等待
    2.同步,當一個進程將一定數量的數據寫入,然後就去睡眠等待,直到讀進程將數據取走,再去喚醒。讀進程與之類似
    3.確定對方是否存在
  3. 消息傳遞系統
    1.直接通信方式
    發送進程利用OS所提供的發送原語直接把消息發給目標進程
    2.間接通信方式
    發送和接收進程都通過共享實體(郵箱)的方式進行消息的發送和接收
  4. 客戶機服務器系統
    1.套接字 – 通信標識型的數據結構是進程通信和網絡通信的基本構件
    基於文件型的 (當通信進程都在同一臺服務器中)其原理類似於管道
    基於網絡型的(非對稱方式通信,發送者需要提供接收者命名。通信雙方的進程運行在不同主機環境下被分配了一對套接字,一個屬於發送進程,一個屬於接收進程)
    2.遠程過程調用和遠程方法調用

3、詳解

3.1 管道

管道,通常指無名管道,是 UNIX 系統IPC最古老的形式。

1、特點:
它是半雙工的(即數據只能在一個方向上流動),具有固定的讀端和寫端。

它只能用於具有親緣關係的進程之間的通信(也是父子進程或者兄弟進程之間)。

它可以看成是一種特殊的文件,對於它的讀寫也可以使用普通的read、write 等函數。但是它不是普通的文件,並不屬於其他任何文件系統,並且只存在於內存中。

管道分爲pipe(無名管道)和fifo(命名管道)兩種,除了建立、打開、刪除的方式不同外,這兩種管道幾乎是一樣的。他們都是通過內核緩衝區實現數據傳輸。

  • pipe用於相關進程之間的通信,例如父進程和子進程,它通過pipe()系統調用來創建並打開,當最後一個使用它的進程關閉對他的引用時,pipe將自動撤銷。
  • FIFO即命名管道,在磁盤上有對應的節點,但沒有數據塊——換言之,只是擁有一個名字和相應的訪問權限,通過mknode()系統調用或者mkfifo()函數來建立的。一旦建立,任何進程都可以通過文件名將其打開和進行讀寫,而不侷限於父子進程,當然前提是進程對FIFO有適當的訪問權。當不再被進程使用時,FIFO在內存中釋放,但磁盤節點仍然存在。

管道的實質是一個內核緩衝區,進程以先進先出的方式從緩衝區存取數據:管道一端的進程順序地將進程數據寫入緩衝區,另一端的進程則順序地讀取數據,該緩衝區可以看做一個循環隊列,讀和寫的位置都是自動增加的,一個數據只能被讀一次,讀出以後再緩衝區都不復存在了。當緩衝區讀空或者寫滿時,有一定的規則控制相應的讀進程或寫進程是否進入等待隊列,當空的緩衝區有新數據寫入或慢的緩衝區有數據讀出時,就喚醒等待隊列中的進程繼續讀寫。

在這裏插入圖片描述

3.2 無名管道

pipe的例子:父進程創建管道,並在管道中寫入數據,而子進程從管道讀出數據

在這裏插入圖片描述

3.3 命名管道

和無名管道的主要區別在於,命名管道有一個名字,命名管道的名字對應於一個磁盤索引節點,有了這個文件名,任何進程有相應的權限都可以對它進行訪問。

而無名管道卻不同,進程只能訪問自己或祖先創建的管道,而不能訪任意訪問已經存在的管道——因爲沒有名字。

Linux中通過系統調用mknod()或makefifo()來創建一個命名管道。最簡單的方式是通過直接使用shell

mkfifo myfifo
  • 1

等價於

mknod myfifo p
  • 1

以上命令在當前目錄下創建了一個名爲myfifo的命名管道。用ls -p命令查看文件的類型時,可以看到命名管道對應的文件名後有一條豎線"|",表示該文件不是普通文件而是命名管道。

使用open()函數通過文件名可以打開已經創建的命名管道,而無名管道不能由open來打開。當一個命名管道不再被任何進程打開時,它沒有消失,還可以再次被打開,就像打開一個磁盤文件一樣。

可以用刪除普通文件的方法將其刪除,實際刪除的事磁盤上對應的節點信息。

例子:用命名管道實現聊天程序,一個張三端,一個李四端。兩個程序都建立兩個命名管道,fifo1,fifo2,張三寫fifo1,李四讀fifo1;李四寫fifo2,張三讀fifo2。

用select把,管道描述符和stdin假如集合,用select進行阻塞,如果有i/o的時候喚醒進程。(粉紅色部分爲select部分,黃色部分爲命名管道部分)

在這裏插入圖片描述

在這裏插入圖片描述

3.4 消息隊列

消息隊列,就是一個消息的鏈表,是一系列保存在內核中消息的列表。用戶進程可以向消息隊列添加消息,也可以向消息隊列讀取消息。

消息隊列與管道通信相比,其優勢是對每個消息指定特定的消息類型,接收的時候不需要按照隊列次序,而是可以根據自定義條件接收特定類型的消息。

可以把消息看做一個記錄,具有特定的格式以及特定的優先級。對消息隊列有寫權限的進程可以向消息隊列中按照一定的規則添加新消息,對消息隊列有讀權限的進程可以從消息隊列中讀取消息。

消息隊列的常用函數如下表:

在這裏插入圖片描述

進程間通過消息隊列通信,主要是:創建或打開消息隊列,添加消息,讀取消息和控制消息隊列。

3.5 共享內存

共享內存允許兩個或多個進程共享一個給定的存儲區,這一段存儲區可以被兩個或兩個以上的進程映射至自身的地址空間中,一個進程寫入共享內存的信息,可以被其他使用這個共享內存的進程,通過一個簡單的內存讀取錯做讀出,從而實現了進程間的通信。

採用共享內存進行通信的一個主要好處是效率高,因爲進程可以直接讀寫內存,而不需要任何數據的拷貝,對於像管道和消息隊裏等通信方式,則需要再內核和用戶空間進行四次的數據拷貝,而共享內存則只拷貝兩次:一次從輸入文件到共享內存區,另一次從共享內存到輸出文件。

在這裏插入圖片描述

一般而言,進程之間在共享內存時,並不總是讀寫少量數據後就解除映射,有新的通信時在重新建立共享內存區域;而是保持共享區域,直到通信完畢爲止,這樣,數據內容一直保存在共享內存中,並沒有寫回文件。共享內存中的內容往往是在解除映射時才寫回文件,因此,採用共享內存的通信方式效率非常高。

在這裏插入圖片描述

共享內存有兩種實現方式:1、內存映射 2、共享內存機制

3.6 信號量

信號量(semaphore)與已經介紹過的 IPC 結構不同,它是一個計數器。信號量用於實現進程間的互斥與同步,而不是用於存儲進程間通信數據。

1、特點
信號量用於進程間同步,若要在進程間傳遞數據需要結合共享內存。

信號量基於操作系統的 PV 操作,程序對信號量的操作都是原子操作。

每次對信號量的 PV 操作不僅限於對信號量值加 1 或減 1,而且可以加減任意正整數。

支持信號量組。

2、原型
最簡單的信號量是隻能取 0 和 1 的變量,這也是信號量最常見的一種形式,叫做二值信號量(Binary Semaphore)。而可以取多個正整數的信號量被稱爲通用信號量。

Linux 下的信號量函數都是在通用的信號量數組上進行操作,而不是在一個單一的二值信號量上進行操作。

#include <sys/sem.h>
// 創建或獲取一個信號量組:若成功返回信號量集ID,失敗返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 對信號量組進行操作,改變信號量的值:成功返回0,失敗返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);  
// 控制信號量的相關信息
int semctl(int semid, int sem_num, int cmd, ...);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章