关闭

Perl fork

52人阅读 评论(0) 收藏 举报
分类:

转自:http://blog.csdn.net/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()) {
  print "I'm parent, my pid=$$, child'spid=$child_pid./n";
} else {
  print "I'm child, pid=$$./n";
}

运行之后显示下面的结果。

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 {
  print "I'm child, pid=$$./n";
}

while (1) {
  if (fork() == 0) {
   &child;   # 如果当前进程是子进程,则执行 child 函数
 exit;     # 并退出
  } else {
    sleep5;   # 如果是父进程,则睡眠 5 秒
  }
}

该程序每隔 5 秒创建一个子进程,子进程输出一行文字后退出。 执行该程序片刻之后,从其他终端用 ps -ef 命令可以看到进程状态。 标有 < defunct >
的就是僵尸进程。


charlee  11687 10870  0 02:01pts/1   00:00:00 /usr/bin/perl perl/zombie-test.pl
charlee  11688 11687  0 02:01pts/1   00:00:00 [zombie-test.pl]
< defunct >

charlee  1169111687  0 02:01pts/1   00:00:00 [zombie-test.pl]< defunct >

charlee  1169511687  0 02:01pts/1   00:00:00 [zombie-test.pl]< defunct >

如何避免僵尸进程?当子进程结束时,系统会向父进程发送 SIGCHLD 信号。父进程只要在处理这个信号时回收子进程占用的资源即可。
利用 waitpid 回收僵尸进程

一个方法就是在父进程中利用 waitpid 函数。该函数回收指定进程的资源, 并返回已结束进程的进程id。若指定进程不存在,则返回-1。 我们可以通过调用 waitpid(-1, WNOHANG) 来回收所有子进程。 Perl 提供了全局变量%SIG,只要设置该变量即可安装信号处理程序。 下面的 waitpid_test1.pl演示了使用方法。完整的代码可以从本文的附件中下载。

use POSIX ":sys_wait_h";
$SIG{CHLD} = /&REAPER;
sub REAPER {
  my $pid;
  while (($pid = waitpid(-1, WNOHANG)) > 0){
    #进行一些处理
  }
}

执行这个程序并用 ps -ef 查看进程,可以发现僵尸进程不再出现了。

不过上面这个程序有个问题。Linux的信号是不能排队的, 如果信号到达进程时进程不能接收该信号,这个信号就会丢失。 REAPER中包含比较耗时的 while 循环,如果在 REAPER 执行过程中 发生 SIGCHLD 信号,这个信号就会丢失。为了避免这种情况,我们可以尽量减少信号处理的执行时间。参考下面的 waitpid_test2.pl。

our $zombies =0;                # 记录系统中僵尸进程的数目
$SIG{CHLD} = sub { $zombies++ };  #信号处理程序中仅仅统计僵尸进程数目

# 主程序
while (1) {
  if (fork() == 0) {
   &child;   # 如果当前进程是子进程,则执行 child 函数
 exit;     # 并退出
  } else {
    &REAPERif $zombies;
    sleep5;   # 如果是父进程,则睡眠 5 秒
  }
}

实际上,waitpid_test2.pl 并不能及时回收结束的子进程—— 由于 REAPER在主程序中执行,如果子进程结束时主程序尚未执行到 REAPER 一行, 那么系统中可能会出现相当数量的僵尸进程,直到主程序执行REAPER 为止。 不过一般情况下这种方法已经足够用了。
忽略 SIGCHLD 回收僵尸进程

另一个比较简单的方法就是直接忽略 SIGCHLD 信号,系统会自动回收结束的子进程。 参见下面的ignore_sigchld_test.pl。

$SIG{CHLD} = 'IGNORE';  # 忽略 SIGCHLD 信号

与前面的 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;   # 如果当前进程是子进程,则执行 child 函数
exit;     # 并退出
} else {
sleep 5;   # 如果是父进程,则睡眠 5 秒
}
}
################################################
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:12067次
    • 积分:541
    • 等级:
    • 排名:千里之外
    • 原创:44篇
    • 转载:11篇
    • 译文:0篇
    • 评论:0条
    文章分类