birmaga.ru
добавить свой файл

1
Лекция 7.

Сигналы ОС UNIX.


Сигналы – механизм, специфичный для всего семейства UNIX. Они дают возможность вызвать какую-либо функцию, которая называется обработчик сигнала при возникновении определенного события. Такие события возникают постоянно. События обозначаются целыми числами или символьными константами.

Сигналы могут быть синхронными или асинхронными (SIGSEGV). Независимо от того, откуда пришел сигнал, все сигналы доставляются процессу через ядро ОС.

Функции сигналов:

1.

2. Они являются механизмом для связи и синхронизации между прикладными процессами.
Процесс оповещения о наступлении события состоит из двух этапов:

  1. Генерация сигнала;

При наступлении события ОС генерирует сигнал: в структуре TASK_STRUCT она взводит соответствующий бит в маске поступивших сигналов.

  1. Обработка сигнала;

После установки бита процесс должен обработать этот сигнал. Процесс не может обработать сигнал мгновенно, он делает это только в определенные моменты времени:

  • из очереди готовых процесс выбирается на выполнение диспетчером (ожидая в очереди, процесс не может получить сигнал);

  • перед блокировкой;

  • во время блокировки, если блокировка является прерываемой (например, ожидание ввода с клавиатуры); если тот же read читает с диска, а не с клавиатуры, то такая блокировка является непрерываемой.

Каждый сигнал обладает действием по умолчанию. Это действие выполняется ОС, если процесс не установит для этого сигнала другое действие. Всего существуют 5 действий по умолчанию:

  1. аварийное завершение с дампом памяти (D);

  2. завершение без дампа (A);

  3. игнорирование сигнала (I);

  4. остановка (процесс может быть приостановлен) (S);
  5. продолжение (С)



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

  • SIGKILL (это всегда снятие – реакция 2)

  • SIGSTOP – перевод процесса в приостановленное состояние.


Альтернативы:

  • Игнорировать сигнал;

  • Определить свой обработчик сигнала.


В программе для одного и того же сигнала можно неоднократно переопределять действия. Процесс также может временно блокировать некоторые сигналы – команда sigprocmask(). Тогда сигналы не будут временно доставляться процессу до тех пор, пока они не будут разблокированы той же командой. Блокировать и разблокировать в программе можно неоднократно.

Рассмотрим, какой набор сигналов может поддерживать каждый UNIX. В стандарте POSIX определены 20 сигналов, поддержка которых является обязательной (номера этих сигналов могут различаться в разных ОС). Второй столбец – действие по умолчанию:


SIGINT

A

прерывание от терминала Ctrl + C

SIGILL

D

исключение, неверная команда (ее можно перехватить и обработать своим обработчиком)

SIGFPE

D

исключение с плавающей точкой

SIGKILL

A

снятие (завершение) процесса

SIGUSER1

A

пользовательский сигнал 1

SIGUSER2


A

пользовательский сигнал 2

SIGALARM

I

будильник

SIGCHLD

I

этот сигнал посылается родителю, когда завершился или остановлен потомок.

SIGSTOP

S

остановить процесс (этот сигнал нельзя переопределить)

SIGCONT

C

продолжить процесс


В программе чтобы определить действие, которым необходимо обработать сигнал, нужно выполнить следующее:

1. написать обработчик сигнала по следующему шаблону:

void myhand (int signum) {…};

2. зарегистрировать эту функцию с помощью системного вызова

#include

int sigaction(int signnum,const struct sigaction * sigact,

struct sigaction * oldact);

0 – OK

!=0 - ошибка

Второй параметр – структура, содержащая следующие поля:

  • sa_handler – имя функции обработчика, либо константы SIG_IGN (игнорирование сигнала), SIG_DFL – восстановление действия по умолчанию.

  • sa_mask – блокируемые сигналы – сигналы, которые не должны прерывать работу обработчика.

  • sa_flags – определяет последовательность обработки сигналов.

В третьем параметре запоминаются предыдущие действия для этого сигнала (чаще всего, это поле = NULL).

Вызовы этой функции можно производить многократно.
Пример:

#include

#include

#include

#include

// будем обрабатывать сигналы прерывания с клавиатуры;

// стандартный обработчик – снятие программы;
void hand(int s)

{

// будем просто выводить номер прерывания

printf(“Получен сигнал прерывания”);
// на самом деле, эту функцию нельзя использовать

// в обработчике; все, что можно – описаны в стандарте POSIX

}
int main(void)

{

struct sigaction act;

// по правилам, сначала структуру нужно обнулить

memset(&act,a,sizeof(act));

act.sa_handler = hand; // адрес функции – это ее имя

if (sigaction(SIGINT, &act, 0))

{

perror(“Сообщение об ошибке\n”);

};

else;

// продолжение работы.
return 0;

}
Можно сделать так:

Signal (SIGINT, hand);

Signal (SIGINT,SIG_JGN,SIG_DFL);

Посылка процессу сигнала.


int kill (pid_t pid, int sig); // послать сигнал процессу

  • возвращаемое значение 0 – ок, < 0 – ошибка.

  • Multicast pid<1 - сигнал будет послан всем процессам с номером pid=1

  • для групповой рассылки можно использовать pid=0, послать сигнал своей группе процессов.

  • если послать pid=-1 , то пошлется сигнал всем процессам компьютера.

Посылка сигнала родительскому процессу.


Определим обработчик сигнала:

Void hand(int s)

{

printf(“SISUSR1 от потомка\n”);

wait(); // ждет завершения дочернего процесса

}

int main()

{

int pid;

signal(SIGUSER1,hand);

return 0;

pid=fork();//создаем родительский процесс

if(pid==0)

{

kill(getppid(),SIGUSR1);

exit(0);

}

}

Послать сигнал группе дочерних процессов

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


2 способ. То же самое можно сделать с помощью групповой рассылки, используя группы процессов. Помимо идентификатора, процесс имеет идентификатор группы. Он наследует его от своего родителя, но может его себе поменять, выйдя таким образом выйдя из родительской группы, а его потомки уже унаследуют другой номер группы.
Общая функция для установки номера группы называется:

Int setpgid(pid_t pid, pid_t pgid);

Pid – для какого процесса устанавливаем

Pgid – номер группы
Setpgid(0,0)

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

setpgrp()=setpgid(0,0),

она выполняет ту же функцию.

Setpgrp();

For (i=0; i<10; i++)

{

if (fork()==0) // к Child

{

if(i&1) setpgrp(); // сбрасываем номер группы
printf(“pid=%d gpid=%d\n”,getpid(),getpgrp());

}

else // Parent

{

kill(0,SIGINT); // будет действие по умолчанию, все //дочерние процессы снимутся.

//во время обработки сигнала, аналогичные типы //сигнала блокируются

}

}

Если одновременно придет несколько сигналов одного типа, то не все будут обработаны, некоторые потеряются, из-за блокировки во время удаления одного сигнала. Современные системы unix используют режим реального времени, строя очередь из сигналов.