進程間通信(Interprocess Communication,IPC)是指兩個或者多個進程之間進行數據交換的過程 ==進程擁有獨立的記憶體空間== 類別 簡單進程間通信 命令行參數(向子進程傳遞和exec系列函數) 這裡可以這麼理解:在創建子進程的時候,命令行參數是共用的 可以通過fork 的返回 ...
進程間通信(Interprocess Communication,IPC)是指兩個或者多個進程之間進行數據交換的過程 進程擁有獨立的記憶體空間
類別
簡單進程間通信
- 命令行參數(向子進程傳遞和exec系列函數)
- 這裡可以這麼理解:在創建子進程的時候,命令行參數是共用的
- 可以通過fork 的返回值,傳遞
- 環境列表 (子進程繼承父進程的環境列表和exec系列函數)
- 信號 (信號本身就是一個數據,不同的信號表示不同的數據,sigqueue還可以攜帶信號附加值)
- 文件 (文件就不贅述,Linux下萬物皆文件)
傳統進程間通信
-
管道
- 這裡管道又可以細分為有名管道和無名管道
- 有名管道
# mkfifo fifo # echo 要寫入的數據 > fifo # cat fifo # 管道本身不存儲數據。可以當成水管來理解 # 水桶(文件)才是存放數據的容器 # 水管是負責運輸,並且一根水管不可能同時做到往水桶裡加水和取水
- 編程模型
步驟 進程A 函數 進程B 步驟 1 創建管道 mkfifo ---- 2 打開管道 open 打開管道 1 3 讀寫管道 read/write 讀寫管道 2 4 關閉管道 close 關閉管道 3 5 刪除管道 unlink - 無名管道 只適用於父子進程之間的通信
#include <unistd.h>
int pipe(int pipefd[2]);
//成功返回0 失敗返回-1
pipefd[2] 作為輸出參數
-
編程步驟:
- 通過輸出參數pipefd得到兩個文件描述符,其中 pipefd[0]用於讀,pipefd[1]用於寫
- pipe函數在內核中創建管道文件,並打開兩次,一次讀,一次寫
- 需要在fork之前調用pipe函數
- 調用fork創建子進程
- 父子進程只允許使用無名管道的一端(如果進程想讀,則必須關閉寫,如果想寫,則必須關閉讀)
- 寫數據的進程關閉讀端(pipefd[0]),讀數據的進程關閉寫端(pipefd[1])
- 父子進程傳輸數據
- 父子進程分別關閉自己的文件描述符
-
記憶體映射(mmap)
- mmap/munmap底層不維護任何東西,只是返回一個首地址,所分配記憶體位於堆中
- brk/sbrk底層維護一個白板紙地,記錄所分配記憶體的結尾位置,所分配記憶體位於堆中,底層調用mmap/munmap
- malloc底層維護一個雙向鏈表和必要的控制信息,不可越界訪問,所分配記憶體位於堆中,底層調用brk/sbrk
- 每個進程都有虛擬的記憶體空間,虛擬記憶體地址只是一個數字 ,並沒有和實際的物理記憶體將關聯
- 所謂記憶體分配與釋放,其本質就是建立或者取消虛擬記憶體和物理記憶體之間的映射關係
#include <sys/mman.h>
//虛擬記憶體映射到物理記憶體或者文件
void *mmap(
void *addr, //虛擬記憶體起始位置,如果為NULL則系統自動選定合適的虛擬記憶體,成功則返回 一般給NULL
size_t length, //映射長度,以位元組為單位,自動按照(4K)頁對齊
int prot, //映射許可權
int flags, //映射標誌
int fd, //文件描述符,如果映射到文件則需要指定 如果不是映射到文件(匿名映射)則給0即可
off_t offset //文件偏移量,自動按照頁(4k)對齊
);
/*
成功返回映射區記憶體的起地址,失敗返回-1 (MAP_FAILED)
prot 許可權取值:
PROT_EXEC - 映射區可執行
PROT_READ - 映射區可讀
PROT_WRITE - 映射區可寫
PROT_NONE - 映射區不可訪問
如果既需要讀,也需要寫,則 PROT_READ|PROT_WRITE
flags 映射標誌:
MAP_FIXED - 若在addr記憶體地址上無法創建映射,則失敗(無此標誌系統會自動調整合適位置)
MAP_SHARED - 對映射區域的寫入操作直接寫入到文件中
MAP_PRIVATE - 對映射區的寫入操作只寫入到緩衝區中,不會真正寫入到文件
MAP_ANONYMOUS - 匿名映射 將虛擬記憶體映射到物理記憶體而非文件 忽略fd 和 offset參數
MAP_DENYWRITE - 拒絕其它對文件的寫入操作
MAP_LOCKED - 鎖定映射區域,保證其不被置換
一定需要 MAP_SHARED 和 MAP_PRIVATE 二選一
*/
//取消記憶體映射
int munmap(void *addr,size_t length);
XSI進程間通信
IPC標識
- 內核為每個進程間通信維護一個結構體形式的IPC對象
- 該IPC對象可通過一個非負整數的IPC標識來引用
- 與 文件描述符不同,IPC標識在使用時會持續加1,當達到最大值時,向0迴轉
- 非負整數,唯一標識一個進程間通信的IPC對象
IPC鍵值
- IPC標識是IPC對象的內部名稱(編號)
- 若多個進程需要在同一個IPC對象上會合(使用同一個進程間通信渠道),則必須通過鍵值作為其外部名稱來引用該IPC對象,IPC鍵值外部名稱
- 無論何時,只要創建IPC對象,就必須指定個鍵值
- 鍵值的數據類型在sys/types.h頭文件中被定義為key_t類型,其原始類型就是長整型
兩個進程如何在同一個IPC對象上匯合
- 方式一: 伺服器進程以PIC_PRIVATE為鍵值創建一個新的IPC對象,並將該IPC對象的標識存放在某外(如文件中),客戶端進程就只可以去該文件中讀取
- 方式二: 在一個公共頭文件中,定義一個兩個進程都認可的鍵值,伺服器進程用此鍵值創建IPC對象,客戶端進程用該鍵值獲取 IPC對象
- 方式三: 兩個進程事先約定好一個路徑名和一個項目ID(0-255),通過路徑名和ID調用ftok函數,將二者轉換為一個唯一的鍵值
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname,int pro_id);
pathname - 一個真實存在的文件或者目錄的路徑名
pro_id - 項目ID,低8位有效,其值域[0,255]
成功返回鍵值,失敗返回-1
註意:起作用的是pathname參數所表示的路徑,而非pathname字元串本身
實現方式
-
共用記憶體
基本特點:
- 兩個或者更多的進程,共用同一塊系統內核負責維護的記憶體區域,其地址空間通常被映射到堆棧之間
- 無需複製信息(數據),最快的一種IPC機制
- 需要考慮同步訪問的問題
- 內核為每個共用記憶體,維護一個shmid_ds結構體形式的共用記憶體對象
#include <sys/types.h> #include <sys/shm.h> //1.創建/獲取共用記憶體 內核維護 int shmget(key_t key,size_t size,int shmflg); /* A.以參數key作為鍵值創建共用記憶體,如果共用記憶體已經存在,則獲取該共用記憶體 B.size參數指定共用記憶體的大小(單位位元組),建議取4096的整數倍 若希望創建共用記憶體,則必須指定size參數 若只是獲取已有的共用記憶體,則size參數可以傳遞0 C.參數shmflg標識 0 - 獲取,如果共用記憶體不存在則獲取失敗 IPC_CREAT - 創建,不存在則創建 存在則獲取(除非指定IPC_EXCL) 如果IPC_CREAT需要給定共用記憶體的許可權(mode) IPC_CREAT|0644 IPC_EXCL - 排斥,和IPC_CREAT按位域,如果共用記憶體已經存在則失敗 成功返回共用記憶體的標識,失敗返回-1 */ //2.載入共用記憶體 將進程中的虛擬記憶體地址映射到共用記憶體中 void* shmat(int shmid,void *shmaddr,int shmflg); /* A.將shmid(shmget的返回值)參數所標識的共用記憶體,映射到調用進程的地址空間 B.可以通過參數shmaddr(進程中的虛擬地址)人為指定映射地址,也可以將參數置為NULL,由系統自動選擇 C.參數shmflg標識: 0 - 以讀寫方式使用共用記憶體 SHM_RDONLY - 以只讀方式使用共用記憶體 SHM_RND - 只在shmaddr參數非NULL時才起作用,表示對shmaddr參數向下取記憶體頁的整數倍作為映射地址 成功返回映射地址,失敗返回-1(0XFFFFFFF) 如果載入成功,內核將該共用記憶體的載入計數加1(共用記憶體由內核維護,記錄有多少個進程載入了該共用記憶體) */ //3.卸載共用記憶體 int shmdt(const void *shmaddr); /* 將參數shmaddr所指向載入的共用記憶體映射從調用進程的取消映射 成功返回0,失敗返回-1 如果卸載成功,內核會將該共用記憶體的載入計數減1 */ //4.銷毀/控制共用記憶體 int shmctl(int shmid,int cmd,struct shmid_ds* buf); /* A.參數shmid是shmget的返回值 是對shmid所標識的共用記憶體進行刪除/獲取共用記憶體的信息 B.cmd取值 IPC_STAT - 獲取共用記憶體的屬性,通過buf參數輸出 IPC_SET - 設置共用記憶體的屬性,通過buf參數輸入,僅三個屬性可設置 shm_perm.uid 用戶ID shm_perm.gid 組ID shm_perm.mode 許可權 IPC_RMID - 標記刪除共用記憶體 並非真正刪除共用記憶體,只是做一個刪除標記,禁止其被繼續載入,但已有載入依然保留。 只有當該共用記憶體的載入計數為0且使用IPC_RMID時才真正被刪除 成功返回0 失敗返回-1 */ struct shmid_ds{ struct ipc_perm shm_perm; //所有者及許可權 size_t shm_segsz; //共用記憶體大小(以位元組為單位) time_t shm_atime; //最後載入時間 time_t shm_dtime; //最後卸載時間 time_t shm_ctime; //最後修改時間 pid_t shm_cpid; //創建共用記憶體的進程ID pid_t shm_lpid; //最後載入、卸載進程的ID shmatt_t shm_nattch; //當前載入計數 ... }; struct ipc_perm{ key_t __key; //鍵值 uid_t uid; //有效屬主ID gid_t gid; //有效屬組ID uid_t cuid; //有效創建者ID gid_t cgid; //有效創建組ID unsigned short mode; //許可權 unsigned short __seq;//序列號 };
#ipcs -m #查看當前系統的共用記憶體 #ipcrm -m shmid #刪除指定的共用記憶體
-
消息隊列
基本特點
- 消息隊列是由一個系統內核負責存儲和管理,並通過消息隊列標識引用的數據鏈表
- 可以通過msgget函數創建一個新的消息隊列 ,或者獲取一個已經存在的消息隊列
- 通過msgsnd函數向消息隊列的後端追加消息(需要把消息從用戶空間拷貝到內核空間)
- 通過msgrcv函數從消息隊列的前端按要求提取消息(需要把消息從內核空間拷貝到用戶空間)
- 消息隊列中的每個消息除了消息本身數據以外,還包含消息類型和數據長度
- 內核為每個消息隊列 ,維護一個msqid_ds結構體形式的消息隊列對象
#include <sys/msg.h> //msgget 創建或者獲取消息隊列 int msgget(key_t key,int msgflg); /* A.該函數以參數key作為鍵值創建消息隊列,如果存在則獲取消息隊列 B.msgflg標識 0 - 獲取,不存在即失敗 IPC_CREAT - 創建,不存在則創建,已存在則獲取,除非 創建時需要給定許可權 IPC|0644 IPC_EXCL - 排斥,創建時如果已經存在則創建失敗 成功返回消息隊列標識,失敗返回-1 */ //msgsnd向消息隊列發送消息 int msgsnd(int msgqid,const void *msgp,size_t msgsz,int msgflg); /* A. msgqid 消息隊列的標識 msgget函數的返回值 B. msgp參數是一個指針,指針指向一塊記憶體,記憶體中包含消息類型和消息數據 記憶體中的前4/8個位元組必須是一個大於0的整數,代表消息類型,其後緊跟消息數據 消息數據的位元組長度用msgsz參數表示 註意:msgsz長度並不包含消息類型4/8個位元組 +------------+--------------------+ msgp--> |消息類型(>0) | 消息數據 | +------------+--------------------+ |<-4/8Byte-> |<----msgsz--------->| C.若內核中消息隊列緩衝區有足夠的空閑空間,則此函數會將消息拷入緩衝區並立即返回0,表示發送成功,否則此函數會阻塞,直到內核中的消息隊列緩衝區有足夠的空閑空間為止(比如有消息被接收) D.若msgflg參數包含IPC_NOWAIT位,則當內核中的消息隊列沒有足夠空閑空間時,此函數不會阻塞,而是直接返回-1,且errno設置為EAGAIN 成功返回0 失敗返回-1 */ //msgrcv 從消息隊列中接收消息 ssize_t msgrcv(int msgqid,void *msgp,size_t msgsz,long msgtype,int msgflg); /* A.msgqid 消息隊列標識,msgget函數的返回值 B.msgp指針指向一個包含消息類型(4byte)和消息數據的記憶體塊,用於存儲消息類型和消息數據本身 C.msgsz參數用來標明消息數據緩衝區位元組大小 msgp指針指向的記憶體塊的大小-4/8byte D.若所接收到的消息位元組數據大於msgsz參數,即消息太長 E.如果msgflg參數中包含MSG_NOERROR位,則消息太長會被截取msgsz位元組返回,剩餘部分會被丟棄 如果msgflg參數不包含MSG_NOERROR五個,消息太長時,不會對該消息做任何處理,直接返回-1,且errno設置為E2BIG F.msgtype參數表示期望接收哪類消息 msgtype = 0 - 返回消息隊列中的第一條消息 msgtype > 0 - 若msgflg參數不包含MSG_EXCEPT位,則返回消息隊列中第一個類型為msgtype的消息 如果msgflg參數包含MSG_EXCEPT位,則返回消息隊列中第一個消息類型不為msgtype的消息 msgtype < 0 - 返回消息隊列中類型小於等於msgtype絕對值的消息 如果有多條消息滿足,則返回消息類型最小的第一條消息 G.若消息隊列中有可接收的消息,則此函數會將該消息移出消息隊列拷貝到msgp記憶體中並立即返回0,表示接收成功 如果消息隊列中沒有可接收的消息,則此函數會阻塞,直到消息隊列中有可接收的消息為止 H.如果msgflg參數包含IPC_NOWAIT位,則當消息隊列中沒有可接收的消息時(沒有滿足要求的消息),則此函數不會阻塞,而是返回-1,設置errno為ENOMSG 成功返回所接收到消息數據的位元組數,失敗返回-1 */ //msgctl銷毀/控制消息隊列 int msgctl(int msgqid,int cmd,struct smqid_ds *buf); /* cmd的取值: IPC_STAT - 獲取消隊列的屬性,通過buf參數輸出 IPC_SET - 設置消息隊列的屬性,通過buf輸入 msg_perm.uid msg_perm.gid msg_perm.mode msg_qbytes IPC_RMID - 立即刪除消息隊列 此時所有阻塞在該消息隊列的,msgsnd/msgrcv函數調用都會立即返回失敗,errno設置為EIDRM 成功返回0 失敗返回-1 */ struct msqid_ds{ struct ipc_perm msg_perm; //許可權依賴 time_t msg_stime; //最後發送時間 time_t msg_rtime; //最後接收時間 time_t msg_ctime; //最後修改時間 unsigned long _msg_cbytes; //消息隊列中的位元組數 msgqumt_t msg_qnum; //消息隊列中消息數 msglen_t msg_qbytes; //消息隊列能容納的最大位元組數 pid_t msg_lspid; //最後發送消息進程ID pid_t msg_lrpid; //最後接收消息進程ID }; struct ipc_perm{ key_t __key; //鍵值 uid_t uid; //有效屬主ID gid_t gid; //有效屬組ID uid_t cuid; //有效創建者ID gid_t cgid; //有效創建組ID unsigned short mode; //許可權 unsigned short __seq;//序列號 };
#ipcs -q # 查看消息隊列 #ipcrm -q msqid # 刪除指定的消息隊列
-
信號量
基本特點
- 本質上是用於限制對於共用資源訪問的進程數量 計數器
- 計數器如果設置為1,表示任意時刻只允許一個進程對共用資源進行訪問 文件鎖寫鎖 獨占鎖
- 多個進程獲取有限資源操作模式
-
- 獲取控制該資源的信號量
- 若信號量的值大於0,則進程可以使用該資源,為了表示該進程已獲得該資源,需要將信號量的值減1
- 若信號等於0,則該進程休眠等待資源,直到信號量的值大於0,進程被喚醒,執行1步驟
- 當進程不再使用該資源時,為了表示進程釋放該資源,需要將信號量的值加1,正在休眠等待該資源的其它進程將會被喚醒
-
- 信號量 類似於 鎖
#include <sys/sem.h> //semget 創建/獲取信號量集 信號量數組 int semget(key_t key,int nsems,int semflg); /* 該函數是以key作為鍵值創建一個信號量集合(nsems參數表示集合中信號量的數量),如果是獲取已經存在的信號量集合則nsems可以取0 semflg取值: 0 - 獲取,不存在則失敗 IPC_CREAT - 創建,不存在則創建,存在即獲取,除非IPC_EXCL IPC_EXCL - 排斥,和IPC_CREAT一起使用,如果信號量集合存在則失敗 成功返回信號量集合標識,失敗返回-1 */ //semop 操作信號量/信號量集合 int semop(int semid,struct sembuf *sops,unsigned nsops); /* semid參數是信號量集合的標識,semget函數的返回值 sops: 其實是一個數組的首地址 如果只有一個元素時,可以是一個元素的首地址 nsops:數組長度 sops數組中每個元素都是stuct sembuf的數據 執行操作如下: 若sem_op大於0,則將其加到sem_num下標所表示的信號量的計數值上,以表示對資源的釋放 若sem_op小於0,則將其從sem_num下標所表示的信號量減去sem_op的絕對值,以表示對資源的獲取 若sem_num信號量的計數值不夠減(信號量數值不能為負),則此函數會阻塞,直到該信號量夠減為止,以表示對資源的等待; 若sem_flg包含IPC_NOWAIT,則當sem_num信號量計數值不夠減時,此函數不會阻塞,而是返回-1,errno設置為EAGAIN,以便在等待資源的同時還可以做其它處理 若sem_op等於0,則直到sem_num所表示的信號量的計數值為0時才返回,除非sem_flg包含IPC_NOWAIT 成功返回0,失敗返回-1 */ struct sembuf{ unsigned short sem_num; //信號量下標 下標從0開始,表示操作哪一個信號量 short sem_op; //操作數 1 -1 short sem_flg; //操作標記 }; //semctl 銷毀/控制信號量集 int semctl(int semid,int semnum,int cmd); int semctl(int semid,int semnum,int cmd,union semun arg); /* IPC_STAT- 獲取信號量集合的屬性,通過arg.buf輸出 IPC_SET - 設置信號量集合的屬性,通過arg.buf輸入 sem_perm.uid sem_perm.gid sem_perm.mode IPC_RMID- 立即刪除信號量集合 此時所有阻塞在對該信號量集合的semop函數調用,都會立即返回失敗,errno設置為EIDRM GETALL - 獲取信號量集合中每個信號量的計數值,通過arg.array輸出 SETALL - 設置信號量集合中每個信號量的計數值,通過arg.array輸入 GETVAL - 獲取信號量集合中,下標為semnum信號量的計數值,通過返回值輸出 SETVAL - 設置信號量集合中,下標為semnum信號量的計數值,通過arg.val輸入 註意:只有針對信號量集合中具體某個信號量操作時,才會使用semnum參數,針對整個信號量集合操作,會忽略semnum 成功因cmd而異,失敗返回-1 */ union emun{ int val; //value for SETVAL struct sem_ds *buf; //Buffer for IPC_STAT IPC_SET unsigned short *array; //Array for GETALL SETALL struct seminfo *__buf; //buffer for IPC_INFO }; struct sem_ds{ struct ipc_perm sem_perm; //許可權 time_t sem_otime; //最後semop操作的時間 time_t sem_ctime; //最後修改時間 unsigned short sem_nsems; //信號量集合中信號量的數據 }; struct ipc_perm{ key_t __key; //鍵值 uid_t uid; //有效屬主ID gid_t gid; //有效屬組ID uid_t cuid; //有效創建者ID gid_t cgid; //有效創建組ID unsigned short mode; //許可權 unsigned short __seq;//序列號 };
- 本質上是用於限制對於共用資源訪問的進程數量 計數器
網路進程間通信
- socket套接字
- 這裡參考我另一邊博文 TCP/UDP編程模型
本文來自博客園,作者:打工搬磚日記,轉載請註明原文鏈接:https://www.cnblogs.com/FlyingDoG--BoxPiG/p/16725644.html