進程間通信之管道,消息隊列,共享內存

Linux下進程間通信的幾種主要手段:

  1. 管道:包括有名管道和無名管道;
  2. 信號(signal):用於通知接受進程有某事件發生,除了進程間通信,還可以發信號給進程本身;
  3. 消息隊列:是消息的鏈接表;
  4. 共享內存:使得多個進程訪問同一塊共享內存。與信號量結合,實現進程間的同步與互斥;
  5. 信號量(semaphore):主要作爲進程間或同一進程下不同線程間的同步手段;
  6. 套接字(socket):進程間通信機制,也可以不同機器間的進程間通信。

管道

從一個進程連接數據流到另一個進程時,使用管道(pipe)。通常是把一個進程的輸出通過管道連接到另一個進程的輸入。管道是半雙工的,數據只能單向。

管道可分爲有名管道和無名管道,

  • 有名管道:可以在任意進程進行;
  • 無名管道/匿名管道:只能在父子進程間通用 (親緣關係的進程).

先看一下進程管道

#include <stdio.h>
FILE *popen(const char *command, const char *open_mode);
int pclose(FILE *stream_to_close);

popen函數允許一個程序將另一個程序作爲新進場來啓動,並可以傳遞數據給他或者通過它接收數據。

創建無名管道

#include <unistd.h>
int pipe( int fd[2] );
    //fd[0] r(讀)     fd[1] w(寫)

管道內部傳輸的數據是字節流,與TCP字節流的區別是,應用層程序能往一個TCP連接中寫入多少字節的數據,取決於對方的接受通告窗口大小和本端的擁塞窗口大小;而管道本身有容量限制,自Linux 2.6.11內核起,管道容量的大小默認是65536.可通過fcntl函數修改管道容量。
F_SETPIPE_SZ操作是設置指定的管道的容量。/proc/sys/fs/pipe-size-max內核參數指定管道容量上限。F_GETPIPE_SZ操作是獲取管道容量大小。

有名/命名管道:FIFO,在文件系統中以文件名的形式存在,但它的程序是mknod,
mknodfilenamep mkfifo filename

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo( const char * filename, mode_t mode );
int mknod( const char *filename, mode_t mode | S_IFIFO, (dev_t)0 );

由於命名管道出現在文件系統中,所以可以像文件一樣在命令中使用。

  1. 使用open打開FIFO文件,但不能以O_RDWR模式打開FIFO文件進行讀寫操作。
    open(const char *path, O_RDONLY);
    //open將會阻塞,除非有一個進程以寫方式打開同一個FIFO文件,否則不會返回。
    open(const char *path, O_WRONLY | O_NONBLOCK);//open調用立刻返回
  2. 不帶O_NONBLOCK標誌的O_RDONLY和O_WRONLY(讀進程和寫進程會同步)
  3. 帶O_NONBLOCK標誌的O_RDONLY和不帶標誌的O_WRONLY
    open(const char *path, O_RDONLY | NONBLOCK); //調用會成功並立刻返回
    open(const char *path, O_WRONLY);
    //open調用阻塞,知道一個進程一度方式打開同一個FIFO文件爲止。
  4. 對FIFO進行讀寫操作
    對一個空的,阻塞的FIFO的read調用將等待,直到有數據可讀時才繼續指向。
    對一個空的,非阻塞的read調用將立刻返回。
    對一個完全阻塞FIFO的write調用將等待,直到數據可以被寫入時才繼續執行。

(當一個Linux進程被阻塞時,並不消耗CPU資源)

雙向管道socketpair:

#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protcol, int fd[2] );
                //fd[0]是主線程,fd[1]子線程

雙向管道不是管道,是兩個套接字的封裝。

管道數據存放地點

  • 管道文件(代碼)——> 磁盤
  • 打開之後(數據)——>內存

消息隊列

消息隊列時在兩個進程之間傳遞二進制數據塊的一種方式。每個數據塊都有特定的類型,接收方可以根據類型來有選擇的接收數據,而不一定像管道和命名管道必須以先進先出的方式接收數據。
消息隊列並未解決使用命名管道時遇到的一些問題,比如管道滿時的阻塞問題。
與命名管道相比,少了在打開和關閉管道方面的複雜性,消息隊列的優勢在於,它獨立於發送和接受進程而存在,消除了在同步命名管道的打開和關閉時可能產生的困難。

優勢:

  • 可通過發送消息來幾乎完全避免命名管道的同步和阻塞問題;
  • 可提前查看緊急消息。

    缺陷:

  • 每個數據塊有最大長度的限制;

  • 系統中所有隊列所包含的全部數據塊的總長度也有上限。

Linux系統有兩個宏定義,
MSGMAX:以字節爲單位,一條消息的最大長度
MSGMNB:以字節爲單位,一個隊列的最大長度

限制
由於消息緩衝機制中所用的緩衝區爲共用緩衝區,因此使用消息緩衝機制傳送數據時,兩通信進程必須滿足如下條件:
(1)發送進程把寫入消息的緩衝區掛入消息隊列時,應禁止其他進程對消息隊列的訪問,否則,將引起消息隊列的混亂。同理,當接收進程正從消息隊列中取消息時,應禁止其他進程對該隊列的訪問。
(2)當緩衝區無消息時,接受進程不能接收任何消息;而發送進程是否可以發送消息,則只能由發送進程是否能夠申請緩衝區決定。

創建和訪問一個消息隊列,

#include <sys/msg.h>
int msgget( key_t key, int msgflg );
成功返回一個正整數,即隊列標識符,失敗返回-1.
msgflg一般設置IPC_CREAT,如果已有會自動忽略。

把消息添加到消息隊列中,

int msgsnd( int msqid, ocnst void *msg_ptr, size_t msg_sz, int msgflg );

函數成功返回0,消息數據的一份副本將被放到消息隊列中;失敗時返回-1.
msqid是由msgget函數返回的消息隊列標識符
msg_ptr是一個指向準備發送消息的指針
msg_sz是msg_ptr指向的消息的長度。(長度不能不包含長整型類型成員變量的長度)
msgflg若爲IPC_NOWAIT標誌,將立刻返回,不發送消息且返回值爲-1;
若爲IPC_NOWAIT被清除,則發送進程將掛起以等待隊列中騰出可用空間。

從一個消息隊列中獲取消息,

int msgrcv( int msqid, void *msg_ptr, size_t msg_sz, long int msgtype,
                                        int msgflg );

函數成功返回接收緩存區的字節數,消息被複制到由msg_ptr指向的用戶分配的緩存區中,然後刪除消息隊列總的對應消息。失敗返回-1.

msqid由msgget返回的消息隊列標識符
msg_ptr是一個指向準備接收消息的指針
msg_sz是msg_ptr指向的消息的長度
msgtype是一個長整數(接收優先級),0是獲取隊列中第一個可用消息;大於0是獲取相同消息類型
的第一個消息;小於0是獲取消息類型等於或小於msgtype的絕對值第一個消息。
msgflg若爲IPC_NOWAIT標誌,將立刻返回,返回值爲-1;

若爲IPC_NOWAIT被清除,則進程將掛起等待一條相應類型的消息到達。

操作函數:

int msgctl( int msqid, int command, struct msqid_ds *buf );

函數成功返回0,失敗返回-1.
如果刪除消息隊列時,某個進程正在msgsnd或msgrcv函數中等待,這兩個函數將失敗。
command是將要採取的動作,如下圖
這裏寫圖片描述


共享內存

允許兩個不相關的進程訪問同一個邏輯內存。共享內存是由IPC爲進程創建的一個特殊的地址範圍,出現在該進程的地址空間中。其他進程可以訪問共享內存中的地址空間中。如果某個進程向共享內存寫入數據,所做的改動將立刻被可以訪問同一段共享內存的任何其他進程看到。
由於它未提供同步機制,所以我們通常需要用其他的機制來同步對共享內存的訪問,否則會產生競態條件。一般是用共享內存來提供對大塊內存區域的有效訪問,同時通過傳遞小消息來同步對該內存的訪問。

這裏寫圖片描述

#include <sys/shm.h>

void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
int shmdt(const void *shm_addr);
int shmget(key_t key, size_t size, int shmflg);
int shm_open(const char* name, int oflag, mode_t mode );

shmget函數:創建共享內存,創建一段新的共享內存或獲取一段已存在的共享內存。
int shmget (key_t key, size_t size, int shmflg);

    函數返回一個共享內存標識符(非負整數),用於後續的共享內存函數;失敗返回-1.
    key有效地爲共享內存段命名
    size以字節爲單位指定需要共享的內存容量。
    這段共享內存的所有字節都被初始化爲0.

shmflg包含9個比特的權限標誌,與創建文件的mode標誌一樣。
可通過設置權限,避免數據被其他用戶修改。
shmget支持兩個額外的標誌:

  • SHM_HUGETLB,類似於mmap的MAP_HUGETLB標誌,系統將使用“大頁面”來爲共享內存分配空間。
  • SHM_NORESERVE,類似於mmap的MAP_NORESERVE標誌,不爲共享內存保留交換分區(swap空間)。這樣,當物理內存不足時,對該共享內存執行寫操作將觸發SIGSEGV信號。

共享內存被創建/獲取之後,不能立即訪問它,需要將共享內存連接到一個進程的地址空間中
void *shmat(int shm_id, const void *shm_addr, int shmflg);

shm_addr指定的是共享內存連接到當前進程中的地址位置。通常是一個空指針NULL,表示讓系統來選擇。

共享內存出現的地址。
函數成功返回一個指向共享內存第一字節的指針;失敗返回-1.
shm_id是由shmget返回的共享內存標識符。
shmflg是一組位標誌。一般使用SHM_RND(與shm_addr聯合使用,用來控制共享內存連接的地址)
和SHM_RDONLY(使得連接的內存只讀)。一般是讓系統選擇一個地址。

共享內存的讀寫權限由屬主(創建者),訪問權限和當前進程的屬主決定。
當shmflg & SHM_RDONLY爲true時的情況。此時即使共享內存的權限允許寫操作,他都不能被寫入。

shmdt:使用完共享內存之後,需要將共享內存從當前進程中分離

    函數成功返回0,失敗時返回-1.

將共享內存分離並未刪除它,只是使該共享內存對當前進程不在可用

控制函數
int shmctl(int shm_id, int command, struct shmid_ds *buf);
其中shmid_ds結構包含:
struct shmid_ds {
uid_t shm_perm.uid;
uid_t shm_perm.gid;
uid_t shm_perm.mode;
}

    函數成功返回0,失敗返回-1.
    shm_id是shmget返回的共享內存標識符。
    buf是個指針,指向包含共享內存模式和訪問權限的結構。

command是要採取的動作如下,
這裏寫圖片描述

當試圖刪除一個正處於連接狀態的共享內存段時,這個內存段還能繼續使用,直到它從最後一個進程中分離爲止。


IPC狀態命令
$ ./ipcs -s 顯示信號量狀態

$ ./ipcrm -s 刪除信號量

$ ipcs -m 顯示共享內存狀態

$ ipcrm -m 刪除共享內存

ipcs -q 顯示消息隊列轉檯
ipcrm -q 刪除一個消息隊列


共享內存比管道和消息隊列效率高的原因

共享內存是是進程間通信的一種方式。共享內存允許兩個或多個進程訪問同一塊內存,就如同
malloc()函數向不同進程返回來指向同一個物理內存區域的指針。
因爲所有進程共享同一塊內存,共享內存在各種進程間通信方式中具有最高的效率。訪問共享
來完成。同時它也避免了對數據的各種不必要的複製。

共享內存塊提供了在任意數量的進程之間進行高效雙向的通信機制。每個使用者都可以讀取寫入
等競爭狀態的出現。不幸的是,Linux無法嚴格保證提供對共享內存塊的獨立訪問,甚至是在通
共享內存塊的進程之間必須協調使用同一個鍵值。

共享內存區時最快的可用IPC形式,一旦這樣的內存區映射到共享它的進程的地址空間,這些進程
間數據的傳遞就不再通過執行任何進入內核的系統調用來傳遞彼此的數據,節省了時間。

——消息隊列,FIFO,管道的消息傳遞方式一般爲:

  1. 服務器得到輸入
  2. 通過管道,消息隊列寫入數據,通常需要從進程拷貝到內核
  3. 客戶從內核拷貝到進程
  4. 然後在從進程中拷貝到輸出文件

——共享內存需要,

1.從輸入文件到共享內存區
2.從共享內存區輸出到文件
共享內存不涉及到內核的拷貝,時間消耗少。


參考資料:
《Linux程序設計 第4版》
《Linux高性能服務器編程》
參考博客:
http://blog.csdn.net/ttyue_123/article/details/52370676

發佈了40 篇原創文章 · 獲贊 6 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章