KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Марк Митчелл - Программирование для Linux. Профессиональный подход

Марк Митчелл - Программирование для Linux. Профессиональный подход

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

3.4.2. Системные вызовы wait()

Самая простая функция в семействе называется wait(). Она блокирует вызывающий процесс до тех пор, пока один из его дочерних процессов не завершится (или не произойдет ошибка). Код состояния потомка возвращается через аргумент, являющийся указателем на целое число. В этом коде зашифрована различная информация о потомке. Например, макрос WEXITSTATUS() возвращает код завершения дочернего процесса. Макрос WIFEXITED() позволяет узнать, как именно завершился процесс: обычным образом (с помощью функции exit() или оператора return функции main()) либо аварийно вследствие получения сигнала. В последнем случае макрос WTERMSIG() извлекает из кода завершения номер сигнала.

Ниже приведена доработанная версия функции main() из файла fork-exec.c. На этот раз программа вызывает функцию wait(), чтобы дождаться завершения дочернего процесса, в котором выполняется команда ls.

int main() {

 int child_status;

 /* Список аргументов, передаваемых команде ls. */

 char* arg_list[] = {

  "ls", /* argv[0] — имя программы. */

  "-l",

  "/",

  NULL /* Список аргументов должен оканчиваться указателем

          NULL. */

 };


 /* Порождаем дочерний процесс, который выполняет команду ls.

    Игнорируем возвращаемый идентификатор дочернего процесса. */

 spawn("ls*, arg_list);

 /* Дожидаемся завершения дочернего процесса. */

 wait(&child_status);

 if (WTFEXITED(child_status));

 printf("the child process exited normally, with exit code %dn",

  WEXITSTATUS(child_status));

 else

  printf("the child process exited abnormallyn");

 return 0;

}

Расскажем о других функциях семейства. Функция waitpid() позволяет дождаться завершения конкретного дочернего процесса, а не просто любого. Функция wait3() возвращает информацию о статистике использования центрального процессора завершившимся дочерним процессом. Функция wait4() позволяет задать дополнительную информацию о том, завершения каких процессов следует дождаться.

3.4.3. Процессы-зомби

Если дочерний процесс завершается в то время, когда родительский процесс заблокирован функцией wait(), он успешно удаляется и его код завершения передается предку через функцию wait(). Но что произойдет, если потомок завершился, а родительский процесс так и не вызвал функцию wait()? Дочерний процесс просто исчезнет? Нет, ведь в этом случае информация о его завершении (было ли оно аварийным или нет и каков код завершения) пропадет. Вместо этого дочерний процесс становится процессом-зомби.

Зомби — это процесс, который завершился, но не был удален. Удаление зомби возлагается на родительский процесс. Функция wait() тоже это делает, поэтому перед ее вызовом не нужно проверять, продолжает ли выполняться требуемый дочерний процесс. Предположим, к примеру, что программа создает дочерний процесс, выполняет нужные вычисления и затем вызывает функцию wait(). Если к тому времени дочерний процесс еще не завершился, функция wait() заблокирует программу. В противном случае процесс на некоторое время превратится в зомби. Тогда функция wait() извлечет код его завершения, система удалит процесс и функция немедленно завершится.

Что же всё-таки случится, если родительский процесс не удалит своих потомков? Они останутся в системе в виде зомби. Программа, показанная в листинге 3.6, порождает дочерний процесс, который немедленно завершается, тогда как родительский процесс берет минутную паузу, после чего тоже заканчивает работу, так и не позаботившись об удалении потомка.

Листинг 3.6. (zombie.c) Создание процесса-зомби

#include «stdlib.h>

#include <sys/types.h>

#include <unistd.h>


int main() {

 pid_t child_pid;

 /* Создание дочернего процесса. */

 child_pid = fork();

 if (child_pid > 0) {

  /* Это родительский процесс — делаем минутную паузу. */

  sleep(60);

 } else {

  /* Это дочерний процесс — немедленно завершаем работу. */

  exit(0);

 }

 return 0;

}

Скомпилируйте этот файл и запустите программу. Пока программа работает, перейдите в другое окно и просмотрите список процессов с помощью следующей команды:

% ps -е -o pid,ppid,stat,cmd

Эта команда отображает идентификатор самого процесса и его предка, а также статус процесса и его командную строку. Обратите внимание на присутствие двух процессов с именем zombie. Один из них — предок, другой — потомок. У последнего идентификатор родительского процесса равен идентификатору основного процесса zombie, при этом потомок обозначен как <defunct> (несуществующий), а его код состояния равен Z (т.е. zombie — зомби).

Итак, мы хотим узнать, что будет, когда программа zombie завершится, не вызвав функцию wait(). Останется ли процесс-зомби? Нет — выполните команду ps и убедитесь в этом: оба процесса zombie исчезли. Дело в том, что после завершения программы управление ее дочерними процессами принимает на себя специальный процесс — демон init, который всегда работает, имея идентификатор 1 (это первый процесс, запускаемый при загрузке Linux). Демон init автоматически удаляет все унаследованные им дочерние процессы-зомби.

3.4.4. Асинхронное удаление дочерних процессов

Если дочерний процесс просто вызывает другую программу с помощью функции exec(), то в родительском процессе можно сразу же вызвать функцию wait() и пассивно дожидаться завершения потомка. Но очень часто нужно, чтобы родительский процесс продолжал выполняться одновременно с одним или несколькими своими потомками. Как в этом случае получать сигналы об их завершении?

Один подход заключается в периодическом вызове функции wait3() или wait4(). Функция wait() в данной ситуации не подходит, так как в случае отсутствия завершившегося дочернего процесса она заблокирует основную программу. А вот упомянутые две функции принимают дополнительный флаг WNOHANG, переводящий их в неблокируемый режим, в котором функция либо удаляет дочерний процесс, если он есть, либо просто завершается. В первом случае возвращается идентификатор процесса, во втором — 0.

Более элегантный подход состоит в асинхронном уведомлении родительского процесса о завершении потомка. Существуют разные способы сделать это, но проще всего воспользоваться сигналом SIGCHLD, посылаемым как раз тогда, когда завершается дочерний процесс. По умолчанию программа никак не реагирует на этот сигнал, поэтому раньше вы могли и не знать о его существовании.

Таким образом, нужно организовать удаление дочерних процессов в обработчике сигнала SIGCHLD. Естественно, код состояния удаляемого процесса следует сохранять в глобальной переменной, если эта информация необходима основной программе. В листинге 3.7 показана программа, в которой реализована данная методика.

Листинг 3.7. (sigchld.c) Удаление дочерних процессов в обработчике сигнала SIGCHLD

#include <signal.h>

#include <string.h>

#include <sys/types.h>

#include <sys/wait.h>


sig_atomic_t child_exit_status;


void clean_up_child_process(int signal_number) {

 /* Удаление дочернего процесса. */

 int status;

 wait(&status);

 /* Сохраняем статус потомка в глобальной переменной. */

 child_exit_status = status;

}


int main() {

 /* Обрабатываем сигнал SIGCHLD, вызывая функцию

    clean_up_child_process(). */

 struct sigaction sigchld_action;

 memset(&sigchld_action, 0, sizeof(sigchld_action));

 sigchld_action.sa_handler = &clean_up_child_process;

 sigaction(SIGCHLD, &sigchld_action, NULL);


 /* Далее выполняются основные действия, включая порождение

    дочернего процесса. */

 /* ... */


 return 0;

}

Глава 4

Потоки

Потоки, как и процессы, — это механизм, позволяющий программам выполнять несколько действий одновременно. Потоки работают параллельно. Ядро Linux планирует их работу асинхронно, прерывая время от времени каждый из них, чтобы дать шанс остальным.

С концептуальной точки зрения поток существует внутри процесса, являясь более мелкой единицей управления программой. При вызове программы Linux создает для нее новый процесс, а в нем — единственный поток, последовательно выполняющий программный код. Этот поток может создавать дополнительные потоки. Все они находятся в одном процессе, выполняя ту же самую программу, но, возможно, в разных ее местах.

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