Сигналы, очереди и семафоры в Linux: от истоков до ядра и DevOps

Сигналы, очереди сообщений и семафоры – три классических механизма IPC в Linux. Они пришли из UNIX, живут в ядре и до сих пор нужны в контейнерах, демонах, оркестрации и скриптах. Ниже – краткая история, как это устроено в kernel, API System V и POSIX, плюс рабочие примеры. Упор на то, что полезно DevOps и тем, кто пишет системный код, а не только «теория ОС».

История сигналов: от UNIX к Linux

Сигналы появились в UNIX в 1970-х (Ритчи, Томпсон) как способ уведомить процесс о событии: SIGINT, SIGCHLD и другие. UNIX System V (1983) встроил их в общую модель IPC. POSIX в 1980-е добавил сигналы реального времени (диапазон SIGRTMIN … SIGRTMAX). В Linux с 1991 года та же идея: сейчас доступно больше 60 сигналов, доставка и маски живут в структурах задачи.

История очередей сообщений: System V и POSIX

Очереди System V (1983) работают через ключи key_t и структуру msg_queue. POSIX-очереди (POSIX.1b, 1993) видны как объекты в /dev/mqueue: приоритеты, уведомления, удобнее для real-time и современного кода.

История семафоров: от Дейкстры до Linux

Семафоры формализовал Эдсгер Дейкстра в 1965: счётчик и операции P (уменьшить / ждать) и V (увеличить / разбудить). В UNIX System V (1983) они стали частью IPC для синхронизации между процессами. POSIX (1990-е, POSIX.1b) добавил именованные и неименованные семафоры, лучше стык с потоками. В Linux оба мира живут рядом: System V – legacy и сложные наборы, POSIX – проще API и futex под капотом.

Теория: как это выглядит в ядре

Сигналы

  • Жизненный цикл: генерация (kill, sigqueue), очередь sigpending, доставка (get_signal), действие: SIG_DFL, SIG_IGN или свой обработчик.
  • Код ядра: kernel/signal.c.
  • Структуры: signal_struct в task_struct: sigpending, blocked, sigqueue.

Очереди сообщений

System V

  • Хранение: цепочка msg_msg внутри msg_queue.
  • API: msgsnd / msgrcv, выбор по mtype.
  • Код: ipc/msg.c.

POSIX

  • Создание: mq_open(“/name”, flags) и mq_attr (mq_maxmsg, mq_msgsize).
  • Хранение: кольцевой буфер в /dev/mqueue, приоритеты сообщений 0-31.
  • API: mq_send (добавить с приоритетом), mq_receive (забрать с максимальным приоритетом), mq_notify (уведомление через sigevent / сигнал).
  • Код: ipc/mqueue.c.

Семафоры

System V

  • Модель: массив семафоров (sem_array), операции semget, semctl, semop.
  • Поля: semval, sempid, semncnt, semzcnt внутри struct sem.
  • Код: ipc/sem.c. Часто всплывают SEM_UNDO и сложные наборы операций за один semop.

POSIX

  • Модель: один семафор sem_t и простой API.
  • Именованные: sem_open(“/name”, O_CREAT, mode, value), объекты в /dev/shm.
  • Неименованные: sem_init(&sem, pshared, value) в shared memory или куче процесса.
  • API: sem_wait (уменьшить / ждать при 0), sem_post (увеличить), sem_timedwait (с таймаутом).
  • Реализация: kernel/futex.c + glibc.
Схема IPC: сигналы, очереди, семафоры в Linux

Практика: короткие примеры

Сигналы

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void handler(int sig) {
    printf("Сигнал %d\n", sig);
}

int main(void) {
    signal(SIGUSR1, handler);
    printf("PID: %d\n", getpid());
    while (1)
        sleep(1);
}

В другом терминале: kill -USR1 <pid>. Для продакшена обычно берут sigaction вместо signal – предсказуемее поведение и маски.

POSIX-очередь: отправитель

#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>

int main(void) {
    struct mq_attr attr = { .mq_maxmsg = 10, .mq_msgsize = 128 };
    mqd_t mq = mq_open("/myqueue", O_CREAT | O_WRONLY, 0644, &attr);
    mq_send(mq, "High priority", 14, 10);
    mq_close(mq);
    return 0;
}

POSIX-очередь: получатель

#include <mqueue.h>
#include <stdio.h>

int main(void) {
    mqd_t mq = mq_open("/myqueue", O_RDONLY);
    char buf[128];
    unsigned int prio;
    mq_receive(mq, buf, 128, &prio);
    printf("Сообщение: %s, приоритет: %u\n", buf, prio);
    mq_close(mq);
    mq_unlink("/myqueue");
    return 0;
}

Линкуйте с -lrt на старых системах. Лимиты очереди смотрите в /proc/sys/fs/mqueue/.

POSIX-семафор (именованный)

#include <semaphore.h>
#include <fcntl.h>
#include <stdio.h>

int main(void) {
    sem_t *s = sem_open("/mysem", O_CREAT, 0644, 1);
    sem_wait(s);
    /* критическая секция */
    printf("critical\n");
    sem_post(s);
    sem_close(s);
    return 0;
}

Где это встречается в DevOps

  • Сигналы: graceful reload (SIGHUP), останов контейнера (SIGTERM, затем SIGKILL), watchdog-процессы.
  • Очереди: локальный обмен между воркерами без сети, когда shared memory + mq проще, чем брокер.
  • Семафоры: ограничение параллелизма, координация init-скриптов и sidecar-процессов.

В Kubernetes и systemd вы чаще видите сигналы и cgroup-лимиты, чем сырые System V IPC. Но legacy-приложения, Oracle-стек, старые UNIX-утилиты и часть C/C++ сервисов по-прежнему держат msg/sem/shm. ipcs и ipcrm – must-have в чеклисте «почему не освобождается IPC».

Итог

Сигналы управляют жизненным циклом процесса, очереди передают типизированные куски данных, семафоры синхронизируют доступ. System V – массивы, ключи, SEM_UNDO; POSIX – имена, проще API, futex. Для углубления смотрите исходники ядра: signal.c, mqueue.c, sem.c.

Источники и ссылки


Комментарии загружаются…