linux系统(进程间通信)06_IPC概念,pipe管道,fifo通信,mmap 共享映射区

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值