深入理解PIPE
在linux中要进行进程间通信有多种方法:pipe、fifo、共享内存,信号量,消息队列,共享文件等等。其中pipe和fifo 使用最广泛,二者的区别为pipe为匿名管道,只能用在有父子关系的进程间通信,而fifo可以通过文件系统中的一个文件取得,所以不受上述限制。作为父子进程间通信的通道,pipe同样可以看作是一个先进先出的队列。
PIPE基本用法
pipe的使用很简单,原型如下:
- <span style="color:#339933">#include <unistd.h></span>
- <span style="color:#993333">int</span> pipe<span style="color:#0990">(</span><span style="color:#993333">int</span> pipefd<span style="color:#0990">[</span><span style="color:#00dd">2</span><span style="color:#0990">]</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
典型的使用方法如下:
- <span style="color:#339933">#include <stdlib.h></span>
- <span style="color:#339933">#include <stdio.h></span>
- <span style="color:#339933">#include <unistd.h></span>
- <span style="color:#993333">int</span> main<span style="color:#0990">(</span><span style="color:#0990">)</span>
- <span style="color:#0990">{</span>
- <span style="color:#993333">int</span> pfd<span style="color:#0990">[</span><span style="color:#00dd">2</span><span style="color:#0990">]</span><span style="color:#339933">;</span>
- <span style="color:#993333">int</span> ret <span style="color:#339933">=</span> pipe<span style="color:#0990">(</span>pfd<span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#b1b10">if</span> <span style="color:#0990">(</span>ret <span style="color:#339933">!=</span> <span style="color:#00dd">0</span><span style="color:#0990">)</span> <span style="color:#0990">{</span>
- perror<span style="color:#0990">(</span><span style="color:#ff00">"pipe"</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- exit<span style="color:#0990">(</span><span style="color:#339933">-</span><span style="color:#00dd">1</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#0990">}</span>
- pid_t pid <span style="color:#339933">=</span> fork<span style="color:#0990">(</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#b1b10">if</span> <span style="color:#0990">(</span>pid <span style="color:#339933">==</span> <span style="color:#00dd">0</span><span style="color:#0990">)</span> <span style="color:#0990">{</span>
- close<span style="color:#0990">(</span>pfd<span style="color:#0990">[</span><span style="color:#00dd">1</span><span style="color:#0990">]</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#993333">char</span> rdbuf<span style="color:#0990">[</span><span style="color:#00dd">1024</span><span style="color:#0990">]</span><span style="color:#339933">;</span>
- read<span style="color:#0990">(</span>pfd<span style="color:#0990">[</span><span style="color:#00dd">0</span><span style="color:#0990">]</span><span style="color:#339933">,</span> rdbuf<span style="color:#339933">,</span> <span style="color:#993333">sizeof</span><span style="color:#0990">(</span>rdbuf<span style="color:#0990">)</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#b1b10">return</span> <span style="color:#00dd">0</span><span style="color:#339933">;</span>
- <span style="color:#0990">}</span> <span style="color:#b1b10">else</span> <span style="color:#b1b10">if</span> <span style="color:#0990">(</span>pid <span style="color:#339933">></span> <span style="color:#00dd">0</span><span style="color:#0990">)</span> <span style="color:#0990">{</span>
- close<span style="color:#0990">(</span>pfd<span style="color:#0990">[</span><span style="color:#00dd">0</span><span style="color:#0990">]</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#993333">char</span> wrbuf<span style="color:#0990">[</span><span style="color:#0990">]</span> <span style="color:#339933">=</span> <span style="color:#ff00">"abcd"</span><span style="color:#339933">;</span>
- write<span style="color:#0990">(</span>pfd<span style="color:#0990">[</span><span style="color:#00dd">1</span><span style="color:#0990">]</span><span style="color:#339933">,</span> wrbuf<span style="color:#339933">,</span> <span style="color:#993333">sizeof</span><span style="color:#0990">(</span>wrbuf<span style="color:#0990">)</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- pid_t cpid <span style="color:#339933">=</span> waitpid<span style="color:#0990">(</span><span style="color:#339933">-</span><span style="color:#00dd">1</span><span style="color:#339933">,</span> NULL<span style="color:#339933">,</span> <span style="color:#00dd">0</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#0990">}</span> <span style="color:#b1b10">else</span> <span style="color:#0990">{</span>
- perror<span style="color:#0990">(</span><span style="color:#ff00">"fork"</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#0990">}</span>
- <span style="color:#b1b10">return</span> <span style="color:#00dd">0</span><span style="color:#339933">;</span>
- <span style="color:#0990">}</span>
从代码中可以看出,调用pipe之后,pfd即为一对相关联的管道,其中pfd[0]对应的是数据输入端,pfd[1]对应数据输出端。当父进程想要给子进程发送数据时,直接将数据write到pfd[1]中即可,对应的子进程从pfd[0]中read。
PIPE缓冲区限制
从上边的父子进程间关系可以看到,这是操作系统中典型的1:1生产者消费者模型。管道作为联系生产者与消费者的缓冲区同样会涉及到两个问题:1)缓冲区的大小问题。 2)缓冲区读写的互斥问题。
将上述代码中父进程的write一行去掉,即可测试出在管道为空的情况下,子进程读管道会阻塞(默认是阻塞的,亦可以将其设置为非阻塞,这样返回的将是-1,同时errno设为EAGAIN)。
为了测试缓冲区的大小,再次修改上述代码,使得子进程不再读取数据,同时父进程一直向其中写入数据,代码如下:
- pid_t pid <span style="color:#339933">=</span> fork<span style="color:#0990">(</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#b1b10">if</span> <span style="color:#0990">(</span>pid <span style="color:#339933">==</span> <span style="color:#00dd">0</span><span style="color:#0990">)</span> <span style="color:#0990">{</span>
- close<span style="color:#0990">(</span>pfd<span style="color:#0990">[</span><span style="color:#00dd">1</span><span style="color:#0990">]</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- pause<span style="color:#0990">(</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#b1b10">return</span> <span style="color:#00dd">0</span><span style="color:#339933">;</span>
- <span style="color:#0990">}</span> <span style="color:#b1b10">else</span> <span style="color:#b1b10">if</span> <span style="color:#0990">(</span>pid <span style="color:#339933">></span> <span style="color:#00dd">0</span><span style="color:#0990">)</span> <span style="color:#0990">{</span>
- close<span style="color:#0990">(</span>pfd<span style="color:#0990">[</span><span style="color:#00dd">0</span><span style="color:#0990">]</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- pause<span style="color:#0990">(</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#993333">char</span> buf<span style="color:#0990">[</span><span style="color:#0990">]</span> <span style="color:#339933">=</span> <span style="color:#ff00">"a"</span><span style="color:#339933">;</span>
- <span style="color:#b1b10">for</span> <span style="color:#0990">(</span><span style="color:#993333">int</span> i <span style="color:#339933">=</span> <span style="color:#00dd">0</span><span style="color:#339933">;</span> <span style="color:#339933">;</span> i<span style="color:#339933">++</span><span style="color:#0990">)</span> <span style="color:#0990">{</span>
- write<span style="color:#0990">(</span>pfd<span style="color:#0990">[</span><span style="color:#00dd">1</span><span style="color:#0990">]</span><span style="color:#339933">,</span> buf<span style="color:#339933">,</span> <span style="color:#993333">sizeof</span><span style="color:#0990">(</span>buf<span style="color:#0990">[</span><span style="color:#00dd">0</span><span style="color:#0990">]</span><span style="color:#0990">)</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#0066">printf</span><span style="color:#0990">(</span><span style="color:#ff00">"write pipe %d, total %d<span style="color:#0099; font-weight:bold">\n</span>"</span><span style="color:#339933">,</span> i<span style="color:#339933">+</span><span style="color:#00dd">1</span><span style="color:#339933">,</span> <span style="color:#0990">(</span>i<span style="color:#339933">+</span><span style="color:#00dd">1</span><span style="color:#0990">)</span><span style="color:#339933">*</span><span style="color:#993333">sizeof</span><span style="color:#0990">(</span>buf<span style="color:#0990">[</span><span style="color:#00dd">0</span><span style="color:#0990">]</span><span style="color:#0990">)</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#0990">}</span>
- pid_t cpid <span style="color:#339933">=</span> waitpid<span style="color:#0990">(</span><span style="color:#339933">-</span><span style="color:#00dd">1</span><span style="color:#339933">,</span> NULL<span style="color:#339933">,</span> <span style="color:#00dd">0</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#0990">}</span> <span style="color:#b1b10">else</span> <span style="color:#0990">{</span>
- perror<span style="color:#0990">(</span><span style="color:#ff00">"fork"</span><span style="color:#0990">)</span><span style="color:#339933">;</span>
- <span style="color:#0990">}</span>
修改之后的父进程每次向管道中写入一个字符,并将已经写入的字节数打印出来。当程序停住时,在Linux2.6.27上显示的结果为:
- write pipe <span style="color:#00dd">65536</span><span style="color:#339933">,</span> total <span style="color:#00dd">65536</span>
由此可以看出,此时pipe的缓冲区大小为64KB。相同的程序在MacOSX中显示为:
- write pipe <span style="color:#00dd">16384</span><span style="color:#339933">,</span> total <span style="color:#00dd">16384</span>
可以看出MacOSX的pipe默认缓冲区大小16KB。
已知了pipe默认缓冲区的大小了,那么自然就会想这个缓冲区大小是不是可以人工设定呢?搜索一番之后,发现好多人都说这个值是限定死了的,在内核代码中固定就是64K。后来下了一份linux 3.0代码发现这个默认值已经是可以改的了。相关代码在include/linux/pipe_fs_i.h中:
- <span style="color:#339933">#define PIPE_DEF_BUFFERS 16</span>
- …
- …
- <span style="color:#808080; font-style:italic">/* for F_SETPIPE_SZ and F_GETPIPE_SZ */</span>
- <span style="color:#993333">long</span> pipe_fcntl<span style="color:#0990">(</span><span style="color:#993333">struct</span> file <span style="color:#339933">*,</span> <span style="color:#993333">unsigned</span> <span style="color:#993333">int</span><span style="color:#339933">,</span> <span style="color:#993333">unsigned</span> <span style="color:#993333">long</span> arg<span style="color:#0990">)</span><span style="color:#339933">;</span>
看到PIPE_DEF_BUFFERS就是默认的pipe缓冲区大小,不过是以页为单位的,默认的页大小为4K,所以默认的pipe缓冲区大小即为64KB。但是在最下边又有个函数pipe_fcntl,同时有两个常量F_SETPIPE_SZ,F_GETPIPE_SZ。看来应该是用来修改默认缓冲区大小的。
果然这个特性是在2.6.35的内核中加入的。发行说明可以见这里,相应的commit log中看到有相应的Commit。这样就可以通过使用fcntl配合上边两个常量指令更改pipe缓冲区大小。同时在/proc/sys/fs/pipe-max-size中可以方便查看pipe缓冲区最大值。
至此已经知道了linux默认的pipe缓冲区为64KB,同时在2.6.35之后的内核可以使用fcntl更改缓冲区大小。还剩另一个问题,就是原子读写问题。
PIPE的原子访问
同样在include/linux/pipe_fs_i.h中,有如下代码:
- <span style="color:#808080; font-style:italic">/* Differs from PIPE_BUF in that PIPE_SIZE is the length of the actual
- memory allocation, whereas PIPE_BUF makes atomicity guarantees. */</span>
- <span style="color:#339933">#define PIPE_SIZE PAGE_SIZE</span>
看到定义了PIPE_SIZE这个常量,大小为一个页面大小(默认是4K)。从注释中可以看到这个PIPE_SIZE值即为PIPE最大可保证的原子操作字节数。同样在Write Man Page中写到:
- Write requests of <span style="color:#0990">{</span>PIPE_BUF<span style="color:#0990">}</span> bytes or less shall not be interleaved with data from other processes doing writes on the same pipe.
- <span style="color:#202020">Writes</span> of greater than <span style="color:#0990">{</span>PIPE_BUF<span style="color:#0990">}</span> bytes may have data interleaved<span style="color:#339933">,</span> on arbitrary boundaries<span style="color:#339933">,</span> with writes by other processes<span style="color:#339933">,</span> whether or not the O_NONBLOCK flag of the file status flags is set.
这个PIPE_BUF常量在/usr/include/linux/limits.h中定义:
- <span style="color:#339933">#define PIPE_BUF 4096 /* # bytes in atomic write to a pipe */</span>
另外也可以通过ulimit -p查看这个限制,不过这是个只读值,不能修改的。ulimit -a输出如下,其中的pipe size 即是:
- core file size <span style="color:#0990">(</span>blocks<span style="color:#339933">,</span> <span style="color:#339933">-</span>c<span style="color:#0990">)</span> <span style="color:#00dd">0</span>
- data seg size <span style="color:#0990">(</span>kbytes<span style="color:#339933">,</span> <span style="color:#339933">-</span>d<span style="color:#0990">)</span> unlimited
- scheduling priority <span style="color:#0990">(</span><span style="color:#339933">-</span>e<span style="color:#0990">)</span> <span style="color:#00dd">0</span>
- file size <span style="color:#0990">(</span>blocks<span style="color:#339933">,</span> <span style="color:#339933">-</span>f<span style="color:#0990">)</span> unlimited
- pending signals <span style="color:#0990">(</span><span style="color:#339933">-</span>i<span style="color:#0990">)</span> <span style="color:#00dd">81920</span>
- max locked memory <span style="color:#0990">(</span>kbytes<span style="color:#339933">,</span> <span style="color:#339933">-</span>l<span style="color:#0990">)</span> <span style="color:#00dd">32</span>
- max memory size <span style="color:#0990">(</span>kbytes<span style="color:#339933">,</span> <span style="color:#339933">-</span>m<span style="color:#0990">)</span> unlimited
- open files <span style="color:#0990">(</span><span style="color:#339933">-</span>n<span style="color:#0990">)</span> <span style="color:#00dd">1024</span>
- pipe size <span style="color:#0990">(</span><span style="color:#00dd">512</span> bytes<span style="color:#339933">,</span> <span style="color:#339933">-</span>p<span style="color:#0990">)</span> <span style="color:#00dd">8</span>
- POSIX message queues <span style="color:#0990">(</span>bytes<span style="color:#339933">,</span> <span style="color:#339933">-</span>q<span style="color:#0990">)</span> <span style="color:#00dd">819200</span>
- real<span style="color:#339933">-</span>time priority <span style="color:#0990">(</span><span style="color:#339933">-</span>r<span style="color:#0990">)</span> <span style="color:#00dd">0</span>
- stack size <span style="color:#0990">(</span>kbytes<span style="color:#339933">,</span> <span style="color:#339933">-</span>s<span style="color:#0990">)</span> <span style="color:#00dd">10240</span>
- cpu time <span style="color:#0990">(</span>seconds<span style="color:#339933">,</span> <span style="color:#339933">-</span>t<span style="color:#0990">)</span> unlimited
- max user processes <span style="color:#0990">(</span><span style="color:#339933">-</span>u<span style="color:#0990">)</span> <span style="color:#00dd">1024</span>
- virtual memory <span style="color:#0990">(</span>kbytes<span style="color:#339933">,</span> <span style="color:#339933">-</span>v<span style="color:#0990">)</span> unlimited
- file locks <span style="color:#0990">(</span><span style="color:#339933">-</span>x<span style="color:#0990">)</span> unlimited
参考
http://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html
http://stackoverflow.com/questions/4624071/pipe-buffer-size-is-4k-or-64k
http://home.gna.org/pysfst/tests/pipe-limit.html
http://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer
http://stackoverflow.com/questions/4739348/is-it-possible-to-change-the-size-of-a-named-pipe-on-linux
浙公网安备 33010602011771号