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.04x64的虛擬機上測試,即使沒有pclose(),似乎也沒有特別的問題。

但是,在ARM板上子跑的時候,會出現僵屍進程。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM