??博客主頁(yè)??:??https://blog.csdn.net/wkd_007??
??博客內(nèi)容??:??嵌入式開發(fā)、Linux、C語(yǔ)言、C++、數(shù)據(jù)結(jié)構(gòu)、音視頻??
??本文內(nèi)容??:??介紹 ??
??金句分享??:??你不能選擇最好的,但最好的會(huì)來(lái)選擇你——泰戈?duì)??
本文未經(jīng)允許,不得轉(zhuǎn)發(fā)?。。?/font>
下表是進(jìn)程間通信的十種方式
??一、管道(無(wú)名管道)
?1.1 管道介紹
管道是半雙工的通信方式,數(shù)據(jù)只能單向流動(dòng),管道的作用是在有親緣關(guān)系的進(jìn)程之間傳遞消息。所謂親緣關(guān)系是指,只要調(diào)用進(jìn)程使用pipe函數(shù), 打開的管道文件就會(huì)在fork之后, 被各個(gè)后代進(jìn)程所共享。
這個(gè)無(wú)名管道可以理解為:沒(méi)有實(shí)體文件與之關(guān)聯(lián), 靠的是世代相傳的文件描述符來(lái)進(jìn)行數(shù)據(jù)的讀寫。
無(wú)名管道可以使用函數(shù)pipe來(lái)創(chuàng)建,函數(shù)原型如下:
#include <unistd.h>
int pipe(int pipefd[2]);
?1.2 例子
看使用例子,父進(jìn)程調(diào)用了pipe函數(shù)創(chuàng)建了管道文件,fork之后的子進(jìn)程可以直接使用管道文件:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define PIPE_INPUT 0
#define PIPE_OUTPUT 1
int main()
{
int pipe_fds[2];
pipe(pipe_fds); // 創(chuàng)建無(wú)名管道
pid_t pid = fork();
if(pid == 0) {// 子進(jìn)程
printf("子進(jìn)程[%d]開始執(zhí)行, 關(guān)閉輸入管道,寫數(shù)據(jù)到輸出管道\n", getpid());
close(pipe_fds[PIPE_INPUT]);// 關(guān)閉輸入管道
write(pipe_fds[PIPE_OUTPUT], "test data", strlen("test data"));// 寫入管道
exit(0);
}
else if(pid > 0)
{
sleep(2); //延時(shí)一會(huì),讓子進(jìn)程先運(yùn)行
printf("父進(jìn)程[%d]開始執(zhí)行, 關(guān)閉輸出管道,讀取管道數(shù)據(jù)\n", getpid());
close(pipe_fds[PIPE_OUTPUT]);// 關(guān)閉輸出管道
char buf[256] = {0,};
int readSize = read(pipe_fds[PIPE_INPUT], buf, sizeof(buf));
printf("父進(jìn)程[%d]從管道讀取到%d個(gè)字節(jié)的數(shù)據(jù)[%s]\n", getpid(), readSize, buf);
exit(0);
}
else
{
printf("Error in fork\n");
exit(1);
}
return 0;
}
??二、命名管道FIFO
?2.1 命名管道FIFO介紹
上面的無(wú)名管道沒(méi)有與實(shí)體文件關(guān)聯(lián),靠的是世代相傳的文件描述符來(lái)進(jìn)行數(shù)據(jù)交換。命名管道就是為了解決無(wú)名管道的這個(gè)問(wèn)題而引入的。 FIFO與管道類似, 最大的差別就是有實(shí)體文件與之關(guān)聯(lián)。 由于存在實(shí)體文件, 不相關(guān)的、沒(méi)有親緣關(guān)系的進(jìn)程
也可以通過(guò)使用FIFO來(lái)實(shí)現(xiàn)進(jìn)程之間的通信。
創(chuàng)建命名管道的3種方式:
- 1、調(diào)用C語(yǔ)言接口函數(shù)
mkfifo
創(chuàng)建:mkfifo("my_fifo", 0666);
;#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode);
- 2、使用
mkfifo
命令創(chuàng)建:mkfifo -m 0666 my_fifo
;- 3、使用
mknod
命令創(chuàng)建:mknod -m 0666 my_fifo p
。
一旦FIFO文件創(chuàng)建好了, 就可以把它用于進(jìn)程間的通信了。 一般的文件操作函數(shù)如open、 read、 write、 close、 unlink等都可以用在FIFO文件上。
?2.2 例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
if(0 == access("./my_fifo",F_OK))
{
system("rm ./my_fifo");
}
/*創(chuàng)建管道文件, 下次運(yùn)行需要先刪除my_fifo文件,否則mkfifo報(bào)錯(cuò)*/
if(mkfifo("my_fifo", 0666) < 0)
{
perror("mkfifo");
return -1;
}
pid_t pid = fork();
if(pid == 0) {// 子進(jìn)程
printf("子進(jìn)程[%d]開始執(zhí)行, 打開my_fifo文件,循環(huán)往里寫數(shù)據(jù)\n", getpid());
int fd = open("my_fifo", O_WRONLY);
if(fd < 0)
{
return -1;
}
int i = 9;
while(i>=0)
{
printf("子進(jìn)程[%d]寫入數(shù)據(jù):%d\n", getpid(), i);
char buf[256] = {0,};
sprintf(buf,"%d",i);
write(fd, buf, strlen(buf));
i--;
sleep(1);
}
close(fd);
printf("子進(jìn)程[%d]退出\n", getpid());
return 0;
}
else if(pid > 0)// 父進(jìn)程
{
sleep(5); //延時(shí)一會(huì),讓子進(jìn)程先運(yùn)行
printf("父進(jìn)程[%d]開始執(zhí)行, 打開my_fifo文件,讀取數(shù)據(jù)\n", getpid());
int fd = open("my_fifo", O_RDONLY);
if(fd < 0)
{
return -1;
}
char buf[256] = {0,};
int readSize = 0;
while((readSize = read(fd, buf, sizeof(buf)) ) > 0)
{
printf("父進(jìn)程[%d]讀取到%d個(gè)字節(jié)數(shù)據(jù):[%s]\n", getpid(),readSize, buf);
memset(buf, 0, sizeof(buf));
}
close(fd);
printf("父進(jìn)程[%d]退出\n", getpid());
return 0;
}
else
{
printf("Error in fork\n");
exit(1);
}
return 0;
}
運(yùn)行結(jié)果:
??三、消息隊(duì)列(System V IPC)
?3.1 消息隊(duì)列(System V IPC)介紹
有三種被稱為XSI IPC
的進(jìn)程間通信,消息隊(duì)列,信號(hào)量,共享內(nèi)存。XSI IPC
函數(shù)是基于System V
的IPC函數(shù)。這里介紹的消息隊(duì)列就屬于其中一種,后面還有介紹其余兩種,消息隊(duì)列比較少用了,是一種逐漸被淘汰的通信方式,為了完整性,這里還是介紹一下,感興趣的可以繼續(xù)了解。
前面的管道通信,如果從管道中讀取到100個(gè)字節(jié),你無(wú)法確認(rèn)這100個(gè)字節(jié)是單次寫入的100字節(jié), 還是分10次每次10字節(jié)寫入的, 你也無(wú)法知曉這100個(gè)字節(jié)是幾個(gè)消息。System V消息隊(duì)列就不存在這種問(wèn)題,因?yàn)樗腔谙⑼ㄐ诺?。無(wú)需從字節(jié)流解析完整的消息,而且每個(gè)消息有type字段作為消息類型。
消息隊(duì)列編程步驟:
- 1、生成 key,System V IPC的標(biāo)識(shí)ID都是通過(guò)key來(lái)獲取的,key的生成方式有三種:
①隨機(jī)選擇一個(gè)整數(shù)值作為key值,這個(gè)值必須不和其他key重復(fù),例如:#define MSG_KEY 10086
②使用IPC_PRIVATE,例如:id = msgget(IPC_PRIVATE,S_IRUSR | S_IWUSR);
③使用ftok函數(shù), 根據(jù)文件名生成一個(gè)key,例如:key_t key = ftok(".", 100);
- 2、使用
msgget()
創(chuàng)建/獲取消息隊(duì)列,返回值為隊(duì)列標(biāo)識(shí)符。
服務(wù)端創(chuàng)建:int msgid = msgget(key, 0666|IPC_CREAT);
客戶端獲?。?code>int msgid = msgget(key, 0);- 3、寫入/取出消息;
服務(wù)端寫入:msgsnd(msgid, &msg, sizeof(msg.buf), 0);
客戶端獲?。?code>msgrcv(msgid, &msg, sizeof(msg)-sizeof(long), 0, 0);- 4、msgctl刪除消息隊(duì)列
msgctl(msgid, IPC_RMID, NULL);
?3.2 例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct _MSG_TYPE
{
long mtype;//消息類型
char buf[256];//有效數(shù)據(jù)
}MSG_TYPE;
int main()
{
// 1 生成key
key_t key = ftok(".", 100);
// 2 創(chuàng)建子進(jìn)程
pid_t pid = fork();
if(pid == 0) {// 子進(jìn)程
printf("子進(jìn)程[%d]開始執(zhí)行, 創(chuàng)建消息隊(duì)列,循環(huán)往里寫數(shù)據(jù)\n", getpid());
// 創(chuàng)建消息隊(duì)列
int msgid = msgget(key, 0666|IPC_CREAT);
if(msgid == -1)
{
perror("msgget failed");
exit(1);
}
// 發(fā)送數(shù)據(jù)
int i = 9;
MSG_TYPE msg;
while(i>=0)
{
memset(&msg, 0, sizeof(msg));
msg.mtype = i;
sprintf(msg.buf, "hello-%d", i);
msgsnd(msgid, &msg, sizeof(msg.buf), 0);//阻塞
printf("子進(jìn)程[%d]寫入數(shù)據(jù):hello-%d\n", getpid(), i);
i--;
sleep(1);
}
// 刪除隊(duì)列
if(msgctl(msgid, IPC_RMID, NULL) == -1)
{
perror("msgctl failed");
exit(3);
}
printf("子進(jìn)程[%d]退出\n", getpid());
return 0;
}
else if(pid > 0)// 父進(jìn)程
{
sleep(3); //延時(shí)一會(huì),讓子進(jìn)程先運(yùn)行
printf("父進(jìn)程[%d]開始執(zhí)行, 獲取消息隊(duì)列,讀取數(shù)據(jù)\n", getpid());
int msgid = msgget(key, 0);
if(msgid == -1)
{
perror("msgget failed");
exit(1);
}
MSG_TYPE msg;
while(1)
{
memset(&msg, 0, sizeof(msg));
int res = msgrcv(msgid, &msg, sizeof(msg)-sizeof(long), 0, 0);//阻塞
printf("res=%d, 消息:%s, 類型:%ld\n", res, msg.buf, msg.mtype);
if(res == -1)
{
perror("msgrcv failed");
break;
}
}
// 刪除隊(duì)列
if(msgctl(msgid, IPC_RMID, NULL) == -1)
{
perror("msgctl failed");
exit(3);
}
printf("父進(jìn)程[%d]退出\n", getpid());
return 0;
}
else
{
printf("Error in fork\n");
exit(1);
}
return 0;
}
運(yùn)行結(jié)果:
??四、信號(hào)量(System V IPC)
?4.1 消息隊(duì)列(System V IPC)介紹
信號(hào)量的作用是為了同步多個(gè)進(jìn)程的操作。一般來(lái)說(shuō), 信號(hào)量是和某種預(yù)先定義的資源相關(guān)聯(lián)的。
信號(hào)量是一個(gè)計(jì)數(shù)器,控制訪問(wèn)共享資源的最大并行進(jìn)程總數(shù)??梢酝ㄟ^(guò)下面這個(gè)故事來(lái)了解信號(hào)量。
一套豪宅里有8個(gè)一模一樣的衛(wèi)生間和8把通用的鑰匙。最初有8把鑰匙放在鑰匙存放處。 當(dāng)同時(shí)使用衛(wèi)生間的人數(shù)小于或等于8時(shí), 大家都可以拿到一把鑰匙, 各自使用各自的衛(wèi)生間。 但是到第9個(gè)人和第10個(gè)人要使用衛(wèi)生間時(shí), 發(fā)現(xiàn)已經(jīng)沒(méi)有鑰匙了, 所以他們就不得不等待了。
使用最廣泛的信號(hào)量是二值信號(hào)量(binary semaphore), 對(duì)于這種信號(hào)量而言, 它只有兩種合法值: 0和1, 對(duì)應(yīng)一個(gè)可用的資源。 若當(dāng)前有資源可用, 則與之對(duì)應(yīng)的二值信號(hào)量的值為1; 若資源已被占用, 則與之對(duì)應(yīng)的二值信號(hào)量的值為0。 當(dāng)進(jìn)程申請(qǐng)資源時(shí), 如果當(dāng)前信號(hào)量的值為0, 那么進(jìn)程會(huì)陷入阻塞, 直到有其他進(jìn)程釋放資源, 將信號(hào)量的值加1才能被喚醒。
資源個(gè)數(shù)超過(guò)1個(gè)的信號(hào)量稱為計(jì)數(shù)信號(hào)量(counting semaphore),例如,有個(gè)8個(gè)資源,最大同時(shí)允許8個(gè)進(jìn)程使用。
信號(hào)量編程步驟:
- 1、生成 key,System V IPC的標(biāo)識(shí)ID都是通過(guò)key來(lái)獲取的,key的生成方式有三種。參考上一節(jié)消息隊(duì)列編程步驟;
- 2、使用
int semget(key_t key, int nsems, int semflg);
創(chuàng)建/獲取信號(hào)量集,返回值為信號(hào)量集標(biāo)識(shí)符。
第二個(gè)參數(shù)nsems表示信號(hào)量集中信號(hào)量的個(gè)數(shù)。如果并非創(chuàng)建信號(hào)量, 僅僅是訪問(wèn)已經(jīng)存在的信號(hào)量集, 可以將nsems指定為0。
semflg支持多種標(biāo)志位。 目前支持IPC_CREAT和IPC_EXCL標(biāo)志位- 3、設(shè)置信號(hào)量的初始值
int semctl(int semid, int semnum, int cmd,/* union semun arg*/);
- 4、正常使用,實(shí)現(xiàn)信號(hào)量的++ --的原子性
int semop(int semid, struct sembuf *sops, unsigned nsops);
- 5、semctl刪除消息信號(hào)量
semctl(semid, 0, IPC_RMID);
?4.2 例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
// 生成key
#define SEM_KEY 10086
int main()
{
// 2 創(chuàng)建子進(jìn)程
pid_t pid = fork();
if(pid == 0) {// 子進(jìn)程
printf("子進(jìn)程[%d]開始執(zhí)行, 創(chuàng)建信號(hào)量,使用資源\n", getpid());
// 創(chuàng)建信號(hào)量集
int semid = semget(SEM_KEY, 1, IPC_CREAT|0666);
if(semid == -1)
{
perror("semget failed");
exit(1);
}
// 設(shè)置第0個(gè)信號(hào)量的資源數(shù)量為1
if(semctl(semid, 0, SETVAL, 1) == -1)
{
perror("semctl setval failed");
exit(1);
}
// 使用資源,數(shù)量 -1
struct sembuf op;
op.sem_num = 0;//對(duì)下標(biāo)為0的信號(hào)量操作
op.sem_op = -1;//對(duì)信號(hào)量-1
op.sem_flg = 0;//無(wú)法完成時(shí)阻塞等待
semop(semid, &op, 1);
printf("子進(jìn)程[%d]訪問(wèn)共享資源\n", getpid());
sleep(20);
printf("子進(jìn)程[%d]完成共享資源的訪問(wèn)\n",getpid());
// 釋放資源,數(shù)量 +1
op.sem_op = 1;
semop(semid, &op, 1);
return 0;
}
else if(pid > 0)// 父進(jìn)程
{
sleep(3); //延時(shí)一會(huì),讓子進(jìn)程先運(yùn)行
printf("父進(jìn)程[%d]開始執(zhí)行, 獲取信號(hào)量,準(zhǔn)備使用資源\n", getpid());
int semid = semget(SEM_KEY, 0, 0);
if(semid == -1)
{
perror("semget failed");
exit(1);
}
// 使用資源,數(shù)量 -1
struct sembuf op;
op.sem_num = 0;//對(duì)下標(biāo)為0的信號(hào)量操作
op.sem_op = -1;//對(duì)信號(hào)量-1
op.sem_flg = 0;//無(wú)法完成時(shí)阻塞等待
semop(semid, &op, 1);
printf("父進(jìn)程[%d]訪問(wèn)共享資源\n", getpid());
sleep(3);
printf("父進(jìn)程[%d]完成共享資源的訪問(wèn)\n",getpid());
// 釋放資源,數(shù)量 +1
op.sem_op = 1;
semop(semid, &op, 1);
// 刪除信號(hào)量
if(semctl(semid, 0, IPC_RMID) == -1)
{
perror("semctl failed");
exit(3);
}
printf("父進(jìn)程[%d]退出\n", getpid());
return 0;
}
else
{
printf("Error in fork\n");
exit(1);
}
return 0;
}
??五、共享內(nèi)存(System V IPC)
?5.1 共享內(nèi)存(System V IPC)介紹
共享內(nèi)存是所有IPC手段中最快的一種。 它之所以快是因?yàn)楣蚕韮?nèi)存一旦映射到進(jìn)程的地址空間,進(jìn)程之間數(shù)據(jù)的傳遞就不須要涉及內(nèi)核了。
建立共享內(nèi)存之后, 進(jìn)程從此就像操作普通進(jìn)程的地址空間一樣操作這塊共享內(nèi)存, 一個(gè)進(jìn)程可以將信息寫入這片內(nèi)存區(qū)域, 而另一個(gè)進(jìn)程也可以看到共享內(nèi)存里面的信息, 從而達(dá)到通信的目的。
允許多個(gè)進(jìn)程同時(shí)操作共享內(nèi)存, 就不得不防范競(jìng)爭(zhēng)條件的出現(xiàn)。因此, 共享內(nèi)存這種進(jìn)程間通信的手段通常不會(huì)單獨(dú)出現(xiàn), 總是和信號(hào)量、 文件鎖等同步的手段配合使用。
信號(hào)量編程步驟:
- 1、生成 key,System V IPC的標(biāo)識(shí)ID都是通過(guò)key來(lái)獲取的,key的生成方式有三種。參考上一節(jié)消息隊(duì)列編程步驟;
- 2、使用
int shmget(key_t key, size_t size, int shmflg);
創(chuàng)建/獲取共享內(nèi)存段,返回值為共享內(nèi)存的標(biāo)識(shí)符。
其中第二個(gè)參數(shù)size必須是正整數(shù), 表示要?jiǎng)?chuàng)建的共享內(nèi)存的大小。
第三個(gè)參數(shù)支持IPC_CREAT和IPC_EXCL標(biāo)志位。 如果沒(méi)有設(shè)置IPC_CREAT標(biāo)志位, 那么第二個(gè)參數(shù)size對(duì)共享內(nèi)存段并無(wú)實(shí)際意義, 但是必須小于或等于共享內(nèi)存的大小, 否則會(huì)有EINVAL錯(cuò)誤。- 3、映射共享內(nèi)存,得到虛擬地址。
void *shmat(int shmid, const void *shmaddr, int shmflg);
。
其中, 第二個(gè)參數(shù)是用來(lái)指定將共享內(nèi)存放到虛擬地址空間的什么位置的。 大部分的普通青年都會(huì)將第二個(gè)參數(shù)設(shè)置為NULL, 表示用戶并不在意, 一切交由內(nèi)核做主。
shmat如果調(diào)用成功, 則返回進(jìn)程虛擬地址空間內(nèi)的一個(gè)地址。就可以像使用malloc分配的空間一樣使用共享內(nèi)存。- 4、讀寫共享內(nèi)存數(shù)據(jù)。
- 5、解除映射。
int shmdt(const void *shmaddr);
。- 6、銷毀共享內(nèi)存。
shmctl(shmid, IPC_RMID, NULL) ;
?5.2 例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
// 生成key
#define SHM_KEY 10010
int main()
{
// 2 創(chuàng)建子進(jìn)程
pid_t pid = fork();
if(pid == 0) {// 子進(jìn)程
printf("子進(jìn)程[%d]開始執(zhí)行, 創(chuàng)建共享內(nèi)存段,使用創(chuàng)建共享內(nèi)存\n", getpid());
// 2.1 創(chuàng)建共享內(nèi)存段
int shmid = shmget(SHM_KEY, 8, IPC_CREAT|0666);
if(shmid == -1)
{
perror("semget failed");
exit(1);
}
// 2.2 映射共享內(nèi)存,得到虛擬地址
void *p = shmat(shmid, 0, 0);
if((void *)-1 == p)
{
perror("shmat failed");
exit(2);
}
// 2.3 讀寫共享內(nèi)存
int *pi = p;
*pi = 0xaaaaaaaa;
*(pi+1) = 0x55555555;
printf("子進(jìn)程[%d]寫入%x, %x\n", getpid(), *pi, *(pi+1));
// 2.4 解除映射
if(shmdt(p) == -1)
{
perror("shmdt failed");
exit(3);
}
printf("子進(jìn)程[%d]解除映射, 結(jié)束進(jìn)程\n\n", getpid());
return 0;
}
else if(pid > 0)// 父進(jìn)程
{
sleep(3); //延時(shí)一會(huì),讓子進(jìn)程先運(yùn)行
printf("父進(jìn)程[%d]開始執(zhí)行, 獲取共享內(nèi)存段,準(zhǔn)備使用資源\n", getpid());
// 3.1 獲取共享內(nèi)存段
int shmid = shmget(SHM_KEY, 0, 0);
if(shmid == -1)
{
perror("shmget failed");
exit(1);
}
// 3.2 映射共享內(nèi)存,得到虛擬地址
void *p = shmat(shmid, 0, 0);
if((void *)-1 == p)
{
perror("shmat failed");
exit(2);
}
// 3.3 讀寫共享內(nèi)存
int x = *((int *)p);
int y = *((int *)p + 1);
printf("父進(jìn)程[%d]讀取數(shù)據(jù):x=%#x y=%#x\n",getpid(), x, y);
// 3.4 解除映射
if(shmdt(p) == -1)
{
perror("shmdt failed");
exit(3);
}
printf("父進(jìn)程[%d]解除映射\n", getpid());
// 3.5 銷毀共享內(nèi)存
if(shmctl(shmid, IPC_RMID, NULL) == -1)
{
perror("shmctl");
exit(4);
}
printf("父進(jìn)程[%d]銷毀共享內(nèi)存, 結(jié)束進(jìn)程\n", getpid());
return 0;
}
else
{
printf("Error in fork\n");
exit(1);
}
return 0;
}
允許結(jié)果:
??六、總結(jié)
Linux 進(jìn)程間通信有10種方式,本文先介紹了5種:無(wú)名管道、命名管道、XSI消息隊(duì)列、XSI信號(hào)量、XSI共享內(nèi)存,下篇文章將會(huì)介紹剩下的5個(gè)方式:POSIX消息隊(duì)列、POSIX信號(hào)量、POSIX共享內(nèi)存、信號(hào)、網(wǎng)絡(luò)通信。
Linux 進(jìn)程間通信的10種方式(2)文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-824934.html
如果文章有幫助的話,點(diǎn)贊??、收藏?,支持一波,謝謝 ??????文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-824934.html
到了這里,關(guān)于【Linux C | 進(jìn)程】Linux 進(jìn)程間通信的10種方式(1)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!