管道调用
我们已经了解了高层的popen函数,现在我们继续来了解低层的pipe函数。这个函数提供了一个在两个函数之间传递数据的方法,而不必调用shell来解释所请求的命令的。同时他也为我们提供了更多的数据读写控制。
pipe函数的原型如下:
#include <unistd.h>
int pipe(int file_descriptor[2]);
pipe函数接受一个两个整数文件描述符的数组作为参数。他会使用两个新的文件描述符来填充这个数据并且返回一个零值。如果失败则会返回-1并且设置errno来指明失败原因。Linux手册页中所定义的错误如下:
EMFILE:进程正在使用的文件描述符过多
ENFILE:系统文件表已满
EFAULT:文件描述符不可用
返回的两个文件描述符以一个特殊的方式相连接。任何写入file_descriptor[1]的数据可以由file_descriptor[0]中读取。数据以先进先出的方式进行处理,通常简记为FIFO。这就意味着如果我们向file_descriptor[1]中写入字节1,2,3,那么由file_descriptor[0]中的读取就会产生1,2,3。这与堆栈不同,后者以后进先出的方式进行操作,通常简记为LIFO。
注意:在这里我们要清楚,这些是文件描述符,而不是文件流,所以我们必须使用低层的read与write调用来访问数据,而不是fread与fwrite。
下面的程序,pipe1.c,使用pipe来创建一个管道。
试验--pipe函数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int data_processed;
int file_pipes[2];
const char some_data[] = "123";
char buffer[BUFSIZ +1];
memset(buffer,'/0',sizeof(buffer));
if(pipe(file_pipes) == 0)
{
data_processed = write(file_pipes[1],some_data,strlen(some_data));
printf("Wrote %d bytes/n",data_processed);
data_processed = read(file_pipes[0],buffer,BUFSIZ);
printf("Read %d bytes: %s/n",data_processed,buffer);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
当我们运行这个程序,我们会得到下面的输出:
$ ./pipe1
Wrote 3 bytes
Read 3 bytes: 123
工作原理
这个程序使用两个文件描述符file_pipes[]来创建一个管道。然后他使用文件描述符file_pipes[1]向管道中写入数据,并且由file_pipes[0]中读取。注意管道有内部缓冲,从而可以在两个调用write与read之间存储数据。
我们应该清楚尝试使用file_pipes[0]写入数据,或是使用file_pipes[1]读取数据的效果是未定义的,所以这样的行为结果会非常奇怪,并且也许会毫无警告的发生改变。在作者的系统上,这样的调用会失败并且返回-1,这至少保证了是很容易捕获这个错误的。
粗看起来这个管道的例子似乎并没有为我们提供任何我们使用一个简单的文件不能完成的任务。管道的真正优点在我们希望在两个进程之间传递数据时才会体现出来。正如我们在第12章所看到的,当一个程序使用fork调用创建一个新进程时,以前打开的文件描述符会保持打开。通过在原始进程中创建一个管道,然后通过fork创建一个新进程,我们可以由一个进程向另一个进程传递数据。
试验--使用fork的管道
1 这是pipe2.c。这个程序的开始部分与第一个例子类似,直到我们调用fork。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int data_processed;
int file_pipes[2];
const char some_data[] = "123";
char buffer[BUFSIZ + 1];
pid_t fork_result;
memset(buffer,'/0',sizeof(buffer));
if(pipe(file_pipes)==0)
{
fork_result = fork();
if(fork_result == -1)
{
fprintf(stderr,"Fork failure");
exit(EXIT_FAILURE);
}
2 我们已经保证fork正常工作,所以如果fork_result等于0,我们是在子进程中:
if(fork_result == 0)
{
data_processed = read(file_pipes[0],buffer,BUFSIZ);
printf("Read %d bytes: %s/n",data_processed,buffer);
exit(EXIT_SUCCESS);
}
3 否则,我们一定在父进程中:
else
{
data_processed = write(file_pipes[1],some_data,strlen(some_data));
printf("Wrote %d bytes/n",data_processed);
}
}
exit(EXIT_SUCCESS);
}
当我们运行这个程序,我们得到与前面一样的输出:
$ ./pipe2
Wrote 3 bytes
Read 3 bytes: 123
我们也许会发现实际上命令提示符会在输出的最后部分之前出现,但是我们在这里已经对输出进行整理从而使其更易于阅读。
工作原理
首先这个程序使用pipe调用创建一个管道。然后他使用fork调用来创建一个新的进程。如果fork调用成功,父进程就会向管道写入数据,而子进程由管道中读取数据。父子进程都会在一个简单的write与read之后退出。如果父进程在子进程之前退出,我们就会看到shell提示符出现在两个输出之间。
尽管这个程序与前面的管道例子十分相似,但是我们已经通过使用单独的进程用于读写而向前进了一大步.