分类: C/C++
2010-03-12 10:09:51
管道是利用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
#include
#include
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);
Linux 还支持另外一种管道形式,称为命名管道,或 FIFO,这是因为这种管道的操作方式基于“先进先出”原理。上面讲述的管道类型也被称为“匿名管道”。命名管道中,首先写入管道的数据是首先被读出的数据。匿名管道是临时对象,而 FIFO 则是文件系统的真正实体,如果进程有足够的权限就可以使用 FIFO。FIFO 和匿名管道的数据结构以及操作极其类似,二者的主要区别在于,FIFO 在使用之前就已经存在,用户可打开或关闭 FIFO;而匿名管道只在操作时存在,因而是临时对象。
为了创建先进先出文件,可以从shell提示符使用mknod命令或可以在程序中使用mknod()系统调用。
mknod()系统调用的原型为:
#include <sys/type.h>
#inlcude <sys/state.h>
#include <fcntl.h>
#include <unistd.h>
int mknod(char *pathname,node_t mode, dev_t dev);
其中pathname是被创建的文件名称,mode表示将在该文件上设置的权限位和将被创建的文件类型(在此情况下为S_IFIFO),dev是当创建设备特殊文件时使用的一个值。因此,对于先进先出文件它的值为0。
一旦先进先出文件已经被创建,它可以由任何具有适当权限的进程利用标准的open()系统调用加以访问。当用open()调用打开时,一个先进先出文件和一个匿名管道具有同样的基本功能。即当管道是空的时候,read()调用被阻塞。当管道是满的时候,write()等待被阻塞,并且当用fcntl()设置O_NONBLOCK标志时,将引起read()调用和write()调用立即返回。在它们已被阻塞的情况下,带有一个EAGAIN错误信息。
由于命名管道可以被很多无关系的进程同时访问,那么,在有多个读进程和/或多个写进程的应用中使用FIFO是非常有用的。
多个进程写一个管道会出现这样的问题,即多个进程所写的数据混在一起怎么办?幸好系统有这样的规则:一个write()调用可以写管道能容纳(Linux为4K字节)的任意个字节,系统将保证这些数据是分开的。这表示多个写操作的数据在FIFO文件中并不混合而将被维持分离的信息。