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特性的程序並不是好的設計,非常容易出亂子(目前還未發生),最好還是老老實實地做內存越界判斷,降低與系統的耦合性。


歡迎轉載,轉載請註明出處和鏈接

by judwenwen2009

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章