Linux进程间通信(上)

本文详细介绍了Unix系统中的管道通信机制,包括匿名管道和命名管道的概念、创建方法、特点以及实际应用。匿名管道主要用于具有亲缘关系的进程间通信,而命名管道则打破了这一限制,允许不相关进程间通信。文中通过实例代码演示了如何使用匿名管道实现父子进程间的通信,并探讨了管道的阻塞与非阻塞属性。同时,文章还讲解了如何利用fcntl函数设置管道的非阻塞属性,并讨论了命名管道的创建及其在不同进程间通信的应用。
摘要由CSDN通过智能技术生成

目录

什么是管道

匿名管道

匿名管道接口

使用匿名管道通信

匿名管道特性

fcntl函数

函数接口

设置非阻塞属性

命名管道

创建命名管道

 命名管道特性

代码验证


什么是管道


管道是Unix中最古老的进程间通信的形式。

我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

 

匿名管道

匿名管道接口

pipe函数可以用来创建匿名管道,从而实现进程间通信,函数如下:

先来理解一下pipe()函数的参数到底是什么意思,调用完pipe函数后将参数打印出来:

 输出了"3,4",这是两个小正数,这很容易让我们想到了文件描述符,那么到底是不是呢?我们使用指令进入以该进程号命名的文件夹中:

 显然,这两个参数正是文件描述符,那么这两个文件描述符与匿名管道的关系呢?请看下图来理解:

知道了这层关系之后,就可以来用一用这个缓冲区了,如下代码:

代码完成的功能只是在同一个进程中使用管道,没有体现进程间通信,那么想要用匿名管道完成进程间通信,需要怎么做呢?

使用匿名管道通信

 通过以上图解分析,要让不同的进程使用匿名管道进行通信,那么这些进程必须拥有匿名管道读写两端的文件描述符,也就是说这些进程有亲缘关系,比如父子进程,满足下图对应的关系:

如下代码中,父进程往管道中写,子进程从管道中读:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <string.h>
  4 int main(){
  5     int fd[2];
  6     pipe(fd);//创建匿名管道
  7     pid_t ret=fork();
  8     if(ret<0){
  9         return 0;
 10     }else if(ret==0){
 11         char arr[10]={0};                                                                             
 12         read(fd[0],arr,sizeof(arr)-1);//从匿名管道中读
 13         printf("%s\n",arr);
 14         while(1){
 15         }
 16     }else{
 17         const char* str="Hello";
 18         write(fd[1],str,strlen(str));//往匿名管道中写
 19         while(1){
 20         }
 21     }
 22     return 0;
 23 }

代码执行后:

 成功打印了"Hello",说明子进程读到了管道中的内容,进程间通信成功了,这时我们再来查看父子进程是否都拥有文件描述符呢?如下:

此时,父子进程同时拥有匿名管道两端的文件描述符,进程通信成功。

匿名管道特性

1.管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。

2.只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

3.一般而言,进程退出,管道释放,所以管道的生命周期随进程。

4.管道大小是64KB,如下代码中一直往管道中写,一次写一个字节,直到将管道写满,可以看到最终打印的数字是65536,也就是65536个字节,等于64KB。

5.管道提供字节流服务,也就是说从读端进行读的时候,是将管道中的内容读走了,且读端可以自定义读多少内容。

6.当读写小于pipe_size时,管道保证原子性。pipe_size是4096字节,通过命令:"ulimit   -a"可以查看,原子性的意思就是说:一个操作要么不间断的全部被执行,要么一个也没有执行,不存在中间状态。如下:

7.调用pipe创建出来的读写两端的文件描述符,默认都是阻塞属性。就是说:调用write一直写,读端不去读,写满之后write会阻塞。调用read一直读,管道内部被读完后,read会阻塞。如下代码来验证写阻塞,读阻塞与之相类似。使用命令:"pstack [pid]"可以查看进程当前正在干什么。

fcntl函数

函数接口

fcntl函数可以用来设置管道为非阻塞属性,该函数如下:

来测试下这个函数,用来获取管道读写两端的: 

 此时输出的0和1就分别代表读写两端文件描述符的属性信息不同。

设置非阻塞属性

1.写不关闭,读设置为非阻塞属性。让子进程来读,看看会发生什么现象,代码如下:

  1 #include <stdio.h>                                                                                    
  2 #include <unistd.h>
  3 #include <fcntl.h>     
  4 int main(){      
  5     int fd[2];
  6     int ret=pipe(fd);
  7     if(ret<0){
  8         perror("pipe");
  9         return 0;
 10     }
 11     pid_t pid=fork();
 12     if(pid<0){
 13         perror("fork");
 14         return 0;
 15     }else if(pid==0){
 16         close(fd[1]);
 17         int flag=fcntl(fd[0],F_GETFL);
 18         fcntl(fd[0],F_SETFL,flag|O_NONBLOCK);
 19         char str[1000]={0};
 20         ssize_t size_read=read(fd[0],str,sizeof(str)-1);
 21         printf("%ld\n",size_read);
 22         perror("read");
 23     }else{
 24         close(fd[0]);
 25         while(1){}
 26     }                                                                                                 
 27     return 0;
 28 }

运行后:

此时read的返回值为-1,而errno被设置为EAGAIN,读失败。

2. 写关闭,读设置为非阻塞属性。让子进程来读,上述代码中父进程关闭写端就可以了,直接来看运行结果:

 read的返回值为0,读成功了,读到了0个字节。

3.读不关闭,写设置为非阻塞。让子进程来写,观察当把管道写满后的现象,代码如下:

  1 #include <stdio.h>                                                                                    
  2 #include <unistd.h>
  3 #include <fcntl.h>
  4 int main(){
  5     int fd[2];
  6     int ret=pipe(fd);
  7     if(ret<0){
  8         perror("pipe");
  9         return 0;
 10     }
 11     pid_t pid=fork();
 12     if(pid<0){
 13         perror("fork");
 14         return 0;
 15     }else if(pid==0){
 16         close(fd[0]);
 17         int flag=fcntl(fd[1],F_GETFL);
 18         fcntl(fd[1],F_SETFL,flag|O_NONBLOCK);
 19         while(1){
 20             ssize_t size_write=write(fd[1],"s",1);
 21             printf("%ld\n",size_write);
 22             perror("write");
 23         }
 24     }else{
 25         close(fd[1]);
 26         while(1){}
 27     }
 28     return 0;
 29 } 

运行代码后:

此时write返回-1,告诉我们资源不可用,写失败。

 4.读关闭,写设置为非阻塞。让子进程来写,在上述代码父进程中将读写都关闭即可,运行结果如下:

好像什么都没有发生?我们使用命令来查看当前进程的状态:

子进程中有死循环,父进程没有退出,子进程不会自动退出的,但是此时的现象表明子进程退出了,为什么呢?

本质原因是没有进程在从管道中读了,写入的内容永远不会被读出来,此时继续写入,管道就会破碎,写端进程就会收到一个SIGPIPE信号,导致写端进程崩溃。

命名管道

创建命名管道

1.mkfifo命令创建

 2.mkfifo函数创建

 下面来用这个函数创建一个命名管道:

 命名管道特性

支持不同的进程进行进程间通信,不依赖亲缘性了。

代码验证

拥有了命名管道后,就可以让不同的进程进行进程间通信,如下代码中,命名管道文件存在于当前代码所在目录的上级目录,用open函数打开后,通过各自的文件描述符就可以进行进程间通信了。

观察现象,成功进行了进程间通信。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值