行程間通信(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
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/509456.html
標籤:其他
下一篇:CentOS系統磁盤目錄空間調整
