signalfd(2) создаёт файловый дескриптор для приёма сигналов

ОБЗОР

#include <sys/signalfd.h>

int signalfd(int fd, const sigset_t *mask, int flags);

ОПИСАНИЕ

Вызов signalfd() создаёт файловый дескриптор, который можно использовать для приёма сигналов, предназначенных вызывающему. Его можно использовать как замену обработчику сигналов или sigwaitinfo(2); преимущество в том, что за файловым дескриптором можно следить с помощью select(2), poll(2) и epoll(7).

В аргументе mask указывается набор сигналов, который вызывающий хочет принимать через файловый дескриптор. Этот аргумент, содержащий набор сигналов, можно инициализировать с помощью макросов, описанных в sigsetops(3). Обычно, набор сигналов, принимаемых через файловый дескриптор, должен блокироваться с помощью sigprocmask(2), чтобы предотвратить обработку сигналов назначенными им обработчиками по умолчанию. Через файловый дескриптор signalfd нельзя получить сигнал SIGKILL или SIGSTOP; при указании их в mask они просто игнорируются.

Если значение аргумента fd равно -1, то вызов создаёт новый файловый дескриптор и связывает с ним набор сигналов, указанный в mask. Если fd не равно -1, то в нём должен быть указан допустимый существующий файловый дескриптор signalfd, а значение mask используется для замены набора сигналов, связанного с этим файловым дескриптором.

Начиная с Linux 2.6.27, для изменения поведения signalfd() можно использовать следующие значения flags (через OR):

SFD_NONBLOCK
Устанавливает флаг состояния файла O_NONBLOCK для нового открытого файлового дескриптора. Использование данного флага заменяет дополнительные вызовы fcntl(2) для достижения того же результата.
SFD_CLOEXEC
Устанавливает флаг close-on-exec (FD_CLOEXEC) для нового открытого файлового дескриптора. Смотрите описание флага O_CLOEXEC в open(2) для того, чтобы узнать как это может пригодиться.

До версии Linux 2.6.26 аргумент flags не использовался, и должен быть равен нулю.

Вызов signalfd() возвращает файловый дескриптор, который поддерживает следующие операции:

read(2)
Если один или несколько сигналов, указанных в mask, ожидают обработки, то буфер, указанный в read(2), используется для возврата одной или нескольких структур signalfd_siginfo (см. ниже), описывающих сигналы. Вызов read(2) возвращает информацию о всех ожидающих сигналах, которые поместились в предоставленный буфер. Размер буфера должен быть не менее sizeof(struct signalfd_siginfo) байт. Возвращаемое read(2) значение представляет собой общее количество прочитанных байт.
После выполнения read(2) сигналы считаются учтёнными, они больше не считаются ожидающими обработки (т.е., они не будут переданы обработчикам сигналов и не могут быть приняты с помощью sigwaitinfo(2)).
Если ни один из сигналов из mask не ожидает обработки, то вызов read(2) или блокируется до поступления сигналов согласно mask, или завершается с ошибкой EAGAIN, если файловый дескриптор помечен как неблокируемый.
poll(2), select(2) (и подобные)
Файловый дескриптор доступен для чтения (в select(2) аргумент readfds; в poll(2) флаг POLLIN), если один или более сигналов из mask ожидают обработки.
Файловый дескриптор signalfd также поддерживает другие мультиплексные вызовы: pselect(2), ppoll(2) и epoll(7).
close(2)
Если файловый дескриптор больше не требуется, его нужно закрыть. Когда все файловые дескрипторы, связанные с одним объектом signalfd, будут закрыты, ядро освобождает ресурсы объекта.

Структура signalfd_siginfo

Формат структур(ы) signalfd_siginfo, возвращаемых read(2) из файлового дескриптора signalfd, имеет следующий вид:
struct signalfd_siginfo {
    uint33_t ssi_signo;   /* номер сигнала */
    int33_t  ssi_errno;   /* номер ошибки (не используется) */
    int32_t  ssi_code;    /* код сигнала */
    uint32_t ssi_pid;     /* PID отправителя */
    uint32_t ssi_uid;     /* реальный UID отправителя */
    int32_t  ssi_fd;      /* файловый дескриптор (SIGIO) */
    uint32_t ssi_tid;     /* ID таймера ядра (таймеры POSIX)
    uint32_t ssi_band;    /* внутреннее событие (SIGIO) */
    uint32_t ssi_overrun; /* счётчик переполнений таймера POSIX */
    uint32_t ssi_trapno;  /* номер ловушки, поймавшей сигнал */
    int32_t  ssi_status;  /* код выхода или сигнала (SIGCHLD) */
    int32_t  ssi_int;     /* целое, посланное sigqueue(3) */
    uint64_t ssi_ptr;     /* указатель, посланный sigqueue(3) */
    uint64_t ssi_utime;   /* пользовательское потреблённое
                             время ЦП (SIGCHLD) */
    uint64_t ssi_stime;   /* системное потреблённое
                             время ЦП (SIGCHLD) */
    uint64_t ssi_addr;    /* сгенерированный сигналом адрес
                              (для сигналов аппаратуры) */
    uint8_t  pad[X];      /* заполнитель до 128 байт (для
                              будущих дополнительных полей) */
};
Каждое из полей в этой структуре аналогично полям с тем же именем в структуре siginfo_t. Структура siginfo_t описана в sigaction(2). Не все поля в возвращаемой структуре signalfd_siginfo будут заполнены правильно для каждого сигнала; набор допустимых полей можно определить по значению, возвращённому в поле ssi_code. Это поле является аналогом поля si_code в siginfo_t; подробней смотрите в sigaction(2).

Поведение при fork(2)

После вызова fork(2) потомок наследует копию файлового дескриптора signalfd. Вызов read(2) для файлового дескриптора в потомке вернёт информацию о сигналах для потомка.

Семантика передачи файлового дескриптора

Как и другие файловые дескрипторы, файловые дескрипторы signalfd можно передавать в другой процесс через доменный сокет UNIX (смотрите unix(7)). В принимающем процессе вызов read(2) из принятого файлового дескриптора возвратит информацию о сигналах в очереди этого процесса.

Поведение при execve(2)

Как и любой файловый дескриптор, файловый дескриптор signalfd остаётся открытым после execve(2), если он не помечен как close-on-exec (см. fcntl(2)). Все сигналы, которые были доступны для чтения перед execve(2), остаются доступными и для новой загруженной программы (аналогично обычному поведению сигналов, когда блокированный сигнал, ожидающий обработки, остаётся в очереди ожидания после execve(2)).

Поведение в нитях

Поведение файловых дескрипторов signalfd в многонитиевых программах отражает стандартное поведение сигналов. Иначе говоря, когда нить выполняет чтение из файлового дескриптора signalfd, она прочтёт сигналы, которые предназначены самой нити и сигналы, предназначенные процессу (т.е., всей группе нитей). Нить не может прочитать сигналы, которые предназначены другим нитям процесса.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

При успешном выполнении signalfd() возвращает файловый дескриптор signalfd; это будет или новый файловый дескриптор (если fd равно -1), или fd, если fd содержит допустимый файловый дескриптор signalfd. При ошибке возвращается -1, а errno присваивается соответствующее значение.

ОШИБКИ

EBADF
Неправильный файловый дескриптор в fd.
EINVAL
Значение fd не является правильным файловым дескриптором signalfd.
EINVAL
Неправильное значение flags или, для Linux 2.6.26 и старее, flags не равно 0.
EMFILE
Было достигнуто ограничение по количеству открытых файловых дескрипторов на процесс.
ENFILE
Достигнуто максимальное количество открытых файлов в системе.
ENODEV
Не удалось смонтировать (внутреннее) безымянное устройство inode.
ENOMEM
Недостаточно памяти для создания нового файлового дескриптора signalfd.

ВЕРСИИ

Вызов signalfd() доступен в Linux, начиная с ядра 2.6.22. Поддержка в glibc появилась в версии 2.8. Системный вызов signalfd4() (см. ЗАМЕЧАНИЯ) доступен в Linux, начиная с ядра 2.6.27.

СООТВЕТСТВИЕ СТАНДАРТАМ

Вызовы signalfd() и signalfd4() есть только в Linux.

ЗАМЕЧАНИЯ

Процесс может создать несколько файловых дескрипторов signalfd. Это позволяет принимать различные сигналы через различные файловые дескрипторы (может быть полезно при слежении за файловым дескриптором с помощью select(2), poll(2) или epoll(7): прибытие различных сигналов делает готовым различные файловые дескрипторы). Если сигнал указан в mask для нескольких файловых дескрипторов, то появление этого сигнала можно прочесть (однократно) из любого файлового дескриптора.

Отличия между библиотекой C и ядром

Лежащий в основе системный вызов Linux требует дополнительного аргумента, size_t sizemask, в котором указывается размер аргумента mask. В обёрточной функции glibc signalfd() нет этого аргумента — требуемое для системного вызова значение добавляется библиотекой.

Существуют два системных вызова Linux: signalfd() и более новый signalfd4(). В первом системном вызове не реализован аргумент flags. Во втором системном вызове реализованы значения flags, описанные ранее. Начиная с glibc 2.9, обёрточная функция signalfd() использует signalfd4(), если он доступен.

ДЕФЕКТЫ

В ядрах до версии 3.6.25, поля ssi_ptr и ssi_int не заполнялись данными, поступающими при посылке сигнала с помощью sigqueue(3).

ПРИМЕР

Программа, представленная далее, принимает сигналы SIGINT и SIGQUIT через файловый дескриптор signalfd. Она завершает работу при приёме сигнала SIGQUIT. Вот сеанс работы в оболочке, демонстрирующий использование программы:
$ ./signalfd_demo
^C                   # Control-C генерирует SIGINT
Получен SIGINT
^C
Получен SIGINT
^\                    # Control-\ генерирует SIGQUIT
Получен SIGQUIT
$

Исходный код программы

#include <sys/signalfd.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)
int
main(int argc, char *argv[])
{
    sigset_t mask;
    int sfd;
    struct signalfd_siginfo fdsi;
    ssize_t s;
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGQUIT);
    /* Заблокировать сигналы для того, чтобы они не обрабатывались
       их обработчиками по умолчанию */
    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
        handle_error("sigprocmask");
    sfd = signalfd(-1, &mask, 0);
    if (sfd == -1)
        handle_error("signalfd");
    for (;;) {
        s = read(sfd, &fdsi, sizeof(struct signalfd_siginfo));
        if (s != sizeof(struct signalfd_siginfo))
            handle_error("read");
        if (fdsi.ssi_signo == SIGINT) {
            printf("Получен SIGINT\n");
        } else if (fdsi.ssi_signo == SIGQUIT) {
            printf("Получен SIGQUIT\n");
            exit(EXIT_SUCCESS);
        } else {
            printf("Прочитан неожидаемый сигнал\n");
        }
    }
}