父子进程
我们的pipe调用探索的下一步就是使得子进程是与父进程不同的一个程序,而不是运行相同程序的另一个进程。我们可以使用exec调用来完成这个任务。这样做的一个困难就是通过exec执行的新进程需要知道访问哪一个文件描述符。在exec调用之后,就不再是这样的情况了,因为老进程已经被新的子进程所替代。我们可以通过向exec所执行的新进程传递文件描述符作为参数就可以解决这个问题。
要显示这是如何工作的,我们需要两个程序。第一个就是数据生产者,他创建管道并且调用子进程,数据消费者。
试验--管道与exec
1 作为第一个程序,我们将pipe2.c修改为pipe3.c。对比这两个文件我们可以看到修改的行:
#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 == (pid_t) -1)
{
fprintf(stderr,"Fork failure");
exit(EXIT_FAILURE);
}
if(fork_result == 0)
{
sprintf(buffer,"%d",file_pipes[0]);
(void)execl("pipe4","pipe4",buffer,(char *)0);
exit(EXIT_FAILURE);
}
else
{
data_processed = write(file_pipes[1],some_data,strlen(some_data));
printf("%d - wrote %d bytes\n",getpid(),data_processed);
}
}
exit(EXIT_SUCCESS);
}
2 读取数据的消费者程序,pipe4.c,与此相类似:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int data_processed;
char buffer[BUFSIZ +1];
int file_descriptor;
memset(buffer,'\0',sizeof(buffer));
sscanf(argv[1],"%d",&file_descriptor);
data_processed = read(file_descriptor,buffer,BUFSIZ);
printf("%d - read %d bytes: %s\n",getpid(),data_processed,buffer);
exit(EXIT_SUCCESS);
}
记住,当我们运行pipe3程序时,pipe3会为我们调用pipe4,我们会得到下面的结果:
$ ./pipe3
980 - wrote 3 bytes
981 - read 3 bytes: 123
工作原理
pipe3程序的开始部分与前面的例子相似,使用pipe调用来创建一个管道然后使用fork调用来创建一个新的进程。然后他使用sprintf将管道的"读"文件描述号存入一个缓冲区并构成pipe4程序的一个参数。
使用execl调用来调用pipe4程序。execl的参数如下:
要调用的程序
argv[0]为程序名
argv[1]包含我们希望程序由其中进行读取的文件描述号
(char *)0结束参数
pipe4程序由参数字符串获取文件描述符号并且由这个文件描述符中进行读取以获取数据。
读取关闭的管道
在我们继续之前,我们需要更为仔细的了解一下打开的文件描述符。直到此时,我们只是使得读取进程简单的读取一些数据然后退出,假定Linux会作为进程结束的一部分进行文件清理。
大多数由标准输入读取数据的程序的行为方式与我们到目前为止所见到的例子有些不同。他们通常并不知道他们要读取多少数据,所以他们通常进行循环,读取,处理,然后读取更多的数据,直到没有更多的数据可以读取。
一个read调用通常是阻塞的,也就是说,他会使得进程等待直到有数据变为可用。如果管道的另一端已经被关闭,那么就没有进程使得管道打开用于写入,而read就会阻塞。因为这并不是非常有益的,在并没有打开进行写入的管道上调用read会返回零而不是返回阻塞。这使得读取进程可以象检测文件结尾一样检测管道并且进行正确的动作。
注意,这与读取一个不可用的文件描述符时并不一样,此时read会认为这是一个错误并且返回-1来表示错误。
如果我们通过一个fork调用来使用管道,那么就会有两个不同的文件描述符可以供我们使用来写入管道:一个是父进程中的而另一个是子进程中的。我们必须在管道被认为是关闭的之前关闭父子进程中的write文件描述符,否则管道上的read调用就会失败。当我们返回到这个主题来更为详细的了解O_NONBLOCK标记与FIFO时,我们会看到这样的一个例子。
作为标准输入输出使用的管道
现在我们知道如何使在一个空管道上的read调用失败,我们可以看到通过一个管道连接两个进程的更为简洁的方法。我们为管道文件描述中的一个指定一个已知的值,通常是标准输入0,或是标准输出1。在父进程中进行设置会更为复杂,但是他会使得子程序更为简单。
这样做的一个好处就是我们可以调用那些不需要文件描述符作为参数的标准程序。为了这样做,我们需要使用dup函数,这个函数我们在第3章已经了解过了。有两个紧密相关的dup版本,其函数原型如下:
#include <unistd.h>
int dup(int file_descriptor);
int dup2(int file_descriptor_one, int file_descriptor_two);
dup调用的目的就是打开一个新的文件描述符,与open调用有些类似。所不同的就是由dup所创建的新的文件描述符与已存在的文件描述符指向同一个文件(或管道)。在dup调用的情况下,新的文件描述符总是最小可用的整数,而dup2调用的情况中,创建的文件描述符或者等于参数file_descriptor_two,或者是大于参数file_descriptor_two的第一个可用的文件描述符。
注意:我们可以通过使用fcntl调用与F_DUPFD命令来达到与dup和dup2相同的效果。也就是说,dup调用更容易使用,因为他更适合于创建复制文件描述符的需要。他也是非常通用的,所以我们会发现在已存在的程序中,他比fcntl与F_DUPFD更为常见。
那么dup是如何帮助我们在两个进程之间传递数据的呢?这个小技巧就在于标准输入描述符总是0,而dup总是使用最小可用的数字返回新的描述符。通过首先关闭文件描述符0,然后调用dup,新的文件描述符就是数字0。因为新的描述符是已存在文件描述符的一个复制,标准输入已经发生改变来访问具有我们传递给dup的文件描述符的文件或是管道。我们将会创建指向同一个文件或是管道的两个文件描述符,而其中的一个将是标准输入。
通守close与dup操作文件描述
理解当我们关闭文件描述符0并且调用dup之后发生了什么的最简单的方法就是查看在这一过程中前四个文件描述符的状态生了哪些变化。如下表所示:
文件描述符号 初始值 关闭文件描述0之后 dup调用之后
0 标准输入 关闭 管道文件描述符
1 标准输出 标准输出 标准输出
2 标准错误输出 标准错误输出 标准错误输出
3 管道文件描述符 管道文件描述符 管道文件描述符
试验--管道与dup
让我们回到我们前面的例子中,但是这一次我们会使得子程序使用我们所创建的管道的读端来替换其stdin文件描述符。我们也会做一些清理工作,从而子程序可以正确的检测管道中数据的结束。如平时一样,为了代码的简洁,我们忽略了一些错误检测。
修改pipe3.c为pipe5.c,代码如下:
#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";
pid_t fork_result;
if(pipe(file_pipes) == 0)
{
fork_result = fork();
if(fork_result == (pid_t) -1)
{
fprintf(stderr,"Fork failure");
exit(EXIT_FAILURE);
}
if(fork_result == (pid_t) 0)
{
close(0);
dup(file_pipes[0]);
close(file_pipes[0]);
close(file_pipes[1]);
execlp("od","od","-c",(char *)0);
exit(EXIT_FAILURE);
}
else
{
close(file_pipes[0]);
data_processed = write(file_pipes[1],some_data,strlen(some_data));
close(file_pipes[1]);
printf("%d - wrote %d bytes\n",(int)getpid(),data_processed);
}
}
exit(EXIT_SUCCESS);
}
如果我们运行这个程序,我们会得到下面的结果:
$ ./pipe5
1239 - wrote 3 bytes
0000000 1 2 3
0000003
工作原理
与前面的例子一样,程序创建一个管道然后进行fork调用生成一个子进程。此时,父子进程都具有访问管道的文件描述符,用于读写的各一个,所以共有四个打开的文件描述符。
让我们首先来看一下子进程。子进程通过close(0)来关闭其标准输入,然后调用dup(file_pipes[0])。这会复制与管道读端相关联的文件描述符作为文件描述符0,标准输入。子进程然后关闭由pipe调用所获得的用于读操作的原始文件描述符,file_pipes[0]。因为子进程绝不会向管道写入,他同时也关闭了与管道相关联的写文件描述符,file_pipes[1]。现在他只有与管道相关联的一个文件描述符:文件描述符0,其标准输入。
子进程然后使用exec来调用读取标准输入的任何程序。在这个例子中,我们使用od命令。od命令会等待可用的数据,如同他正等待用户终端的输入一样。事实上,如果没有一些特殊的代码显示的检测这些区别,他并不会知道其输入是来自一个管道,而不是一个终端。
父进程通过关闭管道的读端file_pipes[0]开始,因为他绝不会由管道中读取。他然后向管道中写入数据。当写入所有的数据以后,父进程会关闭管道的写端并且退出。因为没有可以向管道中写入的打开的文件描述符,od程序可以读取写入管道的三个字节,但是后续的读取会返回0字节,表明文件的结束。当读取返回0时,od程序退出。这与在一个终端上运行od命令相类似,然而后者是按下Ctrl+D来向od命令表明文件结束。
图13-3表明了pipe调用之后的操作序列,图13-4表明了fork调用之后的操作序列,而图13-5表示程序已准备好来传输数据。
分享到:
相关推荐
### Linux进程间通信与同步详解 #### 一、概述 在多任务操作系统中,进程间通信与同步机制是解决进程间数据交换与资源共享的关键技术。这些机制确保了多个并发运行的任务能够有效地协作,并且避免了资源冲突。对于...
七种进程间通信方式: 一.无名管道( pipe ) 二.有名管道( fifo ) 三.共享内存 ( shared memory ) 四.信号 ( sinal ) 五.消息队列 ( message queue ) 六.信号量 ( semophore ) 七.套接字 ( socket ) 进程间通信...
VC++ 四种进程间通信的完整实例,一般来说,进程通信采取四种形式:剪贴板、匿名管道、命名管道、邮槽。孙鑫将带你用实例来验证这四种方式的优略性,进而帮助VC编程者熟悉掌握进程间通信的方方面面,并附有孙鑫老师...
UNIX/Linux 进程间通信实验教程 UNIX/Linux 操作系统提供了一组系统调用作为用户使用其进程间通信机制的接口。进程间通信是指在多任务计算机系统中,每个进程都具有自己独立的全局变量和局部变量,子进程几乎完全...
进程间通信之信号 sinal ) 唯一的异步通信方式 七种进程间通信方式: 一 无名管道( pipe ) 二 有名管道( fifo ) 三 共享内存 shared memory 四 信号 sinal 五 消息队列 message queue ) 六 信号量 ...
接下来,书中全面深入地介绍了以下四种进程间通信形式: 1. 消息传递,包括管道(pipe)、FIFO(命名管道)以及消息队列。管道是最早用于UNIX系统进程间通信的机制,它允许两个进程进行单向通信。FIFO则在此基础上...
实验将涉及四种主要的进程间通信方式: 1. **管道通信**:管道是一种半双工通信方式,数据只能单向流动。它利用文件系统中的匿名管道,允许一个进程写入数据,另一个进程读取。管道的容量有限,且不支持随机访问,...
操作系统实验报告(LINUX进程间通信) 操作系统实验报告(LINUX进程间通信)是操作系统课程的一部分,涵盖了Linux进程间通信的原理和应用,包括消息队列、C/S结构等内容。下面将对这些知识点进行详细的解释。 一、...
进程间通信之套接字( socket ) 网络间通信 七种进程间通信方式: 一.无名管道( pipe ) 二.有名管道( fifo ) 三.共享内存 ( shared memory ) 四.信号 ( sinal ) 五.消息队列 ( message queue ) 六.信号量 ( ...
#### 四、其他进程间通信机制 除了管道之外,Linux还提供了多种其他的进程间通信机制,包括消息队列、共享内存、信号量和套接口等。 ##### 1. 消息队列 消息队列允许进程发送和接收消息,消息可以携带任意类型的...
本文将详细解析标题“四种进程间通信的方式demo”所涵盖的四个主要方法:Activity、Service与AIDL、ContentProvider以及BroadcastReceiver,并结合提供的文件“ProcessCommunication_Client”和“Process...
Linux进程间通信发展历程包括早期UNIX进程间通信、基于System V进程间通信、基于Socket进程间通信和POSIX进程间通信。 目前Linux使用的进程间通信方式包括管道、消息队列、共享内存、信号量和信号等。 管道通信是...
在编程领域,进程间通信(IPC,Inter-Process Communication)是一项关键的技术,它允许不同的进程之间交换数据,协同工作。Delphi,作为一个强大的面向对象的编程环境,提供了多种方式进行进程间通信。本篇文章将...
该书共分为四部分,涵盖了从基础套接口编程到高级套接口编程的多个方面,旨在为读者提供全面的进程间通信知识和编程实践。 第一部分介绍和TCP/IP是本卷的起点,讲述了UNIX网络编程的基础知识和TCP/IP协议族的构成。...
### Linux进程间通信方式之socket使用实例详解 #### 一、引言 在现代操作系统中,进程间的通信(IPC)是实现多进程协同工作的重要手段之一。Linux提供了多种进程间通信的方法,包括信号量、消息队列、共享内存以及...
进程间通信之消息队列 ( message queue ) 消息队列是消息的链表,具有特定的格式,并由消息队列标识符标识. 七种进程间通信方式: 一.无名管道( pipe ) 二.有名管道( fifo ) 三.共享内存 ( shared memory ) 四....
在Linux操作系统中,进程间通信(IPC,Inter-Process Communication)是多个进程之间共享数据、交换信息的关键技术。本文将深入探讨Linux进程间通信的多种方法、原理以及实际应用。 一、管道(Pipe) 管道是一种...
在Windows操作系统中,进程间通信(IPC,Interprocess Communication)是一种技术,允许不同的进程之间共享数据、协调工作或交换信息。这种技术对于多线程和多进程应用的开发至关重要,尤其是在分布式系统和并发编程...
### Binder与进程间通信 #### 一、Binder机制概述 Binder机制是Android系统中实现进程间通信(Inter-Process Communication, IPC)的核心架构之一。它采用客户端/服务器(Client/Server, C/S)架构,允许不同进程...