您的位置:首页 > 运维架构

子进程中调用system命令执行openssl后,程序退出了,原因是SIGCHLD信号的处理

2014-08-14 08:50 951 查看
源代码:

void quit_proc(int t)
{
kill(0,15);  exit(0);
}

int main_worker(int argc, char *argv[])
{

......

//自定义信号处理函数
signal(SIGCHLD, quit_proc);
//子进程结束时, 父进程会收到这个信号然后父进程也退出

for (i=0; i<childcnt; i++) {
procid = fork();
if ( !procid ) {
//子进程

......

system("openssl ...");  //程序执行到这里就退出了,下面的跟踪没有打印出来
printf("openssl ok!\n");

} else if ( procid > 0 )  {
//
} else { i--; sleep( 5 ); }
}
while (1) sleep(1);

return 0;
}


后来研究了一下,发现是由于父进程中(父进程A),SIGCHLD信号的处理方式被继承到子进程中(子进程B),子进程中调用system,system又会fork一个子进程(子进程C),这样当system的子进程C执行完后,子进程B收到了SIGCHLD信号,于是执行了quit_proc函数一起退出了

由于本身对信号机制不太懂吧,导致这种低级错误,上网搜索了一下,原来是要在子进程中要恢复SIGCHLD信号的默认处理方式就可以了

修改后的源代码:

void quit_proc(int t)
{
kill(0,15);  exit(0);
}

int main_worker(int argc, char *argv[])
{

......

//自定义信号处理函数
signal(SIGCHLD, quit_proc);
//子进程结束时, 父进程会收到这个信号然后父进程也退出

for (i=0; i<childcnt; i++) {
procid = fork();
if ( !procid ) {
//子进程
signal(SIGCHLD,SIG_DFL);  //子进程以默认的方式处理该信号(system外部命令时不退出)

......

system("openssl ...");  //程序执行到这里就退出了,下面的跟踪没有打印出来
printf("openssl ok!\n");

} else if ( procid > 0 )  {
//
} else { i--; sleep( 5 ); }
}
while (1) sleep(1);

return 0;
}


1、关于在system中获取子进程的返回值与SIGCHLD

在Linux我们一般写的是Server程序,所以,一般在main函数中,首先将进程转换为后台进程,即调用deamon,deamon的一般实现。

deamon的实现中会忽略下面的信号:

signal(SIGINT, SIG_IGN);    //当在终端上按下ctrl+c后,会产生SIGINT信号。

signal(SIGHUP, SIG_IGN);    //终端退出时,会给所有的进程发送SIGHUP信号。

signal(SIGQUIT, SIG_IGN);   //终端退出时,会给所有的进程发送SIGQUIT信号。

signal(SIGPIPE, SIG_IGN);   //往没有读进程的管道中进行写操作。

signal(SIGTTOU, SIG_IGN);   //后台进程写tty

signal(SIGTTIN, SIG_IGN);   //后台进程读tty

signal(SIGCHLD, SIG_IGN);   

/*

子进程先于父进程结束时,会给父进程发送SIGCHLD信号

如果

1、父进程没有忽略SGICHLD信号;

或者

2、父进程没有调用wait或waitpid函数。

那么子进程将僵死。

(

在2.6内核,只要父进程显式忽略了SIGCHLD信号,

那么子进程将不会僵死,那么system将得不到子进程的退出状态。

也就是说system函数的返回值并不是子进程退出时的状态。

而2.4内核,只要父进程没有调用wait系列函数,子进程就将僵死。

不论是否忽略了SIGCHLD信号。

)

*/

signal(SIGTERM, SIG_IGN);   

/*

当kill pid时,向进程发送SIGTERM信号。

SIGTERM信号的默认处理是进程退出。

SIGTERM是进程在有可能的情况下退出。

注意::

killall  -9    process_name

发送的SIGKILL信号,强制进程退出。

*/

如果,我们在我们的server中需要调用system来调用外部脚本或程序来执行某写工作。

例如:

在脚本中通过wget下载文件::

#!/bin/bash
FILENAME=$0
PATHNAME=$1
wget $FILENAME $PATHNAME
if [ $? -eq 0 ] ; then
exit 0
else
exit -1
fi


例如::
char  command[1024];
url=http://test/test.rar;
pathname="./data";
sprintf(command,"./download.sh  %s   %s",url,pathname);
int ret = system(command);
if( ret == 0)
{
//成功
}
else
{
//失败
}
注意::

其中ret用来接收子进程退出是的返回值。即exit的返回值。

但是由于在deamon中忽略了SIGCHLD信号,所以主进程将不再接收子进程的返回值。所以,ret的值不能正确反映子进程的退出状态。

正确的做法是::

signal(SIGCHLD,SIG_DFL);                //默认处理方式,是接收子进程的返回值。

system(command);

signal(SIGCHLD,SIG_IGN);

2、system相关问题::

system函数其实是调用fork,exec,waitpid来实现的。

1、fork一个进程;

2、在子进程中调用exec去执行新程序。

3、在父进程中调用waitpid去等待子进程结束。

如果在父进程已经signal(SIGCHLD,SIG_IGN);那么子进程结束时,子进程的返回值不能被waitpid接收。

这个是必须关注的问题。

下面我们来分析system的实现:

下面给出system函数及SIGCHLD信号处理分别在2.6及2.4内核下的区别。system函数源码的一个实现如下:
int system(const char * cmdstring)
{
pid_t pid;
int status;
if(cmdstring == NULL)
{
return (1);
}
if((pid = fork())<0)
{
status = -1;
}
else if(pid == 0)
{
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127);
}
else
{
while(waitpid(pid, &status, 0) < 0)
{
if(errno != EINTR)
{
status = -1;
break;
}
}
}
return status;
}


2.6内核下当父进程未调用wait系列函数等待子进程结束且未显式地忽略SIGCHLD信号,则子进程将成为僵死进程;(如果显示忽略,则子进程不僵死)

而在2.4内核中只要父进程未调用wait系列函数,则子进程就会成为僵死进程,不管是否显式地忽略SIGCHLD信号。

因而在SIGCHLD信号被显式忽略的情况下,2.6内核子进程将直接退出并释放资源,等父进程调用waitpid时,发现对应的pid进程已不存在,因而返回-1错误,errno为10(No child processes);

而在2.4内核下子进程在父进程waitpid之前不会退出,因而waitpid能成功获取子进程状态。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  system