Строки 49–54 обрабатывают завершение процесса, включая вызов manage() для вывода состояния.
Строки 55–60 обрабатывают случай неожиданного завершения потомка. Этого не должно происходить, поскольку обработчику сигнала передается специфическая для определенного порожденного процесса информация.
Строки 61–66 представляют для нас интерес: возвращаемое значение для изменений состояния равно 0. manage() имеет дело с деталями (строка 66).
Строки 67–69 обрабатывают прерывания, а строки 70–75 распоряжаются ошибками
80 /* child --- что сделать в порожденном процессе */
81
82 void child(void)
83 {
84 raise(SIGCONT); /* должен быть проигнорирован */
85 raise(SIGSTOP); /* заснуть, родитель снова разбудит */
86 printf("\t---> child restarted <---\n");
87 exit(42); /* нормальное завершение, дать возможность родителю получить значение */
88 }
Функция child() обрабатывает поведение порожденного процесса, предпринимая действия для уведомления родителя[113]. Строка 84 посылает SIGCONT, что может вызвать получение родителем события CLD_CONTINUED. Строка 85 посылает SIGSTOP, который останавливает процесс (сигнал не может быть перехвачен) и вызывает для родителя событие CLD_STOPPED. Когда родитель возобновляет порожденный процесс, последний выводит сообщение, что он снова активен, а затем завершается с известным статусом завершения.
90 /* main --- установка относящихся к порожденному процессу сведений
и сигналов, создание порожденного процесса */
91
92 int main(int argc, char **argv)
93 {
94 pid_t kid;
95 struct sigaction sa;
96 sigset_t childset, emptyset;
97
98 sigemptyset(&emptyset);
99
100 sa.sa_flags = SA_SIGINFO;
101 sa.sa_sigaction = childhandler;
102 sigfillset(&sa.sa_mask); /* при вызове обработчика все заблокировать */
103 sigaction(SIGCHLD, &sa, NULL);
104
105 sigemptyset(&childset);
106 sigaddset(&childset, SIGCHLD);
107
108 sigprocmask(SIG_SETMASK, &childset, NULL); /* блокировать его в коде main */
109
110 if ((kid = fork()) == 0)
111 child();
112
113 /* здесь выполняется родитель */
114 for (;;) {
115 printf("waiting for signals\n");
116 sigsuspend(&emptyset);
117 }
118
119 return 0;
120 }
Программа main() все устанавливает. Строки 100–103 помещают на место обработчик. Строка 100 устанавливает флаг SA_SIGINFO таким образом, что используется обработчик с тремя аргументами. Строки 105–108 блокируют SIGCHLD.
Строка 110 создает порожденный процесс. Строки 113–117 продолжаются в родителе, используя для ожидания входящих сигналов sigsuspend().
123 /* manage --- разрешение различных событий, которые могут случиться с потомком */
124
125 void manage(siginfo_t *si)
126 {
127 char buf[100];
128
129 switch (si->si_code) {
130 case CLD_STOPPED:
131 write(1, "\tchild stopped, restarting\n", 27);
132 kill(si->si_pid, SIGCONT);
133 break;
134
135 case CLD_CONTINUED: /* not sent on Linux */
136 write(1, "\tchild continued\n", 17);
137 break;
138
139 case CLD_EXITED:
140 strcpy(buf, "\tchild exited with status ");
141 strcat(buf, format_num(si->si_status));
142 strcat(buf, "\n");
143 write(1, buf, strlen(buf));
144 exit(0); /* we're done */
145 break;
146
147 case CLD_DUMPED:
148 write(1, "\tchild dumped\n", 14);
149 break;
150
151 case CLD_KILLED:
152 write(1, " \tchild killed\n", 14);
153 break;
154
155 case CLD_TRAPPED:
156 write(1, "\tchild trapped\n", 15);
157 break;
158 }
159 }
Посредством функции manage() родитель обрабатывает изменение состояния в порожденном процессе, manage() вызывается, когда изменяется состояние и когда порожденный процесс завершился.
Строки 130–133 обрабатывают случай, когда потомок остановился; родитель возобновляет его, посылая SIGCONT.
Строки 135–137 выводят уведомление о возобновлении потомка. Это событие на системах GNU/Linux не происходит, и стандарт POSIX использует в этом случае невыразительный язык, просто говоря, что это событие может появиться, а не появится.
Строки 139–145 обрабатывают случай, когда порожденный процесс завершается, выводя статус завершения. Для этой программы родитель также все сделал, поэтому код завершается, хотя в более крупной программе это не то действие, которое должно быть сделано.
Другие случаи более специализированные. В случае события CLD_KILLED для получения дополнительных сведений было бы полезным значение status, заполненной функцией waitpid().
Вот что происходит при запуске:
$ ch10-status /* Запуск программы */
waiting for signals
Entered childhandler /* Вход в обработчик сигнала */
pid 24279 changed status
child stopped, restarting /* Обработчик действует */
Exited childhandler
waiting for signals
---> child restarted <--- /* Из потомка */
Entered childhandler
reaped process 24279 /* Обработчик родителя опрашивает потомка */
child exited with status 42
К сожалению, поскольку нет способа гарантировать доставку по одному SIGCHLD на каждый процесс, ваша программа должна быть готова восстановить несколько потомков за один проход.
10.9. Сигналы, передающиеся через fork() и exec()
Когда программа вызывает fork(), ситуация с сигналами в порожденном процессе почти идентична ситуации в родительском процессе. Установленные обработчики остаются на месте, заблокированные сигналы остаются заблокированными и т.д. Однако, любые ожидающие в родителе сигналы в потомке сбрасываются, включая установленный с помощью alarm() временной интервал. Это просто, и это имеет смысл.
113
Возможно, лучшим именем для функции было бы child_at_school() [ребенок_в_школе] —