转自:http://blog.youkuaiyun.com/politefish/article/details/5567207
首先说说 fork函数。这个函数用来创建一个进程,不过创建方法有些不太好理解。 先看下面的程序fork-test.pl。我是用perl写的,不过相同的功能也可以用 C 来完成。
#!/usr/bin/perl
#------------------------------------
# fork-test.pl
print "Program started, pid=$$./n";
if ($child_pid = fork()) {
} else {
}
运行之后显示下面的结果。
Program started, pid=8934.
I'm child, pid=8935.
I'm parent, my pid=8934, child's pid=8935.
为什么 I'm child 和 I'm parent 都会被显示?这是因为 fork 调用时, 当前的进程会从 fork的位置一分为二,fork 对两个进程的返回值不同。 在父进程中 fork 返回子进程(即另一个进程)的进程id,而在子进程中 fork返回 0。 上例的执行过程如下图。
fork-test.png
上例中执行到 Program started 时,只有一个进程 8934,而执行到 fork 时, 进程分为两个,父进程为8934,子进程为 8935。接下来父进程执行 if 分支, 输入“I'm parent..”,而子进程执行 else 分支,输出“I'm child”。
SIGCHLD信号和僵尸进程
首先说说什么是僵尸进程(zombie process)。我们知道 Linux 使用进程表来管理进程,每个进程都在进程表中占据一个位置。当我们用 fork 生成一个子进程, 然后该子进程退出时,系统不会自动回收该子进程所占位置。此时虽然进程表中有这个子进程的信息,但实际上该子进程早已结束, 于是这个进程就成了“僵尸进程”。
僵尸进程虽然不占用系统资源,但是它会浪费进程表的位置。如果僵尸进程太多, 有可能会导致不能创建新进程。下面的例子zombie-test.pl 演示了如何创建僵尸进程:
#!/usr/bin/perl
#------------------------------------
# zombie-test.pl
sub child {
}
while (1) {
}
该程序每隔 5 秒创建一个子进程,子进程输出一行文字后退出。 执行该程序片刻之后,从其他终端用 ps -ef 命令可以看到进程状态。 标有 < defunct >的就是僵尸进程。
charlee
charlee
charlee
charlee
利用 waitpid 回收僵尸进程
一个方法就是在父进程中利用 waitpid 函数。该函数回收指定进程的资源, 并返回已结束进程的进程id。若指定进程不存在,则返回-1。 我们可以通过调用 waitpid(-1, WNOHANG) 来回收所有子进程。 Perl 提供了全局变量%SIG,只要设置该变量即可安装信号处理程序。 下面的 waitpid_test1.pl演示了使用方法。完整的代码可以从本文的附件中下载。
use POSIX ":sys_wait_h";
$SIG{CHLD} = /&REAPER;
sub REAPER {
}
执行这个程序并用 ps -ef 查看进程,可以发现僵尸进程不再出现了。
不过上面这个程序有个问题。Linux的信号是不能排队的, 如果信号到达进程时进程不能接收该信号,这个信号就会丢失。 REAPER中包含比较耗时的 while 循环,如果在 REAPER 执行过程中 发生 SIGCHLD 信号,这个信号就会丢失。为了避免这种情况,我们可以尽量减少信号处理的执行时间。参考下面的 waitpid_test2.pl。
our $zombies =0;
$SIG{CHLD} = sub { $zombies++ };
# 主程序
while (1) {
}
实际上,waitpid_test2.pl 并不能及时回收结束的子进程—— 由于 REAPER在主程序中执行,如果子进程结束时主程序尚未执行到 REAPER 一行, 那么系统中可能会出现相当数量的僵尸进程,直到主程序执行REAPER 为止。 不过一般情况下这种方法已经足够用了。
忽略 SIGCHLD 回收僵尸进程
另一个比较简单的方法就是直接忽略 SIGCHLD 信号,系统会自动回收结束的子进程。 参见下面的ignore_sigchld_test.pl。
$SIG{CHLD} = 'IGNORE';
与前面的 waitpid 方法相比,此方法虽然方便, 但缺点就是父进程无法在子进程结束时做些处理。可根据实际需要选择最合适的方法。
本文源代码下载 attachperl-source.zip
################################################
#!/usr/bin/perl
#------------------------------------
# ignore_sigchld_test.pl
$SIG{CHLD} = 'IGNORE'; # 忽略 SIGCHLD 信号
sub child {
print "I'm child, pid=$$./n";
}
while (1) {
if (fork() == 0) {
&child;
exit;
} else {
sleep 5;
}
}
################################################