【Linux】进程间通信2-匿名管道2

设置非阻塞属性

读写两端的文件描述符初始的属性是阻塞属性,所以如果我们希望匿名管道的读写两端为非阻塞属性的时候,就需要自己设置非阻塞属性,此时需要用到fcntl函数。
fcntl函数原型:

int fcntl(int fd, int cmd, … /* arg */ );

参数:可变参数列表

fd:待要操作的文件描述符,对匿名管道来说,就是管道的读写两端的文件描述符
cmd:告诉fcntl函数做什么操作,有两个可选项:

  • F_GETFL获取文件描述符的属性信息
  • F_SETFL设置文件描述符的属性信息,要设置的新的属性放到可变参数列表当中。要注意设置属性的时候是直接替换原有属性,并不是在原有属性上面追加属性,设置多个属性的时候,用按位或的方式连接各属性。

返回值:

  • 如果cmd是 F_GETFL,则返回文件描述符的属性信息。
  • 如果cmd是F_SETFL,新属性设置成功返回0,设置失败返回-1.

我们创建一个管道来看一下管道两端读写文件描述符的属性信息:

 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <fcntl.h>
  4 int main(){
  5   //创建管道
  6   int fd[2];
  7   int ret = pipe(fd);
  8   if(ret < 0){
  9     perror("pipe");
 10     return 0;
 11   }
 12 
 13   int flag = fcntl(fd[0],F_GETFL);
 14   printf("read: flag = %d.\n",flag);
 15 
 16 
 17   flag = fcntl(fd[1],F_GETFL);
 18   printf("write: flag = %d.\n",flag);                                          
 19   return 0;
 20 }
~

执行结果:

[jxy@VM-4-2-centos nonblock]$ ./nonblock 
read: flag = 0.
write: flag = 1.

在上述代码的基础上,我们设置管道读端文件描述符为非阻塞属性:

#include <stdio.h>
  2 #include <unistd.h>
  3 #include <fcntl.h>
  4 int main(){
  5   //创建管道
  6   int fd[2];
  7   int ret = pipe(fd);
  8   if(ret < 0){
  9     perror("pipe");
 10     return 0;
 11   }
 12   //获取读端文件描述符的属性信息
 13   int flag = fcntl(fd[0],F_GETFL);
 14   printf("before flag = %d.\n",flag);
 15   //设置fd[0]的文件描述符为非阻塞属性
 16   ret = fcntl(fd[0],F_SETFL,flag | O_NONBLOCK);                                
 17   //获取此时的读端文件描述符的属性信息
 18   flag = fcntl(fd[0],F_GETFL);
 19   printf("after flag:%d.\n",flag);
 20   return 0;
 21 }

执行结果:

[jxy@VM-4-2-centos nonblock]$ ./nonblock 
before flag = 0.
after flag:2048.

上述两端代码的运行结果不难看出,读端文件描述符的属性为0,写端文件描述符的属性为1,那为什么属性是0,为什么是1呢?还有为什么flag |和O_NONBLOCK之间要用或来连接呢?

我们在操作系统内核中查找O_NONBLOCK,可以看出,它的值转化为十进制就是2048.

#define O_NONBLOCK 00004000
//是一个八进制数字

读端文件描述符的属性信息是0,代表着O_RDONLY;
写端文件描述符的属性信息的1,代表着O_WRONLY.

在这里插入图片描述

那为什么文件描述符的属性信息需要用按位或的方式进行设置呢?

因为文件描述符的属性信息在操作系统内核当中是用比特位表示的。

我们以下面这个为例:
flag的值是0,O_NONBLOCK的值是2048

flag | O_NONBLOCK

在这里插入图片描述

按位或得出的结果是2048。

操作系统内核当中大量的在使用位图,有两个明显的优点就是:位操作快、节省内存空间。
系统接口当中,文件打开方式的宏,在内核当中的使用方式是位图,比如O_RDONLY、O_CREAT、O_WRONLY等等。
看到这里,可能会有疑问,为什么文件描述符会有属性呢?它不是一个数字吗?

我们在上一篇提到,文件描述符是指针数组的下标,而且该指针数组存放的指针是指向的是文件的结构体,我们可以把图再拿来看看。
在这里插入图片描述
所以文件描述符的属性其实是指的是struct file{…}里面存储的文件的一些属性,比如该文件是否可读,是否可执行,是否可写等等。

代码验证非阻塞属性

一共有下面这四种情况:

  • 读设置为非阻塞属性,写不关闭,一直读
  • 读设置为非阻塞属性,写关闭,一直读
  • 写设置为非阻塞属性,读不关闭,一直写
  • 写设置为非阻塞属性,读关闭,一直写

读设置为非阻塞属性,写不关闭,一直读

首先就是要创建管道,其次再创建子进程,再设置匿名管道读端文件描述符为非阻塞属性,父进程进行读,子进程进行写。

此时我们需要关心的就是父进程的读端和子进程的写端,所以我们首先需要把不需要的文件描述符的端口关掉,也就是把父进程的写端和子进程的读端关闭了。

我们让子进程一直不退出,模拟出子进程的写端一直打开。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
  //创建管道
  int fd[2];
  int ret = pipe(fd);
  if(ret<0){
    perror("pipe");
    return 0;
  }

  //创建子进程
  ret = fork();
  if(ret < 0){
    perror("fork");
    return 0;
  }else if(ret == 0){
    //child 
    close(fd[0]);
  }else{
    //father
    close(fd[1]);
  }
  return 0;
}

执行结果:

[jxy@VM-4-2-centos pipe_nonblock]$ ./nonblock 
read: Resource temporarily unavailable
r_size:-1.

执行完需要注意的是,父进程退出之后子进程依然在sleep中,子进程就变成了孤儿进程,需要kill掉。

写不关闭,一直读,读端调用read函数之后,返回值为-1,error设置为EAGAIN。

读设置为非阻塞属性,写关闭,一直读

在上述代码的基础上,将子进程的写端关闭,再让父进程去读,为了保证父进程去读的时候,子进程的写端一定是关闭的,所以在进入父进程之后,我们让父进程先sleep一秒。

    1 #include <stdio.h>                                                                                                                                                   
    2 #include <unistd.h>
    3 #include <fcntl.h>
    4 int main(){
    5   //创建管道
    6   int fd[2];
    7   int ret = pipe(fd);
    8   if(ret<0){
    9     perror("pipe");
   10     return 0;
   11   }
   12 
   13   //创建子进程
   14   ret = fork();
   15   if(ret < 0){
   16     perror("fork");
   17     return 0;
   18   }else if(ret == 0){
   19     //child 
   20     close(fd[0]);
   21     close(fd[1]);
   22     while(1){
   23       sleep(1);
   24     }
   25   }else{
   26     //father
   27     sleep(1);
   28     close(fd[1]);
   29     int flag = fcntl(fd[0],F_GETFL);
   30     fcntl(fd[0],F_SETFL,flag | O_NONBLOCK);
   31     char buf[1024] = {0};
   32     ssize_t r_size = read(fd[0],buf,sizeof(buf)-1);
   33     perror("read");                                                                                                                                                  
W> 34     printf("r_size:%d.\n",r_size);
   35   }
   36   return 0;
   37 }

执行结果:

[jxy@VM-4-2-centos pipe_nonblock]$ ./nonblock 
read: Success
r_size:0.

此时,管道没有进程去写入了,相当于为空,父进程再去读,就什么也读不到了。

写关闭,一直读,父进程读端read函数返回0,表示什么也没有读到。

写设置为非阻塞属性,读不关闭,一直写

我们设置父进程进行读,子进程进行写,首先和上面一样,关闭父进程的写端和子进程的读端,之后子进程以自己进行写,父进程一直进行读。

代码如下:

  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 
 12   ret = fork();
 13   if(ret < 0){
 14     perror("fork");
 15     return 0;
 16   }else if(ret == 0){
 17     //child 写
 18     close(fd[0]);
 19     int flag = fcntl(fd[1],F_GETFL);
 20     fcntl(fd[1],F_SETFL,flag | O_NONBLOCK);
 21     int count = 0;
 22     while(1){
 23       int ret = write(fd[1],"a",1);
 24       if(ret < 0){
 25         perror("write");
 26         break;
 27       }
 28       count++;
 29       printf("count=%d\n",count);
 30     }
 31   }else{
 32     //father 读
 33     close(fd[1]);
 34     while(1){
 35       sleep(1);
 36       //模拟一直读
 37     }
 38   }                                                                            
 39   return 0;
 40 }

执行结果:

在这里插入图片描述

读不关闭,一直写,当把管道写满之后,则在调用write,就会返回-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 
 12   ret = fork();
 13   if(ret < 0){
 14     perror("fork");
 15     return 0;
 16   }else if(ret == 0){
 17     //child 写
 18     sleep(1);
 19     close(fd[0]);
 20     int flag = fcntl(fd[1],F_GETFL);
 21     fcntl(fd[1],F_SETFL,flag | O_NONBLOCK);
 22     int count = 0;
 23     while(1){
 24       int ret = write(fd[1],"a",1);
 25       if(ret < 0){
 26         perror("write");
 27         break;
 28       }
 29       count++;
 30       printf("count=%d\n",count);
 31     }
 32   }else{
 33     //father 读
 34     close(fd[1]);
 35     close(fd[0]);
 36     while(1){
 37       sleep(1);                                                                                                                                                        
 38     }
 39   }
 40   return 0;
 41 }

执行程序后,我们发现,子进程变成了僵尸进程。

[jxy@VM-4-2-centos nonblock]$ ps aux | grep nonblock
jxy      17875  0.0  0.0   4212   352 pts/1    S+   16:51   0:00 ./nonblock
jxy      17876  0.0  0.0      0     0 pts/1    Z+   16:51   0:00 [nonblock] <defunct>
jxy      17923  0.0  0.0 112816   984 pts/2    S+   16:51   0:00 grep --color=auto nonblock

管道读端关闭,但一直往管道中,写端调用write进行写的时候,就会发生崩溃,本质上是因为读端关闭,写端的进程就会收到SIGPIPE信号,导致写端进程崩溃,就和水管是一样的,堵住一头,再想往水管里面一直注水,那水管就会破裂了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值