Linux—进程间通信—管道详解

1. 进程间通信介绍

  1. 目前进程间最大的通信方式:网络!!!
  2. 由于进程间独立性的存在,两个进程间想要交换数据是非常困难的,所以,我们需要进程通信来解决进程间的数据交换。
  3. 进程间通信目的:
  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知他们发生了某种事件(如进程终止时要通知父进程)
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
  1. 进程间通信发展
  • 管道(分类:匿名管道、命名管道)
  • System V进程间通信(System V消息队列、System V 共享内存、System V 信号量)
  • POSIX进程间通信(消息队列、共享内存、信号量、互斥量、条件变量、读写锁)

2. 管道介绍

什么是管道?

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
    在这里插入图片描述

3. 匿名管道

3.1 从命令行感受管道

在这里插入图片描述

3.2 从内核角度理解管道

在这里插入图片描述
管道就是内核当中的一块缓冲区(一块内存),进程A和进程B可以通过这个缓冲区进行交换数据,匿名管道不具备标识符

3.3 代码创建匿名管道

#include<unistd.h>
功能:创建一无名管道
原型:  int pipe(int pipefd[2]);
参数:pipefd:文件描述符数组,其中有两个元素,分别是pipefd[0]和pipefd[1],它们当中保存的是一个文件描述符
     pipefd[0]表示读端,不能写可读,pipefd[1]表示写端,不能读可写
     (参数为输出型参数)返回值:创建成功返回0,创建失败返回-1.

探究点:pipefd[0]、pipefd[1]当中的值是pipe函数进行赋值的,直白的说,当我们调用pipe函数的时候,只需要给pipe函数传递一个拥有两个元素的整型数据的数组,pipe函数在创建完毕管道之后,会给pipefd[0]、pipefd[1]进行赋值。
在这里插入图片描述
下面是代码创建管道的一个演示:

  1 #include<stdio.h>                                                                                                                                                                                             
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6     int fd[2];
  7     int ret=pipe(fd);
  8     if(ret<0)
  9     {
 10         perror("pipe");
 11         return 0;
 12     }
 13     pid_t pid=fork();
 14     if(pid<0)
 15     {
 16         perror("fork");
 17         return 0;
 18     }
 19     else if(pid==0)
 20     {
 21         //child
 22         char buf[1024]={0};
 23         read(fd[0],buf,sizeof(buf)-1);
 24         printf("child read:%s\n",buf);
 25 
 26     }
 27     else
 28     {
 29         //father
 30         write(fd[1],"I am father",11);
 31     }
 32 }

3.4 从PCB的角度去分析管道

在这里插入图片描述

  1. 匿名管道只适用于具有亲缘关系的进程,例如父进程和子进程。
  2. 在创建管道时,要先进行创建管道,再创建子进程,这样才能实现进程间通信。
  3. 那么两个子进程间想要使用匿名管道实现进程间通信可不可以呢?
    在理论上是可以的,但是还是要遵循先创建管道,在创建子进程的原则
  4. 【注意】管道的数据只能从写端流向读端,这是一种半双工的通信方式。这里对比一下全双工的通信方式:数据可以从写端流向读端也可以从写、读端流向写端。
  5. 通过fd[0]从管道中去读数据的时候,是将数据读走了,管道中将不存在这份数据,并不是拷贝一份。
  6. 从管道中读取数据的时候,可以指定读取任意大小的数据。如果管道中没有数据,默认情况下,进行读(read),则会堵塞。
  7. 多次写入的数据之间是没有明显的分界线的,上一条数据的末尾连接下一条数据的开头位置,不能够区分数据是否是分次写入的。
  8. 匿名管道的生命周期跟随进程。
  9. 那么我们再来讨论一下默认情况下(并没有对管道创建出来的文件描述符做任何操作)的读写属性
  • 如果读端不读,写段一直写,会产生什么情况?
    写端将管道写满以后,就会阻塞;管道的大小:65536字节
  • 如果写端不写,读端一直读,会产生什么情况?
    读端将管道的数据读完之后,再次进行读的时候,就会阻塞

3.5 匿名管道的非阻塞读写特性

  1. 要了解匿名管道的非阻塞属性,我们认识一个函数——fcntl
fcntl函数:设置/获取文件描述符的属性
int fcntl(int fd,int cmd,.../*arg*/);
        fd:文件描述符
        cmd: F_GETFL:获取文件描述符的属性(可变参数列表就可以不用传递任何值) eg:fcntl(fd[0],F_GETFL)
             F_SETFL:设置文件描述符的属性
                     可变参数列表为:O_RDONLY(只读)/O_WRONLY(只写)/O_RDWR(可读可写)
                                  O_NONBLOCK(非阻塞属性)   都采用按位或的方式
                                  eg:fcntl(fd[0],F_SETFL,flag0|O_NONBLOCK);
        返回值:如果是获取(F_GETFL),返回文件描述符的属性。
  1. 函数使用演示:
  1 #include<stdio.h>
  2 //fcntl函数
  3 #include<fcntl.h>
  4 #include<unistd.h>
  5 
  6 int main()
  7 {
  8     int fd[2];
  9     int ret=pipe(fd);//创建一个管道
 10     if(ret<0)
 11     {
 12         perror("pipe");
 13         return 0;
 14     }
 15 
 16     int flag0=fcntl(fd[0],F_GETFL);//获取管道属性
 17     printf("read fd[0]:flag:%d\n",flag0);
 18     int flag1=fcntl(fd[1],F_GETFL);
 19     printf("write fd[1]:flag:%d\n",flag1);
 20 
 21    // fcntl(fd[0],F_SETFL,flag0|O_NONBLOCK);//设置管道为非阻塞形式
 22    // printf("read fd[0]:flag:%d\n",flag0);
 23    // fcntl(fd[1],F_SETFL,flag1|O_NONBLOCK);
 24    // printf("write fd[1]:flag:%d\n",flag1);                                                                     
 25 
 26     return 0;
 27 }

这里可以看到fd[0]和fd[1]的属性,分别为0和1,这里再进行讨论一下源码,可以看到O_RDONLY和O_WRONLY的表示为00000000和00000001,这样我们就可以联想到为什么要用或的方式,将特定位置置为1,就可以表示出特定的属性。
在这里插入图片描述

  1. 下面再进行讨论以下两种情况下会发生的情况:
    • 在管道读端进行读(非阻塞情况下),写端不写
      (1)写端关闭:调用read 函数返回-1
      (2)写端不关闭:读端调用read,read函数会返回-1,表示管道当中没有内容
    • 在管道写端进行写(非阻塞情况下),读端不读
      (1)读端关闭:当前在通过fd[1],往管道中去写的时候,会导致管道破裂,调用写的进程的时候,会被直接终止(终止信号)
      (2)读端不关闭:write返回-1,errno为EAGAIN,表示管道写满了。
  2. 代码验证以上两种情况:
  • 写端关闭,读端进行读
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<fcntl.h>                                                                                                                                                                                                                                                                                                       
  4 #include<errno.h>
  5 
  6 void SetNonBlock(int fd)
  7 {
  8     int flag=fcntl(fd,F_GETFL);
  9     fcntl(fd,F_SETFL,flag|O_NONBLOCK);
 10 }
 11 
 12 int main()
 13 {
 14     int fd[2];
 15     int ret=pipe(fd);
 16     if(ret<0)
 17     {
 18         perror("pipe");
 19         return 0;
 20     }
 21 
 22     ret=fork();
 23     if(ret<0)
 24     {
 25         perror("fork");
 26         return 0;
 27     }
 28     else if(ret==0)
 29     {
 30         //child
 31         close(fd[0]);
 32         close(fd[1]);
 33         while(1)
 34         {
 35             sleep(1);
 36         }
 37     }
 38     else
 39     {
 40         //father
 41         close(fd[1]);
 42         SetNonBlock(fd[0]);
 43 
 44         char buf[1024]={0};
 45         int read_size=read(fd[0],buf,sizeof(buf)-1);
 46         printf("read_size:%d,buf:%s\n",read_size,buf);
 47     }
 48     return 0;
 49 
 50 }

  • 写端不关闭,读端进行读
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<fcntl.h>
  4 #include<errno.h>
  5 
  6 void SetNonBlock(int fd)
  7 {
  8     int flag=fcntl(fd,F_GETFL);
  9     fcntl(fd,F_SETFL,flag|O_NONBLOCK);
 10 }
 11 
 12 int main()
 13 {
 14     int fd[2];
 15     int ret=pipe(fd);
 16     if(ret<0)
 17     {
 18         perror("pipe");
 19         return 0;
 20     }
 21 
 22     ret=fork();
 23     if(ret<0)
 24     {
 25         perror("fork");
 26         return 0;
 27     }
 28     else if(ret==0)
 29     {
 30         //child
 31         close(fd[0]);
 32         SetNonBlock(fd[1]);
 33         //写端不关闭
 34         while(1)
 35         {
 36             sleep(1);
 37         }
 38     }
 39     else
 40     {
 41         //father
 42         close(fd[1]);
 43         SetNonBlock(fd[0]);
 44 
 45         char buf[1024]={0};
 46         int read_size=read(fd[0],buf,sizeof(buf)-1);
 47         if(read_size<0)
 48         {                                                                                                                                                                                                                                                
 49             if(errno==EAGAIN)
 50             {
 51                 printf("管道为空\n");
 52                 printf("read_size:%d,buf %s\n",read_size,buf);
 53             }
 54         }
 55     }
 56     return 0;
 57 
 58 }

  • 读端关闭,写端进行写
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<fcntl.h>
  4 #include<errno.h>
  5 
  6 void SetNonBlock(int fd)
  7 {
  8     int flag=fcntl(fd,F_GETFL);
  9     fcntl(fd,F_SETFL,flag|O_NONBLOCK);
 10 }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
 11 
 12 int main()
 13 {
 14     int fd[2];
 15     int ret=pipe(fd);
 16     if(ret<0)
 17     {
 18         perror("pipe");
 19         return 0;
 20     }
 21 
 22     ret=fork();
 23     if(ret<0)
 24     {
 25         perror("fork");
 26         return 0;
 27     }
 28     else if(ret==0)
 29     {
 30         //child
 31         close(fd[0]);
 32         SetNonBlock(fd[1]);
 33         int count=0;
 34         while(1)
 35         {
 36             int write_size=write(fd[1],"a",1);
 37             if(write_size<0)
 38             {
 39                 printf("write_size:%d\n",write_size);
 40                 if(errno==EAGAIN)
 41                 {
 42                     //将管道写满了
 43                     break;
 44                 }
 45             }
 46             printf("count:%d\n",count++);
 47         }
 48 
 49         while(1)
 50         {
 51             sleep(1);
 52         }
 53     }
 54     else
 55     {
 56         //father
 57         close(fd[1]);
 58         close(fd[0]);
 59         while(1)
 60         {
 61             sleep(1);
 62         }
 63     }
 64     return 0;
 65 
 66 }

  • 读端不关闭,写端进行写
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<fcntl.h>
  4 #include<errno.h>
  5 
  6 void SetNonBlock(int fd)
  7 {
  8     int flag=fcntl(fd,F_GETFL);
  9     fcntl(fd,F_SETFL,flag|O_NONBLOCK);
 10 }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
 11 
 12 int main()
 13 {
 14     int fd[2];
 15     int ret=pipe(fd);
 16     if(ret<0)
 17     {
 18         perror("pipe");
 19         return 0;
 20     }
 21 
 22     ret=fork();
 23     if(ret<0)
 24     {
 25         perror("fork");
 26         return 0;
 27     }
 28     else if(ret==0)
 29     {
 30         //child
 31         close(fd[0]);
 32         SetNonBlock(fd[1]);
 33         int count=0;
 34         while(1)
 35         {
 36             int write_size=write(fd[1],"a",1);
 37             if(write_size<0)
 38             {
 39                 printf("write_size:%d\n",write_size);
 40                 if(errno==EAGAIN)
 41                 {
 42                     //将管道写满了
 43                     break;
 44                 }
 45             }
 46             printf("count:%d\n",count++);
 47         }
 48 
 49         while(1)
 50         {
 51             sleep(1);
 52         }
 53     }
 54     else
 55     {
 56         //father
 57         close(fd[1]);
 58         //close(fd[0]);
 59         while(1)
 60         {
 61             sleep(1);
 62         }
 63     }
 64     return 0;
 65 
 66 }

代码验证结果如图所示:
在这里插入图片描述

4. 命名管道

4.1 命名管道介绍

命名管道也是在内核当中开辟了一块缓冲区,这块缓冲区具有标识符,可以被任何进程通过标识符去找到。

4.2 命名管道创建

  • 创建命令:mkfifo
  • 函数创建命名管道
    (1)创建管道
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/shm.h>
  4 
  5 #define key 0x23232323
  6 
  7 int main()
  8 {
  9     int shmid=shmget(key,1024,IPC_CREAT|0664);
 10     if(shmid<0)
 11     {
 12         perror("shmget");
 13         return 0;
 14     }
 15     return 0;                                                                                                                                             
 16 }

(2)创建A_write.c,向管道中写入"I am process A"

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<fcntl.h>
  4 
  5 int main()
  6 {
  7    int fd=open("_shm.c",O_RDWR);                                                                                                                          
  8    if(fd<0)
  9    {
 10        perror("open");
 11        return 0;
 12    }
 13 
 14    while(1)
 15    {
 16        write(fd,"I am process A",14);
 17        sleep(1);
 18    }
 19    close(fd);
 20    return 0;
 21 
 22 }

(2)创建B_read.c,从管道中读出数据

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<fcntl.h>
  4 
  5 int main()
  6 {
  7     int fd=open ("_shm.c",O_RDWR);
  8     if(fd<0)
  9     {
 10         perror("open");
 11         return 0;
 12     }
 13     while(1)
 14     {
 15         char buf[1024]={0};
 16         read(fd,buf,sizeof(buf)-1);
 17 
 18         printf("buf: %s\n",buf);
 19         sleep(1);
 20     }                                                                                                                                                     
 21     close(fd);
 22     return 0;
 23 }

4.3 命名管道特性

  • 命名管道的生命周期也是跟随进程
  • 命名管道有标识符,所以,命名管道支持不同进程之间的进程通信
  • 其他特性和匿名管道一样。
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值