LINUX.ORG.RU

Simplerpc: Простой RPC на Си для Linux-приложений

 , ,


0

3

Есть классическая проблема: изолированные процессы — хорошо для надёжности, но хочется иногда позвать функцию из соседа, как будто она лежала в той же библиотеке. Стандартные IPC — это всегда протокол: описал интерфейс, сгенерировал стабы, поднял сервер, прописал адреса. simplerpc предлагает путь короче: ты пишешь обычную функцию на Си, линкуешь библиотеку — и она автоматически становится RPC-методом, доступным другим процессам на той же машине.

Терминал один: ./log. Терминал два: ./clc , Терминал три: ./app . В выводе первого появляется LOG("Hello, world, from app! my pid=..."). Вызовы log_write("Hello") и calc_add(1, 2) в app — обычные С-функции, но выполняются они в процессах log и clc . Вся сериализация параметров функций скрыта в X-макросах, вручную это делать не нужно. Для ипользования в своих приложениях: подключить в свои исходники #include "libsrpc.h", описать прототипы экспортируемых функций в ./src/libsrpc_rpc_functions.h (это часть исходников самой библиотеки, а не отдельный публичный API-заголовок), пересобрать libsrpc.so и слинковать её в своё приложение с флагом -Wl,--no-as-needed . Подробнее — в ./example.

Архитектура держится на трёх решениях. Во-первых, данные едут не через сокет, а по разделяемой памяти — SHMEM. Аргументы вызова лежат в общем сегменте, отображённом по одному виртуальному адресу во всех процессах, и копирования между адресными пространствами нет. Сокет используется только для сигналов: регистрация функций, передача дескриптора памяти (SCM_RIGHTS), обнаружение смерти клиента. Во-вторых, свой аллокатор — транзакционный TLSF с откатом при падении процесса. Межпроцессный мьютекс встроен прямо в структуру пула, а если владелец мьютекса погибает, следующий автоматически откатывает незавершённую транзакцию. Пул консистентен без внешнего сторожа. В-третьих, вместо стандартных примитивов синхронизации — POSIX-семафоры в разделяемой памяти и lock-free MPMC-очередь для передачи задач от клиента к воркерам.

Но самое интересное — как демон оказывается в системе. Исполняемый файл демона не лежит на диске. Он встроен в libsrpc.so в виде C-массива, записывается в анонимный memfd целиком в оперативной памяти и запускается через fexecve(). При загрузке библиотеки любым приложением автоматически форкается демон. Если процессов несколько — проигравший гонку bind() на абстрактном Unix-сокете молча завершается. Когда последний клиент отключается — демон выходит, а память исчезает вместе с последней ссылкой на memfd. Никакого PID-файла, никакого init-скрипта, никакого сервиса. Библиотека сама себя разворачивает.

Библиотека организована так: guard daemon (фоновый координатор), транспорт (Unix-сокеты и SCM_RIGHTS), аллокатор TLSF с транзакциями, сборщик мусора в разделяемой памяти с hazard pointers, RPC через X-макросы (один файл — единственный источник истины), динамическая линковка через weak alias, дескрипторы процессов и потоков, lock-free очереди и синхронизация. Всё это — в bench/ можно найти бенчмарки, если захотите сравнить сами.

Попробовать три команды: mkdir build && cd build && cmake .. && make, затем в одном терминале ./log, в другом ./app. Готово.

Ограничения: только Linux, только локальные процессы на одной машине, ранняя стадия развития — API стабильностью не отличается. Функции с переменным числом аргументов не поддерживаются, указатели осмыслены только если адресуют разделяемый пул.

Репозиторий: github, лицензия Apache-2.0. Если интересно — смотрите код на Си.



Проверено: hobbit ()
Последнее исправление: hobbit (всего исправлений: 10)

memory_order_release

А ты хорошо разбираешься в тонкостях acquire/release, чтоб не было гонок? :)

dynamic_cast
()

Сколько возни ради решения ненужной задачи. Подозреваю что ещё и с проблемами.

firkax ★★★★★
()

Четыре тыщи строк? А куда столько?

BruteForce ★★★★
()
/* TZ: DeepSeek (Chat)
 * TZ fix: Claude Sonnet 5 Medium (Chat)
 * Implementation & fix TZ: MiMo V2.5 Free (Agent) */

Новости нужен тег [вайбкодинг].

shdown ★★
()

Емнип обычно это через сокеты было, а очередь/протокол потом быстренько переносил в shmem если надо, всё похоже было... Но тут пришёл дипсик!

Shadow ★★★★★
()
Ответ на: комментарий от Shadow

Но тут пришёл дипсик!

Взял вашу разработку, и оп-па, имеем simplerpc. Ловкость рук, и никакого мошенничества.

VIT ★★★
()

а сериализация как устроена? сложные вложенные структуры? указатели?

ergo ★★★★
()
Ответ на: комментарий от Reset

Самый лучший rpc это memcpy структуры в сокет.

Это всё же не rpc, а метод сериализации. Чтобы он записи структуры в сокет произошёл rpc, нужен ещё какой-то код

annulen ★★★★★
()
Ответ на: комментарий от Reset

Самый лучший rpc это memcpy структуры в сокет. Никаких либ не нужно.

Лучше сразу в память нужного процесса! А чтобы вообще без либ и побыстрее - без проверок существования процесса, проверок безопасности, проверок границ памяти, мониторинга, контроля и чтобы без возможности воспроизвести руками :)

И называть не «насрать байтами», а лучший и эффективный rpc :)

skyman ★★★★★
()
Ответ на: комментарий от Reset

memcpy структуры в сокет

Это так?

{
  struct stat x;
  int fd;
  fd = socket(AF_UNIX, SOCK_STREAM, 0);
  memcpy(&fd, &x, sizeof(x));
}

Или так?

{
  struct stat x;
  int fd;
  fd = socket(AF_UNIX, SOCK_STREAM, 0);
  memcpy((void*)fd, &x, sizeof(x));
}

Или так?

{
  struct stat x;
  int fd;
  fd = socket(AF_UNIX, SOCK_STREAM, 0);
  memcpy(mmap(NULL,sizeof(x),PROT_WRITE,MAP_SHARED,fd,0), &x, sizeof(x));
}

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

Фиркакс довольно категоричен в высказываниях, но тут он скорее прав.

изолированные процессы — хорошо для надёжности

и безопасности тоже! И простоты архитектуры. И эти три фактора как бы переводят сабж в разряд наколеночной поделки.

seiken ★★★★★
()

Если не хочешь описание своего велосипеда с квадратными колёсами сам написать, то мало кто захочет этот электробред читать.

Bfgeshka ★★★★★
()
Ответ на: комментарий от firkax

Так лучше

struct Msg {
    uint64_t id;
    double value;
};

constexpr size_t BUF_SIZE = 4096;

alignas(64) std::byte buffer[BUF_SIZE];

iovec iov{
    .iov_base = buffer,
    .iov_len = BUF_SIZE
};

io_uring ring;
io_uring_queue_init(256, &ring, 0);
io_uring_register_buffers(&ring, &iov, 1);

Msg msg{42, 3.14};

std::memcpy(buffer, &msg, sizeof(msg));

io_uring_sqe* sqe = io_uring_get_sqe(&ring);
io_uring_prep_send(sqe, sockfd, buffer, sizeof(msg), 0);
sqe->buf_index = 0;

io_uring_submit(&ring);
Reset ★★★★★
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.