1

我正在使用 Lincoln Stein 的优秀Network Programming With Perl书中的一些习语来编写服务器。对于在 fork 之前声明并在之后引用的变量,我似乎得到了奇怪的行为。

这是一个说明问题的完整程序。(我为它没有被更多地精简而道歉;当我把所有我认为不相关的东西都删掉时,问题就消失了。)如果你寻找,##### MYSTERY #####你会看到声明的两个版本my $pid。一个版本有效,而另一个版本无效。在调用become_daemon()将子 PID 分配给 $pid 之后,我尝试将其写入 PID 文件,然后验证它是否有效。根据我使用的声明方法,它要么成功,要么失败。我不明白!

#!/usr/bin/perl 
#
# Prototype contactd master server

use warnings;
use strict;
use Carp;
use Getopt::Std;
use File::Basename;
use IO::Socket;
use IO::File;
use Net::hostent;    # for OO version of gethostbyaddr
use POSIX qw{WNOHANG setsid};
use Data::Dumper;

#use 5.010;
sub say { print "@_\n"; }

my $program        = basename $0;
my $default_config = "$program.config";

$| = 1;              # flush STDOUT buffer regularly

my %opts;

my $config_file = $opts{c} || $default_config;

# Process the config file to obtain default settings
#
# Note: for now we'll hard code config values into the config hash.
#
my %config;

$config{PORT}      = 2000;
$config{DAEMONIZE} = 0;
$config{VERBOSE}   = 0;
$config{LOGDIR}    = "/mxhome/charrison/private/wdi/logs";
$config{PIDFILE}   = "/var/tmp/$program.pid";

# Process command line args to override default settings
#
my $server_port = $opts{p} || $config{PORT};
my $log_dir     = $opts{l} || $config{LOGDIR};
my $verbose   = !!( exists $opts{v} || $config{VERBOSE} );
my $daemonize = !!( exists $opts{d} || $config{DAEMONIZE} );
my $pid_file = $opts{P} || $config{PIDFILE};

################################################################################
# Set up signal handlers
#
# Caution: these call the logging manager servlog(), which has not yet been
# spawned.
################################################################################

# Set up a child-reaping subroutine for SIGCHLD
#
$SIG{CHLD} = sub {
    local ( $!, $^E, $@ );
    while ( ( my $kid = waitpid( -1, WNOHANG ) ) > 0 ) {
    }
};

# Set up a signal handler for interrupts
#
my $quit = 0;
$SIG{INT} = sub {
    $quit++;
};

# Set up signal handler for pipe errors
#
$SIG{PIPE} = sub {
    local ( $!, $^E, $@ );

};

################################################################################
#                           DAEMONIZATION OCCURS HERE
################################################################################

my $pid_fh = open_pid_file($pid_file);

##### MYSTERY #####

my $pid;           # this makes it work
# my $pid = $$;    # this breaks it!

$daemonize = 1;  # inserted here for demo

if ($daemonize) {
    say "Becoming a daemon and detaching from your terminal.  Bye!";
    $pid = become_daemon();    # update w/new pid
}

say "Here is pid: $pid.  Going to write it to $pid_file and close.";

# If we daemonized, then we are now executing with a different PID
#
# And in that case, the following fails silently!!
#

print $pid_fh $pid;    # store our PID in known location in filesystem
close $pid_fh;

say "Boo boo" if !-e $pid_file;
say qx{cat $pid_file};

##### END OF DEMO #####    

# open_pid_file()
#
# Courtesy of Network Programming with Perl, by Lincoln D. Stein
#
sub open_pid_file {
    my $file = shift;
    if ( -e $file ) {    # PID file already exists
        my $fh = IO::File->new($file) || return;
        my $pid = <$fh>;    # so read it and probe for the process
        croak "Server already running with PID $pid\n"    # die ...
            if kill 0 => $pid;                            # if it responds
        warn "Removing PID file for defunct server process $pid.";
        croak "Can't unlink PID file $file"               # die ...
            unless -w $file && unlink $file;              # if can't unlink
    }
    return IO::File->new( $file, O_WRONLY | O_CREAT | O_EXCL, 0644 )
        or die "Can't create PID file $file: $!\n";
}

# become_daemon()
#
# Courtesy of Network Programming with Perl, by Lincoln D. Stein
#
sub become_daemon {
    die "Can't fork" unless defined( my $child = fork );
    exit 0 if $child != 0;    # die here if parent

    # --- PARENT PROCESS DIES
    # --- CHILD PROCESS STARTS

    setsid();    # Become session leader
    open( STDIN, "</dev/null" );

    # servlog() writes to STDOUT which is being piped to log manager
    #
    #open( STDOUT, ">/dev/null" );
    open( STDERR, ">&STDOUT" );

    chdir '/';    # go to root directory
    umask(0);     # ??
    $ENV{PATH} = '/bin:/sbin:/use/bin:/usr/sbin';
    return $$;
}


END {
    unlink $pid_file if $pid == $$;    # only the daemon unlinks pid file
}
4

1 回答 1

1

在代码的末尾,您有:

END {
    unlink $pid_file if $pid == $$;    # only the daemon unlinks pid file
}

$pid如果在父进程中未定义,这可以正常工作。但是如果你把它初始化为父进程的ID,那么父进程会在become_daemon调用exit.

在我看来,编写 PID 文件的孩子和取消链接的父母之间存在竞争,因此结果可能并不总是相同。

编辑:实际上,没有种族,因为 PID 文件是在分叉之前打开的。所以父进程打开文件,分叉子进程,取消链接文件并退出。子进程仍然拥有该文件的句柄并且它仍然可以对其进行写入,但是该文件不再从文件系统中的任何位置链接,并且一旦子进程退出它就会消失。

于 2012-12-29T23:45:36.193 回答