加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

popen()/pclose()阻塞性问题验证

发布时间:2020-12-15 23:12:51 所属栏目:安全 来源:网络整理
导读:背景: popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。这个管道必须由pclose()函数关闭,而不是fclose()函数。 pclose()函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。如果shell不能被

背景:

popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。这个管道必须由pclose()函数关闭,而不是fclose()函数。

pclose()函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。如果shell不能被执行,则pclose()返回的终止状态与shell已执行exit一样。

而子进程的退出状态,常用以下几个宏进行获取。

1、 WIFEXITED(status) 若此值为非0 表明进程正常结束。

若上宏为真,此时可通过WEXITSTATUS(status)获取进程退出状态(exit时参数)

示例:

??????? if(WIFEXITED(status)){

??????????? printf("退出值为 %dn",WEXITSTATUS(status));

??????? }

2、 WIFSIGNALED(status)为非0 表明进程异常终止。

若上宏为真,此时可通过WTERMSIG(status)获取使得进程退出的信号编号

示例:

??? if(WIFSIGNALED(status)){

??????? printf("使得进程终止的信号编号: %dn",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] = {};

        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 = %dn",iRet);
        printf("wifexited : %dn",WIFEXITED(iRet));
        printf("wifsignaled : %dn",WIFSIGNALED(iRet));
        printf("wifstopped : %dn",WIFSTOPPED(iRet));
        //if (WIFEXITED(iRet))
                printf("exit :%dn",WEXITSTATUS(iRet));
        //if (WIFSIGNALED(iRet))
                printf("signal :%dn",WTERMSIG(iRet));

        return 0;
}

被调用的脚本如下:

#!/bin/sh

#echo "before..."        #注意,echo被注释掉,即,不会输出。
sleep 30

#echo "after..."

exit 1

?

结果:

1,? WIFEXITED()等宏,可以正确获取test.sh的执行结果。

如下三个实验可以验证:

①???? ?test.sh没有执行权限时,WEXITSTATUS()的结果与直接执行test.sh的返回值是一致的。

[email?protected]:~/work/popen$ ./test
sh: 1: ./test.sh: Permission denied
iRet = 32256
wifexited : 1
wifsignaled : 0
wifstopped : 0
exit :126
signal :0

[email?protected]:~/work/popen$ ./test.sh
bash: ./test.sh: Permission denied
[email?protected]:~/work/popen$ echo $?
126

②? ??给test.sh增加权限后,WEXITSTATUS()获取的正是test.sh中的exit 1的结果。

[email?protected]:~/work/popen$ ./test
iRet = 256
wifexited : 1
wifsignaled : 0
wifstopped : 0
exit :1
signal :0

③???? ?popen执行过程中,将shell子进程kill掉,WTERMSIG()获取的是SIGTERM=15。

[email?protected]:~/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

[email?protected]:~/work/popen$ kill 3460   # 注意kill的pid


[email?protected]:~/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的是孙子进程,结果会如何呢?

[email?protected]:~/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
[email?protected]:~/work/popen$ kill 3486

 

[email?protected]:~/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()并不会阻塞,而是直接返回。如下:

[email?protected]:~/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板上子跑的时候,会出现僵尸进程。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读