Выбрать главу

Строка 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] */

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 для обозначения успеха.

9.6. Рекомендуемая литература

Управление заданиями сложно, включает группы процессов, сеансы, механизмы ожидания, сигналы и манипулирование группой процессов терминала. По существу, мы решили не вдаваться в детали. Однако, вы можете захотеть взглянуть на следующие книги:

1. Advanced Programming in the UNIX Environment, 2nd edition, by W. Richard Stevens and Stephen Rago. Addison-Wesley, Reading Massachusetts, USA, 2004. ISBN: 0-201-43307-9.

Эта книга и полна, и основательна, охватывая элементарное и продвинутое программирование под Unix. Она превосходно освещает группы процессов, сеансы, управление заданиями и сигналы

2. The Design and Implementation of the 4.4 BSD Operating System, by Marshall Kirk McKusick, Keith Bostic, Michael J. Karels, and John S. Quarterman. Addison-Wesley, Reading, Massachusetts, USA, 1996. ISBN: 0-201-54979-4.

Эта книга дает хороший обзор того же материала, включая обсуждение структур данных ядра, которое можно найти в разделе 4.8 этой книги.

9.7. Резюме

• Новые процессы создаются с помощью fork(). После этого оба процесса исполняют один и тот же код, причем единственным различием является возвращаемое значение: 0 в порожденном процессе и положительный номер PID в родительском. Порожденный процесс наследует копии почти всех атрибутов родителя, наиболее важными из которых являются, пожалуй, открытые файлы.

• Унаследованные разделяемые дескрипторы файлов делают возможным многое из высокоуровневой семантики Unix и элегантные управляющие структуры оболочки. Это одна из наиболее фундаментальных частей оригинального дизайна Unix. Из-за разделения дескрипторов файл на самом деле не закрывается до тех пор, пока не будет закрыт последний открытый дескриптор файла. Это в особенности касается каналов, но затрагивает также освобождение дисковых блоков для удаленных, но все еще открытых файлов.

• Вызовы getpid() и getppid() возвращают ID текущего и родительского процессов соответственно. Родителем процесса, первоначальный родитель которого завершается, становится специальный процесс init с PID 1. Таким образом, PPID может меняться, и приложения должны быть готовы к этому.

• Системный вызов nice() дает возможность настраивать приоритет вашего процесса. Чем приятнее вы по отношению к другим процессам, тем меньше ваш относительный приоритет, и наоборот. Лишь суперпользователь может иметь больший приоритет по сравнению с другими процессами. На современных системах, особенно однопользовательских, нет действительных причин для изменения знамения относительного приоритета.

• Системный вызов exec() начинает исполнение новой программы в существующем процессе. Шесть различных версий вызова предоставляют гибкость в установке списков аргументов и окружения ценой первоначальной путаницы по поводу того, какую из них лучше всего использовать. Два варианта имитируют механизм поиска оболочки и отступают к использованию оболочки для интерпретации файла в случае, если он не является двоичным исполняемым файлом; эти варианты должны использоваться с предусмотрительностью.

• Значение argv[0] для новой программы обычно происходит от имени исполняемого файла, но это лишь соглашение. Как и в случае с fork(), значительный, но не идентичный набор атрибутов наследуется через exec. Другие атрибуты сбрасываются для использования подходящих значений по умолчанию.

• Функция atexit() регистрирует функции обратного вызова для вызова в порядке LIFO при завершении программы. Функции exit(), _exit() и _Exit() все завершают программу, передавая статус завершения обратно родителю, exit() очищает открытые потоки FILE* и запускает функции, зарегистрированные с помощью atexit(). Две другие функции завершаются немедленно и должны использоваться, лишь когда exec в порожденном процессе завершилась неудачей. Возвращение из main() подобно вызову exit() с данным возвращаемым значением. В C99 и C++ выпадение из main() в конце функции дает тот же результат, что и 'exit(0)', но является плохой практикой.

• wait() и waitpid() являются функциями POSIX для получения статуса завершения порожденного процесса. Различные макросы позволяют определить, завершился ли порожденный процесс нормально, и в таком случае определить статус его завершения, или же порожденный процесс претерпел сигнал завершения, и в этом случае определить совершивший этот проступок сигнал. Со специальными опциями waitpid() предоставляет также сведения о потомках, которые не завершились, но изменили состояние.

вернуться

104

Игра слов kill-overkill (избыточно — overkill) — Примеч. перев.