popen()/pclose()阻塞性问题验证
背景:
popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。这个管道必须由pclose()函数关闭,而不是fclose()函数。
pclose()函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。如果shell不能被执行,则pclose()返回的终止状态与shell已执行exit一样。
而子进程的退出状态,常用以下几个宏进行获取。
1、 WIFEXITED(status) 若此值为非0 表明进程正常结束。
若上宏为真,此时可通过WEXITSTATUS(status)获取进程退出状态(exit时参数)
示例:
if(WIFEXITED(status)){
printf(“退出值为 %d\n”, WEXITSTATUS(status));
}
2、 WIFSIGNALED(status)为非0 表明进程异常终止。
若上宏为真,此时可通过WTERMSIG(status)获取使得进程退出的信号编号
示例:
if(WIFSIGNALED(status)){
printf(“使得进程终止的信号编号: %d\n”,WTERMSIG(status));
}
验证内容:
主要确认以下几点:
1, WEXITSTATUS等宏,能否正确取得shell退出状态?
2, popen之后直接调用pclose是否会等待命令执行结束?
3, 如果没有pclose,会如何?
验证代码:
测试用代码如下:
#include <stdio.h> #include <sys/wait.h> int main(void) { int iRet = 0; FILE *fp = NULL; char buff[512] = {'\0'}; fp = popen("./test.sh", "r"); if (NULL == fp) { printf("popen failed.\n"); return 1; } /* while(fgets(buff, sizeof(buff), fp) != NULL) { printf("%s", buff); } */ iRet = pclose(fp); printf("iRet = %d\n", iRet); printf("wifexited : %d\n", WIFEXITED(iRet)); printf("wifsignaled : %d\n", WIFSIGNALED(iRet)); printf("wifstopped : %d\n", WIFSTOPPED(iRet)); //if (WIFEXITED(iRet)) printf("exit :%d\n", WEXITSTATUS(iRet)); //if (WIFSIGNALED(iRet)) printf("signal :%d\n", WTERMSIG(iRet)); return 0; }
被调用的脚本如下:
#!/bin/sh #echo "before..." #注意,echo被注释掉,即,不会输出。 sleep 30 #echo "after..." exit 1
结果:
1, WIFEXITED()等宏,可以正确获取test.sh的执行结果。
如下三个实验可以验证:
① test.sh没有执行权限时,WEXITSTATUS()的结果与直接执行test.sh的返回值是一致的。
zsy@ubuntu:~/work/popen$ ./test sh: 1: ./test.sh: Permission denied iRet = 32256 wifexited : 1 wifsignaled : 0 wifstopped : 0 exit :126 signal :0 zsy@ubuntu:~/work/popen$ ./test.sh bash: ./test.sh: Permission denied zsy@ubuntu:~/work/popen$ echo $? 126
② 给test.sh增加权限后,WEXITSTATUS()获取的正是test.sh中的exit 1的结果。
zsy@ubuntu:~/work/popen$ ./test iRet = 256 wifexited : 1 wifsignaled : 0 wifstopped : 0 exit :1 signal :0
③ popen执行过程中,将shell子进程kill掉,WTERMSIG()获取的是SIGTERM=15。
zsy@ubuntu:~/work/popen$ ps -ef|grep test zsy 3459 3000 0 06:54 pts/1 00:00:00 ./test zsy 3460 3459 0 06:54 pts/1 00:00:00 sh -c ./test.sh zsy 3461 3460 0 06:54 pts/1 00:00:00 /bin/sh ./test.sh zsy@ubuntu:~/work/popen$ kill 3460 # 注意kill的pid zsy@ubuntu:~/work/popen$ ./test iRet = 15 wifexited : 0 wifsignaled : 1 wifstopped : 0 exit :0 signal :15
注意:
③的例子中,可以看到popen实际上在fork之后,是执行了“sh –c ./test.sh”命令,然后由shell再启动test.sh。所以test.sh实际上是孙子进程。
如果kill的是孙子进程,结果会如何呢?
zsy@ubuntu:~/work/popen$ ps -ef|grep test zsy 3484 3000 0 07:05 pts/1 00:00:00 ./test zsy 3485 3484 0 07:05 pts/1 00:00:00 sh -c ./test.sh zsy 3486 3485 0 07:05 pts/1 00:00:00 /bin/sh ./test.sh zsy@ubuntu:~/work/popen$ kill 3486 zsy@ubuntu:~/work/popen$ ./test Terminated iRet = 36608 wifexited : 1 wifsignaled : 0 wifstopped : 0 exit :143 signal :0
也就是说,pclose返回的结果认为子进程shell是正常结束了,终了code为143(143=128+15,实际上就是test.sh收到了SIGTERM的值)。
2,pclose()调用时,确实会阻塞,等待test.sh中的sleep结束,才会返回。
但是,如果把sleep前的echo打开,则pclose()并不会阻塞,而是直接返回。如下:
zsy@ubuntu:~/work/popen$ ./test iRet = 36096 wifexited : 1 wifsignaled : 0 wifstopped : 0 exit :141 signal :0
原因何在呢?其实答案就在WEXITSTATUS()的结果141中。类似于上面kill 孙子进程时的返回值,141=128+13,说明test.sh(孙子进程)实际上接收到了信号SIGPIPE退出,导致shell子进程立刻返回了。
而test.sh收到SIGPIPE的原因,则是因为pclose()的时候,关闭了popen创建的管道,而test.sh的echo命令,想向管道写数据,就会产生SIGPIPE信号。
※因此,可以考虑两种解决方案。一种就是shell里面不要输出;另一种就是在pclose()前调用fgets,保证shell输出都读取出来后,再关闭。
3,在ubuntu 14.04×64的虚拟机上测试,即使没有pclose(),似乎也没有特别的问题。
但是,在ARM板上子跑的时候,会出现僵尸进程。