进程间通信方式
- 无名管道
- 命名管道(FIFO)
- 消息队列
- 共享内存
- 信号
- 信号量
一,无名管道
1.特点
- 它是半双工的(即数据只能在一个方向流动),具有固定的读端和写端
- 它只能用于具有亲缘关系进程通信(也是父子进程或兄弟进程)
- 它可以看成一种特殊的文件,对于它的读写也可以使用普通的read,write等函数
- 它不属于普通文件,并不属于其他任何文件系统,并且仅存在内存中
2.原型
#include <unistd.h>
int pipe(int fd[2]);//成功返回0,失败返回-1
int read(fd[0],r_buf,size);
int write(fd[1],w_buf,size);
当一个管道被创建时,它会返回两个文件描述符,f[0]为都打开,f[1]为写打开
要关闭管道,只需关闭两个文件描述符即可
使用
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int pid;
int fd[2];
char r_buf[128];
char w_buf[128] = "hello world !";
if(pipe(fd) == -1){
printf("create pipe error\n");
return 1;
}
pid = fork();
if(pid < 0){
printf("create child error\n");
return 2;
}
else if(pid > 0){
printf("this is father\n");
close(fd[0]);
write(fd[1],w_buf,strlen(w_buf));
}
else{
printf("this is child\n");
close(fd[1]);
read(fd[0],r_buf,128);
printf("read from father = %s\n",r_buf);
}
close(fd[0]);
close(fd[1]);
return 0;
}
二,命名管道
1.特点
- FIFO可以无关进程之间进行通信
- FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中
2.原型
#include <sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);//成功返回0,失败返回-1
其中mode参数与open函数中的mode相同,一旦创建一个FIFO,就可以以一般文件io对其进行操作
当一个进程以O_RDONLY打开FIFO时,一般默认为阻塞状态,直到另外一个进程以O_WRONLY打开,才会往下执行
使用加粗样式
//read
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
char r_buf[128];
int fd;
if(mkfifo("./file",0600) == -1){
printf("create fifo error\n");
perror("why");
return 1;
}
fd = open("./file",O_RDONLY);
printf("open success\n");
read(fd,r_buf,128);
printf("%s\n",r_buf);
close(fd);
return 0;
}
//write
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd;
char *w_buf = "hello world !";
fd = open("./file",O_WRONLY);
printf("open success\n");
write(fd,w_buf,strlen(w_buf));
close(fd);
return 0;
}
三,消息队列
1.存在形式,特点
- 消息存在于Linux内核中,以链表的形式存在,一个队列由标识符(ID)来标识
- 队列每一个节点由结构体构成
- 队列中的数据由Linux内核进行管理,与进程的状态无关
- 两个进程间进行通信,使用相同的队列
- 消息队列可以随机查询,不用遵循先进先出的顺序,也可以按照消息的类型查询
- 无关联两个进程间通信,双工通信(相互)
2.队列的创建
基本步骤:获取或创建队列—发送或接收—队列的删除(空间的释放)
- 消息队列的API
#include <sys/msg.h>
//创建或打开消息队列,成功返回队列ID,失败返回-1
int msgget(key_t key,int flag);//key寻找内核中某个队列的索引值,flag打开队列的方式
//发送消息,成功返回0,失败返回-1
int msgsnd(int msgid,const void *ptr,size_t size,int flag);//msgid队列ID,*ptr消息内容,size消息大小
//读取消息,成功返回消息长度,失败返回-1
int msgrcv(int msgid,void *ptr,size_t size,long type,int flag);//*ptr存放读取消息的缓冲区,type要读取消息的类型
//控制消息队列,成功返回0,失败返回-1
int msgctl(int msgid,int cmd,struct msgid_ds *buf);
flag:IPC_CREAT|权限,不存在队列就创建队列
cmd:IPC_RMID,移除队列
- 两进程相互通信
//msgGet.c
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct msgTxt{
long type;
char txt[128];
};
int main()
{
key_t key = ftok(".",12);
struct msgTxt msgtxt;
struct msgTxt msgtxt2 = {555,"recive success\n"};
printf("key = %x\n",key);
int msgId = msgget(key,IPC_CREAT|0777);
if(msgId == -1){
printf("get que failed\n");
}
msgrcv(msgId,&msgtxt,sizeof(msgtxt.txt),888,0);
printf("%s\n",msgtxt.txt);
msgsnd(msgId,&msgtxt2,strlen(msgtxt2.txt),0);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
//msgSend.c
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct msgTxt{
long type;
char txt[128];
};
int main()
{
key_t key = ftok(".",12);
struct msgTxt msgtxt = {888,"message from send\n"};
struct msgTxt msgtxt2;
printf("key = %x\n",key);
int msgId = msgget(key,IPC_CREAT|0777);
if(msgId == -1){
printf("get que failed");
}
msgsnd(msgId,&msgtxt,strlen(msgtxt.txt),0);
msgrcv(msgId,&msgtxt2,sizeof(msgtxt2.txt),555,0);
printf("%s\n",msgtxt2.txt);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
ftok函数一般用于生成队列索引,其用法如下:
key_t ftok(const char *frame,int id);
frame一般写程序所在的当前目录,id可以随机定义一个整型数。
注:为防止进程中产生大量的队列占用Linux内核的内存,一般都在程序最后使用msgctl函数来对程序所产生的队列进行移除。
四,共享内存
1.存在形式及特点
- 无关联两个进程间通信,双工通信(相互)
- 两个进程建立一个公共的内存,两个进程都可以直接访问该内存地址
- 访问效率高
2.创建共享内存
基本步骤:创建/打开共享内存—映射—数据操作—断开连接—释放空间
- 相应的API
#include <sys/shm.h>
//创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
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);
shmget:创建共享内存大小以MB对齐
shmat:addr写0默认Linux内核默认安排内存
flag写0默认共享内存可读可写
flag:IPC_CREAT|权限,不存在队列就创建队列
cmd:IPC_RMID,移除队列
- 两进程间的通信
//shm_w.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>
#include <unistd.h>
int main()
{
key_t key = ftok(".",12);
int shmid;
char *shmaddr;
shmid = shmget(key,1024*4,IPC_CREAT|0666);
if(shmid == -1){
printf("creat shm failed\n");
exit(-1);
}
shmaddr = shmat(shmid,0,0);
strcpy(shmaddr,"message from w");
sleep(5);
shmdt(shmaddr);
shmctl(shmid,IPC_RMID,0);
return 0;
}
//shm_r.c
#include <stdio.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <stdlib.h>
int main()
{
key_t key = ftok(".",12);
int shmid;
char *shmaddr;
shmid = shmget(key,1024*4,0);
if(shmid == -1){
printf("creat shm failed\n");
exit(-1);
}
shmaddr = shmat(shmid,0,0);
printf("%s\n",shmaddr);
shmdt(shmadd);
return 0;
}
可通过ipcs -m
来查看当前存在的共享内存,ipcrm -m shmid
来删除共享内存
五,信号
1.概述
- 实际信号就是软中断,许多重要程序都需要处理信号。信号为Linux提供了一种异步处理事务的方法。
- 信号在系统中有对应的名字和编号可
kill -l
来查看
2.信号处理
- 忽略:大多数信号可以使用这种方式来处理,但有两种信号不能忽略(SIGKILL和SIGSTOP),handler用SIG_IGN代替
- 捕捉:为信号定义函数并告诉内核,当信号产生时就会执行该函数
- 系统默认动作:对于每个信号来说,系统都会存在默认的动作,当信号产生时,就会自动执行该动作
3.信号编程
1.对应使用的API
#include <signal.h>
#include <sys/types.h>
//绑定操作函数
typedef void (*sighandler_t)(int);//定义一个函数类型
sighandler_t signal(int signum,sighandler_t handler);//signum信号编号,handler 当信号产生会执行的函数(该参数为SIG_IGN时忽略该信号)
//程序发送信号
int kill(pid_t pid,int sig);
2.API的编程用法
//signal用法
#include <stdio.h>
#include <signal.h>
void handler(int signum){
printf("%dnever quit!\n",signum);
}
int main()
{
signal(SIGINT,handler);//ctl+c
while(1);
return 0;
}
//kill用法
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc,char **argv)
{
int pid;
int signum;
pid = atoi(argv[1]);
signum = atoi(argv[2]);
kill(pid,signum);
return 0;
}
3 .信号携带信息
发送方
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
int pid;
int signum;
pid = atoi(argv[1]);
signum = atoi(argv[2]);
union sigval value;
value.sival_int = 'A';
sigqueue(pid,signum,value);
printf("%d\n",getpid());
return 0;
}
接收方
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
#include <stdio.h>
#include <signal.h>
void hangler(int signum,siginfo_t info,void *context){
printf("get xinhao %d\n",signum);
if(context != NULL){
printf("send id %d\n",info->si_pid);
printf("%c\n",info->si_int);
}
}
int main()
{
struct sigaction act;
act.sa_sigaction = hangler;
act.sa_flags = SA_SIGINFO;
sigaction(10,&act,NULL);
while(1);
return 0;
}
六,信号量
信号量与其他IPC结构不一样,它是一个计数器,信号量用于实现进程间互斥与同步,而不是储存进程间通信数据
1.特点
- 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存
- 信号量相当于进入房间的一把钥匙,而房间相当于临界资源(临界资源仅提供单个进程在线访问)
- PV操作:P获取信号量,V放回信号量
- 信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作
- 每次对信号量的PV操作不仅限于对信号量的加1减1,还可以加减任意正整数
- 基于信号量组
2.相应的API
#include <sys/sem.h>
//创建或获取一个信号量组:成功返回ID,失败返回-1
int semget(key_t key,int num_sems,int sem_flags);//num_sems:信号量组信号量的个数,sem_flags:IPC_CREAT|权限
//对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid,struct sembuf semoparray[],size_t numops);//numops信号量个数
//控制信号量的相关信息
int semctl(int semid,int sem_num int cmd, union semun);//sem_num:第几个信号量,cmd = SETVAL:赋初值,cmd = IPC_RMID:删除该信号量
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
}
创建信号量并初始化
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int main()
{
key_t key;
int semid;
key = ftok(".",2);
semid = semget(key,1,IPC_CREAT|0666);
union semun initsem;
initsem.val = 1;
semctl(semid,0,SETVAL,initsem);
return 0;
}
PV操作函数
void P(int id){
struct sembuf set;
set.sem_num = 0;//信号量编号
set.sem_op = -1
set.sem_flg = SEM_UNDO//等待
semop(id,&set,1);
}
void V(int id){
struct sembuf set;
set.sem_num = 0;
set.sem_op = +1;
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
}
父子进程结合信号量的应用
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <sys/ipc.h>
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct semid *_buf;
};
void P(int id){
struct sembuf set;
set.sem_num = 0;
set.sem_op = -1;
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
printf("getKey\n");
}
void V(int id){
struct sembuf set;
set.sem_num = 0;
set.sem_op = +1;
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
printf("putKey\n");
}
int main()
{
key_t key;
int semid;
key = ftok(".",2);
semid = semget(key,1,IPC_CREAT|0666);
union semun initsem;
initsem.val = 0;
semctl(semid,0,SETVAL,initsem);
pid_t pid = fork();
if(pid < 0){
printf("create process error\n");
exit(-1);
}
else if(pid > 0){
P(semid);
printf("this is father\n")
V(semid);
}
else{
printf("this is son\n");
V(semid);
}
semctl(semid,0,IPC_RMID,initsem);
return 0;
}
五种通信方式总结
1.管道:速度慢,容量有限,只有父子进程能通讯
2.FIFO:任何进程间都能通讯,但速度慢
3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
4.信号量:不能传递复杂消息,只能用来同步
5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存