[关闭]
@zhongdao 2019-03-28T09:59:57.000000Z 字数 4272 阅读 748

Perl避免defunct进程和CLOSE_WAIT连接过多的方法

未分类


1. 前言

有时系统中会出现defunct进程。
有时运行一个socker server例如 http daemon时,会发现系统有过多的CLOSE_WAIT的连接。
例如:

  1. # netstat -an |grep 8088
  2. tcp 0 0 0.0.0.0:8088 0.0.0.0:* LISTEN
  3. tcp 1 0 103.90.190.176:8088 124.193.192.168:49309 CLOSE_WAIT
  4. tcp 1 0 103.90.190.176:8088 124.193.192.168:65065 CLOSE_WAIT
  5. tcp 0 0 103.90.190.176:8088 104.152.52.18:45546 ESTABLISHED
  6. tcp 1 0 103.90.190.176:8088 124.193.192.168:65422 CLOSE_WAIT
  7. tcp 1 0 103.90.190.176:8088 124.193.192.168:65244 CLOSE_WAIT
  8. tcp 1 0 103.90.190.176:8088 124.193.192.168:49215 CLOSE_WAIT
  9. tcp 1 0 103.90.190.176:8088 124.193.192.168:65393 CLOSE_WAIT

导致新的socket连接无法接入。

经查找资料,发现解决方法相同,在代码中添加一行:

  1. $SIG{CHLD}='IGNORE';

为了了解原因,特找到如下文章,主要的2篇文章如下:

2. 主要原文链接

methods to avoid zombie process
https://duyanghao.github.io/ways_avoid_zombie_process/

Why I got so many CLOSE_WAIT
https://www.perlmonks.org/?replies=1;node_id=532250;displaytype=print

If you don't want to deal with child processes at all when they terminate (i.e., just let them finish and that's that and let them disappear from the process table as soon as they're done), you can set $SIG{CHLD} to 'IGNORE' (if I'm remembering correctly). If you need to do something more elaborate, then that signal entry should be set to a coderef, as usual.

以下是 methods to avoid zombie process 的机器翻译内容

3 methods to avoid zombie process机器翻译

3.1 什么是僵尸进程?

来自维基百科

在Unix和类Unix计算机操作系统上,僵尸进程或失效进程是一个已完成执行(通过退出系统调用)但仍在进程表中有一个条目的进程:它是处于“已终止状态”的进程。对于子进程会发生这种情况,其中仍需要条目以允许父进程读取其子进程的退出状态:一旦通过等待系统调用读取退出状态,僵尸的条目将从进程表中删除,并且据说被“收获”。子进程在从资源表中删除之前始终首先变为僵尸。在大多数情况下,在正常的系统操作下,僵尸会立即被其父级等待,然后被系统收获 - 长时间停留僵尸的进程通常是错误并导致资源泄漏。

僵尸过程这个术语源于僵尸的共同定义 - 一个不死的人。在该术语的比喻中,儿童过程已“死亡”但尚未“收获”。此外,与正常进程不同,kill命令对僵尸进程没有影响。

3.2 僵尸进程和孤儿进程之间有什么区别?

不应将僵尸进程与孤儿进程混淆:孤儿进程是一个仍在执行的进程,但其父进程已经死亡。这些不会像僵尸一样继续存在; 相反,(像所有孤立的进程一样)它们被init(进程ID 1)采用,它等待它的子进程。结果是,将自动获得既是僵尸又是孤儿的过程。

3.3 僵尸进程的例子

  1. use POSIX ":sys_wait_h";
  2. my $pid = fork;
  3. die "Unable to fork: $!." unless defined $pid;
  4. unless ( $pid ) {
  5. sleep 10;
  6. print 'child exit'."\n";
  7. exit 0;
  8. }
  9. sleep 20;
  10. print 'parent exit';

马上,有两个正常的过程如下:

image_1d71r5seq1oidk3d16onijg1hma9.png-9.3kB
10秒后,子进程退出并成为僵尸进程如下:

image_1d71r7u6su8c10a8nhsju71ire1m.png-9.6kB

在接下来的10年里,子进程仍处于“僵尸”状态,父进程仍在运行...

20多岁后,进程和子进程都消失了!

3.4. 避免僵尸进程的方法

有以下三种方法可以避免僵尸进程(Perl语言示例):

方法1

执行两个for来使子进程成为孤立进程。init进程将采用它(pid = 1),因此,被init进程“收集”。

  1. use POSIX ":sys_wait_h";
  2. my $pid = fork;
  3. die "Unable to fork: $!." unless defined $pid;
  4. unless ( $pid ) {
  5. #in child
  6. my $pid = fork;
  7. die "Unable to fork: $!." unless defined $pid;
  8. if ($pid) {
  9. #in parent
  10. exit(0);
  11. }
  12. #in child
  13. sleep 10;#ensure that parent exit before execute child code
  14. print 'child exit'."\n";
  15. exit 0;
  16. }
  17. #in parent
  18. if ( waitpid($pid,0)!=$pid ) { #waitpid for child process
  19. print "waitpid error: $!\n";
  20. }
  21. sleep 20;
  22. print 'parent exit';

立即,有两个正常的过程如下(子进程的父进程init):

image_1d71ra4261g3l1t36opk6sl1cqh4j.png-9.6kB

10秒后,孩子消失如下(通过初始过程“收获”):

image_1d71raj115no1cpi1cec19b238650.png-5.1kB

20多秒后,父进程消失。

方法2

当一个孩子退出时,父进程将收到一个SIGCHLD信号,表明其中一个孩子已经完成了执行; 父进程通常会在此时调用wait()系统调用。该调用将为父级提供子级的退出状态,并将导致子级被收集或从进程表中删除。

因此,我们可以通过为调用waitpid的SIGCHLD定义一个处理程序来避免僵尸进程。

  1. use POSIX ":sys_wait_h";# for nonblocking read
  2. sub REAPER {
  3. # don't change $! and $? outside handler
  4. local ($!, $?);
  5. while ( (my $pid = waitpid(-1, WNOHANG)) > 0 ) {
  6. if ( WIFEXITED($?) ) {
  7. my $ret_code = WEXITSTATUS($?);
  8. print "child process:$pid exit with code:$ret_code\n";
  9. }
  10. }
  11. }
  12. $SIG{CHLD} = \&REAPER;
  13. my $pid = fork;
  14. die "Unable to fork: $!." unless defined $pid;
  15. unless ( $pid ) {
  16. #in child
  17. for (my $i=0;$i<10;$i++){
  18. sleep 1;
  19. }
  20. print 'child exit'."\n";
  21. exit(1);
  22. }
  23. #in parent
  24. for (my $i=0;$i<20;$i++){
  25. sleep 1;
  26. }
  27. print 'parent exit';
  28. child exit
  29. child process:20669 exit with code:1
  30. parent exit

马上,有两个正常的过程如下:
image_1d71rbf3s1g2n1le81q82u11l7e5d.png-10.9kB

10秒后,孩子消失如下(由父进程“收获”:pid = 20668):

image_1d71rbs8lsjd1ubhujm9qk1vu85q.png-5.5kB

20多秒后,父进程消失。

方法3(推荐方法)

将SIGCHLD处理程序明确设置为SIG_IGN。

如果(如上例所示)信号处理程序除了调用waitpid之外什么都不做,那么可以使用替代方法。将SIGCHLD处理程序设置为SIG_IGN将导致自动获取僵尸进程。

  1. use POSIX ":sys_wait_h";
  2. $SIG{CHLD}='IGNORE';
  3. my $pid = fork;
  4. die "Unable to fork: $!." unless defined $pid;
  5. unless ( $pid ) {
  6. #in child
  7. sleep 10;
  8. print 'child exit'."\n";
  9. exit 0;
  10. }
  11. #in parent
  12. sleep 20;
  13. print 'parent exit';

马上,有两个正常的过程如下:
image_1d71rcl9ps4n1e011euk9h51dci67.png-10.7kB

10秒后,孩子如下所示消失(由子过程本身“收获”):
image_1d71rd8bu1hlsi9s5jl1e3b1j4k6k.png-6.1kB

20多秒后,父进程消失。

请注意,SIGCHLD具有导致它被忽略的处置是不够的(正如默认情况下,SIG_DFL会这样做):只有通过将其设置为SIG_IGN才能获得此行为。

与waitpid和double-forks方法相比,这种方法更有效,更简单。因此,它是避免僵尸进程的推荐方法。

但是,这种方法的一个缺点是它比显式调用waitpid的可移植性稍差:它依赖的行为是POSIX.1-2001所需的,以前是单Unix规范所要求的,而不是POSIX.1-1990。

3.5 注意

3.6 参考文献

4. 参考资料

https://duyanghao.github.io/ways_avoid_zombie_process/

https://grokbase.com/t/perl/beginners/0616yh2mgq/how-do-i-avoiding-zombie-processes-without-interfering-with-die-if-system-ls-type-calls

http://fibrevillage.com/scripting/286-avoiding-zombie-processes

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注