進程間通訊

函數摘要

函數 說明 成功 失敗
管道和FIFO
pipe 單工通訊 0 -1
popen 執行一個shell命令,封裝的PIPE和exec功能 文件指針 NULL
pclose 關閉標準I/O流,等待命令終止,而後返回shell的終止狀態 返回cmdstring的終止狀態 -1
mkfifo 打開一個fifo文件 0 -1
mkfifoat 打開一個fifo文件 0 -1
IPC的操做
ftok 路徑和id建立一個IPC的key 返回key -1
消息隊列 不推薦使用,FIFO代替
msgget 建立一個消息隊列 返回消息隊列ID -1
msgctl 修改消息隊列的msqid_ds 0 -1
msgsnd 發送消息 0 -1
msgrcv 接受消息 消息數據部分的長度 -1
信號量 不推薦使用,用記錄鎖代替
semget 得到一個信號量 信號量隊列ID -1
semctl 修改信號量的的semid_ds 0 -1
semop 執行信號集合上的操做數字 0 -1
共享存儲
shmget 建立一個共享存儲 共享存儲的ID -1
shmctl 修改共享存儲的shmid_ds 0 -1
shmat 鏈接一個共享存儲的地址 指向共享存儲段的指針 -1
shmdt 將共享存儲的地址分離 0 -1
unix域套接字
socketpair 建立一個UNIX域套接字傳輸管道(相似於pipe) 0 -1

名稱解釋

  • 管道:shell

    • UNIX系統IPC的最古老形式,全部UNIX系統都提供次通訊機制
    • 返回2個文件描述符
    • 侷限
      • 歷史上是半雙工。不該預約假定系統支持全雙工
      • 只能在具備公共祖先的進程之間使用。一般,一個管道由一個進程建立,在進程調用fork以後,這個管道就能在父子進程之間使用。
  • FIFO:服務器

    • FIFO是一種文件類型
    • 未命名管道(pipe)只能在兩個相關的進程之間使用,這兩個相關的進程還要有一個共同的建立了他們的祖先進程。經過FIFO,不相關的進程也能交換數據。
  • XSI IPC:消息隊列、信號量及共享存儲器網絡

  • IPC的標識符: 每一個內核中的IPC結構都用一個非負證書的標識符加以引用。dom

    • 與文件描述符不一樣,IPC標識符不是小的整數。當一個IPC結構被建立,而後又被刪除時,與這種結構相關的標識符連續加1,直到達到一個整形數的最大值,而後又迴轉到0.
    • 外部名:標識符是IPC對象的內部名。爲了使多個合做進程可以在同一IPC對象上匯聚,須要提供一個外部名。爲此每一個IPC對象都與一個鍵(key)相關聯,將這個鍵做爲該對象的外部名。
    • 獲取方式
      • 服務器進程能夠指定鍵IPC_PRIVATE建立一個新IPC結構,將返回的標識符存放在(如一個文件)以便客戶進程取用。缺點:文件系統操做須要服務器將整型標識符寫到文件中,此後客戶進程又要取得次標識符。
      • 能夠在一個公共頭文件中定義一個客戶進程和服務器進程都承認的鍵,而後服務器進程指定此鍵建立一個新的IPC結構。缺點:此鍵已被使用,則get函數出錯。 服務器需刪除已存在的IPC結構,而後試着建立它。
      • 客戶進程和服務器進程認同一個路徑名和項目ID,調用ftok將這兩個值變換爲一個鍵,而後使用上面的方法中使用此鍵
  • IPC的權限結構: XSI IPC爲每一個IPC結構關聯一個ipc_perm結構,規定了權限和全部者。socket

struct ipc_perm {
    uid_t uid; /*全部者(啓動)userid*/
    gid_t gid; /*全部者(啓動)groupid*/
    uid_t cuid; /*建立者userid*/
    gid_t cgid; /*建立者userid*/
    mode_t mode; /*訪問模塊*/
    ...
}

輸入圖片說明

  • IPC的結構限制: 3種XSI IPC都有內核限制,能夠經過從新配置內核來改變。函數

  • 優勢和缺點oop

    • IPC結構實在系統範圍內起做用,沒有引用計數器。
      • 例如:建立一個消息隊列,而且在該隊列中放入了幾則消息。那麼該消息隊列及其內容不會被刪除,直到主動刪除。
    • IPC結構在文件系統中沒有名字。
    • IPC不使用文件描述符,因此不能使用多路轉接I/O函數(select/poll)。沒有某種形式的忙等待循環(busy-wait loop),就不能使一個服務器進程等待要放在消息隊列中任意一箇中的消息。
  • 消息隊列:消息隊列是消息的連接表,存儲在內核中,由消息隊列標識符標識。測試

函數

管道

建立
#include <unistd.h>

int pipe(int fd[2]);

    -- '成功:0;出錯:-1'
  • 特色:
    • 在fork以前,先建立一個管道。
    • 兩個文件描述符:fd[0]爲讀而打開,fd[1]爲寫而打開
    • 父進程-->子進程:父進程關閉管道fd[0],子進程關閉fd[1]
    • 子進程-->父進程:父進程關閉管道fd[1],子進程關閉fd[0]
      • 當讀(read)一個寫端已被關閉的管道時,在全部數據都被讀取後,read返回0,表示文件結束。
      • 若是(write)一個讀端已被關閉的管道,則產生信號SIGPIPE。若是忽略該信號或者捕捉該信號從其處理程序返回,則write返回-1,errno=EPIPE。
      • 在寫管道(或FIFO)時,常量PIPE_BUF規定了內核的管道緩衝區大小。多個進程同時寫一個管道,字節超過PIPE_BUF,那麼咱們所寫的數據可能會與其它進程所寫的數據相互交叉。

輸入圖片說明

popen和pclose
#include <stdio.h>

FILE *popen(const char *cmdstring, const char *type);

    -- '成功:文件指針;出錯:NULL'

int pclose(FILE *fp);

    -- '成功:返回cmdstring的終止狀態;出錯:-1'
  • 特色:ui

    • popen的功能:建立一個管道,fork一個子進程,關閉未使用的管道端,執行一個shell運行命令,而後等待命令終止。設計

    • popen先執行fork,而後調用exec執行cmdstring,而且返回一個標準I/O文件指針。

      • type=r:文件指針鏈接到cmdstring的標準輸出。
      • type=w:文件指針鏈接到cmdstring的標準輸入。
    • pclose:關閉標準I/O流,等待命令終止,而後返回shell的終止狀態。

輸入圖片說明

FIFO(命名管道)

建立
#include <sys/stat.h>

int mkfifo(const char *path, mode_t mode);

int mkfifoat(int fd, const char *path, mode_t mode);

    -- '成功:0;出錯:-1'
  • 參數:

    • mode:與open函數中的mode相同。
    • mkdfifoat的path:
      • 絕對路徑:fd被忽略
      • 相對路徑:fd是一個打開目錄的有效文件描述符,路徑名和目錄有關。
      • 相對路徑且fd參數有一個特殊值AT_FDCWD:則路徑名以當前目錄開始,mkfifoat和mkfifo相似。
  • 特色:

    • 使用一個FIFO的時候,默認已知其路徑
    • 當mkfifo與mkfifoat建立FIFO時,要用open來打開它。
    • 當open一個FIFO時,非阻塞標誌(O_NONBLOCK) 會產生下列影響:
      • 通常狀況下(沒有指定O_NONBLOCK),只讀open要阻塞到某個其它進程爲寫而打開的FIFO爲止
      • 通常狀況下(沒有指定O_NONBLOCK),只寫open要阻塞到某個其它進程爲讀而打開的FIFO爲止
      • 指定O_NONBLOCK,只讀當即返回。若是沒有寫方向,則返回-1,errno=SIGPIPE
      • 指定O_NONBLOCK,只寫當即返回。若是沒有讀方向,則返回-1,errno=ENXIO
    • 某個FIFO的最後一個寫進程關閉了該FIFO,則將爲FIFO的讀進程產生一個文件結束標誌。
    • 一個給定的FIFO有多個寫進程是常見的,若是不但願多個進程所寫的數據交叉,則必須考慮原子寫操做
  • 用途:

    • shell命令使用FIFO將數據從一條管道傳送到另外一條時,無需建立中間臨時文件。
    • 客戶進程-服務器進程應用程序中,FIFO做用匯聚點,在客戶進程和服務器進程兩者之間傳遞數據。

IPC的操做

ftok:路徑和id建立一個IPC的key
#include <sys/ipc.h>

key_t ftok(const char *path ,int id);

    -- '成功:返回鍵;出錯:返回-1'

消息隊列(不推薦使用,FIFO代替)

msgget:打開一個現有隊列或建立一個新消息隊列
#include <sys/msg.h>

int msgget(key_t key,int flag);

    -- '成功:返回消息隊列ID;出錯:-1'
    
// msqid_ds結構

struct msqid_ds {
    struct icp_perm msg_perm;   /*see Section 15.6.2*/
    msgqnum_t       msg_qnum;   /* # of message on queue */
    msglent_t       msg_qbytes; /* max # of bytes on queue */
    pid_t           msg_lspid;  /* pid of last msgsnd() */
    pid_t           msg_lrpid;  /* pid of last msgrcv() */
    time_t          msg_stime;  /* last-msgsnd() time */
    time_t          msg_rtime;  /* last-msgrcv() time */
    time_t          msg_ctime;  /* last-change time */
    ...
}
msgctl:修改msqid_ds
#include <sys/msg.h>

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

    -- '成功:0;出錯:-1'

*參數:

參數 說明
ICP_STAT 取次隊列的msqid_ds結構,並將它存放在buf指向的結構中
ICP_SET 取次隊列的msg_perm.uid,msg_perm.gid,msg_perm.mode和msg_qbytes從buf指向的結構複製到與這個隊列相關的msqid_ds結構中。
ICP_RMID 從系統中刪除該消息對i額以及人在該隊列中的全部數據。
msgsnd:將數據放到消息隊列
#include <sys/msg.h>

int msgsnd(int msqid, const void *ptr, size_t nbytes,int flag);

    --'成功:0;出錯:-1'

//ptr設計成以下
struct mymesg {
    long mtype;
    char mtext[512];
};
  • 參數:

    • ptr:一個長整型數,包含了正的整型消息類型,其後緊接着的是消息數據。
    • flag:指定是否阻塞。設置成IPC_NOWAIT,使得msgsnd當即出錯返回EAGAIN。
#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *ptr, size_t bytes, long type, int flag );

    -- '成功:消息數據部分的長度;出錯:-1'
  • 參數:

    • ptr:長整型數,消息的類型及存儲實際消息數據的緩衝區。
    • nbytes:長整型數,消息的類型。
    • type: 指定想要哪種消息:
      • type0:隊列中的第一個消息。
      • type>0:返回隊列中消息類型爲type的第一個消息。
      • type<0:返回隊列中消息類型值小於等於type絕對值的消息,若是這種消息有若干個,則類型值最小的消息。

信號量(不推薦使用。記錄鎖代替)

  • 做用:用於共享資源的同步。

  • 信號量操做流程:

    • 測試控制資源的信號量。
    • 若此信號量的值爲正,則進程可使用該資源。若次信號量的值爲0,則進程進入休眠狀態,直到信號量大於0,進程被喚醒,從頭開始。
  • 經常使用的信號量唄稱爲二元信號量(binary semaphore)

  • 缺點:

    • 信號量並不是單個非負值,而必需定義爲一個多個信號量值的集合。
    • 建立(semget)是獨立於它的初始化(semctl)的。不能原子化的建立和初始化一個信號量。
    • 即便沒有進程正則使用各類形式的XSI IPC,他們仍然是存在的。有得程序終止時沒有釋放已分配的信號量,必須考慮這種狀況
建立
#include <sys/sem.h>

int semget(key_t key, int nsems, int flag );

    -- '成功:信號量ID;出錯:-1'
修改
#include <sys/sem.h>

int semctl(int semid, int semnum,int cmd, ..../*uion semun arg */);
    
        -- '根據cmd返回'
執行信號集合上的操做數字
#include <sys/sem.h>

int semop(int semid, struct sembuf semoparray[], sieze_t nops);

    --'成功:0;出錯:-1'

共享存儲

  • 做用:容許兩個或多個進程共享一個給定的存儲區。
  • 最快的IPC:數據不須要在客戶進程和服務器直接複製。
建立
#include <sys/shm.h>

int shmget(key_t key, size_t size, int flag);

    --'成功:共享存儲的ID;出錯:-1'
修改
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmids_ds *buf);

    -- '成功:0;出錯:-1'

// 共享存儲的結構
struct shmid_ds {
    struct      ipc_perm shm_perm;   /*權限結構*/
    size_t      shm_setgsz;          /*size of segment in bytes*/
    pid_t       shm_lpid;            /*pid of last shmop()*/
    pid_t       shm_cpid;            /*pid of creator*/
    shmatt_t    shm_nattch;          /*number of current attaches*/
    time_t      shm_atime;           /*last-attach time*/
    time_t      shm_dtime;           /*last-detach time*/
    time_t      shm_ctime;           /*last-change time*/
    ...
}
  • 參數:

    • cmd
      • IPC_STAT:取此段的shmid_ds結構,並將它存儲在buf指向的結構中。
      • IPC_SET:按buf指向的結構中的值設置共享存儲段相關的shmid_ds結構中的3個字段:shm_perm.uid,shm_perm.gid和shm_perm.mode
      • IPC_RMID:從系統中刪除該共享存儲段。
      • IPC_LOCK:在內存中對共享存儲段加鎖。
      • IPC_UNLOCK:解鎖。
鏈接
#include <sys/shm.h>

void *shmat(int shmid, const void *addr, int flag);

    -- '成功:指向共享存儲段的指針;出錯:-1'
分離
#include <sys/shm.h>

int shmdt(const void *addr);

    -- '成功:0;出錯:-1'
  • 特色:

    • 使得shmid_ds.shm_nattach計數器減1.

UNIX域套接字

  • 像套接字和管道的混合:全雙工的做用
  • 和英特網域套接字區別:UNIX域套接字僅僅複製數據,他們並不執行協議處理,不須要添加或刪除網絡報頭,無需計算驗和,不要生成順序號,無需發送確認報文。
  • UNIX域套接字提供流和數據報兩種結構。
  • 命名UNIX域套接字:sockaddr_un的結構中 sun_path成員包涵一個路徑名,當咱們將一個地址綁定到一個UNIX域套接字時,系統會用該路徑建立一個S_IFSOCK類型的文件。
    • 例如:foo.socket.
  • 惟一鏈接 :服務器進程可使用標準bind、listen和accpet函數,爲客戶進程安排一個惟一UNIX域鏈接。
    • 客戶端進程經過使用connect與服務器進程鏈接,服務器進程結構式connect請求後,服務器進程和客戶端之間就存在惟一鏈接。

輸入圖片說明

#include <sys/socket.h>

int socketpari(int domain,int type, int protocol, int sockfd[2]);

    --'成功:0;出錯:-1'

//用例
int fd_pipe(int fd[2])
{
    return (socketpair(AF_UNIX, SOCK_STREAM,0,fd));
}
相關文章
相關標籤/搜索