Содержание


Инструменты ОС Linux для разработчиков приложений для ОС Windows. Часть 15. Сигналы

Comments

Сигналы играют огромную роль в операционных системах семейства UNIX, так как без их помощи просто невозможно завершить ни одного процесса в системе (но это выполняет ядро системы и программист может и не подозревать как это происходит). Кроме того, сигналы являются и специфической особенностью Unix, поэтому у программистов, переходящих на Unix с ОС Windows, могут возникнуть трудности при использовании функциональности для работы с сигналами. Поэтому в этой статье мы постараемся разобрать базовые принципы работы с сигналами и возможности POSIX API в этой области.

Типы сигналов

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

$ kill -l
1) SIGHUP			2) SIGINT		3) SIGQUIT
4) SIGILL			5) SIGTRAP		6) SIGABRT
7) SIGBUS			8) SIGFPE		9) SIGKILL
10) SIGUSR1			11) SIGSEGV		12) SIGUSR2
13) SIGPIPE			14) SIGALRM		15) SIGTERM
16) SIGSTKFLT			17) SIGCHLD		18) SIGCONT
19) SIGSTOP			20) SIGTSTP		21) SIGTTIN
22) SIGTTOU			23) SIGURG		24) SIGXCPU	
25) SIGXFSZ			26) SIGVTALRM		27) SIGPROF	
28) SIGWINCH			29) SIGIO		30) SIGPWR
31) SIGSYS			34) SIGRTMIN		35) SIGRTMIN+1
36) SIGRTMIN+2		37) SIGRTMIN+3	38) SIGRTMIN+4
39) SIGRTMIN+5		40) SIGRTMIN+6	41) SIGRTMIN+7
42) SIGRTMIN+8		43) SIGRTMIN+9	44) SIGRTMIN+10
45) SIGRTMIN+11		46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14		49) SIGRTMIN+15	50) SIGRTMAX-14
51) SIGRTMAX-13		52) SIGRTMAX-12	53) SIGRTMAX-11
54) SIGRTMAX-10		55) SIGRTMAX-9	56) SIGRTMAX-8
57) SIGRTMAX-7		58) SIGRTMAX-6	59) SIGRTMAX-5
60) SIGRTMAX-4		61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1		64) SIGRTMAX

Примеры кода, используемые в этой статье, находятся в архиве usignal.tgz в разделе "Материалы для скачивания". Все тестовые программы написаны на языке C++ (для разнообразия) и используют общий заголовочный файл, показанный в листинге 1.

Листинг 1. Общие определения (файл head.h)
#include <iostream>
#include <iomanip>
using namespace std;
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#define _SIGMIN  SIGHUP
#define _SIGMAX  SIGRTMAX

Рассмотрим, как проверить наличие того или иного сигнала в конкретной реализации POSIX ОС:

Листинг 2. Проверка сигналов, присутствующих в системе (файл s1.cc)
#include "head.h"

int main( int argc, char *argv[] ) {
   cout << "SIGNO";
   for( int i = _SIGMIN; i <= _SIGMAX; i++ ) {
      if( i % 8 == 1 ) cout << endl << i << ':';
      int res = sigaction( i, NULL, NULL );
      cout << '\t' << ( ( res != 0 && errno == EINVAL ) ? '-' : '+' );
   };
   cout << endl;
   return EXIT_SUCCESS;
};

Программа проверяет наличие (реализованность) всех сигналов из списка, приведённого выше (в строке выводится 8 последовательных результатов). Как можно заметить, сигналы с номерами 32 и 34 в системе отсутствуют.

$ ./s1
SIGNO
1:	+	+	+	+	+	+	+	+
9:	+	+	+	+	+	+	+	+
17:	+	+	+	+	+	+	+	+
25:	+	+	+	+	+	+	+	-
33:	-	+	+	+	+	+	+	+
41:	+	+	+	+	+	+	+	+
49:	+	+	+	+	+	+	+	+
57:	+	+	+	+	+	+	+	+

Модель ненадёжной обработки сигналов

Устаревшая модель ненадёжной обработки сигналов основывается на вызове signal(), устанавливающем новую диспозицию сигнала (новую функцию-обработчик, или игнорирование SIG_IGN, или восстанавливает SIG_DFL), как показано в листинге 3.

Листинг 3. Установка обработчика сигнала (файл s2.cc)
#include "head.h"

static void handler( int signo ) {
   signal( SIGINT, handler );
   cout << endl << "signal #" << signo << endl;
};

int main() {
   signal( SIGINT, handler );
   signal( SIGSEGV, SIG_DFL );
   signal( SIGTERM, SIG_IGN );
   while( true ) pause();
};

После установки нового обработчика для сигнала SIGINT с номером 2 процесс уже невозможно остановить по ^C, поэтому для остановки приходится отправить уже с другого терминала сигнал SIGKILL с номером 9:

$ ./s2
^C
signal #2
^C
signal #2
...
$ ps -A | grep s2
18364 pts/7    00:00:00 s2
$ kill -9 18364

К этой же модели обработки относится и широко используемый вызов alarm() для установки тайм-аута.

Листинг 4. Использование тайм-аута при обработке сигнала (файл s3.cc)
#include "head.h"

int main( void ) {
    alarm( 5 );
    cout << "Waiting to die in 5 seconds ..." << endl;
    pause();
    return EXIT_SUCCESS;
};

Проверка таймера, установленного на 5 секунд.

$ time ./s3
Waiting to die in 5 seconds ...
Сигнал таймера
real	0m5.002s
user	0m0.000s
sys	0m0.003s

В листинге 4 представлен алгоритм перехвата сигнала завершения процесса для сохранения данных перед фактическим завершением процесса.

Листинг 4. Обработчик события завершения процесса (файл s4 .cc)
#include "head.h"

static void handler( int signo ) {
   cout << endl << "Saving data ... wait" << endl;
   sleep( 2 );
   cout << " ... data saved!" << endl;
   exit( EXIT_SUCCESS );
};

int main() {
   signal( SIGINT, handler );
   while( true ) pause();
};

Модель надёжной обработки сигналов

Модель надёжной обработки сигналов работает на основе новой системы понятий. Эту модель составляют:

  1. Структура для хранения маски сигнала типа sigset_t — по одному биту на каждый представляемый сигнал и набор функций API для заполнения или очистки этой маски:
    int sigemptyset( sigset_t* );
    int sigfillset( sigset_t* );
    int sigaddset( sigset_t*, int signo );
    int sigdelset( sigset_t*, int signo );
  2. Вызов sigprocmask для маскирования реакции на сигнал, который принимает на вход следующие параметры:
    • sigset_t* set - устанавливаемая маска процесса;
    • sigset_t* oset - ранее установленная маска процесса;
    • int how может быть равен следующим константам:
      • SIG_BLOCK — добавить сигналы к сигнальной маске процесса (заблокировать доставку);
      • SIG_UNBLOCK — сбросить сигналы из сигнальной маски процесса (разблокировать доставку);
      • SIG_SETMASK — установить как сигнальную маску процесса;
  3. Структура sigaction, описывающей диспозицию сигнала:
    struct sigaction {
       union {    /* обработчик сигнала.  */
          /* используется когда флаг SA_SIGINFO не установлен. */
          void (*sa_handler) ( int );
         /* используется, когда флаг SA_SIGINFO установлен. */
          void (*sa_sigaction) ( int, siginfo_t*, void*); 
       }
       sigset_t sa_mask; /* список сигналов, которые будут блокироваться. */
       int sa_flags; /* cпециальные флаги. */
    ...
    };

    Маска sa_mask содержит сигналы, которые будут автоматически заблокированы в обработчике сигнала. Возможные значения поля флагов sa_flags:
    • SA_RESETHANG — после срабатывания обработчика сигнала будет восстановлен обработчик по умолчанию (SIG_DFL, что позволяет эмулировать модель ненадёжной обработки сигналов);
    • SA_NOCLDSTOP — используется только для сигнала SIGCHLD и указывает системе не генерировать для родительского процесса SIGCHLD, если дочерний процесс завершается по SIGSTOP;
    • SA_SIGINFO — будет организована очередь доставки сигналов (модель сигналов реального времени), при этом обработчику будет доставляться дополнительная информация о сигнале — структура siginfo_t и дополнительные параметры пользователя (при этом используется другой прототип обработчика — sa_sigaction);
  4. Также имеется функция для установки диспозиции сигналов:
    extern int sigaction( int signo, const struct sigaction* act, 
                                     struct sigaction* oact );

    где act и oact — устанавливаемая и предыдущая (для сохранения в случае необходимости) диспозиции.
Листинг 5. Пример надёжной обработки сигналов (файл s8.cc)
#include "head.h"
void catchint( int signo ) {
   cout << "SIGINT: signo = " << signo << endl;
};

int main() {
   static struct sigaction act = { &catchint, 0, 0 }; /* 0 = (sigset_t)NULL };
   sigfillset( &(act.sa_mask) );
   sigaction( SIGINT, &act, NULL );
   for( int i = 0; i < 20; i++ ) sleep( 1 ), cout << "Cycle # " << i << endl;
};

Проверим работу новых функций-обработчиков

$ ./s8
Cycle # 0
^CSIGINT: signo = 2
Cycle # 1
^CSIGINT: signo = 2
Cycle # 2
^CSIGINT: signo = 2
Cycle # 3
^CSIGINT: signo = 2

Модель обработки сигналов реального времени

Осталось рассмотреть ещё один способ использования сигналов - модель обработки сигналов реального времени, которая уже косвенно затронута выше, так как она определяется флагом SA_SIGINFO в структуре struct sigaction. В листинге 6 родительский процесс посылает дочернему "пакеты" сигналов и завершается, после чего дочерний процесс принимает сигналы. В данном примере хорошо видно, как поочерёдно принимается вся последовательность посланных сигналов.

Листинг 6. Модель обработки сигналов реального времени (файл s5.cc)
#include "head.h"

static void handler( int signo, siginfo_t* info, void* context ) {
   cout << "CHILD\t[" << getpid() << ":" << getppid() << "] : "
        << "received signal " << signo << endl;
};

int main( int argc, char *argv[] ) {
   int opt, val, beg = _SIGMAX, num = 3, fin = _SIGMAX - num, seq = 3;
   bool wait = false;
   while ( ( opt = getopt( argc, argv, "b:e:n:w") ) != -1 ) {
      switch( opt ) {
         case 'b' : if( atoi( optarg ) > 0 ) beg = atoi( optarg ); break;
         case 'e' :
            if( ( atoi( optarg ) != 0 ) && ( atoi( optarg ) < _SIGMAX ) ) 
                fin = atoi( optarg );
            break;
         case 'n' : if( atoi( optarg ) > 0 ) seq = atoi( optarg ); break;
         case 'w' : wait = true; break;
         default :
            cout << "usage: " << argv[ 0 ]
                 << " [-b #signal] [-e #signal] [-n #loop] [-w]" << endl;
            exit( EXIT_FAILURE );
            break;
      }
   };
   num = fin - beg;
   fin += num > 0 ? 1 : -1;
   sigset_t sigset;
   sigemptyset( &sigset );
   for( int i = beg; i != fin; i += ( num > 0 ? 1 : -1 ) ) sigaddset( &sigset, i );
   pid_t pid;
   if( pid = fork() == 0 ) {
      // дочерний процесс: здесь сигналы обрабатываются
      sigprocmask( SIG_BLOCK, &sigset, NULL );
      for( int i = beg; i != fin; i += ( num > 0 ? 1 : -1 ) ) {
         struct sigaction act, oact;
         sigemptyset( &act.sa_mask );
         act.sa_sigaction = handler;
         act.sa_flags = SA_SIGINFO;    // установка модели реального времени!
         if( sigaction( i, &act, NULL ) < 0 ) perror( "set signal handler: " );
      };
      cout << "CHILD\t[" << getpid() << ":" << getppid() << "] : "
           << "signal mask set" << endl;
      sleep( 3 );                      // пауза для отсылки сигналов родителем
      cout << "CHILD\t[" << getpid() << ":" << getppid() << "] : "
           << "signal mask unblock" << endl;
      sigprocmask( SIG_UNBLOCK, &sigset, NULL );
      sleep( 3 );                      // пауза для получения сигналов
      cout << "CHILD\t[" << getpid() << ":" << getppid() << "] : "
           << "finished" << endl;
      exit( EXIT_SUCCESS );
   }
   // родительский процесс: отсюда сигналы посылаются
   sigprocmask( SIG_BLOCK, &sigset, NULL );
   sleep( 1 );                         // пауза для установок дочерним процессом
   for( int i = beg; i != fin; i += ( num > 0 ? 1 : -1 ) ) {
      for( int j = 0; j < seq; j++ ) {
         kill( pid, i );
         cout << "PARENT\t[" << getpid() << ":" << getppid() << "] : "
              << "signal sent: " << i << endl;
      };
   };
   if( wait ) waitpid( pid, NULL, 0 );
   cout << "PARENT\t[" << getpid() << ":" << getppid() << "] : "
        << "finished" << endl;
   exit( EXIT_SUCCESS );
};

Из вывода программы хорошо видно, что к моменту получения сигналов, родительским процессом для получателя уже является процесс init (PID=1), то есть настоящий родительский процесс к этому времени уже завершился.

$ ./s5
CHILD	[20934:20933] : signal mask set
PARENT	[20933:5281] : signal sent: 62
PARENT	[20933:5281] : signal sent: 62
PARENT	[20933:5281] : signal sent: 62
PARENT	[20933:5281] : signal sent: 61
PARENT	[20933:5281] : signal sent: 61
PARENT	[20933:5281] : signal sent: 61
PARENT	[20933:5281] : finished
$
CHILD	[20934:1] : signal mask unblock
CHILD	[20934:1] : received signal 62
CHILD	[20934:1] : received signal 62
CHILD	[20934:1] : received signal 62
CHILD	[20934:1] : received signal 61
CHILD	[20934:1] : received signal 61
CHILD	[20934:1] : received signal 61
CHILD	[20934:1] : finished

Более того, сигналы по схеме реального времени могут отправляться не только вызовом kill(), но вызовом sigqueue(), который позволяет присоединять дополнительные данные к отправляемым сигналам:

$ man sigqueue
...
       #include <signal.h>
      int sigqueue(pid_t pid, int sig, const union sigval value);
...
    union sigval {
               int   sival_int;
               void *sival_ptr;
           };

Обычно указатель sival_ptr и используют для присоединения к сигналу поля данных.

Заключение

В данной статье были бегло рассмотрены различные модели обработки сигналов и способы взаимодействия с ними внутри процессов при помощи POSIX API.

В следующей статье мы продолжим эту тему и поговорим о поддержке потоков в POSIX API и использовании сигналов совместно с потоками.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=969238
ArticleTitle=Инструменты ОС Linux для разработчиков приложений для ОС Windows. Часть 15. Сигналы
publish-date=04222014