Linux環境:C編程進程通信
管道通信
標準流管道popen
標準流管道函數:
FILE* popen(const char* command, const char* open_mode);//打開管道流文件
int pclose(FILE* fp);//關閉管道流文件
popen
函數:通過command
參數拉起一個新的進程,並創建一個管道文件,如果open_mode
是"r",則調用程序就可以通過該管道文件讀取被調用程序的標準輸出;反之如果open_mode
是“w”,則被調用程序可以通過標準輸入從管道文件讀取數據,調用程序通過向管道文件寫數據,實現通信。
pclose
函數:用 popen 啓動的進程結束時,我們可以用 pclose 函數關閉與之關聯的文
件流
無名管道pipe
函數原型:
int pipe(int *fd[2]);
函數機制:
創建一個無名管道,如果成功,fds[0]
存放可讀的文件描述符,fds[1]
存放
可寫文件描述符,並且函數返回 0,否則返回-1。
注意:
- 由於返回了一對文件描述符,所以想要用無名管道實現進程通信,兩個進程必須擁有相同的進程空間,即存在繼承關係。
- 生成的無名管道是特殊文件,可以用 read、write 等,只能在內存存在
樣例:
int main(int args,char *argv[])
{
int fd[2];
int ret = pipe(fd);
RET_CHECK(ret,-1,"pipe")
char buf[128]={0};
if(fork()>0)
{
read(fd[0],buf,sizeof(buf));
printf("%s\n",buf);
wait(NULL);
close(fd[0]);
return 0;
}
else
{
write(fd[1],"hello",6);
close(fd[1]);
exit(0);
}
}
命名管道’fifo’
創建FIFO文件函數原型:
int mkfifo(const char *pathname, mode_t mode);
- 參數 pathname 爲要創建的 FIFO 文件的全路徑名;
- 參數 mode 爲文件訪問權限
- 如果創建成功,則返回 0,否則-1。
刪除FIFO文件的函數原型爲:
int unlink(const char *pathname)
如何使用管道見《linux環境:C編程文件操作》
共享內存通信
共享內存原理:
ystem V機制下的共享內存本質是一段特殊的內存區域,進程間需要共享的數據被放在該共享內存區域中,所有需要訪問該共享區域的進程都要把該共享區域映射到本進程的地址空間中
共享內存允許一個或多個進程通過同時出現在它們的虛擬地址空間的內存進行通信,而這塊虛擬內存的頁面被每個共享進程的頁表條目所引用,同時並不需要在所有進程的虛擬內存都有相同的地址。進程對象對於共享內存的訪問通過 key(鍵)來控制,同時通過 key 進行訪問權限的檢查
進程對象對於共享內存的訪問通過 key(鍵)來控制,同時通過 key 進行訪問權限的檢查
使用共享內存步驟:
① 開闢一塊共享內存shmget
② 允許本進程使用共某塊共享內存shmat
③ 寫入/讀取
刪除共享內存步驟
①禁止本進程使用這塊共享內存shmdt
②刪除這塊共享內存shmctl或者命令行下ipcrm
內存共享不提供同步機制,需要自行實現,一般通過信號量實現同步。
ftok
函數
函數 ftok
用於創建一個關鍵字,可以用該關鍵字關聯一個共享內存段,相當於命名。
key_t ftok(const char *pathname, int proj_id);
- 參數
pathname
爲一個全路徑文件名,並且該文件必須可訪問。 - 參數
proj_id
通常傳入一非 0 字符 - 通過
pathname
和proj_id
組合可以創建唯一的key
如果調用成功,返回該關鍵字,否則返回-1。
shmget
函數
int shmget(key_t key, int size, int shmflg);
函數解析:
用於創建或打開一共享內存段
- 參數
key
內存標識,如果事先已經存在一個與指定關鍵字關聯的共享內存段,則直接返回該內存段的標識,表示打開,如果不存在,則創建一個新的共享內存段。key
的值既可以用ftok
函數產生,也可以是IPC_PRIVATE
(用於創建一個只屬於創建進程的共享內存,主要用於父子通信),表示總是創建新的共享內存段; - 參數
size
指定共享內存段的大小,以字節爲單位; - 參數
shmflg
是掩碼合成值,可以是訪問權限碼與(IPC_CREAT 或 IPC_EXCL)的合成。IPC_CREAT 表示如果不存在該內存段,則創建它。IPC_EXCL 表示如果該內存段存在,則函數返回失敗結果(-1)。如果調用成功,返回內存段標識,否則返回-1
shmat
函數
void *shmat(int shmid, const void *shmaddr, int shmflg);
函數解析:
將共享內存段映射到進程空間的某一地址。
- 參數
shmid
是共享內存段的標識 通常應該是 shmget的成功返回值 - 參數
shmaddr
指定的是共享內存連接到當前進程中的地址位置。通常是 NULL,表示讓系統來選擇共享內存出現的地址。 - 參數
shmflg
是一組位標識,通常爲 0 即可。 - 調用成功,返回映射後的進程空間的首地址,否則返回(char *)-1。
shmdt
函數
int shmdt(const void *shmaddr);
函數解析:
用於將共享內存段與進程空間分離
- 參數 shmaddr 通常爲 shmat 的成功返回值。
- 函數成功返回 0,失敗時返回-1.注意,將共享內存分離並沒刪除它,只是使得該共享內存對當前進程不在可用。
shmctl
函數
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函數解析:
用於控制共享內存段:
- 參數
shmid
是共享內存段標識 通常應該是 shmget 的成功返回值 - 參數
cmd
是對共享內存段的操作方式,可選爲IPC_STAT
,IPC_SET
,IPC_RMID
。通常爲IPC_RMID
,表示刪除共享內存段。 - 參數
buf
是表示共享內存段的信息結構體數據,通常爲NULL。 - 有進程連接,執行返回 0,標記刪除成功,最後一個進程解除連接後,共享內存真正被刪除
信號量通信
一般通過信號量實現進程同步
semget
函數
int semget(key_t key,int nsems,int flag);
用於創建一個信號量集合,返回一個標識id,失敗返回-1
- 參數
key
是唯一標識一個信號量的關鍵字,如果爲IPC_PRIVATE
(值爲 0,創建一個只有創建者進程纔可以訪問的信號量,通常用於父子進程之間;非 0 值的 key(可以通過 ftok 函數獲得)表示創建一個可以被多個進程共享的信號量; - 參數
nsems
指定需要使用的信號量數目。如果是創建新集合,則必須指定nsems
。如果引用一個現存的集合,則將nsems
指定爲 0 - 參數
flag
是權限標誌。還可以與鍵值 IPC_CREAT按位或操作,打開現有或創建一個新的信號量。可以通過 IPC_CREAT 和 IPC_EXCL標誌的聯合使用確保自己將創建出一個新的獨一無二的信號量來,如果該信號量已經存在,就會返回一個錯誤。
semop
函數
int semop(int semid,struct sembuf *sops,size_t num_sops);
用於改變信號量對象中各個信號量的狀態。返回值:成功時,返回 0;失敗時,返回-1.
- 參數
semid
是由semget
返回的信號量標識符。 - 參數
sops
是指向一個描述信號量的結構體數組指針。
該結構體包含:- 當前信號量的序號sem_num,
- 對信號量執行的操作sem_op,
- 信號量操作的屬性標誌sem_flag:如果爲0,表示正常操作,如果爲IPC_WAIT,使對信號量的操作是非阻塞的。即指定了該標誌,調用線程在信號量的值不滿足條件的情況下不會被阻塞,而是直接返回-1,並將errno設置爲EAGAIN。如果爲SEM_UNDO,那麼將維護進程對信號量的調整值,以便進程結束時恢復信號量的狀態。
struct sembuf { unsigned short int sem_num; /* 信號量的序號從0~num_sops-1 */ short int sem_op; /* 對信號量的操作,>0, 0, <0 */ short int sem_flg; /* 操作標識:0, IPC_WAIT, SEM_UNDO */ };
- 參數
num_sops
表示信號量數組的大小
semctl
函數
int semctl(int semid, int semnum, int cmd, …);
信號量控制函數:
在 semid
標識的信號量集上,或者該集合的第semnum
個信號量上執行 cmd
指定的控制命令
-
cmd類型如下:
- IPC_RMID(立即刪除信號集,喚醒所有被阻塞的進程)
- GETVAL(根據 semun 返回信號量的值,從 0 開始,第一個信號量編號爲 0)
- SETVAL(根據 semun 設定信號的值,從 0 開始,第一個信號量編號爲 0)
- GETALL(獲取所有信號量的值,第二個參數爲 0,將所有信號的值存入semun.array中)
- SETALL(將所有 semun.array 的值設定到信號集中,第二個參數爲 0)
-
根據 cmd 不同,該函數有三個或四個參數。當有四個參數時,第四個參數的類型是
union semun
。調用程序 必須按照下面方式定義這個聯合體:union semun { int val; // SETVAL使用的值 struct semid_ds *buf; // IPC_STAT、IPC_SET 使用緩存區 unsigned short *array; // GETALL,、SETALL 使用的數組 struct seminfo *__buf; // IPC_INFO(Linux特有) 使用緩存區 };
其中
semid_ds
結構體定義如下:struct semid_ds { struct ipc_perm sem_perm; // 所有者和權限 time_t sem_otime; // 上次執行 semop 的時間 time_t sem_ctime; // 上次更新時間 unsigned short sem_nsems; // 在信號量集合裏的索引 };
ipc_perm
定義如下:struct ipc_perm { key_t __key; // 提供給 semget()的鍵 uid_t uid; // 所有者有效 UID gid_t gid; // 所有者有效 GID uid_t cuid; // 創建者有效 UID gid_t cgid; // 創建者有效 GID unsigned short mode; // 權限 unsigned short __seq; // 序列號 };
信號量應用之生產者——消費者模型
#include <fun.h>
int main(int args,char *argv[])
{
//定義2個信號量,一個統計產品數量,一個統計倉庫容量
int sid = semget(IPC_PRIVATE,2,0600|IPC_CREAT);
RET_CHECK(sid,-1,"semget");
//定義兩組信號量pv操作結構體
struct sembuf p1,v1,p2,v2;
p1.sem_op = -1;
v1.sem_op = 1;
p1.sem_num = 0;
v1.sem_num = 0;
p1.sem_flg = SEM_UNDO;
v1.sem_flg = SEM_UNDO;
p2.sem_op = -1;
p2.sem_num = 1;
p2.sem_flg =SEM_UNDO;
v2.sem_num = 1;
v2.sem_op =1;
v2.sem_flg =SEM_UNDO;
short arr[2]={0,10};
//初始化信號量。0爲初始庫存,10爲倉庫初始容量。
int ret=semctl(sid,1,SETALL,arr);
RET_CHECK(ret,-1,"semctl");
printf("現有產品數量:%d,尚可存儲產品數量:%d\n",semctl(sid,0,GETVAL),semctl(sid,1,GETVAL));
//父進程負責生產
if(fork()>0)
{
printf("I am a producer!\n");
while(1)
{
semop(sid,&p2,1);
printf("一個產品已生產!\n");
semop(sid,&v1,1);
if(semctl(sid,0,GETVAL)>1)
{
printf("there are now %d products.\n",semctl(sid,0,GETVAL));
}
else
{
printf("there is now %d product.\n",semctl(sid,0,GETVAL));
}
sleep(1);
}
wait(NULL);
return 0;
}
//子進程負責消費
else
{
printf("I am a consumer!\n");
while(1)
{
semop(sid,&p1,1);
printf("一個產品已消費\n");
semop(sid,&v2,1);
sleep(2);
}
}
return 0;
}
消息隊列
消息隊列與 FIFO 很相似,都是一個隊列結構,都可以有多個進程往隊列裏面寫信息,
多個進程從隊列中讀取信息。但 FIFO 需要讀、寫的兩端事先都打開,才能夠開始信息傳遞工作。而消息隊列可以事先往隊列中寫信息,需要時再打開讀取信息。但是,消息隊列先打開讀,仍然會阻塞,因爲此時沒有消息可讀。
msgget
函數
函數原型:
int msgget(key_t key, int msgflg);
函數原理:
- 函數
msgget
創建和訪問一個消息隊列 - 參數
key
是唯一標識一個消息隊列的關鍵字,如果爲IPC_PRIVATE
(值爲 0),用創建一個只有創建者進程纔可以訪問的消息隊列,可以用於父子間通信;非 0 值的 key(可以通過 ftok 函數獲得)表示創建一個可以被多個進程共享的消息隊列 - 參數
msgflg
指明隊列的訪問權限和創建標誌,創建標誌的可選值爲IPC_CREAT
和IPC_EXCL
如果單獨指定IPC_CREAT
,msgget
要麼返回新創建的消息隊列 id,要麼返回具有相同 key 值的消息隊列 id;如果IPC_EXCL
和IPC_CREAT
同時指明,則要麼創建新的消息隊列,要麼當隊列存在時,調用失敗並返回-1。
msgsnd
函數和msgrcv
函數
函數原型:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//消息發送函數
- 該函數向一個隊列中發送消息
- 參數
msqid
指明消息隊列的id標識 - 參數
msgp
消息結構體指針struct msgbuf*
,結構體定義如下:struct msgbuf { long mtype; /* 消息類型,正整數,由用戶指定*/ char mtext[1]; /* 消息內容,可以通過重寫結構體來自定義 */ };
- 參數
msgsz
自定義消息結構體中的消息體大小 - 參數
msgflg
可以爲 0(通常爲 0)或IPC_NOWAIT
,如果設置IPC_NOWAIT
,則msgsnd
和msgrcv
都不會阻塞,此時如果隊列滿並調用msgsnd
或隊列空時調用msgrcv
將返回錯誤;
函數原型:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);//消息接收函數
- 參數
msqid
爲消息隊列標識id - 參數
msgp
爲消息結構體struct msgbuf指針 - 參數
msgz
標識消息的長度 - 參數
msgtyp
爲接收消息類型:msgtyp
=0,接收第一個消息msgtyp
>0,接收第一個類型爲msgtyp的消息msgtyp
<0, 接收類型小於或等於 msgtyp 絕對值的第 1 個最低類型消息
- 參數
msgflg
可以爲 0(通常爲 0)或IPC_NOWAIT
,如果設置IPC_NOWAIT
,則msgsnd
和msgrcv
都不會阻塞,此時如果隊列滿並調用msgsnd
或隊列空時調用msgrcv
將返回錯誤;
msgctl
函數
函數原型:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 函數 msgctl 是消息隊列的控制函數,常用來刪除消息隊列。
- 參數 msqid 是由 msgget 返回的消息隊列標識符。
- 參數 cmd 通常爲 IPC_RMID 表示刪除消息隊列。
- 參數 buf 通常爲 NULL 即可