进程间通信方式

进程间通信方式

  • 无名管道
  • 命名管道(FIFO)
  • 消息队列
  • 共享内存
  • 信号
  • 信号量

一,无名管道

1.特点

  1. 它是半双工的(即数据只能在一个方向流动),具有固定的读端和写端
  2. 它只能用于具有亲缘关系进程通信(也是父子进程或兄弟进程)
  3. 它可以看成一种特殊的文件,对于它的读写也可以使用普通的read,write等函数
  4. 它不属于普通文件,并不属于其他任何文件系统,并且仅存在内存中

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.特点

  1. FIFO可以无关进程之间进行通信
  2. 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.存在形式,特点

  1. 消息存在于Linux内核中,以链表的形式存在,一个队列由标识符(ID)来标识
  2. 队列每一个节点由结构体构成
  3. 队列中的数据由Linux内核进行管理,与进程的状态无关
  4. 两个进程间进行通信,使用相同的队列
  5. 消息队列可以随机查询,不用遵循先进先出的顺序,也可以按照消息的类型查询
  6. 无关联两个进程间通信,双工通信(相互)

2.队列的创建

基本步骤:获取或创建队列—发送或接收—队列的删除(空间的释放)

  1. 消息队列的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,移除队列

  1. 两进程相互通信
//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.存在形式及特点

  1. 无关联两个进程间通信,双工通信(相互)
  2. 两个进程建立一个公共的内存,两个进程都可以直接访问该内存地址
  3. 访问效率高

2.创建共享内存

基本步骤:创建/打开共享内存—映射—数据操作—断开连接—释放空间

  1. 相应的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,移除队列

  1. 两进程间的通信
//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.概述

  1. 实际信号就是软中断,许多重要程序都需要处理信号。信号为Linux提供了一种异步处理事务的方法。
  2. 信号在系统中有对应的名字和编号可kill -l来查看

2.信号处理

  1. 忽略:大多数信号可以使用这种方式来处理,但有两种信号不能忽略(SIGKILL和SIGSTOP),handler用SIG_IGN代替
  2. 捕捉:为信号定义函数并告诉内核,当信号产生时就会执行该函数
  3. 系统默认动作:对于每个信号来说,系统都会存在默认的动作,当信号产生时,就会自动执行该动作

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.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值