1.熟练使用pipe进行父子进程间通信
2.熟练使用pipe进行兄弟进程间通信
3.熟练使用fifo进行无血缘关系的进程间通信
4.熟练掌握mmap函数的使用
5.掌握mmap创建匿名映射区的方法
6.使用mmap进行有血缘关系的进程间通信
7.使用mmap进行无血缘关系的进程间通信
IPC:InterProcess Communication 进程间通信,通过内核提供的缓冲区进行数据交换的机制。
IPC通信的方式有几种:
pipe通信
常见的通信方式:单工(广播),半双工(对讲机),全双工(打电话)
管道:半双工通信
管道函数:
int pipe(int pipefd[2])
#include
#include
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;
}
父子进程实现pipe通信,实现ps aux|grep bash 功能
pipe_ps.c:
#include
#include
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;
}
代码的问题:
父进程认为还有写端存在,就有可能还有人给发数据,继续等待。
改造:两进程一个负责写,一个负责读。
#include
#include
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;
}
读管道:
写管道:
#include
#include
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;
}
读端全部关闭 --?产生一个信号SIGPIPE,程序异常终止
#include
#include
#include
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;
}
计算管道大小 512*8
long fpathconf(int fd,int name)
缺点:
父进程中需要关闭管道的读写两端。子进程1中需要关闭管道的读端,子进程2需要关闭管道的写端。
#include
#include
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;
}
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
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
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被取出后,就不在管道文件中。
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);
mmap.c:
#include
#include
#include
#include
#include
#include
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内容。
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.如果不判断返回值会怎么样?
答:会报错
mmap_child.c:
#include
#include
#include
#include
#include
#include
#inclde
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;
}
由于mmap中open的文档无作用,所以产生了匿名映射。
MAP_ANON,ANONYMOUS这两个宏在有些unix系统中没有
/dev/zero 聚宝盆,可以随意映射
/dev/null 无底洞,一般错误信息重定向到这个文件中
#include
#include
#include
#include
#include
#include
#inclde
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;
}
mmap_w.c:
#include
#include
#include
#include
#include
#include
#inclde
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
#include
#include
#include
#include
#include
#inclde
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。
#include
#include
#include
#include
#include
#include
#include
#include
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