|
организовать двустороннюю поочередную связь процесса-родителя и процесса-ребенка через 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 .

