355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Арнольд Роббинс » Linux программирование в примерах » Текст книги (страница 32)
Linux программирование в примерах
  • Текст добавлен: 6 мая 2017, 11:00

Текст книги "Linux программирование в примерах"


Автор книги: Арнольд Роббинс



сообщить о нарушении

Текущая страница: 32 (всего у книги 55 страниц)

10.8.3.2. Снисходительные родители: минимальный надзор

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

В общем вы не можете ожидать получать по одному сигналу SIGCHLD на каждого завершающегося потомка. Следует считать, что SIGCHLD означает «завершился по крайней мере один потомок» и быть готовым собрать при обработке SIGCHLD сведения о как можно большем числе потомков.

Следующая программа, ch10-reap1.с, блокирует SIGCHLD до тех пор, пока не будет готова восстановить потомков.

1  /* ch10-reap1.с – демонстрирует управление SIGCHLD с использованием цикла */

2

3  #include

4  #include

5  #include

6  #include

7  #include

8  #include

9

10 #define MAX_KIDS 42

11 #define NOT_USED -1

12

13 pid_t kids[MAX_KIDS];

14 size_t nkids = 0;

Массив потомков отслеживает ID порожденных процессов. Если элемент содержит NOT_USED, он не представляет необработанного потомка. (Его инициализируют строки 89–90 внизу) nkids указывает, сколько значений в kids следует проверить.

16 /* format_num – вспомогательная функция, поскольку нельзя использовать [sf]printf() */

17

18 const char *format_num(int num)

19 {

20 #define NUMSIZ 30

21  static char buf[NUMSIZ];

22  int i;

23

24  if (num <= 0) {

25   strcpy(buf, "0");

26   return buf;

27  }

28

29  i = NUMSIZ – 1;

30  buf[i–] = '';

31

32  /* Преобразует цифры обратно в строку. */

33  do {

34   buf[i–] = (num % 10) + '0';

35   num /= 10;

36  } while (num > 0);

37

38  return &buf[i+1];

39 }

Поскольку обработчики сигналов не должны вызывать функции семейства printf(), мы предусмотрели для преобразования десятичного сигнала или номера PID в строку простую «вспомогательную» функцию format_num(). Это примитивно, но работает.

41 /* childhandler – перехват SIGCHLD, сбор сведений со всех доступных потомков */

42

43 void childhandler(int sig)

44 {

45  int status, ret;

46  int i;

47  char buf[100];

48  static const char entered[] = "Entered childhandlern" ;

49  static const char exited[] = "Exited childhandlern";

50

51  writed, entered, strlen(entered));

52  for (i =0; i < nkids; i++) {

53   if (kids[i] == NOT_USED)

54    continue;

55

56 retry:

57   if ((ret = waitpid(kids[i], &status, WNOHANG)) == kids[i]) {

58    strcpy(buf, "treaped process ");

59    strcat(buf, format_num(ret));

60    strcat(buf, "n");

61    write(1, buf, strlen(buf));

62    kids[i] = NOT_USED;

63   } else if (ret == 0) {

64    strcpy(buf, "tpid ");

65    strcat(buf, format_num(kids[i]));

66    strcat(buf, " not available yetn");

67    write(1, buf, strlen(buf));

68   } else if (ret == -1 && errno == EINTR) {

69    write(1, "tretryingn", 10);

70    goto retry;

71   } else {

72    strcpy(buf, "twaitpid() failed: ");

73    strcat(buf, strerror(errno));

74    strcat(buf, "n");

75    write(1, buf, strlen(buf));

76   }

77  }

78  write(1, exited, strlen(exited));

79 }

Строки 51 и 58 выводят «входное» и «завершающее» сообщения, так что мы можем ясно видеть, когда вызывается обработчик сигнала. Другие сообщения начинаются с ведущего символа TAB.

Главной частью обработчика сигнала является большой цикл, строки 52–77. Строки 53–54 проверяют на NOT_USED и продолжают цикл, если текущий слот не используется.

Строка 57 вызывает waitpid() с PID текущего элемента kids. Мы предусмотрели опцию WNOHANG, которая заставляет waitpid() возвращаться немедленно, если затребованный потомок недоступен. Этот вызов необходим, так как возможно, что не все потомки завершились.

Основываясь на возвращенном значении, код предпринимает соответствующее действие. Строки 57–62 обрабатывают случай обнаружения потомка, выводя сообщение и помещая в соответствующий слот в kids значение NOT_USED.

Строки 63–67 обрабатывают случай, когда затребованный потомок недоступен. В этом случае возвращается значение 0, поэтому выводится сообщение, и выполнение продолжается.

Строки 68–70 обрабатывают случай, при котором был прерван системный вызов. В этом случае самым подходящим способом обработки является goto обратно на вызов waitpid(). (Поскольку main() блокирует все сигналы при вызове обработчика сигнала [строка 96], это прерывание не должно случиться. Но этот пример показывает, как обработать все случаи.)

Строки 71–76 обрабатывают любую другую ошибку, выводя соответствующее сообщение об ошибке.

81  /* main – установка связанных с порожденными процессами сведений и сигналов, создание порожденных процессов */

82

83  int main(int argc, char **argv)

84  {

85   struct sigaction sa;

86   sigset_t childset, emptyset;

87   int i;

88

89   for (i = 0; i < nkids; i++)

90    kids[i] = NOT_USED;

91

92   sigemptyset(&emptyset);

93

94   sa.sa_flags = SA_NOCLDSTOP;

95   sa.sa_handler = childhandler;

96   sigfillset(&sa.sa_mask); /* блокировать все при вызове обработчика */

97   sigaction(SIGCHLD, &sa, NULL);

98

99   sigemptyset(&childset);

100  sigaddset(&childset, SIGCHLD);

101

102  sigprocmask(SIG_SETMASK, &childset, NULL); /* блокировать его в коде main */

103

104  for (nkids = 0; nkids < 5; nkids++) {

105   if ((kids[nkids] = fdrk()) == 0) {

106    sleep(3);

107    _exit(0);

108   }

109  }

110

111  sleep(5); /* дать потомкам возможность завершения */

112

113  printf("waiting for signaln");

114  sigsuspend(&emptyset);

115

116  return 0;

117 }

Строки 89–90 инициализируют kids. Строка 92 инициализирует emptyset. Строки 94–97 настраивают и устанавливают обработчик сигнала для SIGCHLD. Обратите внимание на использование в строке 94 SA_NOCLDSTOP, тогда как строка 96 блокирует все сигналы при вызове обработчика.

Строки 99–100 создают набор сигналов, представляющих SIGCHLD, а строка 102 устанавливает их в качестве маски сигналов процесса для программы.

Строки 104–109 создают пять порожденных процессов, каждый из которых засыпает на три секунды. По ходу дела они обновляют массив kids и переменную nkids.

Строка 111 дает затем потомкам шанс завершиться, заснув на еще больший промежуток времени. (Это не гарантирует, что порожденные процессы завершатся, но шансы довольно велики.)

Наконец, строки 113–114 выводят сообщение и приостанавливаются, заменив маску сигналов процесса, блокирующую SIGCHLD, пустой маской. Это дает возможность появиться сигналу SIGCHLD, что в свою очередь вызывает запуск обработчика сигнала. Вот что происходит:

$ ch10-reap1 /* Запуск программы */

waiting for signal

Entered childhandler

  reaped process 23937

  reaped process 23938

  reaped process 23939

  reaped process 23940

  reaped process 23941

Exited childhandler

Обработчик сигнала собирает сведения о потомках за один проход.

Следующая программа, ch10-reap2.c, сходна с ch10-reap1.c. Разница в том, что она допускает появление сигнала SIGCHLD в любое время. Такое поведение увеличивает шанс получения более одного SIGCHLD, но не гарантирует это. В результате обработчик сигнала все равно должен быть готов обработать в цикле несколько потомков.

1  /* ch10-reap2.c – демонстрирует управление SIGCHLD, один сигнал на потомка */

2

   /* ...не изменившийся код пропущен... */

12

13 pid_t kids[MAX_KIDS];

14 size_t nkids = 0;

15 size_t kidsleft = 0; /* <<< Добавлено */

16

 /* ...не изменившийся код пропущен... */

41

42 /* childhandler – перехват SIGCHLD, опрос всех доступных потомков */

43

44 void childhandler(int sig)

45 {

46  int status, ret;

47  int i;

48  char buf[100];

49  static const char entered[] = "Entered childhandlern";

50  static const char exited[] = "Exited childhandlern";

51

52  write(1, entered, strlen(entered));

53  for (i = 0; i < nkids; i++) {

54   if (kids[i] == NOT_USED)

55    continue;

56

57 retry:

58  if ((ret = waitpid(kids[i], &status, WNOHANG)) == kids[i]) {

59   strcpy(buf, "treaped process ");

60   strcat(buf, format_num(ret));

61   strcat(buf, "n");

62   write(1, buf, strlen(buf));

63   kids[i] = NOT_USED;

64   kidsleft–; /* <<< Добавлено */

65  } else if (ret == 0) {

    /* ...не изменившийся код пропущен... */

80  write(1, exited, strlen(exited));

81 }

Это идентично предыдущей версии за тем исключением, что у нас есть новая переменная, kidsleft, указывающая, сколько имеется не опрошенных потомков. Строки 15 и 64 помечают новый код.

83  /* main – установка относящейся к порожденным процессам сведений

       и сигналов, создание порожденных процессов */

84

85  int main(int argc, char **argv)

86  {

     /* ...не изменившийся код пропущен... */

100

101  sigemptyset(&childset);

102  sigaddset(&childset, SIGCHLD);

103

104  /* sigprocmask(SIG_SETMASK, &childset, NULL); /* блокирование в коде main */

105

106  for (nkids = 0; nkids < 5; nkids++) {

107   if ((kids[nkids] = fork()) == 0) {

108    sleep(3);

109    _exit(0);

110   }

111   kidsleft++; /* <<< Added */

112  }

113

114  /* sleep(5); /* дать потомкам шанс завершиться */

115

116  while (kidsleft > 0) { /* <<< Добавлено */

117   printf("waiting for signalsn");

118   sigsuspend(&emptyset);

119  } /* <<< Добавлено */

120

121  return 0;

122 }

Здесь код также почти идентичен. Строки 104 и 114 закомментированы из предыдущей версии, а строки 111, 116 и 119 добавлены. Удивительно, при запуске поведение меняется в зависимости от версии ядра!

$ uname -a /* Отобразить версию системы */

Linux example1 2.4.20-8 #1 Thu Mar 13 17:54:28 EST 2003 i686 i686 i386 GNU/Linux

$ ch10-reap2 /* Запустить программу */

waiting for signals

Entered childhandler /* Опрос одного потомка */

  reaped process 2702

  pid 2703 not available yet

  pid 2704 not available yet

  pid 2705 not available yet

  pid 27 06 not available yet

Exited childhandler

waiting for signals

Entered childhandler /* И следующего */

  reaped process 2703

  pid 2704 not available yet

  pid 2705 not available yet

  pid 2706 not available yet

Exited childhandler

waiting for signals

Entered childhandler /* И так далее */

  reaped process 2704

  pid 2705 not available yet

  pid 2706 not available yet

Exited childhandler

waiting for signals

Entered childhandler

  reaped process 2705

  pid 2706 not available yet

Exited childhandler

waiting for signals

Entered childhandler

  reaped process 2706

Exited childhandler

В данном примере на каждый процесс поступает ровно один SIGCHLD! Хотя это прекрасно и полностью воспроизводимо на этой системе, это также необычно. Как на более раннем, так и на более позднем ядре и на Solaris программа получает один сигнал для более чем одного потомка:

$ uname -a /* Отобразить версию системы */

Linux example2 2.4.22-1.2115.npt1 #1 Wed Oct 29 15:42:51 EST 2003 i686 i686 i386 GNU/Linux

$ ch10-reap2 /* Запуск программы */

waiting for signals

Entered childhandler /* Обработчик сигнала вызван лишь однажды */

  reaped process 9564

  reaped process 9565

  reaped process 9566

  reaped process 9567

  reaped process 9568

Exited childhandler

ЗАМЕЧАНИЕ. В коде для ch10-reap2.c есть один важный дефект – состояние гонки. Взгляните еще раз на строки 106–112 в ch10-reap2.c. Что случится, если SIGCHLD появится при исполнении этого кода? Массив kids и переменные nkids и kidsleft могут оказаться разрушенными: код в main добавляет новый процесс, но обработчик сигнала вычитает один.

Этот пример кода является отличным примером критического раздела; он не должен прерываться при исполнении. Правильным способом работы с этим кодом является заключение его между вызовами, которые сначала блокируют, а затем разблокируют SIGCHLD.

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

4  #include

5  #include

6  #include

7  #include

8  #include

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]113
  Возможно, лучшим именем для функции было бы child_at_school() [ребенок_в_школе] – Примеч. автора.


[Закрыть]
. Строка 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 на каждый процесс, ваша программа должна быть готова восстановить несколько потомков за один проход.

10.9. Сигналы, передающиеся через fork() и exec()

Когда программа вызывает fork(), ситуация с сигналами в порожденном процессе почти идентична ситуации в родительском процессе. Установленные обработчики остаются на месте, заблокированные сигналы остаются заблокированными и т.д. Однако, любые ожидающие в родителе сигналы в потомке сбрасываются, включая установленный с помощью alarm() временной интервал. Это просто, и это имеет смысл.

Когда процесс вызывает одну из функций exec(), положение в новой программе следующее:

• Сигналы с установленным действием по умолчанию остаются с этим действием по умолчанию.

• Все перехваченные сигналы сбрасываются в состояние с действием по умолчанию.

• Сигналы, которые игнорируются, продолжают игнорироваться. Особым случаем является SIGCHLD. Если SIGCHLD до вызова exec() игнорировался, он может игнорироваться также и после вызова. В качестве альтернативы для него может быть восстановлено действие по умолчанию. То, что происходит на самом деле, стандартом POSIX намеренно не определяется. (Справочные страницы GNU/Linux не определяют, что делает Linux, и поскольку POSIX оставляет это не определенным, любой код, который вы пишете для использования SIGCHLD, должен быть подготовлен для обработки любого случая.)

• Сигналы, заблокированные до вызова exec(), остаются заблокированными и после вызова. Другими словами, новая программа наследует маску сигналов существующего процесса.

• Любые ожидающие сигналы (те, которые появились, но были заблокированы) сбрасываются. Новая программа не может их получить.

• Временной интервал, остающийся для alarm(), сохраняется на своем месте. (Другими словами, если процесс устанавливает alarm, а затем непосредственно вызывает exec(), новый образ в конечном счете получит SIGALARM. Если он сначала вызывает fork(), родитель сохраняет установки alarm, тогда как потомок, вызывающий exec(), не сохраняет.

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


    Ваша оценка произведения:

Популярные книги за неделю