哈尔滨理工大学软件工程专业08-7李万鹏原创作品,转载请标明出处
http://blog.csdn.net/woshixingaaa/archive/2010/06/10/5661164.aspx
普通的Linux shell 都允许重定向,而重定向使用的就是管道。例如:
$ ls | pr | lpr
把命令ls (列出目录中的文件)的输出通过管道连接到命令pr 的标准输入上进行分页。最后,命令pr 的标准输出通过管道连接到命令lpr 的标准输入上,从而在缺省打印机上打印出结果。进程感觉不到这种重定向,它们和平常一样地工作。正是shell 建立了进程之间的临时管道。
管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。
传统上有很多种实现管道的方法,如利用文件系统、利用套接字(sockets )、利用流等。在Linux 中,使用两个file 数据结构来实现管道。这两个file 数据结构中的f_inode (f_dentry )指针指向同一个临时创建的VFS I 节点,而该VFS I 节点本身又指向内存中的一个物理页,如图5.1 所示。两个file 数据结构中的f_op 指针指向不同的文件操作例程向量表:一个用于向管道中写,另一个用于从管道中读。这种实现方法掩盖了底层实现的差异,从进程的角度来看,读写管道的系统调用和读写普通文件的普通系统调用没什么不同。当写进程向管道中写时,字节被拷贝到了共享数据页,当读进程从管道中读时,字节被从共享页中拷贝出来。Linux 必须同步对于管道的存取,必须保证管道的写和读步调一致。Linux 使用锁、等待队列和信号(locks ,wait queues and signals )来实现同步。
图5.1 管道示意图
参见include/linux/inode_fs.h
当写进程向管道写的时候,它使用标准的write 库函数。这些库函数(read 、write 等)要求传递一个文件描述符作为参数。文件描述符是该文件对应的file 数据结构在进程的file 数据结构数组中的索引,每一个都表示一个打开的文件,在这种情况下,是打开的管道。Linux 系统调用使用描述这个管道的file 数据结构中f_op 所指的write 例程,该write 例程使用表示管道的VFS I 节点中存放的信息,来管理写请求。如果共享数据页中有足够的空间能把所有的字节都写到管道中,而且管道没有被读进程锁定,则Linux 就在管道上为写进程加锁,并把字节从进程的地址空间拷贝到共享数据页。如果管道被读进程锁定或者共享数据页中没有足够的空间,则当前进程被迫睡眠,它被挂在管道I 节点的等待队列中等待,而后调用调度程序,让另外一个进程运行。睡眠的写进程是可以中断的(interruptible ),所以它可以接收信号。当管道中有了足够的空间可以写数据,或者当锁定解除时,写进程就会被读进程唤醒。当数据写完之后,管道的VFS I 节点上的锁定解除,在管道I 节点的等待队列中等待的所有读进程都会被唤醒。
参见fs/pipe.c pipe_write()
从管道中读取数据和写数据非常相似。Linux 允许进程无阻塞地读文件或管道(依赖于它们打开文件或者管道的模式),这时,如果没有数据可读或者管道被锁定,系统调用会返回一个错误。这意味着进程会继续运行。另一种方式是阻塞读,即进程在管道I 节点的等待队列中等待,直到写进程完成。
如果所有的进程都完成了它们的管道操作,则管道的I 节点和相应的共享数据页会被废弃。
参见fs/pipe.c pipe_read()
Linux 也支持命名管道(也叫FIFO ,因为管道工作在先入先出的原则下,第一个写入管道的数据也是第一个被读出的数据)。与管道不同,FIFO 不是临时的对象,它们是文件系统中真正的实体,可以用mkfifo 命令创建。只要有合适的访问权限,进程就可以使用FIFO 。FIFO 的打开方式和管道稍微不同。一个管道(它的两个file 数据结构、VFS I 节点和共享数据页)是一次性创建的,而FIFO 已经存在,可以由它的用户打开和关闭。Linux 必须处理在写进程打开FIFO 之前读进程对它的打开,也必须处理在写进程写数据之前读进程对管道的读。除此以外,FIFO 几乎和管道的处理完全一样,而且它们使用一样的数据结构和操作。
从IPC 的角度看,管道提供了从一个进程向另一个进程传输数据的有效方法。但是,管道有一些固有的局限性:
-
因为读数据的同时也将数据从管道移去,因此,管道不能用来对多个接收者广播数据。
-
管道中的数据被当作字节流,因此无法识别信息的边界。
-
如果一个管道有多个读进程,那么写进程不能发送数据到指定的读进程。同样,如果有多个写进程,那么没有办法判断是它们中那一个发送的数据。