Текст книги "Linux программирование в примерах"
Автор книги: Арнольд Роббинс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 27 (всего у книги 55 страниц)
/dev/fd/XX
Многие современные системы Unix, включая GNU/Linux, поддерживают в каталоге /dev/fd
[98]98
На системах GNU/Linux /dev/fd
является символической ссылкой на /proc/self/fd
, но поскольку /dev/fd
является общеизвестным, в своем коде следует использовать именно его – Примеч. автора.
[Закрыть] специальные файлы. Эти файлы представляют дескрипторы открытых файлов с именами /dev/fd/0
, /dev/fd/1
и т.д. Передача такого имени функции open()
возвращает новый дескриптор файла, что в сущности является тем же самым, что и вызов dup()
для данного номера дескриптора.
Эти специальные файлы находят свое применение на уровне оболочки: Bash, ksh88
(некоторые версии) и ksh93
предоставляют возможность замещения процесса (process substitution), что позволяет создавать нелинейные конвейеры. На уровне оболочки для входного конвейера используется запись '<(...)
', а для выходного конвейера запись '>(...)
'. Например, предположим, вам нужно применить команду diff
к выводу двух команд. Обычно вам пришлось бы использовать временные файлы:
command1 > /tmp/out.$$.1
command2 > /tmp/out.$$.2
diff /tmp/out.$$.1 /tmp/out.$$.2
rm /tmp/out.$$.1 /tmp/out.$$.2
С замещением процессов это выглядит следующим образом:
diff <(command1) <(command2)
Не надо никаких беспорядочных файлов для временного запоминания и удаления. Например, следующая команда показывает, что наш домашний каталог является ссылкой на другой каталог:
$ diff <(pwd) <(/bin/pwd)
1c1
< /home/arnold/work/prenhall/progex
–
> /d/home/arnold/work/prenhall/progex
Незамысловатая команда pwd
является встроенной в оболочку: она выводит текущий логический путь, который управляется оболочкой с помощью команды cd
. Программа /bin/pwd
осуществляет обход физической файловой системы для вывода имени пути.
Как выглядит замещение процессов? Оболочка создает вспомогательные команды[99]99
Хотя мы показали простые команды, допустимы произвольные конвейеры – Примеч. автора.
[Закрыть] ('pwd
' и '/bin/pwd
'). Выход каждой из них подсоединяется к каналу, причем читаемый конец открыт в дескрипторе нового файла для главного процесса ('diff
'). Затем оболочка передает главному процессу имена файлов в /dev/fd
в качестве аргументов командной строки. Мы можем увидеть это, включив в оболочке трассировку исполнения.
$ set -х /* Включить трассировку исполнения */
$ diff <(pwd) <(/bin/pwd) /* Запустить команду */
+ diff /dev/fd/63 /dev/fd/62 /* Трассировка оболочки: главная,
программа, обратите внимание на аргументы */
++ pwd /* Трассировка оболочки: вспомогательные программы */
++ /bin/pwd
1c1 /* Вывод diff */
< /home/arnold/work/prenhall/progex
–
> /d/home/arnold/work/prenhall/progex
Это показано на рис. 9.6.
Рис. 9.6. Замещение процесса
Если на вашей системе есть /dev/fd
, вы также можете использовать преимущества этой возможности. Однако, будьте осторожны и задокументируйте то, что вы делаете. Манипуляции с дескриптором файла на уровне С значительно менее прозрачны, чем соответствующие записи оболочки!
fcntl()
Системный вызов fcntl()
(«управление файлом») предоставляет контроль над различными атрибутами либо самого дескриптора файла, либо лежащего в его основе открытого файла. Справочная страница GNU/Linux fcntl(2) описывает это таким способом:
#include
#include
int fcntl (int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
Другими словами, функция принимает по крайней мере два аргумента; в зависимости от второго аргумента, она может принимать и третий аргумент.
Последняя форма, в которой третий аргумент является указателем на struct flock
, предназначена для блокировки файла. Блокировка файлов сама по себе представляет большую тему; мы отложим обсуждение до раздела 14.2 «Блокировка файлов».
После вызова fork()
и перед вызовом exec()
следует убедиться, что новая программа наследует лишь те открытые файлы, которые ей нужны. Вы не захотите, чтобы порожденный процесс мешался в открытых файлах родителя, если только это так не задумано. С другой стороны, если у родителя множество открытых файлов, это будет искусственно ограничивать число новых файлов, которые может открыть порожденный процесс. (См. сопроводительную врезку.)
Организационно такое поведение может представлять проблему. Часть вашей программы, порождающая новый процесс, не должна особенно нуждаться в других частях программы, манипулирующей открытыми файлами. И цикл наподобие следующего неприятный, поскольку может не быть открытых файлов:
int j;
for (j = getdtablesize(); j >= 3; j–) /* закрыть все, кроме 0, 1, 2 */
(void)close(j);
Решением является флаг close-on-exec (закрытие при исполнении exec). Он является атрибутом самого дескриптора файла, а не лежащего в его основе открытого файла. Когда этот флаг установлен, система автоматически закрывает файл, когда процесс осуществляет exec
. Установив этот флаг сразу после открытия файла, вам не нужно беспокоиться о том, что какой-нибудь порожденный процесс случайно его унаследует. (Оболочка автоматически устанавливает этот флаг для всех дескрипторов файлов, которые она открывает, начиная с номера 3 и выше.)
Аргумент cmd
имеет два значения, относящиеся к флагу close-on-exec:
F_GETFD
Получает флаги дескриптора файла. Возвращаемое значение является значением всех установленных флагов дескриптора или -1 при ошибке.
F_SETFD
Устанавливает флаги дескриптора файла в содержащееся в arg
(третий аргумент) значение. Возвращаемое значение равно 0 при успехе или -1 при ошибке.
В настоящий момент определен лишь один «флаг дескриптора файла»: FD_CLOEXEC
. Эта именованная константа является нововведением POSIX[100]100
Стандарт POSIX умышленно не приписывает ей значение. Однако, чтобы старый код продолжал работать, единственным значением, которое могла бы разумно использовать любая реализация, является 1 – Примеч. автора.
[Закрыть], а большая часть кода использует просто 1 или 0:
if (fcntl(fd, F_SETFD, 1) < 0) ...
/* установить close-on-exec, обработать ошибки */
if (fcntl(fd, F_GETFD) == 1) ...
/* бит close-on-exec уже установлен */
Однако, определение POSIX допускает дальнейшее расширение, поэтому правильный способ написания такого кода больше соответствует этим строкам:
int fd;
long fd_flags;
if ((fd_flags = fcntl(fd, F_GETFD)) < 0) /* Получить флаги */
/* обработать ошибки */
fd_flags |= FD_CLOEXEC; /* Add close-on-exec flag */
if (fcntl(fd, F_SETFD, fd_flags) < 0) /* Установить флаги */
/* обработать ошибки */
ЗАМЕЧАНИЕ. Флаг close-on-exec является собственностью дескриптора, а не лежащего в его основе файла. Поэтому новый дескриптор, возвращенный функциями
dup()
илиdup2()
(илиfcntl()
сF_DUPD
, которую мы намереваемся посмотреть), не наследует установки флага close-on-exec первоначального дескриптора. Если вам нужно установить его также и для нового дескриптора файла, вы должны не забыть сделать это сами. Такое поведение имеет смысл: если вы просто вызвалиdup()
, копируя один конец канала в 0 или 1, вы не захотите, чтобы система закрыла его вместо вас, как только процесс осуществит exec!
История борьбы close-on-exec от
gawk
В языке awk операторы ввода/вывода используют обозначение перенаправления, сходное с обозначением для оболочки. Это включает односторонние каналы к и от подпроцесса:
print "something brilliant" > "/some/file" /* Вывод в файл */
getline my_record < "/some/other/file" /* Ввод из файла */
print "more words of wisdom" | "a_reader process" /* Вывод в подпроцесс */
"a_write_process" | getline some_input /* Ввод из подпроцесса */
У интерпретатора
awk
есть дескрипторы открытых файлов для всех перенаправлений файлов, а для обозначений каналов, создающих подпроцессы, интерпретаторawk
создает канал, а затем осуществляетfork
иexec
оболочки для запуска команды, приведенной в строке.Теперь на современных системах часть стартового кода библиотеки С времени исполнения (который запускается до вызова
main()
) нуждается для управления использованием разделяемых библиотек во временно открытых файлах. Это означает, что для новой программы после исполненияexec
должны быть по крайней мере один или два неиспользуемых дескриптора файла, иначе программа просто не будет работатьОднажды один пользователь сообщил, что когда в программе было максимальное количество открытых файлов, ни один процесс, для которого она пыталась использовать для конвейера
fork
иexec
, не мог успешно начаться!Вы, возможно, можете догадаться, что произошло. Порожденная оболочка унаследовала дескрипторы открытых файлов, которые
gawk
сама использовала для своих перенаправлений. Мы модифицировалиgawk
так, чтобы установить флаг close-on-exec для всех перенаправлений файлов и каналов, что и решило проблему.
Когда аргумент cmd
функции fcntl()
равен F_DUPFD
, ее поведение похоже, но не идентично поведению dup2()
. В этом случае arg
является дескриптором файла, представляющим наименьшее приемлемое значение для нового дескриптора файла:
int new_fd = fcntl(old_fd, F_DUPFD, 7);
/* Возвращаемое значение между 7 и максимумом или неудача */
int new_fd = dup2(old_fd, 7);
/* Возвращаемое значение 7 или неудача */
Вы можете имитировать поведение dup()
, которая возвращает наименьший свободный дескриптор файла, использовав 'fcntl(old_fd, F_DUPED, 0)
'.
Если вы помните, что дескрипторы файлов являются просто индексами внутренней таблицы, работа этой функции должна быть ясна. Третий аргумент просто предоставляет индекс, с которого ядро должно начать поиск неиспользуемого дескриптора файла.
Использовать ли в собственном коде fcntl()
с F_DUPED
или dup()
или dup2()
, в значительной степени является делом вкуса. Все три функции API являются частью POSIX и широко поддерживаются. У нас легкое пристрастие к dup()
и dup2()
, поскольку они более специфичны в своих действиях, поэтому являются самодокументирующимися. Но поскольку все они довольно просты, эта аргументация может вас не убедить.
В разделе 4.6.3 «Возвращаясь к open()
» мы предоставили полный список флагов O_xx, которые принимает open()
. POSIX разбивает их по функциям, классифицируя в соответствии с табл. 9.4.
Таблица 9.4. Флаги O_xx для open()
, creat()
и fcntl()
Доступ к файлу | open() , fcntl() | O_RDONLY , O_RDWR , O_WRONLY |
Создание файла | open() | O_CREAT , O_EXCL , O_NOCTTY , O_TRUNC |
Статус файла | open() , fcntl() | O_APPEND , O_DSYNC , O_NONBLOCK , O_RSYNC , O_SYNC |
Помимо первоначальной установки различных флагов с помощью open()
, вы можете использовать fcntl()
для получения текущих установок, а также их изменения. Это осуществляется с помощью значений cmd
F_GETFL
и F_SETFL
соответственно. Например, вы можете использовать эти команды для изменения установки неблокирующего флага, O_NONBLOCK
, подобным образом:
int fd_flags;
if ((fd_flags = fcntl(fd, F_GETFL)) < 0)
/* обработать ошибку */
if ((fd_flags & O_NONBLOCK) != 0) { /* Установлен неблокирующий флаг */
fd_flags &= ~O_NONBLOCK; /* Сбросить его */
if (fcntl(fd, F_SETFL, fd_flags) != 0) /* Дать ядру новое значение */
/* обработать ошибку */
}
Помимо самих режимов именованная константа O_ACCMODE
является маской, которую вы можете использовать для выделения из возвращаемого значения режимов прав доступа.
fd_flags = fcntl(fd, F_GETFL);
switch (fd_flags & O_ACCESS) {
case O_RDONLY:
/* ...действия только для чтения... */
break;
case O_WRONLY:
/* ...действия только для записи... */
break;
case O_RDWR:
/* ...действия для чтения и записи... */
break;
}
POSIX требует, чтобы O_RDONLY
, O_RDWR
и O_WRONLY
были побитово различными, таким образом, гарантируется, что код, подобный только что показанному, будет работать и является простым способом определения того, как был открыт произвольный дескриптор файла.
Используя F_SETFL
вы можете также изменить эти режимы, хотя по-прежнему применяется проверка прав доступа. Согласно справочной странице GNU/Linux fcnlt(2) флаг O_APPEND
не может быть сброшен, если он использовался при открытии файла.
Ранее для описания способа работы каналов мы использовали сравнение с двумя людьми, моющими и вытирающими тарелки с использованием сушилки; когда сушилка заполняется, останавливается моющий, а когда она пустеет, останавливается вытирающий. Это блокирующее поведение: производитель или потребитель блокируются в вызове write()
или read()
, ожидая либо освобождения канала, либо появления в нем данных.
В действительности человек, ожидающий опустения или заполнения сушилки, не должен просто неподвижно стоять.[101]101
Ну, мы игнорируем мысль, что два супруга могли бы хотеть поговорить друг с другом и насладиться компанией – Примеч. автора.
[Закрыть] Вместо этого незанятый супруг мог бы пойти и найти другую работу по кухне (такую, как подметание всех крошек за детьми на полу), пока сушилка снова не будет готова.
На языке Unix/POSIX эта концепция обозначается термином неблокирующий ввод/вывод, т.е. запрошенный ввод/вывод либо завершается, либо возвращает значение ошибки, указывающее на отсутствие данных (для читающего) или отсутствие места (для записывающего). Неблокирующий ввод/вывод применяется к каналам и FIFO, а не к обычным файлам на диске. Он может применяться также и к определенным устройствам, таким как терминалы, и к сетевым соединениям, обе эти темы выходят за рамки данной книги.
С функцией open()
может использоваться флаг O_NONBLOCK
для указания неблокирующего ввода/вывода, он может быть установлен и сброшен с помощью fcntl()
. Для open()
и read()
неблокирующий ввод/вывод прост.
Открытие FIFO с установленным или сброшенным O_NONBLOCK
демонстрирует следующее поведение:
open("/fifо/file", O_RDONLY, mode)
Блокируется до открытия FIFO для записи.
open("/fifo/file", O_RDONLY | O_NONBLOCK, mode)
Открывает файл, возвращаясь немедленно.
open("/fifo/file", O_WRONLY, mode)
Блокирует до открытия FIFO для чтения.
open("/fifo/file", O_WRONLY | O_NONBLOCK, mode)
Если FIFO был открыт для чтения, открывает FIFO и немедленно возвращается. В противном случае возвращает ошибку (возвращаемое значение -1 и errno
установлен в ENXIO
).
Как описано для обычных каналов, вызов read()
для FIFO, который больше не открыт для чтения, возвращает конец файла (возвращаемое значение 0). Флаг O_NONBLOCK
в данном случае неуместен. Для пустого канала или FIFO (все еще открытых для записи, но не содержащих данных) все становится интереснее:
read(fd, buf, count) и сброшенный O_NONBLOCK
Функция read()
блокируется до тех пор, пока в канал или FIFO не поступят данные.
read(fd, buf, count) и установленный O_NONBLOCK
Функция read()
немедленно возвращает -1 с установленным в errno EAGAIN
.
В заключение, поведение write()
более сложно. Для обсуждения этого нам нужно сначала представить концепцию атомарной записи. Атомарная запись – это такая запись, при которой все данные записываются целиком, не чередуясь с данными от других записей. POSIX определяет в
константу PIPE_BUF
. Запись в канал или FIFO данных размером менее или равным PIPE_BUF
байтов либо успешно завершается, либо блокируется в соответствии с подробностями, которые мы скоро приведем. Минимальным значением для PIPE_BUF
является _POSIX_PIPE_BUF
, что равняется 512. Само значение PIPE_BUF
может быть больше; современные системы GLIBC определяют ее размер в 4096, но в любом случае следует использовать эту именованную константу и не ожидать, что PIPE_BUF
будет иметь то же значение на разных системах.
Во всех случаях для каналов и FIFO write()
добавляет данные в конец канала. Это происходит от того факта, что у каналов нет файловых смещений: в них нельзя осуществлять поиск.
Также во всех случаях, как упоминалось, записи размером вплоть до PIPE_BUF
являются атомарными: данные не перемежаются с данными от других записей. Данные записи размером более PIPE_BUF
байтов могут перемежаться с данными других записей в произвольных границах. Это последнее означает, что вы не можете ожидать, что каждая порция размером PIPE_BUF
большого набора данных будет записана атомарно. Установка O_NONBLOCK
не влияет на это правило.
Как и в случае с read()
, когда O_NONBLOCK
не установлен, write()
блокируется до тех пор, пока все данные не будут записаны.
Наиболее все усложняется, когда установлен O_NONBLOCK
. Канал или FIFO ведут себя следующим образом:
nbytes ≤ PIPE_BUF | write() успешна | write() возвращает (-1)/EAGAIN |
nbytes > PIPE_BUF | write() записывает, что может | write() возвращает (-1)/EAGAIN |
Для файлов, не являющихся каналами и FIFO и к которым может быть применен O_NONBLOCK
, поведение следующее:
размер > 0 write()
записывает, что может
размер = 0 write()
возвращает -1/EAGAIN
Хотя есть ряд сбивающих с толку изменений поведения в зависимости от того, канал это или не канал, установлен O_NONBLOCK
или сброшен, есть в канале место для записи или нет, а также в зависимости от размера предполагаемой записи, эти правила предназначены для упрощения программирования:
• Всегда можно отличить конец файла: read()
возвращает 0 байтов.
• Если нет доступных для чтения данных, read()
либо завершается успешно, либо возвращает указание «нет данных для чтения»: EAGAIN
, что означает «попытайтесь снова позже».
• Если для записи нет места, write()
либо блокируется до успешного завершения (O_NONBLOCK
сброшен), либо завершается неудачей с ошибкой «в данный момент нет места для записи»: EAGAIN
.
• Когда место есть, будет записано столько данных, сколько возможно, так что в конечном счете все данные будут переписаны.
Подводя итог, если вы собираетесь использовать неблокирующий ввод/вывод, любой код, который использует write()
, должен быть способен обработать укороченную запись, когда успешно записан меньший объем данных, чем было затребовано. Устойчивый код в любом случае должен быть написан таким способом: даже в случае обычного файла диск может оказаться заполненным и write()
сможет записать лишь часть данных.
Более того, вы должны быть готовы обработать EAGAIN
, понимая, что в этом случае неудача write()
не обязательно означает фатальную ошибку. То же верно для кода, использующего для чтения неблокирующий ввод/вывод: признайте, что и здесь EAGAIN
не является фатальным. (Однако, может стоит подсчитывать число таких отказов, оставив попытки, когда их слишком много.)
Неблокирующий ввод/вывод действительно усложняет вашу жизнь, в этом нет никакого сомнения. Но для многих приложений он является необходимостью, позволяющей выполнить задание. Снова рассмотрите спулер печати. Демон спулера не может позволить себе находиться в блокирующем read()
для файла FIFO, которому представлены входящие задания. Он должен иметь также возможность отслеживать запущенные задания и, возможно, периодически проверять состояние печатающих устройств (например, убедиться, что не заело бумагу).
Сводка для системного вызова fcntl()
приведена в табл. 9.5.
Таблица 9.5. Сводка fcntl()
cmd | arg | |
---|---|---|
F_DUPFD | Наименьший новый дескриптор | Дублирует аргумент fd |
F_GETFD | Получает флаги дескриптора файла (close-on-exec) | |
F_SETFD | Новое значение флага | Устанавливает флаги дескриптора файла (close-on-exec) |
F_GETFL | Получает флаги основного файла | |
F_SETFL | Новое значение флага | Устанавливает флаги основного файла |
Флаги создания, статуса и прав доступа файла копируются, когда дескриптор файла дублируется. Флаг close-on-exec не копируется.
9.5. Пример: двусторонние каналы вgawk
Двусторонний канал соединяет два процесса двунаправленным образом. Обычно, по крайней мере для одного из процессов, на канал с другим процессом настраиваются как стандартный ввод, так и стандартный вывод. Оболочка Корна (ksh
) ввела двусторонние каналы на уровне языка, обозначив термином сопроцесса (coprocess):
команды и аргументы движка базы данных |& /* Запустить сопроцесс в фоновом режиме */
print -p "команда базы данных" /* Записать в сопроцесс */
read -p db_response /* Прочесть из сопроцесса */
Здесь движок базы данных представляет любую серверную программу, которая может управляться интерфейсной частью, в данном случае, сценарием ksh
. У движка базы данных стандартный ввод и стандартный вывод подсоединены к оболочке посредством двух отдельных односторонних каналов.[102]102
В одно и то же время есть только один сопроцесс по умолчанию (доступный посредством 'read -p
' и 'print -p
'). Сценарии оболочки могут использовать команду exec
со специальной записью перенаправления для назначения дескрипторов файла сопроцесса определенным номерам. После этого можно запустить другой сопроцесс – Примеч. автора.
[Закрыть] Это показано на рис. 9.7.
Рис. 9.7. Сопроцессы оболочки Корна
В обычном awk
каналы к или от подпроцесса являются односторонними: нет способа послать данные в программу и прочесть посланные от нее в ответ данные – нужно использовать временный файл. GNU awk
(gawk
) заимствует обозначение '|&
' от ksh
для расширения языка awk
:
print "команда" |& "движок базы данных" /* Запустить сопроцесс, записать в него */
"движок базы данных" |& getline db_response /* Прочесть из сопроцесса */
gawk
использует запись '|&
' также для сокетов TCP/IP и порталов BSD, которые не рассматриваются в данной книге. Следующий код из io.c
в дистрибутиве gawk
3.1.3 является частью функции two_way_open()
, которая устанавливает простой сопроцесс: она создает два канала, порождает новый процесс и осуществляет все манипуляции с дескриптором файла. Мы опустили ряд не относящихся к делу частей кода (эта функция занимает больше места, чем следовало бы):
1561 static int
1562 two_way_open(const char *str, struct redirect *rp)
1563 {
...
1827 /* случай 3: двусторонний канал с порожденным процессом */
1828 {
1829 int ptoc[2], сtop[2];
1830 int pid;
1831 int save_errno;
1835
1836 if (pipe(ptoc) < 0)
1837 return FALSE; /* установлен errno, диагностика от вызывающего */
1838
1839 if (pipe(ctop) < 0) {
1840 save_errno = errno;
1841 close(ptoc[0]);
1842 close(ptoc[1]);
1843 errno = save_errno;
1844 return FALSE;
1845 }
Первым шагом является создание двух каналов, ptoc
является каналом «от родителя к потомку», а ctop
– «от потомка к родителю». Во время чтения держите в уме, что индекс 0 является читаемым концом, а 1 – записываемым.
Строки 1836–1837 создают первый канал, ptoc
. Строки 1839–1845 создают второй канал, закрывая при неудачном создании и первый. Это важно. Небрежность в закрытии открытых, но не используемых каналов ведет к утечкам дескрипторов файлов. Как и память, дескрипторы файлов являются конечным ресурсом, и когда они иссякают, то теряются.[103]103
Очевидно, вы можете их закрыть. Но если вы не знаете, что они открыты, они теряются с таким же успехом, как и память через утечку памяти – Примеч. автора.
[Закрыть] То же верно и для открытых файлов: убедитесь, что ваш обрабатывающий ошибки код всегда закрывает все открытые файлы и каналы, которые не нужны, когда происходит ошибка.
save_errno
сохраняет значения errno
, установленные pipe()
, на тот редкий случай, когда close()
может завершиться неудачей (строка 1840). Затем errno
восстанавливается в строке 1843.
1906 if ((pid = fork()) < 0) {
1907 save_errno = errno;
1908 close(ptoc[0]); close(ptoc[1]);
1909 close(ctop[0]); close(ctop[1]);
1910 errno = save_errno;
1911 return FALSE;
1912 }
Строки 1906–1912 порождают процесс, на этот раз закрывая оба канала, если fork()
потерпит неудачу. Здесь также первоначальное значение errno
сохраняется и восстанавливается для последующего использования при диагностике.
1914 if (pid == 0) { /* порожденный процесс */
1915 if (close(1) == -1)
1916 fatal(_("close of stdout in child failed (%s)"),
1917 strerror(errno));
1918 if (dup(ctop[1]) != 1)
1919 fatal(_{"moving pipe to stdout in child failed (dup: %s)"), strerror(errno));
1920 if (close(0) == -1)
1921 fatal(_("close of stdin in child failed (%s)"),
1922 strerror(errno));
1923 if (dup(ptoc[0]) != 0)
1924 fatal(_("moving pipe to stdin in child failed (dup: %s)"), strerror(errno));
1925 if (close(ptoc[0]) == -1 || close(ptoc[1]) == -1
1926 || close(ctop[0]) == -1 || close(ctop[1]) == -1)
1927 fatal(_("close of pipe failed (%s)"), strerror(errno));
1928 /* stderr HE дублируется в stdout потомка */
1929 execl("/bin/sh", "sh", "-c", str, NULL);
1930 _exit(errno == ENOENT ? 127 : 126);
1931 }
Строки 1914–1931 обрабатывают код потомка, с соответствующей проверкой ошибок и сообщениями на каждом шагу. Строка 1915 закрывает стандартный вывод. Строка 1918 копирует записываемый конец канала от потомка к родителю на 1. Строка 1920 закрывает стандартный ввод, а строка 1923 копирует читаемый конец канала от родителя к потомку на 0. Если это все работает, стандартные ввод и вывод теперь на месте и подключены к родителю.
Строки 1925–1926 закрывают все четыре первоначальные дескрипторы файлов каналов, поскольку они больше не нужны. Строка 1928 напоминает нам, что стандартная ошибка остается на месте. Это лучшее решение, поскольку пользователь увидит ошибки от сопроцесса. Программа awk
, которая должна перехватить стандартную ошибку, может использовать в команде обозначение '2>&1
' для перенаправления стандартной ошибки сопроцесса или записи в отдельный файл.
Наконец, строки 1929–1930 пытаются запустить для оболочки execl()
и соответственно выходят, если это не удается.
1934 /* родитель */
1935 rp->pid = pid;
1936 rp->iop = iop_alloc(ctop[0], str, NULL);
1937 if (rp->iop == NULL) {
1938 (void)close(ctop[0]);
1939 (void)close(ctop[1]);
1940 (void)close(ptoc[0]);
1941 (void)close(ptoc[1]);
1942 (void)kill(pid, SIGKILL); /* overkill? (pardon pun) */
1943
1944 return FALSE;
1945 }
Первым шагом родителя является настройка входного конца от сопроцесса. Указатель rp
указывает на struct redirect
, которая содержит поле для сохранения PID порожденного процесса, FILE*
для вывода и указатель IOBUF*
с именем iop
. IOBUF
является внутренней структурой данных gawk
для осуществления ввода. Она, в свою очередь, хранит копию нижележащего дескриптора файла.
Строка 1935 сохраняет значение ID процесса. Строка 1936 выделяет память для новой IOBUF
для данных дескриптора файла и командной строки. Третий аргумент здесь равен NULL
: он позволяет при необходимости использовать предварительно выделенный IOBUF
.
Если выделение памяти потерпело неудачу, строки 1937–1942 производят очистку, закрывая каналы и посылая сигнал «kill» порожденным процессам, чтобы заставить их завершить работу. (Функция kill()
описана в разделе 10.6.7 «Отправка сигналов kill()
и killpg()
».)
1946 rp->fp = fdopen(ptoc[1], "w");
1947 if (rp->fp == NULL) {
1948 iop_close(rp->iop);
1949 rp->iop = NULL;
1950 (void)close(ctop[0]);
1951 (void)close(ctop[1]);
1952 (void)close(ptoc[0]);
1953 (void)close(ptoc[1]);
1954 (void)kill(pid, SIGKILL); /* избыточно? (пардон, каламбур)
[104]104
Игра слов kill-overkill (избыточно – overkill) – Примеч. перев.
[Закрыть] */
1955
1956 return FALSE;
1957 }
Строки 1946–1957 аналогичны. Они устанавливают вывод родителя на потомка, сохраняя дескриптор файла для записывающего конца канала от родителя к потомку в FILE*
, используя функцию fdopen()
. Если это завершается неудачей, строки 1947–1957 предпринимают те же действия, что и ранее: закрывают все дескрипторы каналов и посылают сигнал порожденным процессам.
С этого момента записываемый конец канала от родителя к потомку и читаемый конец канала от потомка к родителю хранятся в более крупных структурах: FILE*
и IOBUF
соответственно. Они автоматически закрываются обычными процедурами, которые закрывают эти структуры. Однако, остаются две задачи:
1960 os_close_on_exec(ctop[0], str, "pipe", "from");
1961 os_close_on_exec(ptoc[1], str, "pipe", "from");
1962
1963 (void)close(ptoc[0]);
1964 (void)close(ctop[1]);
1966
1967 return TRUE;
1968 }
...
1977 }
Строки 1960–1961 устанавливают флаг close-on-exec для двух дескрипторов, которые остались открытыми. os_close_on_exec()
является простой функцией-оболочкой, которая выполняет эту работу на Unix– и POSIX-совместимых системах, но ничего не делает на системах, в которых нет флага close-on-exec. Это скрывает проблему переносимости в одном месте и позволяет избежать в коде множества запутывающих #ifdef
здесь и в других местах io.c
.
Наконец, строки 1963–1964 закрывают концы каналов, которые не нужны родителю, а строка 1967 возвращает TRUE для обозначения успеха.