EnglishРусский Map

Notify-and-Pull IPC

title
Notify-and-Pull IPC
type
concept
summary
Паттерн, сочетающий синхронную передачу сообщений с неблокирующими уведомлениями для защиты от взаимных блокировок между серверами
tags
ipc, microkernel, concurrency, operating-systems
created
2026-05-06
updated
2026-05-06
lang
ru
translation_of
notify-and-pull-ipc
source_updated
2026-05-06
translated
2026-09-01
translator
lllm/antigravity/gemini-3.7-flash-medium

У синхронного IPC есть ощутимые преимущества - детерминированное поведение, отсутствие выделения очередей на стороне ядра, защита от DoS через переполнение очередей и более простая отладка. Проблема возникает в тот момент, когда двум процессам-серверам нужно отправить сообщения друг другу. Каждый отправитель блокируется в ожидании, пока другой перейдёт в состояние приёма, и ни один из них туда так и не попадает. В заметке Сэйи Нуты (Seiya Nuta) microkernel-ipc-design приводится классический пример с сервером TCP/IP и драйвером Ethernet: запросы на передачу (TX) блокируются в ожидании приёма драйвером, а доставка принятых пакетов (RX) блокируется в ожидании приёма стеком TCP/IP - взаимная блокировка.

Решение, к которому сходятся микроядра с синхронным IPC (Resea, Hubris, FTL), - это второй, ограниченный асинхронный канал, называемый уведомлением (notification). Уведомление не несёт полезной нагрузки и не имеет счётчика - это просто один булев флаг на mailbox. Установка флага никогда не блокирует поток. Получатель при следующем вызове ipc_receive получает либо обычное сообщение, либо статус NOTIFIED. Отправитель уведомления никак не может узнать, сколько раз он его отправил; контракт сводится к простому сигналу: "соседний процесс хочет, чтобы ты что-то сделал, приди и спроси".

Паттерн целиком выглядит так:

  1. Сервер (например, драйвер Ethernet) получает данные (например, прерывание с пакетом RX).
  2. Он помещает данные в свой внутренний буфер.
  3. Он вызывает ipc_notify(client_mbox) - без блокировки.
  4. Клиент (например, сервер TCP/IP) в конечном счёте просыпается от ipc_receive со статусом NOTIFIED.
  5. Клиент отправляет обычный синхронный запрос RECEIVE_PACKET_MSG.
  6. Сервер синхронно отвечает, передавая буферизованные данные.

Именно асимметрия позволяет избежать взаимной блокировки: этап уведомления (notify) асинхронен (и тривиально мал - один бит состояния), а этап вытягивания (pull) представляет собой обычный синхронный обмен запросом и ответом. Что критически важно, для этого система должна чётко разделять роли клиента и сервера для каждого канала, даже если оба процесса в каком-то другом смысле являются серверами. Уведомление идёт в одну сторону, а синхронный запрос - в противоположную.

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

Связь с другими паттернами

Pull-based IPC (см. microkernel-ipc-design §"Pull, not Push") служит естественным дополнением на уровне полезной нагрузки: клиент запрашивает данные только тогда, когда готов, а очередь сервера ограничена тем, что помещается в его собственный буфер, а не скоростью, с которой ядро готово принимать сообщения в очередь. В совокупности notify-and-pull и pull-based подход дают полностью синхронный путь передачи данных с однобитным асинхронным пробуждением.

Аналог в Linux - это приблизительно eventfd плюс синхронное чтение из отдельного файлового дескриптора: ядро сигнализирует о готовности, а пространство пользователя вычитывает байты. io_uring развивает ту же идею с помощью явных очередей завершения. Эта схема воспроизводится снова и снова, потому что управлять обратным давлением (backpressure) гораздо проще, когда извлечением из очереди управляет получатель.

Это одно из точечных исправлений типовых сбоев, которые системы обмена сообщениями надстраивают над базовой передачей сообщений, что перекликается с тезисом из message-passing-vs-shared-memory: передача сообщений не устраняет ошибки конкурентности, а лишь меняет их названия.

См. также