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