Linux——进程间通信(一):进程间通信的作用,匿名管道(命令,内核,PCB三个角度分析)


1. 进程间通信的作用

由于进程独立性的存在,两个进程想要直接交换数据是非常困难的,所以应用进程间通信来解决进程与进程之间交换数据的问题

2. 匿名管道

2.1 通过命令感受管道

在这里插入图片描述

  • ps: 命令
  • aux: 命令行参数
  • |: 管道
  • grep: 可执行程序

2.2 从内核角度解释管道

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

在这里插入图片描述

2.3 代码创建管道

int pipe(int pipefd[2]);
 

  • 功能:创建一个匿名管道
     
  • 参数(为输出型参数):
     
    (1)pipefd:类型为整型数组,有两个元素,pipefd[0],pipefd[1]
     
    (2)pipefd[0],pipefd[1]:当中保存的是一个文件描述符
     
    (3)pipefd[0]:对应的文件描述符可以从管道当中进行读,但不能写
     
    (4)pipefd[1]:对应的文件描述符可以往管道中进行写,但不能读
     
    (5)pipefd[0],pipefd[1]当中的值是pipe函数进行赋值的,当我们调用pipe函数的时候,只需要给pipe函数传递一个拥有两个元素的整型数组,pipe函数在创建完毕管道之后,会给pipefd[0],pipefd[1]进行赋值
     
  • 返回值:
     
                  -1:创建失败
     
                    0:创建成功
    在这里插入图片描述

验证如下:
我们先写一个test.c文件,在此文件中调用pipe函数,并打印出fd[0],fd[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     printf("fd[0]:%d",fd[0]);  
 14     printf("fd[1]:%d",fd[1]);  
 15       
 16       
 17     while(1)
 18     {
 19         sleep(1);
 20     }
 21     return 0;
 22 }   

运行程序我们可以看到fd[0]对应的文件描述符是3,文件权限为只读,fd[1]对应的文件描述符是4,文件权限为致谢,符合我们的预期
在这里插入图片描述

[拓展]——输出型参数

参数赋值在被调用函数内部,使用在调用函数处

如下图所示:
在这里插入图片描述

2.4 从PCB角度去分析管道

进程之间是如何通过匿名管道进行通信的呢?

答:通过fork来实现管道共享

父子进程共享管道代码验证如下:

  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     printf("fd[0]:%d\n",fd[0]);
 14     printf("fd[1]:%d\n",fd[1]);
 15     pid_t pid = fork();
 16     if(pid < 0)
 17     {
 18         perror("fork\n");
 19     }
 20 
 21     while(1)
 22     {
 23         sleep(1);
 24     }
 25     return 0;
 26 }         

在这里插入图片描述
父子进程共享管道图解如下:
在这里插入图片描述

  • (1)匿名管道只适用于具有亲缘关系的进程,进行进程间的通信
  • (2) 先创建管道,再创建子进程,父子进程才可以进行进程间通信

如果两个子进程想要使用匿名管道进行进程通信可不可以?

答:理论上是可以的,因为两个子进程都拷贝了父进程中的文件描述符,可以找到内核中的那一块缓冲区,但具体操作上面,一定要遵守先创建管道,再创建子进程

2.5 匿名管道配合父子进程使用及其特性验证

2.5.1 如果关闭子进程的fd[0],fd[1]会不会影响父进程中的fd[0],fd[1]?

答:因为进程的独立性,在一个进程中操作并不会影响另一个进程,所以在父子进程任意一个进程关闭文件描述符,不会影响另一个进程

代码验证如下:

我们预期关闭子进程当中的fd[0],fd[1],子进程中的fd[0],fd[1]没有了,但父进程中的fd[0],fd[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     printf("fd[0]:%d\n",fd[0]);
 14     printf("fd[1]:%d\n",fd[1]);
 15     pid_t pid = fork();
 16     if(pid < 0)
 17     {
 18         perror("fork\n");
 19     }
 20     else if(pid == 0)
 21     {
 22         //child
 23         printf("i am child,fd[0]:%d,fd[1]:%d\n",fd[0],fd[1]);
 24         close(fd[0]);
 25         close(fd[1]);
 26     }
 27     else
 28     {
 29         //father
 30     }
 31     
 32     while(1)
 33     {
 34         sleep(1);
 35     }
 36     return 0;
 37 }                    

测试结果如下图所示,子进程的fd[0],fd[1]被关闭了,父进程没受影响
在这里插入图片描述

2.5.2 实现父子进程间进程通信

假设让父进程写,子进程读,预期,子进程会读到父进程写端内容

  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     printf("fd[0]:%d\n",fd[0]);
 14     printf("fd[1]:%d\n",fd[1]);
 15     pid_t pid = fork();
 16     if(pid < 0)
 17     {
 18         perror("fork\n");
 19     }
 20     else if(pid == 0)
 21     {
 22         //child
 23         //子进程读
 24         char buf[1024] = {0};
 25         read(fd[0],buf,sizeof(buf)-1);
 26         printf("i am child,buf:%s\n",buf);                                                                                                          
 27     }
 28     else
 29     {
 30         //father
 31         //父进程写
 32         char buf[] = "i am father write!";
 33         write(fd[1],buf,18);
 34 
 35     }
 36     
 37     while(1)
 38     {
 39         sleep(1);
 40     }
 41     return 0;
 42 }                                     

测试结果如下,符合我们的预期:
在这里插入图片描述

管道的数据只能从写端流向读端,这是一种半双工的通信方式
 
全双工通信(网络):数据可以从A端流往B端,也可以从B端流向A端

2.5.3 fd[0]是将管道中的数据读走函数拷贝了一份

通过fd[0]从管道中读取数据的时候,是将数据读走了,并不是拷贝了一份

代码验证如下:

我们要想验证fd[0]是将管道中的数据读走了函数拷贝了一份,只需再进行一次读取,若读到数据,则证明是拷贝了一份数据,若没读到数据,则证明fd[0]将数据读走了
在这里插入图片描述
在这里插入图片描述
如上图所示,当我们将程序跑起来,我们看到的运行结果和之前子进程只读一次的结果一样,此时要验证如上结论,我们就要通过pstack命令查看子进程现在到底在做什么,如果子进程还是进行读,那么说明管道当中的数据已经被读走了,子进程阻塞在read处
在这里插入图片描述
如上图所示的结果验证了我们的结论,管道当中的数据已经被读走了,子进程阻塞在read处,所以fd[0]是将管道中的数据读走

2.5.4 字节流服务

  • (1) 从管道当中去读取数据的时候,可以指定读取任意大小的数据。如果管道当中没有数据,默认情况下,进行读(read),则会阻塞
  • (2) 多次写入的数据之间是没有明显的分界的,上一条数据的末尾链接下一条数据的开头
     
    如上两个内容可以称为字节流服务

代码验证如下:

让父进程write两次,子进程每次read1一个字节
在这里插入图片描述
在这里插入图片描述

2.5.5 匿名管道的生命周期跟随进程

2.6 默认情况(不对管道创建出来的文件描述符做任何操作)下的读写属性

2.6.1 如果读端不读,写端一直写,会产生什么情况

  • 写端写满之后就会阻塞

验证如下:
在这里插入图片描述
在这里插入图片描述

此时我们通过pstack命令可以看到写端进行阻塞

[拓展]–>管道的大小为65536

2.6.2 如果写端不写,读端一直读,会产生什么情况

  • 读端将管道中的数据读完之后,再次进行读的时候,就会进行阻塞
     
    (在2.5.3中已验证过,此处不再验证)

2.7 [拓展]–>原子性

原子性:非黑即白,要么完成了,要么还没开始

pipe_buf:程序员在操作管道进行读写的时候,保证读写原子性的数据最大的大小

我们在ulimit -a中看到的pipe size就是保证原子数最大的大小

8 * 512 = 4096字节
在这里插入图片描述

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值