1.引言
管道是UNIX系统IPC的最古老形式,在shell下的表现形式为管道线。每当在管道线中输入一个由shell执行的命令序列时,shell为每一条命令单独创建一进程,然后将前一条命令进程的标准输出用管道与后一条命令的标准输入相连接。管道有两个主要局限:
①管道是半双工的,即数据只能在一个方向上流动。虽然某些系统提供全双工管道但是为了可移植性,不能假定系统提供此功能。
②管道只能在具有公共祖先的进程之间使用。(一般就是父子进程之间)
2.pipe函数
管道由pipe函数创建。
#include <unistd.h>
int pipe(int filedes[2]);
//返回值:成功0,出错-1
经由参数filedes返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开(读0写1)。filedes[1]的输出是filedes[0]的输入。
可以用fidldes[0] 或fidldes[1]来调用fstat函数。
单个进程中的管道几乎没有任何用处。通常,调用pipe函数的进程接着调用fork,这样就创建了从父进程到子进程(或反向)的IPC通道。对于从父进程到子进程的管道,父进程关闭管道的读端,子进程则关闭写端。对于反向的管道,父进程关闭写端,子进程则关闭读端。如下图:
可以直接调用read和write对管道描述符进行读写。更常见的方法是将管道描述符复制为标准输入和标准输出。在此之后,子进程通常执行另一个程序,该程序或者从标准输入(管道的读端)读数据,或者将数据写到其标准输出(管道的写端)。
在写管道时,常量PIPE_BUF规定了内核中管道缓冲区的大小。如果有多个进程同时写一个管道,而且有进程要求写的字节数超过PIPE_BUF字节数时,则写操作的数据可能互相穿插。用pathconf或fpathconf函数可以确定PIPE_BUF的值。
当管道的一端被关闭后,下列的两条规则起作用:
①当读一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,以指示达到了文件结尾处。
②如果写一个读端已被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从其信号处理函数返回,则write返回-1,errno设置为EPIPE。
3.示例
//经由管道父进程向子进程传递数据
#include"apue.h"
#define MAXLINESIZE 100
int main()
{
int n;
int fd[2];
pid_t pid;
char line[MAXLINESIZE];
if(pipe(fd)<0)
err_sys("pipe error");
if((pid=fork())<0)
err_sys("fork error");
else if(pid>0) //父进程
{
close(fd[0]);//关闭读端
write(fd[1],"data from parent process\n",25);
}
else //子进程
{
close(fd[1]);//关闭写端
n=read(fd[0],line,MXALINESIZE);
write(STDOUT_FILENO,line,n);
}
exit(0);
}
4.FIFO
FIFO就是命名管道。管道只能由相关进程使用,这些相关进程的共同祖先进程创建了管道。但是通过命名管道,不相关的进程也能交换数据。
FIFO是一种文件类型。在stat结构中的st_mode的编码指明文件是否FIFO类型。创建FIFO类似于创建文件:
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
//返回值:若成功返回0,若出错返回-1
//mode参数和open中的mode参数的规格说明是一样的
因为FIFO是一种文件,所以一旦用mkfifo创建了一个FIFO,就可以用open打开它。其实一般的输入输出函数(close,read,write,unlink等)都可以用于FIFO。
当打开一个FIFO时,非阻塞标志(O_NONBLOCK)产生下列影响:
在一般情况下,即没有指定O_NONBLOCK,只读open要阻塞到某个其它进程为写而打开此FIFO。类似地,只写open要阻塞到某个其它进程为读而打开它。
如果指定了O_NONBLOCK,则只读open立即返回。但是,如果没有进程为读而打开一个FIFO,那么只写open将出错返回-1,errno设置为ENXIO。
类似于管道,若用write写一个尚无进程为读而打开的FIFO,则产生信号SIGPIPE。若某个FIFO的最后一个写进程关闭了该FIFO,则将为该FIFO的读进程产生一个文件结束标志。