这篇文章简单介绍一下操作系统中的管道,并主要解决以下两个问题:
1、管道的内部实现
2、管道的容量?
管道是操作系统中,不同进程之间进行通信的方式。
根据通信的进程之间的关系,管道分为匿名管道和非匿名管道
其中,匿名管道只能用于有“血缘关系”的进程之间进行通信,而命名管道则可以用于任意两进程的通信
此外,管道是单向的,所以2个管道就可以实现进程之间的双向通信
管道的实现原理:
我们知道,进程之间的数据是私有的,即使是父子进程,也是如此,所以,要想让2个进程共享某个数据,我们可以在指定的路径下创建一个文件,然后其中一个进程将要传输的数据写入这个文件,另一个进程读取这个文件的信息,就可以实现进程之间的通信了,当然,考虑到效率,这一切通常是不可能发生在磁盘上的。
此外,由于一些机制,管道提供“流式”服务,对具体一次写入多少数据,一次读取多少数据,都不需要严格的规定
在Linux下,管道的实现没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。
通过两个file结构指向同一个临时的VFS索引节点,而这个VFS索引节点又指向一个物理页面而实现的。
如下图:
上图中有两个file数据结构,但他们定义文件操作例程地址是不同的,其中一个是向管道中写入数据的例程地址,另一个是从管道中读取数据d例程地址。
这样,用户程序系统调用仍然是通常的文件操作,而内核却利用这种抽象机制实现了管道这一特殊的操作。
//inode结点信息结构 struct inode { ... struct pipe_inode_info *i_pipe; ... }; //管道缓冲区个数 #define PIPE_BUFFERS (16) //管道缓存区对象结构 struct pipe_buffer { struct page *page; //管道缓冲区页框的描述符地址 unsigned int offset, len; //页框内有效数据的当前位置,和有效数据的长度 struct pipe_buf_operations *ops; //管道缓存区方法表的地址 }; //管道信息结构 struct pipe_inode_info { wait_queue_head_t wait; //管道等待队列 unsigned int nrbufs, curbuf; //包含待读数据的缓冲区数和包含待读数据的第一个缓冲区的索引 struct pipe_buffer bufs[PIPE_BUFFERS]; //管道缓冲区描述符数组 struct page *tmp_page; //高速缓存区页框指针 unsigned int start; //当前管道缓存区读的位置 unsigned int readers; //读进程的标志,或编号 unsigned int writers; //写进程的标志,或编号 unsigned int waiting_writers; //在等待队列中睡眠的写进程的个数 unsigned int r_counter; //与readers类似,但当等待写入FIFO的进程是使用 unsigned int w_counter; //与writers类似,但当等待写入FIFO的进程时使用 struct fasync_struct *fasync_readers; //用于通过信号进行的异步I/O通知 struct fasync_struct *fasync_writers; //用于通过信号的异步I/O通知 };
至于上文提到的VFS对象,在linux2.6以后的版本中,把这些对象组织成pipfs特殊文件系统以加速它们的处理
管道的容量:
如果管道被写满了,这时候写端就不会向管道再继续写入数据了
那么管道的容量具体是多大呢?
实际使用中,还有两个概念对理解管道至关重要。一个是管道容量。另一个是管道操作原子性。 管道容量有限。如果管道满,阻塞方式下write操作会阻塞,非阻塞方式下会返回失败。不同的系统有不同的管道容量限制。应用模块不应该依赖特定 的容量限制,正确的设计是:一旦数据到达进程应尽快消费数据,避免写进程长时间阻塞。从linux 2.6.11版本开始,管道容量是65536字节。 POSIX 1-2001规定向管道写小于PIPE_BUF字节长度的数据时原子操作;写超过PIPE_BUF字节长度的数据不是原子操作。Linux上PIPE_BUF是4096字节,更细致的描述: 1、阻塞方式,n<=PIPE_BUF(n为写入的字节数,下同):写操作是原子操作,如果pipe空间不足则阻塞。 2、非阻塞方式,n<=PIPE_BUF:写操作是原子操作,如果pipe空间不足,则失败,errno设置为EAGAIN。 3、阻塞方式,n>PIPE_BUF:写操作不是原子操作,写入的数据可能与其他进程写入的交叉排列,写操作阻塞直到所有数据写完。 4、非阻塞方式,n>PIPE_BUF:写操作不是原子操作,如果pipe空间不足,则失败,errno设置为EAGAIN。写入的数据可能与其他进程写入的数据交叉排列。同时实际写入可能小于n(部分写入);调用者应该检查write实际写入的长度。
三个概念: 1、页缓冲区大小:4K 2、总缓冲区大小:64K 1、<4K的数据立即发送,以页为单位 2、>4K的数据,将会分成多个页的数据,分批发送。 函数 write要么阻塞,要么成功(copy全部数据到内核缓冲区,不存在只copy部分数据的情况),异常换回-1
参考:
http://www.cppblog.com/aaxron/archive/2014/03/24/206312.aspx
(linux管道容量的测试)
http://blog.sina.com.cn/s/blog_629b701e0100zrk3.html
(linux管道的实现机制)