前一段时间用了system()函数调用脚本启动另一个进程,发现两个问题:
1.执行killall命令杀新启进程时会连原进程一起kill掉.
2.原进程打开的侦听端口,如果新启动的进程不退出无法释放(socket资源未释放).
查看system()代码,原来system函数也是用的fork实现的,示例代码如下:
02 | int system ( const char *cmd) |
06 | struct sigaction sa, savintr, savequit; |
10 | sa.sa_handler = SIG_IGN; |
11 | sigemptyset(&sa.sa_mask); |
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); |
29 | while (waitpid(pid, &stat, 0) == -1) { |
36 | sigaction(SIGINT, &savintr, ( struct sigaction *)0); |
37 | sigaction(SIGQUIT, &savequit, ( struct sigaction *)0); |
38 | sigprocmask(SIG_SETMASK, &saveblock, (sigset_t *)0); |
两进程存在父子关系,那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); |
02 | signal (SIGHUP, SIG_IGN); |
03 | signal (SIGQUIT, SIG_IGN); |
04 | signal (SIGPIPE, SIG_IGN); |
05 | signal (SIGTTOU, SIG_IGN); |
06 | signal (SIGTTIN, SIG_IGN); |
07 | signal (SIGCHLD, SIG_IGN); |
30 | signal (SIGTERM, SIG_IGN); |
如果,我们在我们的server中需要调用system来调用外部脚本或程序来执行某写工作。
例如:
在脚本中通过wget下载文件:
04 | wget $FILENAME $PATHNAME |
05 | if [ $? -eq 0 ] ; then |
16 | sprintf (command, "./download.sh %s %s" ,url,pathname); |
17 | int ret = system (command); |
注意::
其中ret用来接收子进程退出是的返回值。即exit的返回值。
但是由于在deamon中忽略了SIGCHLD信号,所以主进程将不再接收子进程的返回值。所以,ret的值不能正确反映子进程的退出状态。
正确的做法是:
1 | signal (SIGCHLD,SIG_DFL); |
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) |
15 | execl( "/bin/sh" , "sh" , "-c" , cmdstring, ( char *)0); |
20 | while (waitpid(pid, &status, 0) < 0) |
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