进程间通讯,进程与进程间的通信
我们已经在高层次上了解了popen函数。现在让我们继续学习底层的管道功能。该函数提供了一种在两个函数之间传递数据的方法,而无需调用shell来解释所请求的命令。同时也为我们提供了更多的数据读写控制。
管道函数的原型如下:
#包括unistd.h
int pipe(int file _ descriptor[2]);
管道函数接受两个整数文件描述符的数组作为参数。他将用两个新的文件描述符填充这个数据,并返回一个零值。如果失败,它将返回-1并设置errno来指示失败的原因。Linux手册页中定义的错误如下:
EMFILE:进程使用了太多的文件描述符。
ENFILE:系统文件表已满。
默认:文件描述符不可用。
由返回的两个文件描述符以特殊方式连接。写入file_descriptor[1]的任何数据都可以在file_descriptor[0]中读取。数据以先进先出的方式处理,通常缩写为FIFO。这意味着,如果我们将字节1,2,3写入file_descriptor[1],那么读取file_descriptor[0]将生成1,2,3。这与堆栈不同,堆栈以后进先出的方式操作,通常缩写为LIFO。
注意:这里我们要明确的是,这些是文件描述符,不是文件流,所以我们必须使用低级别的读写调用来访问数据,而不是fread和fwrite。
以下程序pipe1.c使用pipe创建管道。
实验管道功能
#包含stdio.h
#包含stdlib.h
#包含字符串. h
#包括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(管道(文件管道)==0)
{
data _ processed=write(file _ pipes[1],some_data,strlen(some _ data));
printf(已写入%d字节/n ,data _ processed);
data _ processed=read(file _ pipes[0],buffer,BUFSIZ);
printf(Read %d bytes: %s/n ,data_processed,buffer);
退出(EXIT _ SUCCESS);
}
退出(EXIT _ FAILURE);
}
当我们运行这个程序时,我们将得到以下输出:
$ ./管道1
写入了3个字节
读取3个字节:123
这个程序使用两个文件描述符file_pipes[]来创建一个管道。然后他使用文件描述符file_pipes[1]将数据写入管道,并从file_pipes[0]中读取数据。请注意,该管道有一个内部缓冲区,因此可以在两次调用write和read之间存储数据。
我们要清楚的是,试图用file_pipes[0]写数据或者用file_pipes[1]读数据的效果是未定义的,所以这种行为的结果会非常奇怪,可能会毫无预警地发生变化。在作者的系统上,这样的调用会失败并返回-1,这至少保证了很容易捕捉到这个错误。
似乎这个管道的例子并没有为我们提供任何一个简单文件无法完成的任务。管道的真正优势只有在我们想在两个进程之间传输数据的时候。正如我们在第12章中看到的,当一个程序使用fork call创建一个新进程时,先前打开的文件描述符将保持打开。通过在原始流程中创建一个管道,然后通过fork创建一个新流程,我们可以将数据从一个流程转移到另一个流程。
使用叉测试管道
1这是pipe2.c这个程序的开头类似于第一个例子,直到我们调用fork。
#包含stdio.h
#包含stdlib.h
#包含字符串. h
#包括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(管道(文件管道)==0)
{
fork_result=fork()。
if(fork_result==-1)
{
fprintf(stderr, Fork failure );
退出(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 _ SUCCESS);
}
3否则,我们必须在父进程中:
其他
{
data _ processed=write(file _ pipes[1],some_data,strlen(some _ data));
printf(已写入%d字节/n ,data _ processed);
}
}
退出(EXIT _ SUCCESS);
}
当我们运行这个程序时,我们得到和以前一样的输出:
$ ./管道2
写入了3个字节
读取3个字节:123
我们可能会发现命令提示符实际上出现在输出的最后一部分之前,但是我们在这里对输出进行了整理,以便于阅读。
首先,这个程序使用管道调用创建一个管道。然后,他使用fork调用创建一个新流程。如果fork调用成功,父进程将向管道写入数据,而子进程将从管道读取数据。在简单的读写之后,父进程和子进程都将退出。如果父进程在子进程之前退出,我们将看到shell提示符出现在两个输出之间。
尽管这个程序与前面的管道示例非常相似,但我们通过使用单独的读写过程向前迈进了一大步。