• 【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 
      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 }
    ~
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    执行结果:

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

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

    #include 
      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 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    执行结果:

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

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

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

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

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

    在这里插入图片描述

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

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

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

    flag | O_NONBLOCK
    
    • 1

    在这里插入图片描述

    按位或得出的结果是2048。

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

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

    代码验证非阻塞属性

    一共有下面这四种情况:

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

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

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

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

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

    #include 
    #include 
    #include 
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    执行结果:

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

    执行完需要注意的是,父进程退出之后子进程依然在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 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    执行结果:

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

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

    写关闭,一直读,父进程读端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 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    执行结果:

    在这里插入图片描述

    读不关闭,一直写,当把管道写满之后,则在调用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 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

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

    [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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

  • 相关阅读:
    业务开发流程
    C. Cyclic Permutations(组合数学+单峰序列)
    浅谈xss
    单例模式实现及防止反射与序列化
    css:两栏三栏布局
    硬链接和软连接的区别
    Mysql(二)------Mysql的配置文件位置
    文件系统类数据读取与保存HBase_大数据培训
    大量数据同步
    TypeScript可选链,非空断言操作符,空值合并运算符等
  • 原文地址:https://blog.csdn.net/weixin_56916549/article/details/127637024