这篇文章简单介绍一下操作系统中的管道,并主要解决以下两个问题:

1、管道的内部实现

2、管道的容量?

 

 

管道是操作系统中,不同进程之间进行通信的方式。

根据通信的进程之间的关系,管道分为匿名管道和非匿名管道

其中,匿名管道只能用于有“血缘关系”的进程之间进行通信,而命名管道则可以用于任意两进程的通信

此外,管道是单向的,所以2个管道就可以实现进程之间的双向通信

 

 

管道的实现原理:

我们知道,进程之间的数据是私有的,即使是父子进程,也是如此,所以,要想让2个进程共享某个数据,我们可以在指定的路径下创建一个文件,然后其中一个进程将要传输的数据写入这个文件,另一个进程读取这个文件的信息,就可以实现进程之间的通信了,当然,考虑到效率,这一切通常是不可能发生在磁盘上的。

此外,由于一些机制,管道提供“流式”服务,对具体一次写入多少数据,一次读取多少数据,都不需要严格的规定

 

 

在Linux下,管道的实现没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。

通过两个file结构指向同一个临时的VFS索引节点,而这个VFS索引节点又指向一个物理页面而实现的。

 

如下图:

wKioL1d3P0ijrGycAABVCGHD_1U664.gif

 

 

上图中有两个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管道的实现机制)