Вы сможете увидеть создание процесса-зомби, если измените количество сообщений в программе из примера с вызовом fork. Если дочерний процесс выводит меньше сообщений, чем родительский, он закончится первым и будет существовать как зомби, пока не завершится родительский процесс.
Упражнение 11.5. Зомби
Программа fork2.c такая же, как программа fork1.с, за исключением того, что количества сообщений, выводимых родительским и дочерним процессами, поменяли местами. Далее приведены соответствующие строки кода:
switch (pid) {
case -1:
perror("fork failed");
exit(1);
case 0:
message = "This is the child";
<i> n = 3;</i>
break;
default:
message = "This is the parent";
<i> n = 5;</i>
break;
}
Как это работает
Если вы выполните только что приведенную программу с помощью команды ./fork2 & и затем вызовите программу ps после завершения дочернего процесса, но до окончания родительского, то увидите строку, подобную следующей. (Некоторые системы могут сказать <zombie> вместо <defunct>.)
$ <b>ps -аl</b>
F S UID PID PPID С PRI NI ADDR SZ WCHAN TTY TIME CMD
004 S 0 1273 1259 0 75 0 - 589 wait4 pts/2 00:00:00 su
000 S 0 1274 1273 0 75 0 - 731 schedu pts/2 00:00:00 bash
000 S 500 1463 1262 0 75 0 - 788 schedu pts/1 00:00:00 oclock
000 S 500 1465 1262 0 75 0 - 2569 schedu pts/1 00:00:01 emacs
000 S 500 1603 1262 0 75 0 - 313 schedu pts/1 00:00:00 fork2
<i>003 Z 500 1604 1603 0 75 0 - 0 do_exi pts/1 00:00:00 fork2 <defunct></i>
000 R 500 1605 1262 0 81 0 - 781 - pts/1 00:00:00 ps
Если родительский процесс завершится необычно, дочерний процесс автоматически получит в качестве родителя процесс с PID, равным 1 (init). Теперь дочерний процесс — зомби, который уже не выполняется, но унаследован процессом init из-за необычного окончания родительского процесса. Зомби останется в таблице процессов, пока не пойман процессом init. Чем больше таблица, тем медленнее эта процедура. Следует избегать процессов-зомби, поскольку они потребляют ресурсы до тех пор, пока процесс init не вычистит их.
Есть еще один системный вызов, который можно применять для ожидания дочернего процесса. Он называется waitpid и применяется для ожидания завершения определенного процесса.
<b>#include <sys/types.h></b>
<b>#include <sys/wait.h></b>
<b>pid_t waitpid(pid_t pid, int *stat_loc, int options);</b>
Аргумент pid — конкретный дочерний процесс, окончания которого нужно ждать. Если он равен –1, waitpid вернет информацию о любом дочернем процессе. Как и вызов wait, он записывает информацию о состоянии процесса в место, указанное аргументом stat_loc, если последний не равен пустому указателю. Аргумент options позволяет изменить поведение waitpid. Наиболее полезная опция WNOHANG мешает вызову waitpid приостанавливать выполнение вызвавшего его процесса. Ее можно применять для выяснения, завершился ли какой-либо из дочерних процессов, и если нет, то продолжать выполнение. Остальные опции такие же, как в вызове wait.
Итак, если вы хотите, чтобы родительский процесс периодически проверял, завершился ли конкретный дочерний процесс, можно использовать следующий вызов:
waitpid(child_pid, (int *)0, WNOHANG);
Он вернет ноль, если дочерний процесс не завершился и не остановлен, или child_pid, если это произошло. Вызов waitpid вернет -1 в случае ошибки и установит переменную errno. Это может произойти, если нет дочерних процессов (errno равна ECHILD), если вызов прерван сигналом (EINTR) или аргумент options неверный (EINVAL).
Перенаправление ввода и вывода
Вы можете применить ваши знания о процессах для изменения поведения программ, используя тот факт, что открытые файловые дескрипторы сохраняются вызовами fork и exec. Следующий пример из упражнения 11.6 содержит программу-фильтр, которая читает из стандартного ввода и пишет в свой стандартный вывод, выполняя при этом некоторое полезное преобразование.
Далее приведена программа очень простой фильтрации upper.c, которая читает ввод и преобразует строчные буквы в прописные:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
int main() {
int ch;
while ((ch = getchar()) != EOF) {
putchar(toupper(ch));
}
exit(0);
}
Когда вы выполните программу, она сделает то, что и ожидалось:
$ <b>./upper</b>
hello THERE
HELLO THERE
<b>^D</b>
$
Вы, конечно, можете применить ее для преобразования символов файла, используя перенаправление, применяемое командной оболочкой:
$ <b>cat file.txt</b>
this is the file, file.txt, it is all lower case.