Приглашаем посетить
Отели (hotels.otpusk-info.ru)

16.8. Управление потоками ввода и вывода другой программы

Назад
Глава 16 Управление процессами и межпроцессные взаимодействия
Вперед

16.8. Управление потоками ввода и вывода другой программы

Проблема

Вы хотите управлять как входными, так и выходными данными другой программы. Функция open позволяет решить одну из этих задач, но не обе сразу.

Решение

Воспользуйтесь стандартным модулем 1РС::Ореп2:
use IPC::0реп2;
open2(*README, *WRITEME, $program);
print WRITEME "here's your input\n";
$output = ;
close(WRITEME);
close(README);

Комментарий

Желание управлять вводом и выводом другой программы возникает очень часто, однако за ним таится на удивление много опасностей. Поэтому вам не удастся вызвать open в виде:
open(DOUBLE_HANDLE, "| программа аргументы |") # НЕВЕРНО

Большая часть трудностей связана с буферизацией. Поскольку в общем случае нельзя заставить другую программу использовать небуферизованный вывод, нет гарантии, что операции чтения не будут блокироваться. Если блокировка ввода устанавливается одновременно с тем, как другой процесс заблокируется в ожидании вывода, возникает состояние взаимной блокировки (deadlock). Процессы входят в клинч, пока кто-нибудь не убьет их или не перезагрузит компьютер. Если вы можете управлять буферизацией другого процесса (потому что вы сами написали программу и знаете, как она работает), возможно, вам поможет модуль 1РС::Ореп2. Первые два аргумента функции ореп2, экспортируемой 1РС::Ореп2 в ваше пространство имен, представляют собой файловые манипуляторы. Либо передавайте ссылки на typeglobs, как это сделано в решении, либо создайте собственные объекты IO::Handle и передайте их:
use IPC::0реn2;
use IO::Handle;
($reader, $writer) = (10: :Handle->new, 10: :Handle->new);
open2($reader, $writer, $program);

Чтобы передать объекты, необходимо предварительно создать их (например, функцией 10: :Handle->new). Если передаваемые переменные не содержат файловых манипуляторов, функция ореп2 не создаст их за вас.
Другой вариант - передать аргументы вида "<&OTHERFILEHANDLE" или ">&OTHERFILEHANDLE", определяющие существующие файловые манипуляторы для порожденных процессов. Эти файловые манипуляторы не обязаны находиться под контролем вашей программы; они могут быть подключены к другим программам, файлам или сокетам.
Программа может задаваться в виде списка (где первый элемент определяет имя программы, а остальные элементы - аргументы программы) или в виде отдельной строки (передаваемой интерпретатору в качестве команды запуска программы). Если вы также хотите управлять потоком STDERR программы, воспользуйтесь модулем IPC::Open3 (см. следующий рецепт). Если произойдет ошибка, возврат из ореп2 и орепЗ не происходит. Вместо этого вызывается die с сообщением об ошибке, которое начинается с "о pen 2" или "орепЗ". Для проверки ошибок следует использовать конструкцию eval БЛОК:
eval {
open2($readme, $writeme, @program_and_arguments);
};
if ($@) {
if ($@ =- /~open2/) {
warn "open2 failed: $!\n$@\n";
return;
} die; # Заново инициировать непредвиденное исключение
1


> Смотри также -------------------------------- Документация по стандартным модулям 1РС::Ореn2 и IPC::Open3; рецепт 10.12; описание функции eval в perlfunc(1), описание переменной $@ в разделе "Special Global Variables" perlvar(1).

16.9. Управление потоками ввода, вывода и ошибок другой программы

Проблема

Вы хотите полностью управлять потоками ввода, вывода и ошибок запускаемой команды.

Решение

Аккуратно воспользуйтесь стандартным модулем IPC::Open3, возможно - в сочетании с модулем IO::Select (появившимся в версии 5.004).

Комментарий

Если вас интересует лишь один из потоков STDIN, STDOUT или STDOUT программы, задача решается просто. Но если потребуется управлять двумя и более потоками, сложность резко возрастает. Мультиплексирование нескольких потоков ввода/вывода всегда выглядело довольно уродливо. Существует простое обходное решение:
@аll = '($cmd sed -e 's/"/stdout: /' ) 2>&1';
for (@all) { push @{ s/stdout: // ? \(@out lines : \@errlines }, $_ }
print "STDOUT:\n", @outlines, "\n";
print "STDERR;\n", @errlines, "\n";

Если утилита sed не установлена в вашей системе, то в простых случаях вроде показанного можно обойтись командой perl -ре, которая работает практически так же. Однако то, что здесь происходит, в действительности нельзя считать параллельными вычислениями. Мы всего лишь помечаем строки STDOUT префиксом "stdout:" и затем удаляем их после чтения всего содержимого STDOUT и STDERR, сгенерированного программой.
Кроме того, можно воспользоваться стандартным модулем IPC::Open3. Как ни странно, аргументы функции IPC::Open3 следуют в другом порядке, нежели в 1РС::Ореп2.
open3("WRITEHANDLE, *READHANDLE, *ERRHANDLE, "ЗАПУСКАЕМАЯ ПРОГРАММА"):
Открываются широкие потенциальные возможности для создания хаоса - еще более широкие, чем при использовании о pen 2. Если попытаться прочитать STDERR программы, когда она пытается записать несколько буферов в STDOUT, процесс записи будет заблокирован из-за заполнения буферов, а чтение заблоки-|)уется из-за отсутствия данных.
Чтобы избежать взаимной блокировки, можно имитировать ореnЗ с помощью ork, open и ехес; сделать все файловые манипуляторы небуферизованными и использовать sysread, syswrite и select, чтобы решить, из какого доступного для чтения манипулятора следует прочитать байт. Однако ваша программа становится медленной и громоздкой, к тому же при этом ие решается классическая проблема взаимной блокировки ореп2, при которой каждая программа ждет поступления данных от другой стороны:
use IPC::0реn3;
$pid - open3(*HIS_IN, *HIS_OUT, *HIS_ERR, $cmd);
close(HIS_IN); # Передать порожденному процессу EOF или данные
@outlines = ; # Читать до конца файла
@errlines = ; # XXX: Возможная блокировка
# при больших объемах
print "STDOUT:\n", @outlines, "\n'
print "STDERR:\n", @errlines, "\n'

Кроме того (как будто одной взаимной блокировки недостаточно), такое решение чревато нетривиальными ошибками. Существуют по крайней мере три неприятных ситуации: первая - когда и родитель и потомок пытаются читать одновременно, вызывая взаимную блокировку. Вторая - когда заполнение буферов заставляет потомка блокироваться при попытке записи в STDERR, тогда как родитель блокируется при попытке чтения из STDOUT потомка. Третья - когда заполнение буферов заставляет родителя блокировать запись в STDIN потомка, а потомок блокируется при записи в STDOUT или STDERR. Первая проблема в общем случае не решается, хотя ее можно обойти, создавая таймеры функцией alarm и предотвращая перезапуск блокирующих операций при получении сигнала SIGALRM.
Мы используем модуль IO::Select, чтобы узнать, из каких файловых манипуляторов можно прочитать данные (для этой цели можно использовать встроенную функцию select). Это решает вторую, но не третью проблему. Для решения третьей проблемы также потребуются alarm и SIGALRM.
Если вы хотите отправить программе входные данные, прочитать ее вывод и затем либо прочитать, либо проигнорировать ошибки, работы заметно прибавится (см. пример 16.2). Пример 16.2. cmd3sel
#!/usr/bin/perl
# cmd3sel - управление всеми тремя потоками порожденного процесса
# (ввод, вывод и ошибки).
use IPC::0pen3;
use IO::Select;
$cmd = "grep vt33 /none/such - /etc/termcap";
$pid = open3(*cmd_in, *cmd_out, *cmd_err, $cmd);
$SIG{CHLD} = sub {
print "REAPER: status $? on $pid\n" if waitpid($pid, 0) > 0
};
print CMD_IN "This line has a vt33 lurking in it\n";
close(CMD_IN);
$selector = IO::select->new();
$selector->add(*CMD_ERR, *CMD_OUT);
while (@ready = $selector->can_read) { foreach $fh (@ready) {
if (fileno($fh) == fileno(cmd_err))
{print "STDERR: ", scalar } else
{print "STDOUT: ", scalar } $selector->remove($fh) if eof($fh);
}
}
close(CMD_CUT);
close(CMD_ERR);

Мы отправляем короткую входную строку, а затем закрываем манипулятор. Тем самым предотвращается ситуация взаимной блокировки двух процессов, каждый из которых ожидает записи данных другим процессом.

> Смотри также -------------------------------
Документация по стандартным модулям IO::Select; IPC::Open2 и IPC::Open3: описание функции alarm в perlfunc(1); рецепты 16.8; 16.15-16.16.

16.10. Взаимодействие между родственными процессами

Проблема

Имеются два взаимосвязанных процесса, которые должны обмениваться данными. Вам требуется более высокая степень контроля по сравнению с той, что обеспечивают open, system и '...'.

Решение


Воспользуйтесь pipe, а затем - fork:
pipe(READER, WRITER);
if (fork) {
# Выполнить родительский код, в котором происходит либо чтение,
# либо запись (что-то одно).
} else {
# Выполнить код потомка, в котором происходит либо чтение,
# либо запись (что-то одно).
}
Либо используйте особую форму open:
if ($pid = open(child, "|-")) {
# Выполнить родительский код, передающий данные потомку
} else {
die "cannot fork: $!" unless defined $pid;
# Иначе выполнить код потомка, принимающий данные от родителя
}
Или по-другому:
if ($pid = open(child, "-|")) {
# Выполнить родительский код, принимающий данные от потомка
} else {
die ''cannot fork: $!" unless defined $pid;
# Иначе выполнить код потомка, передающий данные родителю
}

Комментарий

Канал представляет собой два файловых манипулятора, связанных так, что записанные в один файловый манипулятор данные могут быть прочитаны из другого. Функция pipe создает два манипулятора, связанных в канал; первый (приемник) предназначен для чтения, а второй (передатчик) - для записи. Хотя вы не сможете взять два существующих манипулятора и объединить их в канал, функция pipe часто используется при обмене данными между процессами. Один процесс создает пару манипуляторов функцией pipe, после чего создает потомка с помощью fork; в результате возникают два разных процесса, выполняющих одну и ту же программу, каждый из которых обладает копией связанных манипуляторов.
Неважно, какой процесс будет приемником, а какой - передатчиком; когда процесс начинает играть одну из этих ролей, его напарнику достается другая. Такой обмен данными может быть только односторонним (но не бросайте читать!)
Мы воспользуемся модулем IO::Handle, в котором нас интересует метод autoflushO (если вы предпочитаете более эффективные решения, воспользуйтесь решением с select, описанным в главе 7). Если этого не сделать, наша отдельная строка вывода застрянет в канале и не доберется до другого конца до закрытия канала. Версия родителя, передающего данные потомку, приведена в примере 16.3. Пример 16.3. pipel
#!/usr/bin/perl -w
# pipel - применение pipe и fork для отправки данных родителем потомку
use IO::Handle;
pipe(READER, WRITER);
WRITER->autoflush(1);
if ($pid = fork) {
close READER;
print WRITER "Parent Pid $$ is sending this\n":
close WRITER;
waitpid($pid,0);
} else {
die "cannot fork: $!" unless defined $pid;
close WRITER;
chomp($line = );
print "Child Pid $$ ]ust read this: '$line'\n";
close READER; # Все равно это произойдет exit;
}
В примерах этого рецепта основная проверка ошибок была оставлена читателю для самостоятельной работы. Мы так поступили для того, чтобы взаимодействие функции стало более наглядным. В реальной жизни проверяются возвращаемые значения всех вызовов системных функции.
В примере 16.4 показана версия потомка, передающего данные родителю. Пример 16.4. pipe2
#!/usr/bin/perl -w
# pipe2 - применение pipe и fork для передачи данных потомком родителю
use IO::Handle;
pipe(READER, WRITER);
WRITER->autoflush(1);
if ($pid = fork) {
close WRITER;
chomp($line = );
print "Parent Pid $$ just read this: '$line'\n";
close READER;
waitpid($pid,0);
} else {
die "cannot fork: $!" unless defined $pid;
close READER:
print WRITER "Child Pid $$ is sending this\n";
close WRITER; # Все равно это произойдет
exit;
}
Обычно обе половины входят в цикл и приемник продолжает читать до конца файла. Это происходит до тех пор, пока передатчик не закроет канал или не завершится.
Поскольку манипуляторы каналов работают лишь в одном направлении, каждый процесс использует лишь один канал из пары и закрывает неиспользуемый манипулятор. Причина, по которой это делается, нетривиальна; представьте себе ситуацию, при которой принимающий процесс не закрыл передающий манипулятор. Если после этого передающий процесс завершится, пока принимающий процесс пытается что-нибудь прочитать, последний намертво "зависнет". Система не может сообщить приемнику о том, что данных для чтения больше не будет, пока не будут закрыты все копии передающего манипулятора. Функция open, получая в качестве второго аргумента "-1" или " | =", неявно вызывает pipe и fork. Это несколько упрощает приведенный выше фрагмент. Порожденный процесс общается с родителем через stdin или stdout в зависимости от того, какая строка была использована, "- " или " | -". При подобном применении open, когда родитель хочет передать данные потомку, он использует нечто похожее на пример 16.5. Пример 16.5. pipe3
#!/usr/bin/perl -w
# pipe3 - применение разветвляющего вызова open
# для передачи данных от родителя к потомку
use IO::Handle;
if ($pid = open(child, "|-")) {
CHILD->autoflush(1);
print CHILD "Parent Pid $$ is sending this\n";
close(CHILD);
} else {
die "cannot fork: $!" unless defined $pid;
chomp($line = );
print "Child Pid $$ just read this: '$line'\n";
exit;
}


Поскольку STDIN потомка уже подключен к родителю, потомок может запустить через ехес другую программу, читающую данные из стандартного ввода - например, Ipr. Это полезная и часто используемая возможность.
Если потомок захочет передать данные родителю, он делает нечто похожее на пример 16.6. Пример 16.6. pipe4
#!/usr/bin/perl -w
# pipe4 - применение разветвляющего вызова open
# для передачи данных от потомка к родителю
use IO::Handle;
if ($pid = open(child, "-|")) {
chomp($line = );
print "Parent Pid $$ just read this: '$line'\n";
close(CHILD);
} else {
die "cannot fork: $!" unless defined $pid;
STDOUT->autoflush(1);
print STDOUT "Child Pid $$ is sending this\n";
exit;
}


И снова, поскольку STDOUT потомка уже подключен к родителю, потомок может запустить через ехес другую программу, выдающую нечто интересное в его стандартный вывод. Эти данные также будут переданы родителю как ввод от .
При подобном использовании open мы не обязаны вручную вызывать waitpid, поскольку не было явного вызова fork. Однако close вызвать все же надо. В обоих случаях переменная $? содержит статус ожидания порожденного процесса (о том, как интерпретировать это значение, рассказано в рецепте 16.19).
В предыдущих примерах рассматривалась однонаправленная связь. Что делать, если вы хотите, чтобы данные передавались в обе стороны? Дважды вызовите pipe перед вызовом fork. Вам придется следить за тем, кто, что и когда передает, иначе может возникнуть взаимная блокировка (см. пример 16.7). Пример 16.7. pipe5
#!/usr/bin/perl -w
# pipe5 - двусторонний обмен данными с использованием двух каналов
# без применения socketpair
use IO::Handle;
pipe(PARENT_RDR, CHILDJJTR);
plpe(CHILD_RDR, PARENT_WTR);
CHILD_WTR->autoflush(1);
PARENT_WTR->autoflush(1);
if ($pid = fork) {
close PARENT_RDR; close PARENT_WTR;
print CHILD_WTR "Parent Pid $$ is sending this\n";
chomp($line = );
print "Parent Pid $$ just read this: '$line'\n";
close CHILD_RDR;. close CHILD_WTR;
waitpid($pid,0);
} else {
die "cannot fork: $!" unless defined $pid;
close CHILD_RDR; close CHILD_WTR;
chomp($line = );
print "Child Pid $$ just read this: '$line'\n";
print PARENT_WTR "Child Pid $$ is sending this\n":
close PARENT_RDR; close PARENT_WTR;
exit;

Ситуация усложняется. Оказывается, существует специальная системная функция socketpair (см. пример 16.8), которая упрощает предыдущий пример. Она работает аналогично pipe, за исключением того, что оба манипулятора могут использоваться как для приема, так и для передачи.
Пример 16.8. pipe6
#!/usr/bin/perl -w
# pipe6 - двусторонний обмен дпинмми г. применением socketpair
use Socket;
use IO::Handle;
# Мы говорим AF_UNIX, потому что хотя константа *_1_ОСА1_
# соответствует POSIX 1003.1g, на многих компьютерах
# она еще не поддерживается.
socketpair(CHILO, PARENT, AF_UNIX, SOCK_STREAM, PF_UNSPEC) or die "socketpair: $!";
CHILD->autoflush(1);
PARENT->autoflush(1);
if ($pid = fork) {
close PARENT;
print CHILD "Parent Pid $$ is sending this\n";
chomp($line = );
print "Parent Pid $$ just read this: '$line'\n";
close CHILD;
waitpid($pld,0);
} else {
die "cannot fork: $!" unless defined $pid;
close CHILD;
chomp($line = );
print "Child Pid $$ just read this: '$line'\n";
print PARENT "Child Pid $$ is sending this\n";
close PARENT;
exit;
}

В некоторых системах каналы исторически были реализованы как два полузакрытых конца пары сокетов. Фактически реализация pipe(READER, WRITER) выглядела так:
socketpair(READER, WRITER, AF_UNIX, SOCK_STREAM, PF_UNSPEC);
shutdown(READER, 1); # Запретить запись для READER shutdown(WRITER, 0);
# Запретить чтение для WRITER

В ядрах Linux до версии 2.0.34 системная функция shutdown(2) работала неверно. Приходилось запрещать чтение для READER и запись для WRITER.

> Смотри также -------------------------------- Описания всех использованных функций в perlfunc{1) документация по стандартному модулю
IРС::Ореn2; рецепт 16.8.

16.11. Имитация файла на базе именованного канала

Проблема

Вы хотите, чтобы процесс перехватывал все обращения к файлу. Например, файл -/.plan должен превратиться в программу, которая будет возвращать случайную цитату.

Решение

Воспользуйтесь именованными каналами. Сначала создайте канал (вероятно, г командном интерпретаторе):
% mkfifo /path/to/named.pipe Принимающий фрагмент выглядит так:
open(FIFO, "< /path/to/named.pipe")
or die $!;
while () { print "Got: $_";
} close(FIFO);
Передающий фрагмент выглядит так:
open(FIFO, "> /path/to/named.pipe")
or die $1:
print FIFO "Smoke this.\n";
close(FIFO):

Комментарий

Именованный канал (также встречается термин FIFO) представляет собой специальный файл, используемый в качестве буфера для взаимодействия процессов на одном компьютере. Обычные каналы также позволяют процессам обмениваться данными, но они должны наследовать файловые манипуляторы от своих родителей. Для работы с именованным каналом процессу достаточно знать его имя. В большинстве случаев процессы даже не обязаны сознавать, что они читают данные из FIFO.
Операции чтения и записи для именованных каналов выполняются точно так же, как и для обычных файлов (в отличие от сокетов UNIX, рассматриваемых в главе 17). Данные, записанные в FIFO, буферизуются операционной системой, а затем читаются обратно в порядке записи. Поскольку FIFO играет роль буфера для взаимодействия процессов, открытие канала для чтения блокирует его до тех нор, пока другой процесс не откроет его для записи, и наоборот. Если открыть канал для чтения и записи с помощью режима +< функции open, блокировки (в большинстве систем) не будет, поскольку ваш процесс сможет и принимать, и передавать данные. Давайте посмотрим, как использовать именованный канал, чтобы при каждом запуске finger люди получали разные данные. Чтобы создать именованный канал с именем .plan в основном каталоге, воспользуйтесь mkfifo или mknod:
% mkfifo '/.plan # Есть практически везде
% mknod '/.plan p # На случай, если у вас все же нет mkfifo

В некоторых системах приходится использовать }nknod(8). Имена и местонахождение этих программ могут быть другими - обращайтесь к системной документации. Затем необходимо написать программу, которая будет поставлять данные программам, читающим из файла -/.plan. Мы ограничимся выводом текущей даты и времени (см. пример 16.9). Пример 16.9. dateplan
#!/usr/bin/perl -w
# dateplan - вывод текущей даты и времени в файл .plan
while (1) {
open(FIFO, "> $ENV{HOME}/.plan")
or die "Couldn't open $ENV{HOME}/.plan for writing: $!\n";
print FIFO "The current time is ", scalar(localtime), "\n";
close FIFO;
sleep 1;
}

К сожалению, такое решение работает не всегда, потому что некоторые варианты finger и соответствующие демоны проверяют размер файла .plan перед тем, как пытаться читать из него. Поскольку именованные каналы в файловой системе представлены в виде специальных файлов нулевого размера, некоторые клиенты и серверы не станут открывать именованный канал и читать из него, и наш фокус не удастся.
В примере с .plan демон был передатчиком. Приемники-демоны тоже встречаются не так уж редко. Например, именованный канал может применяться для ведения централизованного журнала, собирающего данные от нескольких процессов. Программа-сервер читает сообщения из именованного канала и записывает их в базу данных или файл. Клиенты передают сообщения в именованный канал. Такая схема избавляет клиентов от хлопот, связанных с логикой передачи данных, и позволяет легко внести необходимые изменения в реализацию механизма передачи. В примере 16.10 приведена простая программа для чтения двухстрочных блоков, где первая строка определяет процесс, а вторая - текст сообщения. Все сообщения от httpd игнорируются, а сообщения от login сохраняются в /var/log/login. Пример 16.10. fifolog
#!/usr/bin/perl -w
# fifolog - чтение и сохранение сообщений из FIFO
use IO::File;
$SIG{ALRM} = sub { close(fifo) }; # Переход к следующему
# процессу в очереди
while (1) {
alarm(O); # Отключить таймер
open(FIFO, "< /Imp/log") or die* "Can't open /Imp/log : $!\n":
alarm(1); # 1 секунда на регистрацию
$service = ;
next unless defined $service; # Прерывание или нечего регистрировать
chomp $service;
$message = ;
next unless defined $message; # Прерывание или нечего регистрировать
chomp $message;
alarm(O); # Отключить таймеры
# для обработки сообщений
if ($service eq "http") {
# Игнорировать
} elsif ($service eq "login") { # Сохранить в /var/log/login
if ( open(LOG, "" /tmp/login") ) {
print LOG scalar(localtime), " $service $message\n";
close(LOG);
} else {
warn "Couldn't log $service $message to /var/log/login : $!\n":
}
}
}
Программа получилась сложнее предыдущей по нескольким причинам. Прежде всего, мы не хотим, чтобы наш сервер ведения журнала надолго блокировал передатчики. Нетрудно представить ситуацию, при которой злонамеренный или бестолковый передатчик открывает именованный канал для записи, по не передает полного сообщения. По этой причине мы используем alarm и SIGALRM для передачи сигналов о нарушениях во время чтения.
При использовании именованных каналов могут возникнуть лишь два исклю чительных состояния: когда у приемника исчезает передатчик, и наоборот. Если процесс читает из именованного канала, а передатчик закрывает его со своего конца, то принимающий процесс получит признак конца файла (о возвращает undef). Однако если приемник отключается от канала, то при следующей попытке записи передатчик получит сигнал SIGPIPE. Если игнорировать сигналы о нарушении канала конструкцией $SIG{PIPE}=' ignore', print возвращает false, a' переменной $! присваивается значение epipe:
use POSIX qw(:errno_h);
$SIG{PIPE} = 'ignore';
# . . .
$status = print fifo "are you there?\n";
if (!$status && $! == epipe) {
' warn "My reader has forsaken me!\n";
next;
}

Возможно, у вас возник вопрос: "Если сто процессов одновременно пытаются передать данные серверу, как можно быть уверенным в том, что я получу сто разных сообщений, а не хаотическую мешанину из символов или строк разных процессов?" Хороший вопрос. Согласно стандарту POSIX, запись менее PIPE_BUF байт будет доставлена автоматически, то есть не перепутается с другими. Значение константы PIPE_BUF можно узнать из модуля POSIX:
use POSIX:
print _POSIX_PIPE_BUF, "\n":

К счастью, стандарт POSIX также требует, чтобы значение PIPE_BUF было не менее 512 байт. Следовательно, остается лишь позаботиться о том, чтобы клиенты не пытались передавать более 512 байт за раз.
Но что если вам понадобилось зарегистрировать более 512 байт? Разделите каждое большое сообщение на несколько маленьких (менее 512 байт), снабдите каждое сообщение уникальным идентификатором клиента (например, идентификатором процесса) и организуйте их сборку на сервере. Нечто похожее происходит при разделении и сборке сообщений TCP/IP. Один именованный канал не обеспечивает двухстороннего обмена данными между передатчиком и приемником, что усложняет аутентификацию и другие способы борьбы с передачей ложных сообщений (если не делает их невозможными). Вместо того чтобы упрямо втискивать эти возможности в модель, в которой они неуместны, лучше ограничить доступ к именованному каналу средствами файловой системы (на уровне прав владельца и группы).

> Смотри также --------------------------------
Страницы руководтва mkfifo(8) или mknod(8) (если они есть); рецепт 17.6.

16.12. Совместное использование переменных в разных процессах

Проблема

Требуется организовать совместный доступ к переменным в разветвлениях или неродственных процессах.

Решение

Используйте средства SysV IPC, если ваша система их поддерживает.

Комментарий

Хотя средства SysV IPC (общая память, семафоры и т. д.) реже используются в межпроцессных коммуникациях, нежели каналы, именованные каналы и сокеты, они все же обладают рядом интересных свойств. Тем не менее для совместного использования переменной несколькими процессами обычно нельзя рассчитывать на работу с общей памятью через shmget или mmap(2). Дело в том, что Perl заново выделит память под строку тогда, когда вы этого совсем не ждете. Проблема решается с помощью модуля IPC::Shareable с CPAN. Умный модуль tie, общая память SysV н модуль Shareable с CPAN позволяют организовать совместный доступ к структурам данных произвольной сложности для процессов на одном компьютере. При этом процессы даже не обязаны быть родственными. В примере 16.11 продемонстрирован несложный случай применения этого модуля. Пример 16.11. sharetest
#!/usr/bin/perl
# sharetest - совместный доступ к общим переменным в разветвлениях
use IPC::Shareable;
$handle = tie $buffer, 'ipc::shareable', undef, { destroy => 1 };
$SIG{INT} = sub { die "$$ dying\n" };
for (1 .. 10) {
unless ($child = fork) { # Я - потомок
die "cannot fork: $!" unless defined $child;
squabble();
exit;
} push Okids, $child; # Если нас интересуют идентификаторы процессов
}
while (1) {
print "Buffer is $buffer\n";
sleep 1;
} die "Not reached";
sub squabble { my $i = 0;
while (1) {
next if $buffer =~ /"$$\b/o;
$handle->shlock();
$i++;
$buffer = "$$ $i";
$handle->shunlock();
}
}


Исходный процесс создает общую переменную, разветвляется на 10 потомков, а затем выводит значение буфера примерно каждую секунду в бесконечном цикле или до тех пор, пока вы не нажмете Ctrl+C.
Поскольку обработчик SIGINT был установлен до всех вызовов fork, его наследуют все потомки, которые также уничтожаются при прерывании группы процессов. Сигналы с клавиатуры передаются целой группе процессов, а не одному процессу.
Что же происходит в squabble? Потомки разбираются, кому из них удастся обновить общую переменную. Каждый порожденный процесс смотрит, изменилось ли состояние переменной с момента последнего визита. Если буфер начинается с его собственной сигнатуры (идентификатора процесса), процесс не трогает его. Если буфер был изменен кем-то другим, процесс блокирует общую переменную вызовом специального метода для манипулятора, полученного от tie, обновляет ее и снимает блокировку.
Программа заработает намного быстрее, если закомментировать строку, начинающуюся с next, где каждый процесс проверяет, кто последним прикасался к буферу. Шаблон /"$$\Ь/о выглядит подозрительно, поскольку /о указывает на однократную компиляцию шаблона, а переменная $$ меняется при разветвлении. Впрочем значение фиксируется не во время компиляции программы, а при первой компиляции шаблона в каждом процессе, во время жизни которого $$ остается постоянным.
Модуль IPC::Shareable также поддерживает совместное использование переменных неродственными процессами на одном компьютере. За подробностями обращайтесь к документации.

> Смотри также --------------------------------
Описание функций semcti, semget, semop, shmcti, shmget, shmread и shmwrite в perlfunc(1); документация по модулю IPC::Shareable с CPAN.

16.13. Получение списка сигналов

Проблема

Вы хотите знать, какие сигналы поддерживаются вашей операционной системе-''.

Решение

Если ваш командный интерпретатор поддерживает встроенную команду kill -/, используйте ее: % kill -1 HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR
Чтобы сделать то же самое только па Perl версии 5.004 и выше, выведите ключи хэша %SIG: % perl -e 'print join(" ", keys %SIG), "\n"' XCPU ILL QUIT STOP EMT ABRT BUS USR1 XFSZ TSTP INT IOT USR2 INFO TTOU ALRM KILL HUP URG PIPE CONT SEGV VTALRM PROF TRAP 10 TERM WINCH CHLD FPE TTIN SYS
До выхода версии 5.004 приходилось использовать модуль Config: % perl -MConfig -e 'print $Config{sig_name}' ZERO HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM URG STOP TSTP CONT CHLD TTIN TTOU 10 XCPU XFSZ VTALRM PROF WINCH INFO USR1 USR2 IOT

Комментарий

Если вы работаете в Perl версии младше 5.004, для получения списка сигналов вам также придется использовать Osigname и %signo модуля Config, поскольку конструкция keys %SIG в ранних версиях еще не реализована.
Следующий фрагмент извлекает имена и номера доступных сигналов из стандартного модуля Config.pm. Индексирование @signame по номеру дает имя сигнала, ? индексирование %signo по имени - номер сигнала.
use Config;
defined $Conrig{sig_name} or die "No sigs?";
$1=0; # config добавляет ложный сигнал О
# с именем "ZERO".
foreach $name (split(' ', $Config{sig_name})) {
$signo{$name} = $i;
$signame[$i] = $name;
$i++;
}


> Смотри также -------------------------------
Документация по стандартному модулю Config; раздел "Signals" perlipc(1).

16.14. Посылка сигнала

Проблема

Требуется послать сигнал процессу. Возможна посылка сигнала как вашему собственному процессу, так н другому процессу в той же системе. Например, вы перехватили SIGINT и хотите передать его потомкам.

Решение

Функция kill отправляет сигнал с заданным именем или номером процессам, идентификаторы которых перечисляются в качестве остальных аргументов:
kill 9 => $pid; , # Послать $pid сигнал 9
kill -1 => $рgrр; # Послать всему заданию сигнал 1
kill USR1 => $$; # Послать себе SIGUSR1
kill HUP => @pids; # Послать SIGHUP процессам из @pids

Комментарий

Функция Perl kill обеспечивает интерфейс к системной функции с тем же именем. Первый аргумент определяет посылаемый сигнал и задается по номеру или по имени; остальные аргументы определяют идентификаторы процессов, которым отправляется сигнал. Функция возвращает количество процессов, успешно получивших сигнал. Сигналы можно отправлять только процессам, для которых реальный или сохраненный идентификатор пользователя совпадает с вашим реальным или текущим идентификатором - если только вы не являетесь привилегированным пользователем. Если номер сигнала отрицателен, Perl интерпретирует остальные аргументы как идентификаторы групп процессов и отправляет сигнал процессам, входящим в эти группы, с помощью системной функции killpg(2).
Группа процессов фактически представляет собой задание. Именно так операционная система объединяет родственные процессы. Например, когда вы с помощью командного интерпретатора сцепляете две команды, при этом запускаются два процесса, но лишь одно задание. Когда текущее задание прерывается по Ctrl+C или приостанавливается по Ctrl+Z, соответствующие сигналы отправляются всему заданию, которое может состоять из нескольких процессов. Функция kill также позволяет проверить, жив ли процесс. Посылка специального псевдосигнала с номером 0 сообщает, можно ли послать сигнал процессу - хотя сам сигнал при этом не передается. Если функция возвращает true, процесс жив. Если возвращается false, процесс либо сменил свой действующий идентификатор (в этом случае переменной $! присваивается EPERM), либо прекратил существование ($! присваивается ESRCH). Для процессов-зомби (см. рецепт 16.19) также возвращается ESRCH.
use POSIX qw(:errno_h);
if (kill 0 => $minion) {
print "$minion is alive!\n";
} elsif ($! == eperm) { # Изменился uid
print "$minion has escaped my control!\n";
} elsif ($! == esrch) {
print "Sminion is deceased.\n"; # Или зомби
} else {
warn "Odd; I couldn't check on the status of $minion: $!\n";
}


> Смотри также -------------------------------
Раздел "Signals" perlipc(1); страницы руководства sigaction(2), signal(3) и kill(2) вашей системы (если есть); описание функции kill в perlfunc(1).
Назад
Вперед