进程间通信1—管道

 

一、Linux管道的实现机制

在Linux中,管道是一种使用非常频繁的通信机制。从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:

    限制管道的大小。实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不象文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。

      读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。

注意:从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据。

 

二、信号和消息的区别

    我们知道,进程间的信号通信机制在传递信息时是以信号为载体的,但管道通信机制的信息载体是消息。那么信号和消息之间的区别在哪里呢? 

   首先,在数据内容方面,信号只是一些预定义的代码,用于表示系统发生的某一状况;消息则为一组连续语句或符号,不过量也不会太大。在作用方面,信号担任进程间少量信息的传送,一般为内核程序用来通知用户进程一些异常情况的发生;消息则用于进程间交换彼此的数据。 

   在发送时机方面,信号可以在任何时候发送;信息则不可以在任何时刻发送。在发送者方面,信号不能确定发送者是谁;信息则知道发送者是谁。在发送对象方面,信号是发给某个进程;消息则是发给消息队列。在处理方式上,信号可以不予理会;消息则是必须处理的。在数据传输效率方面,信号不适合进大量的信息传输,因为它的效率不高;消息虽然不适合大量的数据传送,但它的效率比信号强,因此适于中等数量的数据传送。 

    三、管道和命名管道的区别 

   我们知道,命名管道和管道都可以在进程间传送消息,但它们也是有区别的。 

   管道技术只能用于连接具有共同祖先的进程,例如父子进程间的通信,它无法实现不同用户的进程间的信息共享。再者,管道不能常设,当访问管道的进程终止时,管道也就撤销。这些限制给它的使用带来不少限制,但是命名管道却克服了这些限制。 

   命名管道也称为FIFO,是一种永久性的机构。FIFO文件也具有文件名、文件长度、访问许可权等属性,它也能像其它Linux文件那样被打开、关闭和删除,所以任何进程都能找到它。换句话说,即使是不同祖先的进程,也可以利用命名管道进行通信。 

   如果想要全双工通信,那最好使用Sockets API。下面我们分别介绍这两种管道,然后详细说明用来进行管道编程的编程接口和系统级命令。

 

四、进程间通信管道编程 

   在利用管道技术进行编程时,处理要用到上面介绍的pipe函数外,还用到另外三个函数,如下所示。

l pipe函数:该函数用于创建一个新的匿名管道。

l dup函数:该函数用于拷贝文件描述符。

    1、创建管道

   函数pipe用来建立一个新的管道,该管道用两个文件描述符进行描述。函数pipe的原型如下所示:
#include <unistd.h> 

int pipe( int fds[2] );

当调用成功时,函数pipe返回值为0,否则返回值为-1。成功返回时,数组fds被填入两个有效的文件描述符。数组的第一个元素中的文件描述符fds[0]供应用程序读取之用,数组的第二个元素中的文件描述符fds[1]可以用来供应用程序写入。 

无名管道是临时的,执行完之后就会自动消失;无名管道的通信是单工的。

 

2、读无名管道

 

从无名管道读取数据是采用无缓冲区I/O的方式实现的

ssize_t read (int fd, void *buf, size_t nbytes)

第一个参数fd为打开的管道文件描述符

buf为读出数据的存储位置

nbytes为读取数据的大小

此函数的功能是从fd中读取nbytes个字节保存在buf中。

返回值:如果成功,将返回时机读取的字节数

            如果失败,将返回-1;如果读取的字节数小于nbytes,则返回读取的字节         如果读到末端,则返回0;

            如果读一个空管道,将会阻塞;

 

3、写无名管道

 

如果向已近满了的管道写入,系统会自动阻塞;一个管道不能同时被两个管道打开,系统通过调用无缓冲区I/O系统调用函数write方式实现对管道的写操作:

ssize_t write (int fd, void *buf, size_t n)

从buf指向的缓冲区向管道写入n个字节,且每次写入的数据都附加在管道尾端,为了防止写入阻塞和交叉写入,建议每次写入的数据小于管道的缓冲区PIPE_BUF(默认为4096个字节)。
   下面我们考察在一个包含多个进程的应用程序中的管道示例。

 

 

 

 

在该程序中,先创建一个管道,然后进程在分叉,变成一个父进程和一个子进程。在子进程中,我们向管道的输入描述符写数据,然后在父进程中等待子进程写入数据结束后,读出数据,并打印出来

执行结果为:

[root@localhost gdao]# ./commi1

now ,write data to pipe

the write data is hello world!!!

read data is hello world!!![root@localhost gdao]# 

   在我们的这个程序中需要加以注意的是,我们的子进程是如何继承父进程利用pipe函数建立的文件描述符的,以及如何利用该文件描述符进行通信的。函数fork一旦执行,子进程会继承父进程的功能和管道的文件描述符,但对于内核来说,父进程和子进程是平等的,它们是独立运行的。也就是说,两个进程分别具有单独的内存空间,它们正是通过pipe函数来互通有无的。

五,重定向

 dup和dup2也是两个非常有用的调用,它们的作用都是用来复制一个文件的描述符。它们经常用来重定向进程的stdin、stdout和stderr。这两个函数的原型如下所示:
#include <unistd.h>

int dup( int fd ); 

int dup2( int fd, int fd2 )

利用函数dup,我们可以复制一个原来已近打开的描述符。传给该函数一个既有的描述符,它就会返回一个新的描述符,这个新的描述符指向系统文件表中下一个可用的最小非负文件描述符。这意味着,这两个描述符共享同一个数据结构

 

dup2函数跟dup函数相似,但dup2函数允许调用者规定一个有效描述符和目标描述符的iddup2函数成功返回时,目标描述符(dup2函数的第二个参数)将变成源描述符(dup2函数的第一个参数)的复制品,换句话说,两个文件描述符现在都指向同一个文件,并且是函数第一个参数指向的文件。下面我们用一段代码加以说明: 

int oldfd; 

oldfd = open("app_log", (O_RDWR | O_CREATE), 0644 ); 

dup2( oldfd, 1 ); 

close( oldfd );

本例中,我们打开了一个新文件,称为“app_log”,并收到一个文件描述符,该描述符叫做fd1。我们调用dup2函数,参数为oldfd1,这会导致用我们新打开的文件描述符替换掉由1代表的文件描述符(即stdout,因为标准输出文件的id1)。任何写到stdout的东西,现在都将改为写入名为“app_log”的文件中。需要注意的是,dup2函数在复制了oldfd之后,会立即将其关闭,但不会关掉新近打开的文件描述符,因为文件描述符1现在也指向它。 
下面我们介绍一个更加深入的示例代码。我们就用一个C程序来加以说明这个过程的实现:将ls --help命令的标准输出作为标准输入连接到more命令。

 

在示例代码中,首先在第13行代码中建立一个管道,然后将应用程序分成两个进程:一个子进程(第23–27行)和一个父进程(第29–35行)。接下来,使用dup2函数把stdout重定向到管道(pfds[1])。(第26行),然后提供了ls --help命令功能,不过它不是写到stdout,而是写到我们建立的管道的输入端,这是通过dup2函数来完成重定向的。然后,使用execlp函数把子进程的映像替换为命令ls --help的进程映像,一旦该命令执行,它的任何输出都将发给管道的输入端。 

    现在来研究一下管道的接收端。从代码中可以看出,管道的接收端是由父进程来担当的。再一次用到dup2函数(第32行),让stdin变成管道的输出端,这是通过让文件描述符0(即常规的stdin)等于pfds[0]来实现的。关闭管道的stdout端(pfds[1]),因为在这里用不到它。最后,使用execlp函数把父进程的映像替换为命令more的进程映像,命令more把管道的内容作为它的输入(第33行)。

   在该程序中,需要格外关注的是,我们的子进程把它的输出重定向的管道的输入,然后,父进程将它的输入重定向到管道的输出。这在实际的应用程序开发中是非常有用的一种技术。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值