Книга: Linux программирование в примерах

10.8.3.3. Строгий родительский контроль

10.8.3.3. Строгий родительский контроль

Структура siginfo_t и перехватчик сигнала с тремя аргументами дают возможность узнать, что случилось с потомком. Для SIGCHLD поле si_code структуры siginfo_t указывает причину посылки сигнала (остановка, возобновление, завершение порожденного процесса и т.д.). В табл. 10.5 представлен полный список значений. Все они определены в качестве расширения XSI стандарта POSIX.

Следующая программа, ch10-status.c, демонстрирует использование структуры siginfo_t.

1  /* ch10-status.c --- демонстрирует управление SIGCHLD, используя обработчик с 3 аргументами */
2
3  #include <stdio.h>
4  #include <errno.h>
5  #include <signal.h>
6  #include <string.h>
7  #include <sys/types.h>
8  #include <sys/wait.h>
9
10 void manage(siginfo_t *si);
11
/* ...не изменившийся для format_num() код опущен... */

Таблица 10.5. Значения si_code XSI для SIGCHLD

Значение Смысл
CLD_CONTINUED Остановленный потомок был возобновлен.
CLD_DUMPED Потомок завершился с ошибкой, создан образ процесса
CLD_EXITED Потомок завершился нормально.
CLD_KILLED Потомок был завершен сигналом
CLD_STOPPED Порожденный процесс был остановлен.
CLD_TRAPPED Трассируемый потомок остановлен (Это условие возникает, когда программа трассируется — либо из отладчика, либо для мониторинга реального времени В любом случае, вы вряд ли увидите его в обычных ситуациях.)

Строки 3–8 включают стандартные заголовочные файлы, строка 10 объявляет manage(), которая имеет дело с изменениями состояния потомка, а функция format_num() не изменилась по сравнению с предыдущим.

37 /* childhandler --- перехват SIGCHLD, сбор данных лишь об одном потомке */
38
39 void childhandler(int sig, siginfo_t *si, void *context)
40 {
41  int status, ret;
42  int i;
43  char buf[100];
44  static const char entered[] = "Entered childhandlern";
45  static const char exited[] = "Exited childhandlern";
46
47  write(1, entered, strlen(entered));
48 retry:
49  if ((ret = waitpid(si->si_pid, &status, WNOHANG)) == si->si_pid) {
50   strcpy(buf, "treaped process ");
51   strcat(buf, format_num(si->si_pid));
52   strcat(buf, "n");
53   write(1, buf, strlen(buf));
54   manage(si); /* обработать то, что произошло */
55  } else if (ret > 0) {
56   strcpy(buf, "treaped unexpected pid ");
57   strcat(buf, format_num(ret));
58   strcat(buf, "n");
59   write(1, buf, strlen(buf));
60   goto retry; /* почему бы нет? */
61  } else if (ret == 0) {
62   strcpy(buf, "tpid ");
63   strcat(buf, format_num(si->si_pid));
64   strcat(buf, " changed statusn");
65   write(1, buf, strlen(buf));
66   manage(si); /* обработать то, что произошло */
67  } else if (ret == -1 && errno == EINTR) {
68   write(1, "tretryingn", 10);
69   goto retry;
70  } else {
71   strcpy(buf, "twaitpid() failed: ");
72   strcat(buf, strerror(errno));
73   strcat(buf, "n");
74   write(1, buf, strlen(buf));
75  }
76
77  write(1, exited, strlen(exited));
78 }

Обработчик сигнала похож на показанные ранее. Обратите внимание на список аргументов (строка 39) и на то, что нет цикла.

Строки 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 signalsn");
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, restartingn", 27);
132   kill(si->si_pid, SIGCONT);
133   break;
134
135  case CLD_CONTINUED: /* not sent on Linux */
136   write(1, "tchild continuedn", 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 dumpedn", 14);
149   break;
150
151  case CLD_KILLED:
152   write(1, " tchild killedn", 14);
153   break;
154
155  case CLD_TRAPPED:
156   write(1, "tchild trappedn", 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 на каждый процесс, ваша программа должна быть готова восстановить несколько потомков за один проход.

Оглавление книги


Генерация: 1.183. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз