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;
}