Основными элементами этой и нескольких следующих глав являются системные вызовы fork(), exit(), wait() и execve(). Каждый из них имеет свои вариации, которые мы тоже не обойдем стороной. Пока что ознакомимся с кратким описанием этих четырех вызовов и узнаем, как они обычно используются в связке друг с другом.
• Системный вызов fork() позволяет одному процессу, родителю, создавать новый, дочерний процесс. Оба этих процесса являются (почти) идентичными: потомок получает копии родительского стека, данных, кучи, копии родительских сегментов стека Х(см. раздел 6.3) и текста. Термин
• Библиотечная функция exit(status) завершает процесс, делая все его ресурсы (память, дескрипторы открытых файлов и т. д.) доступными для последующего перераспределения ядром. Аргумент status — целое число, которое определяет код завершения процесса. Родительский процесс может извлечь этот код с помощью системного вызова wait().
Библиотечная функция exit() является оберткой вокруг системного вызова _exit(). В главе 25 вы узнаете разницу между этими двумя интерфейсами. А пока достаточно помнить о том, что обычно с помощью вызова exit() завершают работу только одного родителя или потомка, порожденного вызовом fork(); остальные процессы следует завершать с помощью вызова _exit().
• Системный вызов wait(&status) имеет два назначения. Во-первых, если работа потомка текущего процесса еще не была завершена путем вызова exit(), функция wait() приостанавливает выполнение родителя, пока не будет завершен один из его потомков. Во-вторых, код завершения потомка возвращается через аргумент функции wait().
• Системный вызов execve(pathname, argv, envp) загружает в память процесса новую программу (расположенную в pathname, с аргументами argv и списком переменных среды envp). Текст существующей программы сбрасывается, а для новой программы заново создаются сегменты со стеком, данными и кучей. Эту операцию часто называют
В некоторых других операционных системах возможности функций fork() и exec() объединены в одну операцию —
Стандарт SUSv3 предусматривает дополнительную функцию posix_spawn(), которая объединяет возможности fork() и exec(). В системе Linux она и еще несколько связанных программных интерфейсов, описанных в стандарте SUSv3, реализована в библиотеке glibc. Функция posix_spawn() позволяет создавать переносимые приложения с поддержкой аппаратных архитектур, которые не предоставляют механизм файла подкачки или блоки управления памятью (что характерно для многих встраиваемых систем). В рамках таких архитектур реализация традиционного вызова fork() является либо затруднительной, либо невозможной в принципе.
На рис. 24.1 показано, как вызовы fork(), exit(), wait() и execve() обычно используются вместе (эта диаграмма изображает пошаговые действия командной оболочки, выполняющей команду: создается непрерывный цикл, в котором оболочка считывает команду, обрабатывает ее различными способами, после чего создает дочерний процесс для ее выполнения).
Применение вызова execve(), показанное на этой диаграмме, не является обязательным. Иногда имеет смысл позволить потомку продолжить выполнение программы родителя. В любом случае выполнение дочернего процесса в конечном счете завершается вызовом exit() (или передачей сигнала) и возвращением кода завершения, доступным родителю через функцию wait(). Вызов wait() тоже необязателен. Родитель может просто игнорировать своего потомка и продолжать работу. Однако позже мы увидим, что использование функции wait() обычно является желательным и выполняется внутри обработчика сигнала SIGCHLD, который генерируется ядром для родителя, когда один из его дочерних процессов завершается (по умолчанию сигнал SIGCHLD игнорируется, поэтому в диаграмме сказано, что его доставка является опциональной).