• 关于pipe管道的读写端关闭问题


    知识概述

      通过pipe在内核中创建一个文件,然后可以实现两个进程通信

    管道是一种最基本的IPC机制,由 pipe 函数创建:

    1 #include <unistd.h>
    2 int pipe(int filedes[2]);

    调用 pipe 函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过 filedes 参数传出给用户程序两个文件描述符,

    filedes[0] 指向管道的读端, filedes[1] 指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,

    通过 read(filedes[0]); 或者 write(filedes[1]); 向这个文件读写数据其实是在读写内核缓冲区。 pipe 函数调用成功返回0,调用失败返回-1。

    开辟了管道之后如何实现两个进程间的通信呢?比如可以按下面的步骤通信。

    1. 父进程调用 pipe 开辟管道,得到两个文件描述符指向管道的两端。
    2. 父进程调用 fork 创建子进程,那么子进程也有两个文件描述符指向同一管道。

    3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,
    管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。

    #include <stdlib.h>
    #include <unistd.h>
    #define MAXLINE 80
    int main(void)
    {
        int n;
        int fd[2];
        pid_t pid;
        char line[MAXLINE];
        if (pipe(fd) < 0) {
        perror("pipe");
        exit(1);
        }
        if ((pid = fork()) < 0) {
            perror("fork");
            exit(1);
        }
        if (pid > 0) { /* parent */
            close(fd[0]);
            write(fd[1], "hello world
    ", 12);
            wait(NULL);
        } 
        else { /* child */
            close(fd[1]);
            n = read(fd[0], line, MAXLINE);
            write(STDOUT_FILENO, line, n);
        }
        return 0;
    }    

    问题

    父进程只用到写端,因而把读端关闭,子进程只用到读端,因而把写端关闭,然后互相通信,不使用的读端或写端必须关闭,请读者想一想如果不关闭会有什么问题。

    思考

    1. 如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),
    而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,
    再次 read 会返回0,就像读到文件末尾一样。

    2. 如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),
    而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,
    那么管道中剩余的数据都被读取后,再次 read 会阻塞,
    直到管道中有数据可读了才读取数据并返回。

     考虑到如下代码

    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<string.h>
    int main(void)
    {
        int n;
        char buff[128];
        pid_t pid;
        int fd[2];
    
        if(pipe(fd)<0)    {
            perror("pipe");
            exit(0);
        }
        
        if((pid=fork())<0)
        {
            perror("fork");
            exit(0);
        }
        
        if(pid>0)
        {
            /* parent */    
            printf("+++++++++++++
    ");
            close(fd[0]);        
            write(fd[1],"hello world",11);
            //sleep(5);
            //write(fd[1],"I am a Student",14);
            printf("+++++++++++++
    ");
        }
        else
        {
            printf("--------------
    ");
            //close(fd[1]);
            memset(buff,0,128);
            n = read(fd[0],buff,20);
            printf("buff=%s
    ",buff);
            memset(buff,0,128);
            printf("read twice
    ");
            n = read(fd[0],buff,20);
            printf("buff=%s
    ",buff);
            printf("--------------
    ");
        }
        return 0;
    }

    父进程关闭了读端口,通过写端口向pipe中写入了hello world。然后父进程结束。关闭相关文件(读写)描述符

      子进程在关闭写端口的时候,父进程结束时候,写文件描述符引用计数为0。所以子进程再次读取后返回0。子进程结束退出。

      子进程在不关闭写端口的时候,父进程结束时候,写文件描述符引用计数为1(自己的没关闭)。所以子进程再次读取时候陷入阻塞状态。

      因为父进程是在SHELL下执行的。所以当父进程结束时候,Shell进程认为命令执行结束了,于是打印Shell提示符,而子进程等待读取输入。

    父进程已经结束,不会给他输入数据,而子进程本身只是为了读取而不是向管道写数据。所以子进程一直在后台运行,通过ps命令可以查看到子进程信息。

      所以,子进程只用到读端,因而把写端关闭。防止造成子进程做无用功。。。

  • 相关阅读:
    JAVA 8 主要新特性 ----------------(二)JDK1.8优点概括
    js 对cookie 的操作
    js 中类似时钟的显示
    js 把数字转成2 ,8,16进制的方法
    js 对闭包的理解
    js 利用throw 写的一个小程序
    js 获取当前的时间
    ios使用jspatch中需要注意的事项
    IOS 开发中要注意的事项
    iOS 利用JSPatch 添加热补丁功能
  • 原文地址:https://www.cnblogs.com/whiteHome/p/4863516.html
Copyright © 2020-2023  润新知