前面这篇文章简单地阐述了用Perl创建守护进程(daemon)的方法。 实际上,创建一个多进程的守护进程需要注意很多事情。 下面的内容来自著名的Unix Programming FAQ的1.7节,介绍了Unix下创建守护进程的方法。

通常将一个不与任何终端相关联的后台进程定义为daemon进程。下面是通常所需的七 个步骤:

a. fork()之后父进程退出。子进程确保不是process group leader,这是成功调用 setsid()所要求的。

b. setsid(),创建新的session和process group,成为其leader,并脱离控制终端。

c. 再次fork()之后父进程退出,子进程确保不是session leader,将永远不会重获 控制终端。这是SVR4的特性所致。

d. chdir( “/” ),减少管理员卸载(unmount)文件系统时可能遇上的麻烦。这一步可 选,也可chdir()到其它目录。

e. umask( 0 ),使当前进程对自己所写文件拥有完全控制权,避免继承的umask()设 置带来困挠。这一步可选。

f. 关闭0、1、2三个句柄。许多daemon程序用sysconf()获取_SC_OPEN_MAX,并在一 个偱环中关闭所有可能打开的文件句柄。目的在于释放不必要的系统资源,它们 是有限资源。

g. 出于安全以及健壮性考虑,即使当前进程不使用stdin、stdout、stderr,也应重 新打开0、1、2三个句柄,使之对应/dev/null。当然,你也可以根据需要使之对 应不同的(伪)文件。总之,保持0、1、2三个句柄呈打开状态,并使之指向无害文 件。

实际上,除了以上7点之外你还应当做两件事情。第一件就是切换到daemon用户——避免由于代码的安全漏洞导致root权限泄漏; 第二件事情就是忽略 SIGCHLD 信号,让 init 进程来回收结束了的子进程。

如果将上述内容用 perl 写出来就是下面的程序:

#!/usr/bin/perl

#------------------------------
# daemon process sample, written by charlee, 2006/10/12
#

use strict;
use POSIX 'setsid';

our $USER = 'lijian';
our $GROUP = 'lijian';

#------------------------------
# child process to be created by daemon
sub child-process {
    # do some work here
}


#------------------------------
# create daemon process

# 1. fork() so the parent can exit, returns control to shell.
exit if fork;

# 2. setsid() to become a process group and session group leader.
&setsid();

# 3. fork() again so the parent (session group leader) can exit.
exit if fork;

# 4. (OPTIONAL) chdir('/') to ensure our daemon doesn't keep any directory in use.
chdir '/';

# 5. (OPTIONAL) umask() so we have complete control over the permissions of anything we write.
umask 022;

# 6. close() fds 0, 1, and 2.
close STDIN;
close STDOUT;
close STDERR;

# 7. redirect fds 0, 1, and 2 to /dev/null
open STDIN, '/dev/null';
open STDOUT, '>/dev/null';
open STDERR, '>/dev/null';

# 8. change to daemon user and group.
&sudo($USER, $GROUP);

# 9. ignore SIGCHLD signal to avoid zombie processes
$SIG{CHLD} = 'IGNORE';

# 10. start main loop.
while(1) {
    my $pid = fork;
    if ($pid == 0) {
        &child_process();
        exit;
    } else {
        sleep 1;
    }
}

# main end

# function to change user and group
sub sudo {
    my ($user, $group) = @_;
    my $uid = (getpwnam($user))[2];
    my $gid = (getgrnam($group))[2];
    ($(, $)) = ($gid, "$gid $gid");
    ($<, $>) = ($uid, $uid);
}    

__END__