Выбрать главу

SIGTTIN

Когда фоновый процесс пытается считывать из терминала, ему передается SIGTTIN.

SIGTTOU

Этот сигнал обычно генерируется фоновым процессом, пытающимся выполнить запись в свой терминал. Сигнал генерируется только в случае установки атрибута терминала TOSTOP, как рассматривается в главе 16.

Данный сигнал генерируется также фоновым процессом, вызывающим tcflush(), tcflow(), tcsetattr(), tcsetpgrp(), tcdrain() или tcsendbreak().

Действием по умолчанию каждого из этих трех сигналов является останов процесса. Все эти процессы можно поймать или игнорировать. В обоих случаях процесс не останавливается.

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

Хотя многие приложения можно останавливать и перезапускать без побочных эффектов, другим процессам требуется обрабатывать состояния останова и запуска. Например, большинству редакторов необходимо модифицировать параметры терминала в рабочем состоянии. Когда пользователи приостанавливают процесс, они ожидают, что их терминал восстановит свое состояние по умолчанию.

Когда процессу нужно выполнять действия перед приостановкой, должен быть предусмотрен обработчик сигнала для SIGTSTP. Это позволяет ядру уведомлять процесс о необходимости приостановки.

При получении SIGTSTP процесс должен немедленно выполнить все необходимые ему действия, чтобы разрешить приостановку (например, восстановление исходного состояния терминала) и приостановиться самому. Самый простой способ приостановки процесса — передача самому себе сигнала SIGSTOP. Однако большинство оболочек отображают сообщения с типом сигнала, вызвавшего остановку процесса, и если процесс передаст себе sigstop, он будет отличаться от большинства приостановленных процессов. Во избежание этого неудобства многие приложения сбрасывают свой обработчик SIGTSTP в SIG_DFL и передают себе SIGTSTP.

Процессам, которые требуют специальный код для правильных приостановок, обычно необходимо выполнять специальные действия при перезапуске. Это легко делается предоставлением обработчика сигнала для SIGCONT, который выполняет такие действия. Если процесс приостанавливается с помощью SIGTSTP, такие специальные действия, возможно, включают установку обработчика сигнала для SIGTSTP.

Ниже приведен код простого обработчика сигналов SIGCONT и SIGTSTP. Когда пользователь приостанавливает или перезапускает процесс, последний отображает сообщение перед остановкой или продолжением.

 1: /* monitor.с */

 2:

 3: #include <signal.h>

 4: #include <stdio.h>

 5: #include <string.h>

 6: #include <unistd.h>

 7:

 8: void catchSignal(int sigNum, int useDefault);

 9:

10: void handler(int signum) {

11:  if (signum == SIGTSTP) {

12:   write(STDOUT_FILENO, "получен SIGTSTP\n", 12);

13:   catchSignal(SIGTSTP, 1);

14:   kill(getpid(), SIGTSTP);

15:  } else {

16:   write(STDOUT_FILENO, "получен SIGCONT\n", 12);

17:   catchSignal(SIGTSTP, 0);

18:  }

19: }

20:

21: void catchSignal(int sigNum, int useDefault) {

22:  struct sigaction sa;

23:

24:  memset(&sa, 0, sizeof(sa));

25:

26:  if (useDefault)

27:   sa.sa_handler = SIG_DFL;

28:  else

29:   sa.sa_handler = handler;

30:

31:  if (sigaction(sigNum, &sa, NULL)) perror("sigaction");

32: }

33:

34: int main() {

35:  catchSignal(SIGTSTP, 0);

36:  catchSignal(SIGCONT, 0);

37:

38:  while(1);

39:

40:  return 0;

41: }

15.2. Управление заданиями в ladsh

Добавление управления заданиями к ladsh — это последнее добавление к простой оболочке, окончательный исходный код которой можно найти в приложении Б. Для начала потребуется добавить по элементу в структуры struct childProgram, struct job и struct jobSet. Поскольку ladsh некоторое время не рассматривался, вернитесь в главу 10, где были впервые представлены эти структуры данных. Ниже показано окончательное определение struct childProgram.

35: struct childProgram {

36:  pid_t pid;           /* 0 на выходе */

37:  char ** argv;        /* имя программы с аргументами * /

38:  int numRedirections; /* элементы в массиве переадресации */

39:  struct redirectionSpecifier * redirections; /* переадресации ввода-вывода */

40:  glob_t globResult;   /* результат универсализации параметров */

41:  int freeGlob;        /* должен ли освобождаться globResult? */

42:  int isStopped;       /* выполняется ли программа в данный момент?*/

43: };

Мы уже различаем работающие и завершенные дочерние программы с помощью элемента pid структуры struct childProgram, равного нулю в случае завершения дочерней программы, а в противном случае содержащего действительный идентификатор процесса. Новый элемент, isStopped, не равен нулю, если процесс был остановлен, в ином же случае он равен нулю. Обратите внимание, что его значение неважно, если pid равен нулю.

Аналогичные изменения потребуется внести и в struct job. Ранее эта структура отслеживала определенное количество программ в задании и количество выполняющихся процессов. Ее новый элемент, stoppedProgs, записывает количество процессов задания, остановленных в данный момент. Он может быть вычислен на основе элементов isStopped дочерних программ, содержащихся в задании, но лучше отслеживать его отдельно. После этого изменения получаем окончательную форму структуры struct job.

45: struct job {

46:  int jobld;         /* номер задания */

47:  int numProgs;      /* количество программ в задании */

48:  int runningProgs;  /* количество выполняющихся программ */

49:  char * text;       /* имя задания */

50:  char * cmdBuf;     /* буфер различных argv */

51:  pid_t pgrp;        /* идентификатор группы процессов задания */

52:  struct childProgram* progs; /* массив программ в задании */

53:  struct job * next; /* для слежения за фоновыми программами */

54:  int stopped Progs; /* количество активных, однако остановленных программ */

55: };

Как и предыдущие версии ladsh, код ladsh4.с игнорирует SIGTTOU. Это делается, чтобы позволить использовать tcsetpgrp() даже тогда, когда оболочка не является процессом переднего плана. Однако поскольку оболочка уже будет поддерживать правильное управление заданиями, дочерним процессам не следует игнорировать сигнал. Как только новый процесс разветвляется с помощью runCommand(), он устанавливает обработчик для SIGTTOU в SIG_DFL. Это позволяет драйверу терминала приостановить фоновые процессы, пытающиеся выполнить запись в терминал или провести с ним еще какие-то действия. Ниже приведен код, который начинается с создания дочернего процесса, где сбрасывается SIGTTOU и выполняется дополнительная работа по синхронизации.