進程間通信(IPC):
進程間通信的方式有很多,這里主要講到進程間通信的六種方式,分別為:管道、FIFO、消息隊列、共享內存、信號、信號量。
一、管道
管道的特點:
- 是一種半雙工的通信方式;
- 只能在具有親緣關系的進程間使用.進程的親緣關系一般指的是父子關系;
- 它可以看成是一種特殊的文件,對于它的讀寫也可以使用普通的read、write等函數。但是它不是普通的文件,并不屬于其他任何文件系統,并且只存在于內存中。
管道的原型:
int pipe(int pipefd[2]);
代碼實現:
#include
#include
#include
/*使用匿名管道實現進程間通信*/
int main()
{
int fd[2];//fd[0]為讀端 fd[1]為寫端
pid_t pid;
char buf[128];
//int pipe(int pipefd[2]);
if(pipe(fd) == -1)//創建管道
{
printf("管道創建失敗n");
perror("why");
}
pid = fork();
if(pid < 0 )
{
printf("子進程開辟失敗n");
perror("why");
}else if(pid > 0){
sleep(3);//讓子進程先執行
printf("這是一個父進程n");//父進程完成寫操作
close(fd[0]);
write(fd[1],"hello from father",strlen("hello from father"));
}else{
printf("這是一個子進程n");//子進程完成讀操作
close(fd[1]);
read(fd[0],buf,sizeof(buf));//沒有數據來時,阻塞在這
printf("buf = %sn",buf);
}
return 0;
}
二、FIFO
FIFO,也叫做命名管道,它是一種文件類型。
FIFO的特點:
- FIFO可以在無關的進程之間交換數據,與無名管道不同;
- FIFO有路徑名與之相關聯,它以一種特殊設備文件形式存在于文件系統中。
FIFO的原型:
#include
int mkfifo(const char *pathname, mode_t mode);
其中的 mode 參數與 open 函數中的 mode 相同。一旦創建了一個 FIFO,就可以用一般的文件 I/O 函數操作它。
當 open 一個 FIFO 時,是否設置非阻塞標志(O_NONBLOCK)的區別:
- 若沒有指定 O_NONBLOCK(默認),只讀 open 要阻塞到某個其他進程為寫而打開此 FIFO。類似的,只寫 open 要阻塞到某個其他進程為讀而打開它。
- 若指定了 O_NONBLOCK,則只讀 open 立即返回。而只寫 open 將出錯返回 -1 如果沒有進程已經為讀而打開該 FIFO,其 errno 置 ENXIO。
代碼實現:
下列代碼有效解決了,當管道存在時,程序報錯的問題,減少了無關錯誤信息的打印。
#include
#include
#include
// int mkfifo(const char *pathname, mode_t mode);
int main()
{
if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)
{
printf("mkfifo failedn");
perror("why");
}
return 0;
}
read.c
#include
#include
#include
#include
#include
// int mkfifo(const char *pathname, mode_t mode);
int main()
{
int nread;
char buf[30] = {'?'};
if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)//創建命名管道
{
printf("mkfifo failedn");
perror("why");
}
int fd = open("./myfifo",O_RDONLY);//以只讀的形式打開管道,程序阻塞在這,直到有另一個進程對其執行寫操作
if(fd < 0)
{
printf("read open failedn");
}else
{
printf("read open successnn");
}
while(1)
{
nread = read(fd,buf,sizeof(buf));
printf("read %d byte,context is:%sn",nread,buf);
}
close(fd);
return 0;
}
write.c
#include
#include
#include
#include
#include
#include
#include
// int mkfifo(const char *pathname, mode_t mode);
int main()
{
int nread;
char buf[30] = "message from myfifo";
if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)//創建命名管道
{
printf("mkfifo failedn");
perror("why");
}
int fd = open("./myfifo",O_WRONLY);//打開管道,程序阻塞在這,直到其他進程為讀而打開它
if(fd < 0)
{
printf("write open failedn");
}
else
{
printf("write open successn");
}
while(1)
{
sleep(1);
write(fd,buf,strlen(buf));
}
close(fd);
return 0;
}
三、消息隊列
消息隊列,是消息的鏈接表,存放在內核之中。一個消息隊列由一個標識符(即隊列ID)來標識。
用戶進程可以向消息隊列添加消息,也可以向消息隊列讀取消息。
消息隊列的特點:
- 消息隊列是面向記錄的,其中的消息具有特定的格式以及特定的優先級;
- 消息隊列是獨立于發送和接收進程的,進程終止時,消息隊列及其內容并不會被刪除;
- 消息隊列可以實現消息的隨機查詢,消息不一定要以先進先出的次序讀取,也可以按消息的類型讀取。
消息隊列函數的原型:
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失敗返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 讀取消息:成功返回消息數據的長度,失敗返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息隊列:成功返回0,失敗返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
代碼演示:
msgSend.c
#include
#include
#include
#include
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf sendbuf={888,"message from send"};
struct msgbuf readbuf;
key_t key;
if((key = ftok(".",'z')) < 0){
printf("ftok errorn");
}
int msgId = msgget(key,IPC_CREAT|0777);
if(msgId == -1){
printf("get quen failedn");
}
msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
printf("send overn");
msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),999,0);
printf("read from get is:%sn",readbuf.mtext);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
msgGet.c
#include
#include
#include
#include
#include
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf readbuf;
memset(readbuf.mtext, '?', sizeof(readbuf.mtext));
struct msgbuf sendbuf={999,"thank for your reach"};
key_t key;
//獲取key值
if((key = ftok(".",'z')) < 0){
printf("ftok errorn");
}
int msgId = msgget(key,IPC_CREAT|0777);
if(msgId == -1){
printf("get quen failedn");
perror("why");
}
msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0);
printf("read from send is:%sn",readbuf.mtext);
msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
四、共享內存
共享內存,指兩個或多個進程共享一個給定的存儲區。
ipcs -m 查看系統下已有的共享內存;ipcrm -m shmid可以用來刪除共享內存。
共享內存的特點:
- 共享內存是最快的一種 IPC,因為進程是直接對內存進行存取。
- 因為多個進程可以同時操作,所以需要進行同步。
- 信號量 + 共享內存通常結合在一起使用,信號量用來同步對共享內存的訪問。
共享內存函數的原型:
int shmget(key_t key, size_t size, int flag);
// 連接共享內存到當前進程的地址空間:成功返回指向共享內存的指針,失敗返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 斷開與共享內存的連接:成功返回0,失敗返回-1
int shmdt(void *addr);
// 控制共享內存的相關信息:成功返回0,失敗返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
代碼演示:
shmw.c
#include
#include
#include
#include
#include
#include
// int shmget(key_t key, size_t size, int shmflg);
// void *shmat(int shmid, const void *shmaddr, int shmflg);
// int shmdt(const void *shmaddr);
int main()
{
int shmId;
key_t key;
char *shmaddr;
if((key = ftok(".",1)) < 0){
printf("ftok errorn");
}
shmId = shmget(key, 1024*4, IPC_CREAT|0666);//內存大小必須得是MB的整數倍
if(shmId == -1){
printf("shmget errorn");
exit(-1);
}
/*第二個參數一般寫0,讓linux內核自動分配空間,第三個參數也一般寫0,表示可讀可寫*/
shmaddr = shmat(shmId, 0, 0);
printf("shmat OKn");
strcpy(shmaddr,"I am so cool");
sleep(5);//等待5秒,讓別的進程去讀
shmdt(shmaddr);
shmctl(shmId, IPC_RMID, 0);//寫0表示不關心
printf("quitn");
return 0;
}
shmr.c
#include
#include
#include
#include
// int shmget(key_t key, size_t size, int shmflg);
// void *shmat(int shmid, const void *shmaddr, int shmflg);
// int shmdt(const void *shmaddr);
int main()
{
int shmId;
key_t key;
char *shmaddr;
if((key = ftok(".",1)) < 0){
printf("ftok errorn");
}
shmId = shmget(key, 1024*4, 0);//內存大小必須得是MB的整數倍
if(shmId == -1){
printf("shmget errorn");
exit(-1);
}
/*第二個參數一般寫0,讓linux內核自動分配空間,第三個參數也一般寫0,表示可讀可寫*/
shmaddr = shmat(shmId, 0, 0);
printf("shmat OKn");
printf("data : %sn",shmaddr);
shmdt(shmaddr);
return 0;
}
五、信號
對于 Linux來說,實際信號是軟中斷,許多重要的程序都需要處理信號。終端用戶輸入了 ctrl+c 來中斷程序,會通過信號機制停止一個程序。
信號的相關概述:
1、信號的名字和編號:
每個信號都有一個名字和編號,這些名字都以“SIG”開頭。我們可以通過kill -l來查看信號的名字以及序號。
不存在0信號,kill對于0信號有特殊的應用。
2、信號的處理:
信號的處理有三種方法,分別是:忽略、捕捉和默認動作。
- 忽略信號,大多數信號可以使用這個方式來處理,但是有兩種信號不能被忽略(分別是 SIGKILL和SIGSTOP);
- 捕捉信號,需要告訴內核,用戶希望如何處理某一種信號,說白了就是寫一個信號處理函數,然后將這個函數告訴內核。當該信號產生時,由內核來調用用戶自定義的函數,以此來實現某種信號的處理。
- 系統默認動作,對于每個信號來說,系統都對應由默認的處理動作,當發生了該信號,系統會自動執行。具體的信號默認動作可以使用man 7 signal來查看系統的具體定義。
信號處理函數的注冊:
- 入門版:函數signal
- 高級版:函數sigaction
信號處理發送函數:
- 入門版:kill
- 高級版:sigqueue
入門版:
函數原型:
sighandler_t signal(int signum, sighandler_t handler);
//發送函數
int kill(pid_t pid, int sig);
接收端:
#include
#include
// typedef void (*sighandler_t)(int);
// sighandler_t signal(int signum, sighandler_t handler);
/*接受到信號后,讓信號處理該函數*/
void handler(int signum)
{
printf("signum = %dn",signum);
switch(signum){
case 2:
printf("SIGINTn");
break;
case 9:
printf("SIGKILLn");
break;
case 10:
printf("SIGUSR1n");
break;
}
}
int main()
{
signal(SIGINT,handler);
signal(SIGKILL,handler);
signal(SIGUSR1,handler);
while(1);
return 0;
}
發送端:
#include
#include
#include
#include
// int kill(pid_t pid, int sig);
int main(int argc,char **argv)
{
int signum;
int pid;
signum = atoi(argv[1]);//將字符型轉為整型
pid = atoi(argv[2]);
kill(pid,signum);
printf("signum = %d,pid = %dn",signum,pid);
return 0;
}
高級版:
函數原型:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); //信號處理程序,不接受額外數據,SIG_IGN 為忽略,SIG_DFL 為默認動作
void (*sa_sigaction)(int, siginfo_t *, void *); //信號處理程序,能夠接受額外數據和sigqueue配合使用
sigset_t sa_mask;//阻塞關鍵字的信號集,可以再調用捕捉函數之前,把信號添加到信號阻塞字,信號捕捉函數返回之前恢復為原先的值。
int sa_flags;//影響信號的行為SA_SIGINFO表示能夠接受數據
};
//回調函數句柄sa_handler、sa_sigaction只能任選其一
我們只需要配置 sa_sigaction以及sa_flags即可。
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
int si_band; /* Band event */
int si_fd; /* File descriptor */
}
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};
接收端:
#include
#include
#include
#include
// int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
//(*sa_sigaction)(int, siginfo_t *, void *);
void handler(int signum, siginfo_t *info, void *context)
{
printf("get signum is:%dn",signum);
if(context != NULL)
{
printf("get data = %dn",info->si_int);
printf("get data = %dn",info->si_value.sival_int);
printf("get pid is = %dn",info->si_pid);
}
}
int main()
{
struct sigaction act;
printf("pid = %dn",getpid());
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1,&act,NULL);
while(1);
return 0;
}
發送端:
#include
#include
#include
#include
// int sigqueue(pid_t pid, int sig, const union sigval value);
int main(int argc,char **argv)
{
int signum;
int pid;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
union sigval value;
value.sival_int = 100;
sigqueue(pid,signum,value);
printf("pid = %d,donen",getpid());
return 0;
}
注意:信號發送字符串,只有在父子進程或者是共享內存下才可發送。
六、信號量
信號量與已經介紹過的 IPC 結構不同,它是一個計數器。信號量用于實現進程間的互斥與同步,而不是用于存儲進程間通信數據。
信號量的特點:
- 信號量用于進程間同步,若要在進程間傳遞數據需要結合共享內存。
- 信號量基于操作系統的 PV 操作,程序對信號量的操作都是原子操作。
- 每次對信號量的 PV 操作不僅限于對信號量值加 1 或減 1,而且可以加減任意正整數。
- 支持信號量組
信號量的函數原型:
int semget(key_t key, int num_sems, int sem_flags);
// 對信號量組進行操作,改變信號量的值:成功返回0,失敗返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信號量的相關信息
int semctl(int semid, int sem_num, int cmd, ...);
當 semget 創建新的信號量集合時,必須指定集合中信號量的個數(即 num_sems),通常為 1; 如果是引用一個現有的集合,則將 num_sems 指定為 0 。
在 semop 函數中,sembuf 結構的定義如下:
{
short sem_num; // 信號量組中對應的序號,0~sem_nums-1
short sem_op; // 信號量值在一次操作中的改變量
short sem_flg; // IPC_NOWAIT, SEM_UNDO
}
在 semctl 函數中的命令有多種,這里就說兩個常用的:
- SETVAL:用于初始化信號量為一個已知的值。
- IPC_RMID:刪除一個信號量集合。如果不刪除信號量,它將繼續在系統中存在,即使程序已經退出,它可能在你下次運行此程序時引發問題,而且信號量是一種有限的資源。
代碼演示:
#include
#include
#include
#include
// int semget(key_t key, int nsems, int semflg);
// int semctl(int semid, int semnum, int cmd, ...);
// int semop(int semid, struct sembuf *sops, size_t nsops);
union semun{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
//P操作,拿鑰匙
void PGetKey(int semid)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = -1;
sops.sem_flg = SEM_UNDO;
semop(semid, &sops, 1);
}
//V操作,放回鑰匙
void VPutBackKey(int semid)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = 1;
sops.sem_flg = SEM_UNDO;
semop(semid, &sops, 1);
}
int main()
{
key_t key;
int semid;
if((key == ftok(".",6)) < 0)
{
printf("ftok errorn");
}
semid = semget(key , 1, IPC_CREAT|0666);//創造鑰匙,數量為1
union semun sem;
sem.val = 0;//初始狀態為沒有鑰匙
semctl(semid, 0, SETVAL, sem);//SETVAL初始化信號量為一個已知的值,這時就需要第四個參數
//0表示操作第一把鑰匙
int pid = fork();
if(pid < 0)
{
printf("fork failedn");
}else if(pid == 0)
{
printf("this is childn");
VPutBackKey(semid);//首先把鑰匙放回
}else
{
PGetKey(semid);//等子進程將鑰匙放回后才會進行操作,保證子進程先執行
printf("this is fathern");
VPutBackKey(semid);
semctl(semid, 0, IPC_RMID);//銷毀鑰匙
}
return 0;
}
七、進程間通信方式總結:
- 管道:速度慢,容量有限,只有父子進程能通訊;
- FIFO:任何進程間都能通訊,但速度慢;
- 消息隊列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完數據的問題;
- 共享內存:能夠很容易控制容量,速度快,但要保持同步,比如一個進程在寫的時候,另一個進程要注意讀寫的問題;
- 信號:有入門版和高級版兩種,區別在于入門版注重動作,高級版可以傳遞消息。只有在父子進程或者是共享內存中,才可以發送字符串消息;
- 信號量:不能傳遞復雜消息,只能用來同步。用于實現進程間的互斥與同步,而不是用于存儲進程間通信數據。
-
通信
+關注
關注
18文章
6166瀏覽量
137325 -
內存
+關注
關注
8文章
3105瀏覽量
74957 -
函數
+關注
關注
3文章
4367瀏覽量
64076 -
進程
+關注
關注
0文章
206瀏覽量
14208
發布評論請先 登錄
進程間通信之Linux下進程間通信概述
Linux進程間通信方式——管道

評論