KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программное обеспечение » Арнольд Роббинс - Linux программирование в примерах

Арнольд Роббинс - Linux программирование в примерах

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Арнольд Роббинс, "Linux программирование в примерах" бесплатно, без регистрации.
Перейти на страницу:

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() предоставляет также сведения о потомках, которые не завершились, но изменили состояние.

• Системы GNU/Linux и большинство Unix-систем поддерживают также функции BSD wait3() и wait4(). GNU/Linux поддерживает также выходящий из употребления union wait. Функции BSD предоставляют struct rusage, давая доступ к сведениям об использовании времени процессора, что может быть удобным. Хотя если waitpid() будет достаточной, то это наиболее переносимый способ выполнения.

• Группы процессов являются частью более крупного механизма управления заданиями, который включает сигналы, сеансы и манипулирование состоянием терминала, getpgrp() возвращает ID группы процессов текущего процесса, a getpgid() возвращает PGID определенного процесса. Сходным образом, setpgrp() устанавливает PGID текущего процесса равным его PID, делая его лидером группы процессов; setpgid() дает возможность родительскому процессу установить PGID порожденного, который еще не выполнил exec.

• Каналы и FIFO предоставляют односторонний коммуникационный канал между двумя процессами. Каналы должны быть установлены общим предком, тогда как FIFO могут использоваться любыми двумя процессами. Каналы создаются с помощью pipe(), а файлы FIFO создаются с помощью mkfifo(). Каналы и FIFO буферируют свои данные, останавливая производителя или потребителя, когда канал заполняется или пустеет.

• dup() и dup2() создают копии дескрипторов открытых файлов. В сочетании с close() они дают возможность поместить дескрипторы файлов на место стандартного ввода и вывода для каналов. Чтобы каналы работали правильно, все копии неиспользуемых концов каналов до исполнения программой назначения exec должны быть закрыты. Для создания нелинейных каналов может быть использован /dev/fd, что демонстрируется возможностью замещения процессов оболочками Bash и Korn.

• fcntl() является функцией для выполнения различных работ. Она управляет атрибутами как самого дескриптора файла, так и лежащего в его основе файла. В данной главе мы видели, что fcntl() используется для следующего:

 • Дублирования дескриптора файла, имитирования dup() и почти имитирования dup2().

 • Получения и установки флага close-on-exec. Флаг close-on-exec является в настоящее время единственным атрибутом дескриптора файла, но он важен. Он не копируется в результате действия dup(), но должен явным образом устанавливаться для дескрипторов файлов, которые не должны оставаться открытыми после выполнения exec. На практике, это должно быть сделано для большинства дескрипторов файла.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*