zoukankan      html  css  js  c++  java
  • pipe

    管道(pipe)

    管道是一种最基本的IPC机制, 作用于有血缘关系的进程之间通信, 完成数据传递.

    本质:
      伪文件, 实质为内核缓冲区不占用磁盘空间

    特点:
      两部分:
        读端, 写端, 对应两个文件描述符
        数据写端流入, 读端流出
      操作管道的进程被销毁之后, 管道自动占用的存储空间自动被释放
      管道默认阻塞, 读写都为阻塞

    原理:
      内部实现方式: 环形队列
      缓冲区大小默认为4k, 大小会根据时间情况做适当调整

    管道的局限性:
      自己先读数据时, 不能自己写, 阻塞
      自己先写数据时, 可以自己读
      数据自己读不能自己写? 有些疑问, 阻塞了?
      数据一旦被读走之后, 便不在管道中存在, 不可反复读取
      半双工的通信方式. 数据只能在一个方向上流动
      只能在公共祖先的进程间使用管道

    管道的读写行为

    使用管道需要注意以下4中特殊情况(假设都是阻塞I/O操作, 没有设置O_NONBLOCK标志):
    (1)如果所有指向管道写端的文件描述符都关闭(管道写端的引用计数为0), 而仍然有进程从管道的读端读数据, 那么管道中剩余的数据都被读取后, 再次read会返回0, 就像读到文件末尾一样
    (2)如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0), 而持有管道写端的进程也没有向管道中写数据, 这时有进程从管道读端读数据, 那么管道中剩余的数据都被读取后, 再次read会阻塞, 直到管道中有数据可读才读取数据并返回
    (3)如果所有指向管道读端的文件描述符都关闭(管道读端引用计数为0), 而持有管道写端的进程也没有向管道中写数据, 这时有进程向管道的写端write, 那么该进程会收到信号SIGPIPE, 通常会导致进程异常终止. 当然可以对SIGPIPE信号进行捕捉, 不终止进程
    (4)如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0), 而持有管道读端的进程也没有从管道中读数据, 这时有进程向管道写端写数据, 那么在管道被写满时再次write会阻塞, 直到管道中有空位置了才写入数据

    总结
      读操作:
        有数据: read(fd), 正常读, 返回读出的字节数
        无数据: (1) 写端全部关闭, read解除阻塞, 返回0, 相当于读文件到尾部; (2) 没有全部关闭, read阻塞
      写操作
        读端全部关闭: 管道破裂, 进程被终止, 内核给当前进程发送SIGPIPE
        读端没有全部关闭: (1) 缓冲区写满, write阻塞; (2) write继续写

    管道的优劣

    优点: 简单, 相比信号和套接字实现进程间通信简单很多
    缺点:

    1. 只能单向通信, 双向通信需要建立两个管道
    2. 只能用于父子, 兄弟(有共同祖先)间通信. 可使用命名管道fifo解决

    基础API

    pipe

    头文件: #include <unistd.h>

    int pipe(int pipefd[2]);
    函数调用成功后返回r/w两个文件描述符. 无须open, 但需手动close. 规定fd[0]读, fd[1]写. 向文件读写数据其实是在读写内核缓冲区

    管道创建成功以后, 创建该管道的进程(父进程)同时掌握着管道的读端和写端. 采用以下步骤实现父子进程间的通信:
    (1)父进程调用pipe函数创建管道, 得到两个文件描述符fd[0]和fd[1]指向管道的读端和写端
    (2)父进程调用fork创建子进程, 那么子进程也有两个文件描述符指向同一管道
    (3)父进程关闭管道读端, 子进程关闭管道写端. 父进程可以像管道中写入数据, 子进程将管道中的数据读出. 由于管道是利用环形队列实现的, 数据从写端流入管道, 从读端流出, 这样就实现了进程间通信

    父子进程使用管道通信, 思考:
      父子进程间通信是否需要sleep函数?
        父进程写的慢, 子进程读的快

    如何设置非阻塞?
      默认读写两端都阻塞
      设置读端为非阻塞pipe(fd)
      fcntl-变参函数: (1) 赋值文件描述符; (2) 修改文件属性-对应open时的flag属性
      设置方法:

    int flags = fcntl(fd[0], F_GETFL);
    // 设置新的flags
    flag |= O_NONBLOCK;
    fcntl(fd[0], F_SETFL, flags);
    

    fpathconf与pathconf

    头文件: #include <unistd.h>

    long fpathconf(int fd, int name);
    long pathconf(char *path, int name);

    查看管道缓冲区大小:
      命令: ulimit -a
      函数: fpathconf

    示例程序

    自己读写阻塞情况

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <string.h>
    
    int main(void) {
        int fd[2];
        char buf[10];
        char p[] = "haha";
    
        int ret = pipe(fd);
        if (ret == -1) {
            perror("pipe error");
            exit(1);
        }
    
        printf("pipe[0] = %d
    ", fd[0]);
        printf("pipe[1] = %d
    ", fd[1]);
    
        // 先读, 阻塞
        //ret = read(fd[0], buf, sizeof(buf));
    
        write(fd[1], p, sizeof(p));
    
        // 后写, 没有被阻塞
        ret = read(fd[0], buf, sizeof(buf));
    
        printf("ret = %d, %s, sizeof(buf)=%d
    ", ret, buf, sizeof(buf));
    
        close(fd[0]);
        close(fd[1]);
    
        return 0;
    }
    
    /*
    pipe[0] = 3
    pipe[1] = 4
    ret = 5, haha, sizeof(buf)=10
    */
    

    管道执行ps和grep命令

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <string.h>
    
    int main(int argc, const char* argv[]) {
        int fd[2];
        int ret = pipe(fd);
        if (ret == -1) {
            perror("pipe error");
            exit(1);
        }
    
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork error");
            exit(1);
        }
    
        // 父进程: ps -aux
        if (pid > 0) {
            // 写管道操作, 关闭读端
            close(fd[0]);
            // 文件描述符重定向, STDOUT_FILENO, 
            dup2(fd[1], STDOUT_FILENO);
            // 执行ps aux
            execlp("ps", "ps",  "aux", NULL);
            perror("excelp");
            exit(1);
        }
        // 子进程: grep "bash"
        else if (pid == 0) {
            close(fd[1]);
            dup2(fd[0], STDIN_FILENO);
            execlp("grep", "grep", "bash", "--color=auto", NULL);
        }
    
        printf("pipe[0] = %d 
    ", fd[0]);
        printf("pipe[1] = %d 
    ", fd[1]);
    
        close(fd[0]);
        close(fd[1]);
    
        return 0;
    }
    

    进程通信

    兄弟进程间通信, 父进程用于回收子进程

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <string.h>
    #include <sys/wait.h>
    
    int main(int argc, const char* argv[]) {
        int fd[2];
        int ret = pipe(fd);
        if (ret == -1) {
            perror("pipe error");
            exit(1);
        }
        
        int i = 0;
        for (i = 0; i < 2; i++) {
            pid_t pid = fork();
            if (pid == 0)
                break;
            if (pid == -1) {
                perror("fork error");
                exit(1);
            }
        }
    
        // 子进程1: ps -aux
        if (i == 0) {
            // 写管道操作, 关闭读端
            close(fd[0]);
            // 文件描述符重定向, STDOUT_FILENO, 
            dup2(fd[1], STDOUT_FILENO);
            // 执行ps aux
            execlp("ps", "ps",  "aux", NULL);
            perror("excelp");
            exit(1);
        }
        // 子进程2: grep "bash"
        else if (i == 1) {
            close(fd[1]);
            dup2(fd[0], STDIN_FILENO);
            execlp("grep", "grep", "bash", "--color=auto", NULL);
        }
        // 父进程回收子进程
        else if (i == 2) {
            close(fd[0]);
            close(fd[1]);
            wait(NULL);  
            // 回收子进程
            pid_t wpid;
            while ((wpid = waitpid(-1, NULL, WNOHANG)) != -1) {
                if (wpid == 0) 
                    continue;
                printf("child die pid = %d
    ", wpid);
            }
        }
    
        printf("pipe[0] = %d 
    ", fd[0]);
        printf("pipe[1] = %d 
    ", fd[1]);
    
        return 0;
    }
    

    查看管道缓冲区

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <string.h>
    
    int main(int argc, const char* argv[]) {
        int fd[2];
        int ret = pipe(fd);
        if (ret == -1) {
            perror("pipe error");
            exit(1);
        }
    
        // 测试管道缓冲区大小
        long size = fpathconf(fd[0], _PC_PIPE_BUF);
        printf("size = %ld
    ", size); 
            
        printf("pipe[0] = %d 
    ", fd[0]);
        printf("pipe[1] = %d 
    ", fd[1]);
    
        close(fd[0]);
        close(fd[1]);
    
        return 0;
    }
    
  • 相关阅读:
    NRF24L01 使用小结/自动应答/通道地址
    nRF24L01单片机通信的总结--看了就会用了
    彻底搞懂状态机(一段式、两段式、三段式)
    面向硬件的设计思维--时钟是电路的实际控制者
    组合逻辑电路-----竞争与冒险现象
    Win7、Win8、Win10系统USB-Blaster驱动程序无法安装的解决办法
    从DTFT到DFS,从DFS到DFT,从DFT到FFT,从一维到二维
    离散傅里叶级数DFS几个主要的特性
    离散傅里叶级数DFS
    Ubuntu12.10下Python(cx_Oracle)访问Oracle解决方案
  • 原文地址:https://www.cnblogs.com/hesper/p/10738854.html
Copyright © 2011-2022 走看看