SysV vs POSIX — shared memory и message queues обзорно
В Linux исторически сложились две параллельные системы межпроцессной коммуникации: System V IPC (унаследована от Unix System V Release 4, 1989 год) и POSIX IPC (стандартизована позже, ~1990-е). Обе обеспечивают три механизма: shared memory, message queues и semaphores. Но API, идентификаторы, видимость и интеграция с остальным kernel-ом разные.
Это не теоретический выбор — разные реальные приложения используют разные системы. PostgreSQL использует SysV shared memory + semaphores (по историческим причинам). systemd, modern message brokers — POSIX. Иногда вы видите процесс, который застрял на semop() — и нужно понимать, какая это система, чтобы продиагностировать через правильные тулзы (ipcs для SysV, ls /dev/shm для POSIX).
В этом уроке: ключевые различия двух систем, когда какую использовать, message queues (часто незаслуженно забытые) и как их сравнить с современными broker-ами типа Redis/Kafka.
Общая картина: три механизма, две системы
Каждая система предлагает три IPC примитива:
Главное практическое отличие:
- POSIX — string names как ‘/myobj’, объекты видны как файлы, интегрированы с обычными file descriptors, поддерживаются select/poll/epoll. Это «современный Linux» способ.
- SysV — integer keys через ftok, отдельные kernel-структуры, отдельная утилита ipcs для просмотра. Старее, иногда удобнее в legacy-коде или специфических сценариях.
SysV API — ipcs и старая магия
# Посмотреть текущие SysV-объекты системы:
ipcs
# ------ Message Queues --------
# key msqid owner perms used-bytes messages
#
# ------ Shared Memory Segments --------
# key shmid owner perms bytes nattch status
# 0x52454441 32768 postgres 600 144441344 4
#
# ------ Semaphore Arrays --------
# key semid owner perms nsems
# 0x52454441 32768 postgres 600 17
postgres использует SysV shared memory для своих shared buffers. nattch = 4 означает 4 процесса присоединены (4 backend-а).
# Минимальный пример SysV shared memory:
cat > sysv_shm.c << 'CEOF'
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(void) {
// Создаём ключ из пути и проектного id
key_t key = ftok("/tmp", 'a');
// Создаём shared memory сегмент
int shmid = shmget(key, 4096, IPC_CREAT | 0666);
// Присоединяем к адресному пространству
char* shared = shmat(shmid, NULL, 0);
strcpy(shared, "Hello from SysV SHM");
printf("Wrote, key=0x%x, id=%d\n", key, shmid);
sleep(30);
shmdt(shared);
// Можно: shmctl(shmid, IPC_RMID, NULL) -- удалить
return 0;
}
CEOF
gcc sysv_shm.c -o sysv_shm
./sysv_shm &
sleep 1
ipcs -m
# Видите свой сегмент с key 0x...
# Через 30 сек сегмент останется (мы не сделали IPC_RMID)
ipcrm shm <shmid> # удалить вручную
Ключевая особенность SysV: объекты живут в kernel независимо от процессов. Даже если все процессы умерли, shm-сегмент висит, пока кто-то явно не вызовет ipcrm. Это создаёт проблему «утечки» SysV-объектов в системе — их находят через ipcs и чистят.
POSIX API — знакомый
POSIX shared memory мы уже видели в прошлом уроке — через shm_open и mmap. Объекты живут в /dev/shm.
POSIX message queues — менее известный, но мощный механизм:
cat > pmq_sender.c << 'CEOF'
#include <mqueue.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(void) {
struct mq_attr attr = {
.mq_maxmsg = 10,
.mq_msgsize = 256
};
mqd_t mq = mq_open("/my_queue", O_CREAT | O_WRONLY, 0666, &attr);
for (int i = 0; i < 5; i++) {
char msg[64];
snprintf(msg, sizeof(msg), "Message #%d", i);
mq_send(mq, msg, strlen(msg) + 1, /*prio*/ 0);
printf("Sent: %s\n", msg);
}
mq_close(mq);
return 0;
}
CEOF
cat > pmq_receiver.c << 'CEOF'
#include <mqueue.h>
#include <stdio.h>
int main(void) {
mqd_t mq = mq_open("/my_queue", O_RDONLY);
char buf[256];
unsigned int prio;
for (int i = 0; i < 5; i++) {
ssize_t n = mq_receive(mq, buf, sizeof(buf), &prio);
printf("Got [prio %u]: %s\n", prio, buf);
}
mq_close(mq);
mq_unlink("/my_queue");
return 0;
}
CEOF
gcc pmq_sender.c -o pmq_sender -lrt
gcc pmq_receiver.c -o pmq_receiver -lrt
./pmq_sender
./pmq_receiver
# Sent: Message #0..4
# Got: Message #0..4
# Удалить очередь:
# mq_unlink делается в receiver, но если процессы упали:
ls /dev/mqueue
# my_queue <-- висит, можно: rm /dev/mqueue/my_queue
POSIX message queues видны через /dev/mqueue (нужно mount -t mqueue none /dev/mqueue, в современных Linux обычно уже смонтировано).
Message queues vs Redis/Kafka
«У меня уже есть Redis/Kafka/RabbitMQ. Зачем мне POSIX MQ?» Хороший вопрос. Сравнение:
POSIX MQ занимает уникальную нишу: ультра-низкая latency для same-machine IPC без overhead-а network broker-а. Используется в high-frequency trading, embedded системах, real-time контроллерах.
Для типичных микросервисов на k8s — забудьте про SysV/POSIX MQ, используйте полноценные брокеры. Но знать, что они есть, полезно: на одной машине sometimes мало 100 мс latency Kafka.
Kafka в compose: реальный message broker для DEКогда какую использовать
Практические рекомендации:
Используйте POSIX shm, когда:
- Передача больших бинарных данных между процессами одной машины.
- Нужна интеграция с mmap и file descriptors.
- Пишете новый код.
Используйте SysV shm, когда:
- Поддерживаете legacy-код, который её использует.
- Нужны точные семантики SEM_UNDO для семафоров (автоматический rollback при смерти процесса).
- Работаете с базами данных (PostgreSQL, Oracle) — они используют SysV.
Используйте POSIX MQ, когда:
- Локальный IPC с сообщениями (не байт-потоком).
- Микросекундная latency между процессами одной машины.
- Простота API важнее features.
Используйте unix socket вместо MQ, когда:
- Уже знакомы с socket API.
- Нужен byte stream.
- Хотите легко перенести на TCP в будущем.
Используйте Redis/Kafka/RabbitMQ, когда:
- Cross-machine.
- Нужна персистентность.
- Несколько consumers.
- Сложные паттерны (pub/sub, fanout, dead letters).
Tunables и лимиты
У SysV есть system-wide лимиты:
# Лимиты SysV в /proc/sys/kernel/:
cat /proc/sys/kernel/shmmax # max size одного segment
# 18446744073709551615 -- ~16 EB на современных Linux
cat /proc/sys/kernel/shmall # max общая память для всех segments в страницах
cat /proc/sys/kernel/msgmax # max размер одного message
cat /proc/sys/kernel/msgmnb # max байт в одной queue
# Изменить:
sysctl -w kernel.shmmax=4G
У POSIX MQ:
# Лимиты POSIX message queues:
cat /proc/sys/fs/mqueue/msg_max # max messages per queue
cat /proc/sys/fs/mqueue/msgsize_max # max message size
cat /proc/sys/fs/mqueue/queues_max # max queues per user
В прошлом эти лимиты были маленькими (256 messages на queue по дефолту), что заставляло многих переходить на сторонние решения. Современные дистрибуции имеют sane defaults.
PostgreSQL до версии 9.2 требовал ручной настройки kernel.shmmax под shared_buffers. С 9.3 PostgreSQL стал использовать комбинацию SysV semaphores + mmap-anonymous shared memory — и больше не упирается в kernel.shmmax. Это уроки эволюции IPC use case.
Попробуй сам
Посмотрите SysV-объекты вашей системы:
# Все SysV IPC:
ipcs
# Только shared memory с детальной инфой:
ipcs -m -p
# покажет creator pid и last access pid для каждого segment
# Удалить осиротевшие объекты (где nattch=0 -- никто не подключён):
# Внимание: можно сломать работающие сервисы!
# Сначала просто посмотреть:
ipcs -m | awk '$6==0 {print "Orphan:", $2}'
POSIX-объекты:
# POSIX shared memory:
ls -la /dev/shm/
# POSIX message queues:
ls -la /dev/mqueue/
# Если /dev/mqueue не смонтирована:
sudo mkdir -p /dev/mqueue
sudo mount -t mqueue none /dev/mqueue
Попробуйте простую POSIX MQ на Python:
pip install posix-ipc
python3 << 'EOF'
import posix_ipc
# Создаём очередь
mq = posix_ipc.MessageQueue('/test_mq', flags=posix_ipc.O_CREAT, max_messages=10, max_message_size=256)
# Отправляем
mq.send(b'Hello via POSIX MQ', priority=5)
mq.send(b'Urgent message', priority=10)
mq.send(b'Low priority', priority=1)
# Получаем (порядок по приоритету: высокий первый)
for _ in range(3):
msg, prio = mq.receive()
print(f'prio={prio}: {msg}')
mq.close()
mq.unlink()
EOF
# Вывод:
# prio=10: b'Urgent message'
# prio=5: b'Hello via POSIX MQ'
# prio=1: b'Low priority'
Priority-based receive — одна из удобных фишек POSIX MQ. Нет аналога в Unix sockets.