阅读 488

linux 进程间通信之管道

1.概述

1.1 一个管道是一个字节流:

  • 使用管道时是不存在消息或消息边界,即读取进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块大小是什么。
  • 管道传递的数据是顺序的,即从管道中读取出来的字节的顺序与被写入管道的顺序一致。

1.2 从管道中读取数据:

  • 读取数据为空的管道将会堵塞直到至少有一个字节被写入到管道为止。
  • 管道的写入端没有关闭,那么读取数据将会堵塞直到所有管道的写入端关闭。
  • 管道的写入端关闭,那么进程可以读取任意大小的数据块,直至读完了管道中的所有数据即read()返回0 。

1.3 管道是单向的:

  • 管道中数据的传递方向是单向的,管道的一端用于写入,另一端用于读取。

1.4 管道的容量是有限的:

  • 管道其实是一个在内核内存中维护的缓冲器,这个缓冲器的存储能力是有限的。
  • 管道被填满之后,后续向管道写入操作都会被堵塞直到有读取进程读取管道中的数据。

1.5 可以确保写入不超过PIPE_BUF字节的操作是原子的:

  • 如果多个进程写入同一个管道,那么如果他们在一个时刻写入的数据量不超过PIPE_BUF字节,那么就可以确保写入的数据不会混淆。
  • 写入的数据块大小超过了PIPE_BUF字节,那么内核会将数据分割成几个较小的片段传输,在读者从管道中消耗数据时再附加上后续的数据。(write()调用会阻塞直到所有数据被写入到管道为止),所以当多个写入进程写入大数据块的时候,可能会出现数据交叉的现象。

2.创建和使用管道

#include<unistd.h>
int pipe(int filedes[2]);//return 0 on success,or -1 on error
复制代码
  • filedes[0]表示管道的读取端;

  • filedes[1]表示管道的写入端;

  • 使用read()和write()系统调用来在管道上执行I/O。

创建完管道之后处理文件描述符

3.关闭未使用管道文件描述符

3.1 读取进程需关闭其持有的管道写入描述符的原因:

read()会堵塞直到所有管道的写入描述符关闭为止。

3.2 写入进程需关闭其持有的管道读取描述符的原因:

当一个进程试图向管道中写入数据但没有任何进程拥有该管道的打开着的读取描述符的时候,内核会向写入进程发送一个SIGPIPE信号(默认会杀死进程),进程可以捕获或忽略该信号,这样write操作将会返回EPIPE的错误。收到SIGPIPE信号或得到EPIPE错误可以获知管道的状态,可以避免写入被堵塞的风险,因为一直写入数据没有读取,将会充满管道,那么后续的写入请求都将会被堵塞。

3.3 关闭未使用管道文件描述符的原因:

当所有进程中的管道文件描述符都关闭之后才会销毁该管道以及释放该管道占用的资源以供其他进程复用。同时,管道中的数据都会丢失。

4.管道可以作为一种进程同步的方法

父进程在创建子进程之前构建了一个管道。每个子进程会继承管道的写入端的文件描述符并完成动作之后关闭这些描述符。当所有子进程都关闭了管道的写入端的文件描述符之后,父进程在管道上的read()就会结束并返回文件结束(0)。这时,父进程就能够做其他工作了。(父进程要关闭管道的写入端,否则将永远阻塞。)

   switch (fork()) {
        case -1:
                ERR_EXIT("fork");
        case 0:
                if (close(pfd[0]) == -1)
                    ERR_EXIT("close");
                
                //Child does some work,and lets parent know It‘s done
                
                if(close(pfd[1])==-1)
                    ERR_EXIT("close");

                //child now carries on to do other things...
               
                _exit(EXIT_SUCCESS);
        default:
                break;
    }

    if(close(pfd[1])==-1)
        ERR_EXIT("close");
    //parent may do other work,then synchronizes with children

    if(read(pfd[0],&dummy,1)!=0)//block
        ERR_EXIT("read");
 
复制代码

5.通过管道与shell命令进行通信:popen()

#include<stdio.h>
FIFE *popen(const char *command,const char *mode);
    //return file stream,or NULL on error
int pclose(FILE *stream);
    //return termination status of child process,or -1 on error
复制代码

popen() 函数创建了一个管道,然后创建了一个子进程来执行shell,而shell又创建了一个子进程来执行command字符串。mode参数是一个字符串,它确调用进程是从管道中读取数据(mode是r)还是将数据写入到管道中(mode是w)。

mode的取值确定了所执行的命令的标准输出是连接到管道的写入端还是将其标准输入连接到管道的读取端。

调用进程fp--(fork(),exec())-->/bin/sh--(fork(),exec())-->命令(stdout)--(管道)-->调用进程fp

a)mode is r

调用进程fp--(fork(),exec())-->/bin/sh--(fork(),exec())-->命令(stdin)

调用进程fp--(管道)-->命令stdin

b) mode is w

关注下面的标签,发现更多相似文章
评论