管道是利用pipe()系统调用而不是利用open()系统调用建立的。pipe()调用的原型是:
int pipe(int fd[2])
我们看到,有两个文件描述符与管道结合在一起,一个文件描述符用于管道的read()端,一个文件描述符用于管道的write()端。由于一个函数调用不能返回两个值,pipe()的参数是指向两个元素的整型数组的指针,它将由调用两个所要求的文件描述符填入。
fd[0]元素将含有管道read()端的文件描述符,而fd[1]含有管道write()端的文件描述符。系统可根据fd[0]和fd[1]分别找到对应的file
结构。在第8章我们会描述pipe()系统调用的实现机制。
注意,在pipe的参数中,没有路径名,这表明,创建管道并不象创建文件一样,要为它创建一个目录连接。这样做的好处是,其它现存的进程无法得到该管道的文件描述符,从而不能访问它。那么,两个进程如何使用一个管道来通信呢
?
我们知道,fork()和exec()系统调用可以保证文件描述符的复制品既可供双亲进程使用,也可供它的子女进程使用。也就是说,一个进程用pipe()系统调用创建管道,然后用fork()调用创建一个或多个进程,那么,管道的文件描述符将可供所有这些进程使用。pipe()系统调用的具体实现将在下一章介绍。
这里更明确的含义是:一个普通的管道仅可供具有共同祖先的两个进程之间共享,并且这个祖先必须已经建立了供它们使用的管道。
注意:在管道中的数据始终以和写数据相同的次序来进行读,这表示lseek()系统调用对管道不起作用。
下面给出在两个进程之间设置和使用管道的简单程序:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
int
fd[2], nbytes;
pid_t
childpid;
char
string[] = "Hello, world!\n";
char
readbuffer[80];
pipe(fd);
if((childpid = fork()) == -1)
{
printf("Error:fork");
exit(1);
}
if(childpid == 0) /* 子进程是管道的写进程 */
{
close(fd[0]); /*关闭管道的读端 */
write(fd[1], string, strlen(string));
exit(0);
}
else
/* 父进程是管道的读进程 */
{
close(fd[1]); /*关闭管道的写端 */
nbytes = read(fd[0], readbuffer,
sizeof(readbuffer));
printf("Received string: %s",
readbuffer);
}
return(0);
}
注意,在这个例子中,为什么这两个进程都关闭它所不需的管道端呢?这是因为写进程完全关闭管道端时,文件结束的条件被正确地传递给读进程。而读进程完全关闭管道端时,写进程无须等待继续写数据。
阻塞读和写分别成为对空和满管道的默认操作,这些默认操作也可以改变,这就需要调用fcntl()系统调用,对管道文件描述符设置O_NONBLOCK标志可以忽略默认操作:
# include <fcntl.h>
fcntl(fd,F_SETFL,O_NONBlOCK);