01 学习目标
1.熟练使用pipe进行父子进程间通信
2.熟练使用pipe进行兄弟进程间通信
3.熟练使用fifo进行无血缘关系的进程间通信
4.熟练掌握mmap函数的使用
5.掌握mmap创建匿名映射区的方法
6.使用mmap进行有血缘关系的进程间通信
7.使用mmap进行无血缘关系的进程间通信
02 IPC概念(InterProcess Communication)
IPC:InterProcess Communication 进程间通信,通过内核提供的缓冲区进行数据交换的机制。
 
 IPC通信的方式有几种:
- pipe 管道 --最简单
- fifo 有名管道
- mmap 文件映射共享IO --速度最快
- 本地socket --最稳定
- 信号 --携带信息量最小
- 共享内存
- 消息队列
03 pipe管道
1.管道的概念
pipe通信
常见的通信方式:单工(广播),半双工(对讲机),全双工(打电话)
管道:半双工通信
半双工通信:同一时刻只能读或者写。
 
2.管道通信举例
管道函数:
int pipe(int pipefd[2])
- pipefd 读写文件描述符,0 代表读,1 代表写
- 返回值:失败返回-1,成功返回0
#include<stdio.h>
#include<unistd.h>
int main()
{
	int fd[2];
	pipe(fd);
	pid_t pid = fork();
	
	if(pid==0)
	{
		//son
		sleep(3);
		write(fd[1],"hello",5);
	}
	else if(pid>0)
	{
		//parent
		char buf[12]={0};
		int ret=read(fd[0],buf,sizeof(buf));
		if(ret>0)
		{
			write(STDOUT_FILENO,buf,ret);
		}
	}
	return 0;
}
read默认是阻塞的,所以子函数sleep时,也会输出
 
3.父子进程实现ps-grep命令
父子进程实现pipe通信,实现ps aux|grep bash 功能
 
 pipe_ps.c:
#include<stdio.h>
#include<unistd.h>
int main()
{
	int fd[2];
	pipe(fd);
	
	pid_t pid = fork();
	if(pid == 0)
	{
		//son
		//son -->ps
		//1.先重定向
		dup2(fd[1],STDOUT_FILENO);//标准输出重定向到管道写端
		//2.execlp
		execlp("ps","ps","aux",NULL);
	}
	else if(pid>0)
	{
		//parent
		//1.先重定向,标准输入重定向到管道读端
		dup2(fd[0],STDIN_FILENO);
		//2.execlp
		execlp("grep","grep","bash",NULL);
	}
	return 0;
}
产生僵尸进程,grep进程也存在,没有退出
 
 
代码的问题:
 父进程认为还有写端存在,就有可能还有人给发数据,继续等待。
 改造:两进程一个负责写,一个负责读。
#include<stdio.h>
#include<unistd.h>
int main()
{
	int fd[2];
	pipe(fd);
	
	pid_t pid = fork();
	if(pid == 0)
	{
		//son
		//son -->ps
		//关闭读端
		close(fd[0]);
		//1.先重定向
		dup2(fd[1],STDOUT_FILENO);//标准输出重定向到管道写端
		//2.execlp
		execlp("ps","ps","aux",NULL);
	}
	else if(pid>0)
	{
		//parent
		//关闭写端
		close(fd[1]);
		//1.先重定向,标准输入重定向到管道读端
		dup2(fd[0],STDIN_FILENO);
		//2.execlp
		execlp("grep","grep","bash",NULL);
	}
	return 0;
}

 
4.管道的读写行为
读管道:
- 写端全部关闭 --read读到0,相当于读到文件末尾
- 写端没有全部关闭
 有数据 --read读到数据
 没有数据 --read阻塞,fcntl函数可以更改非阻塞
写管道:
- 读端全部关闭 --?产生一个信号SIGPIPE,程序异常终止
- 读端未全部关闭
 管道已满 --write阻塞 --如果要显示现象,读端一直不读,写端狂写
 管道未满 --write正常写入
 pipe.c:
#include<stdio.h>
#include<unistd.h>
int main()
{
	int fd[2];
	pipe(fd);
	pid_t pid = fork();
	
	if(pid==0)
	{
		//son
		sleep(3);
		close(fd[0]);//关闭读端
		write(fd[1],"hello",5);
		close(fd[1]);
		while(1)
		{
			sleep(1);
		}
	}
	else if(pid>0)
	{
		//parent
		close(fd[1]);//关闭写端
		char buf[12]={0};
		while(1)
		{
			int ret=read(fd[0],buf,sizeof(buf));
			if(ret == 0)
			{
				printf("read over!\n");
				break;
			}
			if(ret>0)
			{
				write(STDOUT_FILENO,buf,ret);
			}
		}
	}
	return 0;
}
写端全部关闭 --read读到0,相当于读到文件末尾
 
读端全部关闭 --?产生一个信号SIGPIPE,程序异常终止
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
	int fd[2];
	pipe(fd);
	pid_t pid = fork();
	
	if(pid==0)
	{
		//son
		sleep(3);
		close(fd[0]);//关闭读端
		write(fd[1],"hello",5);
		close(fd[1]);
		while(1)
		{
			sleep(1);
		}
	}
	else if(pid>0)
	{
		//parent
		close(fd[1]);//关闭写端
		close(fd[0]);
		int status;
		wait(&status);
		if(WIFSIGNALED(status))
		{
			printf("killed by %d\n",WTERMSIG(status));
		}
		//父进程只是关闭读写两端,但不退出
		while(1)
		{
			sleep(1);
		}
		char buf[12]={0};
		while(1)
		{
			int ret=read(fd[0],buf,sizeof(buf));
			if(ret == 0)
			{
				printf("read over!\n");
				break;
			}
			if(ret>0)
			{
				write(STDOUT_FILENO,buf,ret);
			}
		}
	}
	return 0;
}

5.管道大小和优劣
计算管道大小 512*8
long fpathconf(int fd,int name)
ulimit -a
 
 优点:
- 简单
缺点:
- 只能有血缘关系的进程通信
- 父子进程单方向通信,如果需要双向通信,需要创建多根管道。
6.实现兄弟进程间通信,ps aux|grep bash

 父进程中需要关闭管道的读写两端。子进程1中需要关闭管道的读端,子进程2需要关闭管道的写端。
#include<stdio.h>
#include<unistd.h>
int mian(int argc,const char* argv[])
{
	int fd[2];
	int ret=pipe(fd);
	if(ret==-1)
	{
		perror("pipe error");
		exit(1);
	}
	int i=0;
	for(i=0;i<2;++i)
	{
		pid_t pid = fork();
		if(pid==0)
		{
			break;
		}
	}
	
	//子进程1
	//ps aux
	if(i==0)
	{
		//写管道的操作,关闭读端
		close(fd[0]);
		//文件描述符重定向
		//stdout_fileno->管道的写端
		dup2(fd[1],STDOUT_FILENO);
		execlp("ps","ps","aux",NULL);
		perror("exexlp");
		exit(1);
	}
	//子进程2
	//grep "bash"
	else if(i==1)
	{
		close(fd[1]);
		dup2(fd[0],STDIN_FILENO);
		execlp("grep","grep","bash","--color=auto",NULL);
	}
	//父进程
	else if(i==2)
	{
		close(fd[0]);
		close(fd[1]);
		//回收子进程
		pid_t wpid;
		while((wpid==waitpid(-1,NULL,WNOHANG))!=-1)
		{
			if(wpid==0)
			{
				continue;
			}
			printf("child died pid = %d\n",wpid);
		}
	}
	printf("pipe[0]=%d\n",fd[0]);
	printf("pipe[1]=%d\n",fd[1]);
	
	return 0;
}
04 FIFO通信
FIFO有名管道,实现无血缘关系进程通信
-  创建一个管道的伪文件 
 (1)mkfifo myfifo 命令创建
 (2)也可以用函数int mkfifo(const char* pathname,mode_t mode)
-  内核会针对fifo文件开辟一个缓冲区,操作fifo文件,可以操作缓冲区,实现进程间通信 --实际上就是文件读写 
FIFOs:
 open注意事项,打开fifo文件的时候,read端会阻塞等待write端open,write端同理,也会阻塞等待另外一端打开
fifo_w.c:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int main(int argc,char* argv[])
{
	if(argc!=2)
	{
		printf("./a.out fifoname\n");
		return -1;
	}
	//当前目录有一个myfifo文件
	//打开fifo文件
	printf("begin open...\n");
	int fd = open(argv[1],O_WRONLY);
	printf("end open...\n");
	//写
	char buf[256];
	int num=1;
	while(1)
	{
		memset(buf,0x00,sizeof(buf));
		sprintf(buf,"xiaoming%04d",num++);
		write(fd,buf,strlen(buf));
		sleep(1);
		//循环写
	}
	//关闭描述符
	clsoe(fd);
	return 0;	
}
fifo_r.c:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int main(int argc,char* argv[])
{
	if(argc!=2)
	{
		printf("./a.out fifoname\n");
		return -1;
	}
	printf("begin open read...\n");
	int fd = open(argv[1],O_RDONLY);
	printf("end open read...\n");
	
	char buf[256];
	int ret;
	while(1)
	{
		//循环读
		memset(buf,0x00,sizeof(buf));
		ret=read(fd,buf,sizeof(buf));
		if(ret>0)
		{
			printf("read:%s\n",buf);
		}
	}
	
	clos(fd);
	return 0;	
}

 开多个read的效果,此现象表明read被取出后,就不在管道文件中。
 
05 mmap 共享映射区
1.mmap映射开始

 创建映射区
void *mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset)
-  addr 传入NUL 
-  length 映射区的长度 
-  prot 
 PROT_READ 可读
 PROT_WRITE 可写
-  flags 
 MAP_SHARED 共享的,对内存的修改会影响到源文件
 MAP_PRIVATE 私有的
-  fd 文件描述符,open打开一个文件 
-  offset 偏移量 
-  返回值 
 成功,返回可用的内存首地址
 失败,返回MAP_FAILED
释放映射区
int munmap(void * addr,size_t length);
- addr 传mmap的返回值
- length mmap创建的长度
- 返回值
mmap.c:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/mman.h>
int main()
{
	int fd=open("mem.txt",O_RDWR);
	char *mem=mmap(NULL,8,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(mem == MAP_FAILED)
	{
		perror("mmap err");
		return -1;
	}
	strcpy(mem,"hello");
	
	//释放mmap
	munmap(mem,8);
	close(fd);
	return 0;
}
mem.txt:
 
 运行结果:
 
 将MAP_SHARED改为MAP_PRIVATE不会更改mem.txt内容。
2.mmap九问
1.如果更改mem变量的地址,释放的时候munmap,传入mem还能成功吗?
 答:不能!!
2.如果对mem越界操作会怎么样?
 答:文件的大小对映射区操作有影响,尽量避免
3.如果文件偏移量随便填个数会怎么样?
 答:offset必须是4k的整数倍
4.如果文件描述符先关闭,对mmap映射有没有影响?
 答:没有影响
5.open的时候,可以新创建一个文件来创建映射区吗?
 答:不可以用大小为0的文件
6.open文件选择O_WRONLY,可以吗?
 答:不可以:Permission denied
7.当选择MAP_SHARED的时候,open文件选择O_RDONLY,prot可以选择PROT_READ|PRO_WRITE吗?
 答:Permission denied,SHARED的时候,映射区的权限<=open文件的权限
8.mmap什么情况下会报错?
 答:很多情况
9.如果不判断返回值会怎么样?
 答:会报错
3.mmap实现父子进程通信
mmap_child.c:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/mman.h>
#inclde<sys/wait.h>
int main()
{
	//先创建映射区
	int fd=open("mem.txt",O_RDWR);
	int *mem=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(mem==MAP_FAILED)
	{
		perror("mmap err");
		return -1;
	}
	
	//fork子进程
	pid_t pid =fork();
	//父进程和子线程交替修改数据
	if(pid==0)
	{
		//son
		*mem=100;
		printf("child,*mem=%d\n",*mem);
		sleep(3);
		printf("child,*mem=%d\n",*mem);
	}
	else if(pid>0)
	{
		//parent
		sleep(1);
		printf("parent,*mem=%d\n",*mem);
		*mem=1001;
		printf("parent,*mem=%d\n",*mem);
		wait(NULL);
	}
	munmap(mem,4);
	close(fd);
	return 0;
}

4.匿名映射
由于mmap中open的文档无作用,所以产生了匿名映射。
MAP_ANON,ANONYMOUS这两个宏在有些unix系统中没有
/dev/zero 聚宝盆,可以随意映射
/dev/null 无底洞,一般错误信息重定向到这个文件中
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/mman.h>
#inclde<sys/wait.h>
int main()
{
	*mem=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
	if(mem==MAP_FAILED)
	{
		perror("mmap err");
		return -1;
	}
	
	//fork子进程
	pid_t pid =fork();
	//父进程和子线程交替修改数据
	if(pid==0)
	{
		//son
		*mem=101;
		printf("child,*mem=%d\n",*mem);
		sleep(3);
		printf("child,*mem=%d\n",*mem);
	}
	else if(pid>0)
	{
		//parent
		sleep(1);
		printf("parent,*mem=%d\n",*mem);
		*mem=10001;
		printf("parent,*mem=%d\n",*mem);
		wait(NULL);
	}
	munmap(mem,4);
	close(fd);
	return 0;
}

5.用mmap支持无血缘关系进程通信
mmap_w.c:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/mman.h>
#inclde<sys/wait.h>
typedef struct _Student{
	int sid;
	char sname[20];
}Student;
int main(int argc,char *argv[])
{
	if(argc!=2)
	{
		printf("./a.out fifoname\n");
		return -1;
	}
	//1.open file
	int fd = open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0666);
	int length=sizeof(Student);
	
	ftruncate(fd,length);
	
	//2.mmap Student *stu=mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(stu==MAP_FAILED)
	{
		perror("mmap err");
		return -1;
	}
	int num =1;
	//3.修改内存数据
	while(1)
	{
		stu->sid=num;
		sprintf(stu->sname,"xiaoming-%03d",num++);
		sleep(1);//相当于每隔1s修改一次映射区的内容
	}
	//4.释放映射区和关闭文件描述符
	munmap(stu,length);
	close(fd);
	return 0;
}
mmap_r.c:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/mman.h>
#inclde<sys/wait.h>
typedef struct _Student{
	int sid;
	char sname[20];
}Student;
int main(int argc,char *argv[])
{
	if(argc!=2)
	{
		printf("./a.out fifoname\n");
		return -1;
	}
	//1.open file
	int fd = open(argv[1],O_RDWR);
	int length=sizeof(Student);
	//2.mmap Student *stu=mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(stu==MAP_FAILED)
	{
		perror("mmap err");
		return -1;
	}
	//3.read data
	while(1)
	{
		sprintf("sid=%d,sname=%s\n",stu->sid,stu->sname);
		sleep(1);
	}
	//4.释放映射区和关闭文件描述符
	munmap(stu,length);
	close(fd);
	return 0;
}
由于mmap是内存,数据一直存在
 
 如果进程要通信,flags必须设为MAP_SHARED。
 
06 实现多进程拷贝
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<stdlib.h>
#include<string.h>
#include<sys/stat.h>
int main(int argc,char *argv[])
{
	int n=5;
	//输入参数至少是3,第4个参数可以是进程个数
	if(argc<3)
	{
		printf("./a.out src dst [n] \n");
		return 0;
	}
	if(argc==4)
	{
		n=atoi(argv[3]);
	}
	//打开源文件
	int srcfd=open(argv[1],O_RDONLY);
	if(srcfd<0)
	{
		perror("open err");
		exit(1);
	}
	//打开目标文件
	int dstfd=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0664);
	if(dstfd<0)
	{
		perror("open dst err");
		exit(1);
	}
	//目标拓展,从原文件获得文件大小,stat
	struct stat sb;
	stat(argv[1],&sb);//为了计算大小
	int len=sb.st_size;
	truncate(argv[2],len);
	//将源文件映射到缓冲区
	char *psrc=mmap(NULL,len,PROT_READ,MAP_SHARED,srcfd,0);
	if(psrc==MAP_FAILED)
	{
		perror("mmap src err");
		exit(1);
	}
	//将目标文件映射
	char *pdst=mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,dstfd,0);
	if(pdst==MAP_FAILED)
	{
		perror("mmap dst err");
		exit(1);
	}
	//创建多个子进程
	int i=0;
	for(i=0;i<n;i++)
	{
		if(fork()==0)
			break;
	}
	//计算子进程需要拷贝的起点和大小
	int cpsize=len/n;
	int mod =len%n;
	//数据拷贝,memcpy
	if(i<n){//子进程
		if(i==n-1){//最后一个字进程
			memcpy(pdst+i*cpsize,psrc+i*cpsize,cpsize+mod);
		}
		else
		{
			memcpy(pdst+i*cpsize,psrc+i*cpsize,cpsize);
		}
	}
	else
	{
		for(i=0;i<n;i++)
		{
			wait(NULL);
		}
	}
	//释放映射区
	if(munmap(psrc,len)<0)
	{
		perror("munmap src err");
		exit(1);
	}
	if(munmap(pdst,len)<0)
	{
		perror("munmap dst err");
		exit(1);
	}
	//关闭文件
	close(srcfd);
	close(dstfd);
	return 0;
}



















