Во-первых, 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()
.
В итоге:
- Установить обработчик для
SIGTERM
(который ничего не делает);
- Перед входом в цикл
pselect()
, заблокируйте SIGTERM
, используя sigprocmask()
;
- Вызовите
pselect()
со старой маской сигнала, возвращенной sigprocmask()
;
- Внутри цикла
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;
}