linux中的PIPE_SIZE与PIPE_BUF,管道最大写入值问题

现在开发的项目是从solaris到linux的应用移植。经常用到popen函数,使用8192字节的数组读取popen输出,但没有进行溢出判断。

刚开始认为是一个简单的内存越界,但对popen和PIPE调查以后,疑惑越来越多了。

1)问题的引出

popen使用管道来记录被调用命令的输出,那么popen的最大写入字节数必然是管道的最大值。

使用linux的ulimit -a来查看系统限制:

[syscom@sysbase0-0 linux]$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 16204
max locked memory       (kbytes, -l) 64
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) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 1024
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
 

查看solaris的系统限制:

bash-3.00$ ulimit -a
core file size        (blocks, -c) unlimited
data seg size         (kbytes, -d) unlimited
file size             (blocks, -f) unlimited
open files                    (-n) 256
pipe size          (512 bytes, -p) 10
stack size            (kbytes, -s) 8192
cpu time             (seconds, -t) unlimited
max user processes            (-u) 29995
virtual memory        (kbytes, -v) unlimited


可以看到在linux系统上pipe size 为512bytes * 8= 4096bytes。solaris系统上pipe size为512bytes * 10= 5120bytes。

无论是4096还是5120都是远远小于8912的。因此使用8912字节的buf来读取popen的输出时绝对不会出现内存越界问题了。


2)问题的深入

通过ulimit看似得到了正确的结果,但在实际测试中却让人大跌眼镜!

测试程序:

test_popen.c

#include<stdio.h>
int main()
{
    FILE        *fp;
    int i;
    char        *p;
    char        buf[128];
    fp = popen("./test_print", "r");
    if(fp ==NULL) {
        printf("NG\n");
        return -1;
    }
    fgets(buf, 128, fp);
    pclose(fp);
    return 0;
}

test_print.c

#include <stdio.h>
int main()
{
    unsigned int i;
    for(i=0; i<0xffffffff;i++)
        printf("a");
    return 0;
}

将test_popen.c 和test_print.c分别编译为test_popen和test_print,运行test_popen,程序竟然正常运行!test_print明明输出了4G的字符啊


3)探究原理。

通过man 7 pipe来理解PIPE(我的man版本是1.6f)

 

 PIPE_BUF
       POSIX.1-2001 says that write(2)s of less than PIPE_BUF  bytes  must  be
       atomic:  the  output  data  is  written  to  the  pipe  as a contiguous
       sequence.  Writes of more than PIPE_BUF bytes may  be  non-atomic:  the
       kernel  may  interleave  the data with data written by other processes.
       POSIX.1-2001 requires PIPE_BUF to be at least 512  bytes.   (On  Linux,
       PIPE_BUF  is  4096 bytes.)  The precise semantics depend on whether the
       file descriptor is non-blocking (O_NONBLOCK), whether there are  multi-
       ple writers to the pipe, and on n, the number of bytes to be written:

PIPE_BUF确实为4096,但PIPE_BUF是指原子写的最大值,4kb刚好是1page size,并不是指PIPE SIZE

在谷歌上搜索内核源码,在3.10的内核中有如下:

133 /* Differs from PIPE_BUF in that PIPE_SIZE is the length of the actual
134    memory allocation, whereas PIPE_BUF makes atomicity guarantees.  */
135 #define PIPE_SIZE               PAGE_SIZE

明确说明了PIPE_BUF和PIPE_SIZE的不同

进一步调查,发现在2.6.11之前,PIPE_SIZE为4k,之后内核中PIPE_SIZE为64k,那为何能写入多达4G的字符呢?


这时我想到了popen的源码,查看了popen在BSD的实现,除了使用pipe函数外,与system调用并无区别。

点我查看popen源代码
4)结论

依赖linux的pipe特性的程序并不是好的设计,非常容易出乱子(目前还未发生),最好还是老老实实地做内存越界判断,降低与系统的耦合性。


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。