Unix - статьи


         

Процессы и потоки - часть 6


./exec ls –al ./exec ./exec ls –al ./exec ./exec ./exec ls –al

и так далее. Чего вы не можете, однако, сделать, так это выполнить с помощью нашей программы команду типа ./exec "ls > log.txt"

Перенаправление ввода-вывода выполняется командной оболочкой. Для того, чтобы выполнять такие команды, необходимо запустить сначала оболочку, например: exec sh –c "ls –al > log.txt"

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

Обратимся теперь к родительскому процессу. Мы хотим, чтобы родительский процесс дождался завершения дочернего процесса и вывел сообщение о том, как был завершен дочерний процесс. Первую часть этой задачи мы могли бы решить с помощью уже известного нам механизма сигналов. Каждый раз, когда дочерний процесс приостанавливается или завершается, родительский процесс получает сигнал SIGCHLD. Мы могли бы приостановить работу программы до получения этого сигнала, но таким образом мы бы знали только то, что один из дочерних процессов программы завершился, но не знали бы, ни как он завершился, ни какой именно это был процесс. Причина этого конечно, в том, что сигналы сами по себе не несут никакой дополнительной информации. Однако, в нашем распоряжении есть функция wait(2), которая приостанавливает процесс до тех пор, пока один из его дочерних процессов не будет остановлен или не завершится, после чего возвращает информацию о том, какой процесс завершился и что стало причиной его завершения. Значение, возвращаемое функцией wait() – это PID завершившегося процесса, а аргументом функции должен быть указатель на переменную status типа int. В этой переменной функция вернет дополнительные сведения о том, как завершился процесс. Вы могли подумать, что после того как мы создали два процесса с помощью fork(), не так уж важно, запускаем ли мы новую программу в дочернем или в родительском процессе, ведь разница между ними невелика. Теперь вы знаете как минимум одну причину придерживаться строгих правил, ведь родительский процесс может следить за дочерним, в то время как обратное невозможно.

Значение переменной status, в которой функция wait() передает дополнительные данные о завершившемся процессе, представляет собой маску из нескольких разных параметров. В файле <sys/wait.h> определены макросы, упрощающие «расшифровку» этой маски. Макрос WIFEXITED возвращает значение 1, если процесс завершился «добровольно», то есть в результате вызова exit() или _exit(). В этом случае с помощью макроса WEXITSTATUS можно узнать код завершения, возвращенный процессом. Макрос WIFSIGNALED возвращает 1, если выполнение процесса было завершено сигналом. Номер этого сигнала можно узнать с помощью макроса WTERMSIG. Макрос WIFSTOPPED возвращает значение 1, если выполнение процесса было приостановлено сигналом, номер которого возвращает макрос WSTOPSIG.

Помимо функции wait() в нашем распоряжении есть еще две функции, позволяющие приостановить процесс в ожидании изменения состояния дочерней программы. Это функции waitpid(2) и waitid(2). Функция waitpid() может приостановить процесс в ожидании изменения состояния определенного дочернего процесса, заданного значением PID. У функции waitpid() три параметра. Первый параметр – значение PID процесса, завершения которого ждет функция. Если передать в этом параметре значение -1, функция будет ждать изменения состояния любого процесса, аналогично wait(). Если первый параметр равен нулю, waitpid() ждет завершения любого процесса из той же группы, что и текущий. Второй параметр waitpid() аналогичен параметру wait(). Третий параметр позволяет указать дополнительные флаги функции. Например, если установить флаг NOHANG, функция вернет управление немедленно, даже если ни один дочерний процесс не завершился (подробнее об этом будет сказано ниже). Результатом функции waitpid(), также как и в случае wait(), является идентификатор завершившегося процесса. Функция waitid(), появившаяся в Linux начиная с версии ядра 2.6.9, позволяет установить более тонкий контроль над параметрами ожидаемого события и получить более подробную информацию о сигнале, вызвавшем изменение состояния дочернего процесса. Для большинства задач возможности этой функции явно избыточны.

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

Программируя несколько потоков или процессов, мы всегда должны учитывать подобные варианты развития событий. В нашем конкретном случае беспокоиться не о чем. Если дочерний процесс завершится до завершения родительского процесса, система сохранит его «след», именуемый зомби. Зомби будет существовать до тех пор, пока его родительский процесс не вызовет wait() (waitpid(), waitid()), или пока родительский процесс не завершится. Система сохраняет зомби процессов специально для того, чтобы родительский процесс мог исследовать причины завершения дочернего процесса, однако это не всегда удобно. Рассмотрим, например, процесс init, который является «папой» всех пользовательских процессов сеанса Linux. Поскольку init существует на протяжении всего сеанса, любой из его дочерних процессов по завершении оставался бы в виде зомби до тех пор, пока init не вызвал бы одну из функций wait*. Еще один процесс, который может породить множество зомби, это демон... Иногда я должен напоминать себе, что пишу статью по программированию, а не сценарий фильма ужасов. Для того, чтобы процесс не оставлял зомби, можно вызывать функцию waitpid() периодически (с флагом NOHANG, чтобы она не блокировала вызвавший процесс если в системе нет зомби). Можно поступить и иначе, назначив сигналу SIGCHLD обработчик, в котором вызывалась бы функция wait(). Именно так поступает программа nozombies, которую вы найдете в




Содержание  Назад  Вперед