judwenwen2009的专栏

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

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

1)问题的引出

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

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

[syscom@sysbase0-0 linux]$ ulimit -acore file size(blocks, -c) 0data seg size(kbytes, -d) unlimitedscheduling priority(-e) 0file size(blocks, -f) unlimitedpending signals(-i) 16204max locked memory(kbytes, -l) 64max memory size(kbytes, -m) unlimitedopen files(-n) 1024pipe size(512 bytes, -p) 8POSIX message queues(bytes, -q) 819200real-time priority(-r) 0stack size(kbytes, -s) 8192cpu time(seconds, -t) unlimitedmax user processes(-u) 1024virtual memory(kbytes, -v) unlimitedfile locks(-x) unlimited

查看solaris的系统限制:

bash-3.00$ ulimit -acore file size(blocks, -c) unlimiteddata seg size(kbytes, -d) unlimitedfile size(blocks, -f) unlimitedopen files(-n) 256pipe size(512 bytes, -p) 10stack size(kbytes, -s) 8192cpu time(seconds, -t) unlimitedmax user processes(-u) 29995virtual 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;charbuf[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_BUFPOSIX.1-2001 says that write(2)s of less than PIPE_BUF bytes must beatomic: the output data is written to the pipe as a contiguoussequence. Writes of more than PIPE_BUF bytes may be non-atomic: thekernel 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 thefile 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 actual134 memory allocation, whereas PIPE_BUF makes atomicity guarantees. */135 #define PIPE_SIZEPAGE_SIZE

明确说明了PIPE_BUF和PIPE_SIZE的不同

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

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

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

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

旅行要学会随遇而安,淡然一点,

judwenwen2009的专栏

相关文章:

你感兴趣的文章:

标签云: