Книга: Linux программирование в примерах
9.7. Резюме
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. На практике, это должно быть сделано для большинства дескрипторов файла.
• Получение и установка флагов, управляющих нижележащим файлом. Из них O_NONBLOCK
является, пожалуй, наиболее полезным, по крайней мере, для FIFO и каналов. Это определенно самый сложный флаг.