Почему моя Perl-программа не пожинает дочерние процессы после fork?
Я пытался написать ping сканер с использованием Perl для внутреннего использования. Поскольку он сканирует 24-битную сеть CIDR, сценарий выполняется слишком долго, если он выполняется в одном потоке. Я попытался добавить функциональность форка, чтобы ускорить процесс, но моя первая попытка заняла примерно то же время, поскольку в каждый момент времени был активен только один дочерний процесс.
Я прочитал о дочерних процессах в документе perlipc, а также в книге рецептов Perl и придумал вторую версию:
##Install the CHLD SIG handler
$SIG{CHLD} = \&REAPER;
sub REAPER {
my $childPID;
while (( $childPID = waitpid(-1, WNOHANG)) > 0) {
print "$childPID exited\n";
}
$SIG{CHLD} = \&REAPER;
}
my $kidpid;
for (1 .. 254) {
my $currIP = join ".", (@ipSubset[0,1,2], $_);
die "Could not fork()\n" unless defined ($kidpid = fork);
if ($kidpid) {
#Parent process
#Does nothing
}
else {
#Child process
my $pingConn = Net::Ping->new(); #TCP
say "$currIP up" if $pingConn->ping($currIP);
$pingConn->close();
#Nothing else to do
exit;
}
}
say "Finished scanning $ipRange/24";
Когда я сканирую свою внутреннюю сеть, вывод:
$perl pingrng2.pl 192.168.1.1
192.168.1.2 up
5380 exited
192.168.1.102 up
192.168.1.100 up
5478 exited
5480 exited
Finished scanning 192.168.1.1/24
Как видно из результата, потоки, которые выполняют успешное сканирование, печатают сообщение "вверх", выходят корректно и получают исходный процесс. Тем временем остальные 251 поток остаются привязанными к /sbin/init, как видно из быстрого списка ps -ef. Если я добавлю 'print "Child: $currIP end \n"' в дочерний блок обработки непосредственно перед оператором выхода, я получу вывод от оставшихся 251 процессов на моем терминале "после", когда мой скрипт perl завершился.
Что тут происходит? Я думал, что подпрограмма $SIG{CHLD} в сочетании с циклом waitpid пожнет все дочерние процессы и гарантирует, что в системе не останется никаких зомби / висячих процессов.
В то же время я также хотел бы иметь возможность запускать определенное количество дочерних процессов в любой момент времени, например, "n" дочерних процессов, запущенных одновременно, когда каждый выходит из родительского процесса, запускает другого дочернего процесса, если это необходимо, но не имеет больше чем "н" детей в любой момент. Это возможно? Если да, могу ли я получить псевдокод, чтобы помочь мне?
3 ответа
Похоже, ваш родительский процесс завершается раньше, чем дети (и поэтому никогда не получает возможности пожинать их). Попробуйте это вместо этого:
#!/usr/bin/perl
use 5.010;
use strict;
use warnings;
use Net::Ping;
my @ipSubset = (192, 168, 10);
my $i = 0;
my @pids;
for my $octet (1 .. 254) {
my $currIP = join ".", @ipSubset[0 .. 2], $octet;
die "Could not fork()\n" unless defined (my $pid = fork);
#parent saves chlidren's pids and loops again
if ($pid) {
push @pids, $pid;
next;
}
#child process
my $pingConn = Net::Ping->new;
say "$currIP up" if $pingConn->ping($currIP);
$pingConn->close();
exit;
}
#wait on the children
for my $pid (@pids) {
waitpid $pid, 0;
}
Посмотрите на Parallel:: ForkManager. Он позаботится обо всех этих маленьких деталях для вас.
Когда поток вызывает ping(), он продолжает пытаться пропинговать IP, пока не установит ответ. чтобы исправить это, попробуйте включить таймаут в качестве второго аргумента в ping(). Похоже, прямо сейчас эти оставшиеся потоки продолжают пинговать ответ, пока не получат его.
Что касается набора N потоков, почему бы не разбить 0-255 на куски, например, имея два потока, один из которых идет от 0-127, а другой - от 128 до 255? Я бы использовал кратность 2 для вашего количества потоков для простоты.