ловить сигналы при чтении из канала с помощью select () - PullRequest
5 голосов
/ 06 августа 2011

используя select () с pipe - это то, что я делаю, и теперь мне нужно поймать SIGTERM на этом. как мне это сделать? Должен ли я сделать это, когда select() возвращает ошибку (<0)? </p>

Ответы [ 3 ]

16 голосов
/ 06 августа 2011

Во-первых, SIGTERM будет убивать ваш процесс, если не будет перехвачен, а select() будет не возвращаться. Таким образом, вы должны установить обработчик сигнала для SIGTERM. Сделайте это, используя sigaction().

Однако сигнал SIGTERM может прийти в тот момент, когда ваш поток не заблокирован на select(). Это было бы редким условием, если ваш процесс в основном спит на файловых дескрипторах, но в противном случае это может произойти. Это означает, что либо ваш обработчик сигнала должен что-то сделать для информирования основной процедуры о прерывании, а именно, установить некоторую переменную флага (типа sig_atomic_t), либо вы должны гарантировать, что SIGTERM доставляется только тогда, когда процесс находится в спящем режиме. select().

Я пойду с последним подходом, поскольку он проще, хотя и менее гибок (см. Конец поста).

Таким образом, вы блокируете SIGTERM непосредственно перед вызовом select() и снова блокируете его сразу после возврата из функции, так что ваш процесс получает сигнал только во время сна внутри select(). Но обратите внимание, что это на самом деле создает условия гонки. Если сигнал поступает сразу после разблокировки, но непосредственно перед вызовом select(), системный вызов еще не будет вызван и, следовательно, он не вернет -1. Если сигнал приходит сразу после успешного возврата select(), но перед повторной блокировкой, вы также потеряете сигнал.

Таким образом, вы должны использовать pselect() для этого. Это делает блокировку / разблокировку вокруг select() атомарно.

Сначала блокируйте SIGTERM, используя sigprocmask(), прежде чем войти в цикл pselect(). После этого просто позвоните pselect() с исходной маской, возвращенной sigprocmask(). Таким образом вы гарантируете, что ваш процесс будет прерван только во время сна на select().

В итоге:

  1. Установить обработчик для SIGTERM (который ничего не делает);
  2. Перед входом в цикл pselect(), заблокируйте SIGTERM, используя sigprocmask();
  3. Вызовите pselect() со старой маской сигнала, возвращенной sigprocmask();
  4. Внутри цикла pselect() теперь вы можете безопасно проверить, вернулись ли pselect() -1 и errno EINTR.

Обратите внимание, что если после успешного возврата pselect() вы проделаете большую работу, у вас может возникнуть большая задержка при ответе на SIGTERM (поскольку процесс должен выполнить всю обработку и вернуться к pselect() перед фактической обработкой сигнал). Если это проблема, вы должны использовать переменную flag внутри обработчика сигнала, чтобы вы могли проверять эту переменную в ряде конкретных точек в вашем коде. Использование переменной-флага не устраняет условия гонки и не устраняет необходимость в pselect().

Помните: всякий раз, когда вам нужно подождать некоторых файловых дескрипторов или для доставки сигнала, вы должны использовать pselect() (или ppoll(), для систем, которые его поддерживают).

Редактировать: Нет ничего лучше, чем пример кода, иллюстрирующий использование.

#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <unistd.h>

// Signal handler to catch SIGTERM.
void sigterm(int signo) {
    (void)signo;
}

int main(void) {
    // Install the signal handler for SIGTERM.
    struct sigaction s;
    s.sa_handler = sigterm;
    sigemptyset(&s.sa_mask);
    s.sa_flags = 0;
    sigaction(SIGTERM, &s, NULL);

    // Block SIGTERM.
    sigset_t sigset, oldset;
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGTERM);
    sigprocmask(SIG_BLOCK, &sigset, &oldset);

    // Enter the pselect() loop, using the original mask as argument.
    fd_set set;
    FD_ZERO(&set);
    FD_SET(0, &set);
    while (pselect(1, &set, NULL, NULL, NULL, &oldset) >= 0) {
        // Do some processing. Note that the process will not be
        // interrupted while inside this loop.
        sleep(5);
    }

    // See why pselect() has failed.
    if (errno == EINTR)
        puts("Interrupted by SIGTERM.");
    else
        perror("pselect()");
    return EXIT_SUCCESS;
}
4 голосов
/ 06 августа 2011

Ответ частично содержится в одном из комментариев в вопросах и ответах, на которые вы указываете;

> Прерывание приведет к тому, что select () вернет -1, а для errno установлено значение EINTR

То есть;для любого пойманного прерывания (сигнала) выбор вернется, и для errno будет установлено значение EINTR.

Теперь, если вы специально хотите перехватить SIGTERM, вам нужно установить это с помощью вызова signal, вот так;

signal(SIGTERM,yourcatchfunction);

где ваша функция catch должна быть определена примерно так:

void yourcatchfunction(int signaleNumber) { .... }

Итак, в итоге вы настроили обработчик сигнала yourcatchfunction и ваша программа в данный момент находится вselect() вызов, ожидающий ввода-вывода - когда поступит сигнал, будет вызвана ваша функция перехвата, и когда вы вернетесь с этого, вызов select вернется с значением errno, равным EINTR.

Однако следует помнить, что SIGTERMможет произойти в любое время , поэтому вы можете не быть в вызове выбора, когда это происходит, в этом случае вы никогда не увидите EINTR, а только обычный вызов yourcatchfunction

Следовательно, select (), возвращаемый с err и errno EINTR, предназначен для того, чтобы вы могли выполнять неблокирующее действие - это не то, что перехватит сигнал.

1 голос
/ 06 августа 2011

Вы можете позвонить select() в цикле. Это известно как перезапуск системного вызова. Вот немного псевдо-C.

int retval = -1;
int select_errno = 0;

do {
   retval = select(...);

   if (retval < 0)
   {
       /* Cache the value of errno in case a system call is later
        * added prior to the loop guard (i.e., the while expression). */
       select_errno = errno;
   }

   /* Other system calls might be added here.  These could change the
    * value of errno, losing track of the error during the select(),
    * again this is the reason we cached the value.  (E.g, you might call
    * a log method which calls gettimeofday().) */

/* Automatically restart the system call if it was interrupted by
 * a signal -- with a while loop. */
} while ((retval < 0) && (select_errno == EINTR));

if (retval < 0) {
   /* Handle other errors here. See select man page. */
} else {
   /* Successful invocation of select(). */
}

...