организовать двустороннюю поочередную связь процесса-родителя и процесса-ребенка через pipe, используя для синхронизации сигналы sigusr1 и sigusr2. |
Организация ввода-вывода в UNIX. Файлы устройств. Аппарат прерываний. Сигналы в UNIX
Прогон программы с пользовательской обработкой сигнала SIGINT
Рассмотрим теперь другую программу – 13–14-3.c:
/* Программа с пользовательской обработкой сигнала SIGINT */ #include <signal.h> #include <stdio.h> /* Функция my_handler – пользовательский обработчик сигнала */ void my_handler(int nsig){ printf("Receive signal %d, CTRL-C pressed\n", nsig); } int main(void){ /* Выставляем реакцию процесса на сигнал SIGINT */ (void)signal(SIGINT, my_handler); /*Начиная с этого места, процесс будет печатать сообщение о возникновении сигнала SIGINT */ while(1); return 0; }Листинг 13-14.3. Программа (13–14-3.c) с пользовательской обработкой сигнала SIGINT.
Эта программа отличается от программы из раздела "Прогон программы, игнорирующей сигнал SIGINT " тем, что в ней введена обработка сигнала SIGINT пользовательской функцией. Наберите, откомпилируйте и запустите эту программу, проверьте ее реакцию на нажатие клавиш <CTRL> и <C> и на нажатие клавиш <CTRL> и <4>.
Модификация предыдущей программы для пользовательской обработки сигналов SIGINT и SIGQUIT
Модифицируйте программу из предыдущего раздела так, чтобы она печатала сообщение и о нажатии клавиш <CTRL> и <4>. Используйте одну и ту же функцию для обработки сигналов SIGINT и SIGQUIT . Откомпилируйте и запустите ее, проверьте корректность работы. Снимать программу также придется с другого терминала командой kill .
Восстановление предыдущей реакции на сигнал
До сих пор в примерах мы игнорировали значение, возвращаемое системным вызовом signal() . На самом деле этот системный вызов возвращает указатель на предыдущий обработчик сигнала, что позволяет восстанавливать переопределенную реакцию на сигнал. Рассмотрим пример программы 13—14-4.c, возвращающей первоначальную реакцию на сигнал SIGINT после 5 пользовательских обработок сигнала.
/* Программа с пользовательской обработкой сигнала SIGINT, возвращающаяся к первоначальной реакции на этот сигнал после 5 его обработок*/ #include <signal.h> #include <stdio.h> int i=0; /* Счетчик числа обработок сигнала */ void (*p)(int); /* Указатель, в который будет занесен адрес предыдущего обработчика сигнала */ /* Функция my_handler – пользовательский обработчик сигнала */ void my_handler(int nsig){ printf("Receive signal %d, CTRL-C pressed\n", nsig); i = i+1; /* После 5-й обработки возвращаем первоначальную реакцию на сигнал */ if(i == 5) (void)signal(SIGINT, p); } int main(void){ /* Выставляем свою реакцию процесса на сигнал SIGINT, запоминая адрес предыдущего обработчика */ p = signal(SIGINT, my_handler); /*Начиная с этого места, процесс будет 5 раз печатать сообщение о возникновении сигнала SIGINT */ while(1); return 0; }Листинг 13-14.4. Программа (13—14-4.c) с пользовательской обработкой сигнала SIGINT.
Наберите, откомпилируйте программу и запустите ее на исполнение.
Сигналы SIGUSR1 и SIGUSR2. Использование сигналов для синхронизации процессов
В операционной системе UNIX существует два сигнала, источниками которых могут служить только системный вызов kill() или команда kill , – это сигналы SIGUSR1 и SIGUSR2 . Обычно их применяют для передачи информации о происшедшем событии от одного пользовательского процесса другому в качестве сигнального средства связи.
В материалах семинара 5 (раздел "Написание, компиляция и запуск программы для организации двунаправленной связи между родственными процессами через pipe"), когда рассматривалась связь родственных процессов через pipe, речь шла о том, что pipe является однонаправленным каналом связи, и что для организации связи через один pipe в двух направлениях необходимо задействовать механизмы взаимной синхронизации процессов. Организуйте двустороннюю поочередную связь процесса-родителя и процесса-ребенка через pipe, используя для синхронизации сигналы SIGUSR1 и SIGUSR2 , модифицировав программу из раздела. "Прогон программы для организации однонаправленной связи между родственными процессами через pipe" семинара 5.
Задача повышенной сложности: организуйте побитовую передачу целого числа между двумя процессами, используя для этого только сигналы SIGUSR1 и SIGUSR2 .
При реализации нитей исполнения в операционной системе Linux (см. семинары 6–7, начиная с раздела "Понятие о нити исполнения (thread) в UNIX. Идентификатор нити исполнения. Функция pthread_self() ") сигналы SIGUSR1 и SIGUSR2 используются для организации синхронизации между процессами, представляющими нити исполнения, и процессом-координатором в служебных целях. Поэтому пользовательские программы, применяющие в своей работе нити исполнения, не могут задействовать сигналы SIGUSR1 и SIGUSR2 .
Завершение порожденного процесса. Системный вызов waitpid(). Сигнал SIGCHLD
В материалах семинаров 3–4 (раздел "Завершение процесса. Функция exit() ") при изучении завершения процесса говорилось о том, что если процесс-ребенок завершает свою работу прежде процесса-родителя, и процесс-родитель явно не указал, что он не заинтересован в получении информации о статусе завершения процесса-ребенка, то завершившийся процесс не исчезает из системы окончательно, а остается в состоянии закончил исполнение (зомби-процесс) либо до завершения процесса-родителя, либо до того момента, когда родитель соблаговолит получить эту информацию.
Для получения такой информации процесс-родитель может воспользоваться системным вызовом waitpid() или его упрощенной формой wait() . Системный вызов waitpid() позволяет процессу-родителю синхронно получить данные о статусе завершившегося процесса-ребенка либо блокируя процесс-родитель до завершения процесса-ребенка, либо без блокировки при его периодическом вызове с опцией WNOHANG. Эти данные занимают 16 бит и в рамках нашего курса могут быть расшифрованы следующим образом:
- Если процесс завершился при помощи явного или неявного вызова функции exit(), то данные выглядят так (старший бит находится слева) :
- Если процесс был завершен сигналом, то данные выглядят так (старший бит находится слева):
Каждый процесс-ребенок при завершении работы посылает своему процессу-родителю специальный сигнал SIGCHLD , на который у всех процессов по умолчанию установлена реакция "игнорировать сигнал ". Наличие такого сигнала совместно с системным вызовом waitpid() позволяет организовать асинхронный сбор информации о статусе завершившихся порожденных процессов процессом-родителем.
Используя системный вызов signal() , мы можем явно установить игнорирование этого сигнала ( SIG_IGN ), тем самым проинформировав систему, что нас не интересует, каким образом завершатся порожденные процессы. В этом случае зомби-процессов возникать не будет, но и применение системных вызовов wait() и waitpid() будет запрещено.
Прогон программы для иллюстрации обработки сигнала SIGCHLD
Для закрепления материала рассмотрим пример программы 13—14-5.c с асинхронным получением информации о статусе завершения порожденного процесса.
/* Программа с асинхронным получением информации о статусе двух завершившихся порожденных процессов */ #include <sys/types.h> #include <unistd.h> #include <wait.h> #include <signal.h> #include <stdio.h> /* Функция my_handler – обработчик сигнала SIGCHLD */ void my_handler(int nsig){ int status; pid_t pid; /* Опрашиваем статус завершившегося процесса и одновременно узнаем его идентификатор */ if((pid = waitpid(-1, &status, 0)) < 0){ /* Если возникла ошибка – сообщаем о ней и продолжаем работу */ printf("Some error on waitpid errno = %d\n", errno); } else { /* Иначе анализируем статус завершившегося процесса */ if ((status & 0xff) == 0) { /* Процесс завершился с явным или неявным вызовом функции exit() */ printf("Process %d was exited with status %d\n", pid, status >> 8); } else if ((status & 0xff00) == 0){ /* Процесс был завершен с помощью сигнала */ printf("Process %d killed by signal %d %s\n", pid, status &0x7f,(status & 0x80) ? "with core file" : "without core file"); } } } int main(void){ pid_t pid; /* Устанавливаем обработчик для сигнала SIGCHLD */ (void) signal(SIGCHLD, my_handler); /* Порождаем Сhild 1 */ if((pid = fork()) < 0){ printf("Can\'t fork child 1\n"); exit(1); } else if (pid == 0){ /* Child 1 – завершается с кодом 200 */ exit(200); } /* Продолжение процесса-родителя – порождаем Сhild 2 */ if((pid = fork()) < 0){ printf("Can\'t fork child 2\n"); exit(1); } else if (pid == 0){ /* Child 2 – циклится, необходимо удалять с помощью сигнала! */ while(1); } /* Продолжение процесса-родителя – уходим в цикл */ while(1); return 0; }Листинг 13-14.5. Программа (13—14-5.c) с асинхронным получением информации о статусе двух завершившихся порожденных процессов.
В этой программе родитель порождает два процесса. Один из них завершается с кодом 200, а второй зацикливается. Перед порождением процессов родитель устанавливает обработчик прерывания для сигнала SIGCHLD , а после их порождения уходит в бесконечный цикл. В обработчике прерывания вызывается waitpid() для любого порожденного процесса. Так как в обработчик мы попадаем, когда какой-либо из процессов завершился, системный вызов не блокируется, и мы можем получить информацию об идентификаторе завершившегося процесса и причине его завершения. Откомпилируйте программу и запустите ее на исполнение. Второй порожденный процесс завершайте с помощью команды kill с каким-либо номером сигнала. Родительский процесс также будет необходимо завершать командой kill .