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 zsy345930000 06:54 pts/100:00:00 ./test zsy346034590 06:54 pts/100:00:00 sh -c ./test.sh zsy346134600 06:54 pts/100: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 zsy348430000 07:05 pts/100:00:00 ./test zsy348534840 07:05 pts/100:00:00 sh -c ./test.sh zsy348634850 07:05 pts/100: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.04x64的虛擬機器上測試,即使沒有pclose(),似乎也沒有特別的問題。
但是,在ARM板上子跑的時候,會出現殭屍程序。