Linux下调用system()函数导致的问题

在Linux Server程序中使用system()函数调用外部脚本时,由于daemon进程忽略了SIGCHLD信号,导致无法正确获取子进程的返回值和资源释放问题。通过捕获killall信号和重写system函数解决子进程套接字关闭问题。分析了system函数在2.4和2.6内核下的不同行为,指出在SIGCHLD被忽略时,2.6内核子进程会直接退出释放资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前一段时间用了system()函数调用脚本启动另一个进程,发现两个问题: 
1.执行killall命令杀新启进程时会连原进程一起kill掉. 
2.原进程打开的侦听端口,如果新启动的进程不退出无法释放(socket资源未释放). 
查看system()代码,原来system函数也是用的fork实现的,示例代码如下:
01 #include <signal.h>
02 int system(const char *cmd)
03 {
04   int stat;
05   pid_t pid;
06   struct sigaction sa, savintr, savequit;
07   sigset_t saveblock;
08   if (cmd == NULL)
09       return(1);
10   sa.sa_handler = SIG_IGN;
11   sigemptyset(&sa.sa_mask);
12   sa.sa_flags = 0;
13   sigemptyset(&savintr.sa_mask);
14   sigemptyset(&savequit.sa_mask);
15   sigaction(SIGINT, &sa, &savintr);
16   sigaction(SIGQUIT, &sa, &savequit);
17   sigaddset(&sa.sa_mask, SIGCHLD);
18   sigprocmask(SIG_BLOCK, &sa.sa_mask, &saveblock);
19   if ((pid = fork()) == 0) {
20       sigaction(SIGINT, &savintr, (struct sigaction *)0);
21       sigaction(SIGQUIT, &savequit, (struct sigaction *)0);
22       sigprocmask(SIG_SETMASK, &saveblock, (sigset_t *)0);
23       execl("/bin/sh""sh""-c", cmd, (char *)0);
24       _exit(127);
25   }
26   if (pid == -1) {
27       stat = -1; /* errno comes from fork() */
28   else {
29       while (waitpid(pid, &stat, 0) == -1) {
30       if (errno != EINTR){
31           stat = -1;
32           break;
33       }
34       }
35   }
36   sigaction(SIGINT, &savintr, (struct sigaction *)0);
37   sigaction(SIGQUIT, &savequit, (struct sigaction *)0);
38   sigprocmask(SIG_SETMASK, &saveblock, (sigset_t *)0);
39   return(stat);
40 }

两进程存在父子关系,那killall发送给新进程的信号原进程能收到也就不奇怪了,并在子进程中没有关闭父进程打开的套接字,导致无法释放的问题.通过在原进程中捕捉killall默认信号,重写system函数传入套接字进行close解决了问题.可能有更好的解决方法,回头研究一下fork的实现.看来system还是要慎用。

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

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

http://blog.163.com/xychenbaihu@yeah/blog/static/132229655201121611452549/

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

01 signal(SIGINT, SIG_IGN);       //当在终端上按下ctrl+c后,会产生SIGINT信号。
02  signal(SIGHUP, SIG_IGN);    //终端退出时,会给所有的进程发送SIGHUP信号。
03  signal(SIGQUIT, SIG_IGN);   //终端退出时,会给所有的进程发送SIGQUIT信号。
04  signal(SIGPIPE, SIG_IGN);    //往没有读进程的管道中进行写操作。
05  signal(SIGTTOU, SIG_IGN);  //后台进程写tty
06  signal(SIGTTIN, SIG_IGN);    //后台进程读tty
07  signal(SIGCHLD, SIG_IGN);   /*
08   子进程先于父进程结束时,会给父进程发送SIGCHLD信号
09    如果
10       1、父进程没有忽略SGICHLD信号;
11        或者
12        2、父进程没有调用wait或waitpid函数。
13    那么子进程将僵死。
14  
15    (
16  
17   在2.6内核,只要父进程显式忽略了SIGCHLD信号,
18  
19   那么子进程将不会僵死,那么system将得不到子进程的退出状态。
20  
21    也就是说system函数的返回值并不是子进程退出时的状态。
22  
23  
24  
25    而2.4内核,只要父进程没有调用wait系列函数,子进程就将僵死。
26          不论是否忽略了SIGCHLD信号。
27  
28    )
29  */
30 signal(SIGTERM, SIG_IGN);   /*
31    当kill    pid时,向进程发送SIGTERM信号。
32    SIGTERM信号的默认处理是进程退出。
33    SIGTERM是进程在有可能的情况下退出。
34    注意::
35    killall  -9    process_name
36    发送的SIGKILL信号,强制进程退出。
37    */

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

例如:

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

01 #!/bin/bash
02 FILENAME=$0
03 PATHNAME=$1
04 wget $FILENAME $PATHNAME
05 if [ $? -eq 0 ] ; then
06       exit 
07 else
08       exit -1
09 fi
10  
11 例如::
12  
13 char  command[1024];
14 url=http://test/test.rar;
15 pathname="./data";
16 sprintf(command,"./download.sh  %s   %s",url,pathname);
17 int   ret   =    system(command);   
18  
19 if( ret == 0)
20 {
21     成功
22
23 else
24 {
25     //失败
26 }

注意::

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

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

正确的做法是:

1 signal(SIGCHLD,SIG_DFL);//默认处理方式,是接收子进程的返回值。
2 system(command);
3 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函数源码的一个实现如下:

01 int system(const char * cmdstring)
02 {
03 pid_t pid;
04 int status;
05 if(cmdstring == NULL)
06 {
07 return (1);
08 }
09 if((pid = fork())<0)
10 {
11 status = -1;
12 }
13 else if(pid == 0)
14 {
15 execl("/bin/sh""sh""-c", cmdstring, (char *)0);
16             _exit(127);
17 }
18 else
19 {
20 while(waitpid(pid, &status, 0) < 0)
21 {
22 if(errno != EINTR)
23 {
24 status = -1;
25 break;
26 }
27 }
28 }
29 return status;
30 }

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

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

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

文章出处:http://blog.youkuaiyun.com/fengxinze/article/details/6898405

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值