- Статус - в разработке.
- Текущая версия 0.2.1
- Language: 🇷🇺 Русский · 🇬🇧 English · 🇨🇳 中文
- rpcgen - Замена макросов на кодогенерацию.
simplerpc — это легковесный и высокопроизводительный фреймворк для реализации удалённого вызова процедур (RPC) между процессами в операционной системе Linux, написанный на языке C.
Проект предоставляет механизмы для прозрачного межпроцессного взаимодействия (IPC), используя разделяемую память (Shared Memory) и UNIX-сокеты, а также включает в себя собственные lock-free примитивы (MPMC-очереди, реестр функций) и транзакционный аллокатор разделяемой памяти.
Терминал один: ./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.
Проект simplerpc (библиотека libsrpc) представляет собой высокопроизводительный фреймворк для организации удалённого вызова процедур (RPC) между различными процессами в среде Linux.
Архитектура базируется на использовании разделяемой памяти (Shared Memory) для передачи данных и UNIX-сокетов для сигнального взаимодействия и раздачи дескриптора памяти. Для управления памятью в разделяемых сегментах используется транзакционный аллокатор TLSF (tlsf_txn v4.1), исходники которого включены в дерево проекта (libs/tlsf_txn/). Синхронизация между потоками и процессами построена на POSIX-семафорах, размещённых в разделяемой памяти (pshared), и на lock-free структурах: MPMC-очередях (Multi-Producer Multi-Consumer) и реестре функций с CAS-публикацией.
Особенность проекта — отсутствие IDL и генератора кода: роль процесса (вызывающий или исполнитель) определяется на этапе линковки через __attribute__((weak, alias)), а вся сериализация аргументов генерируется препроцессором из одного X-макроса RPC_LIST. Вторая особенность — встроенный статический загрузчик (embedded loader), который компилируется и интегрируется в динамическую библиотеку libsrpc.so в виде C-массива с помощью утилиты xxd, что позволяет библиотеке самостоятельно запускать фоновый процесс-координатор (демон) прямо из памяти, без файла на диске.
- Межпроцессный RPC: Прозрачный вызов функций, реализованных в одном процессе, из адресного пространства другого процесса.
- Разделяемая память (Shared Memory): Использование общего участка памяти, отображаемого всеми процессами по одному виртуальному адресу, для обмена данными без лишних копирований.
- Транзакционный аллокатор TLSF: Аллокатор
tlsf_txnс детерминированным O(1) выделением, robust-мьютексом и журналом отката BUL (Binary Undo Log): падение процесса посреди операции не разрушает пул. - Модель владения памятью: Каждый блок в пуле помечен UID процесса-владельца (до 12 владельцев на блок). При отключении процесса демон освобождает все его блоки автоматически.
- Сборщик мусора: Фоновый поток GC в демоне забирает служебные блоки, помеченные на отложенную очистку, проверяя их занятость через Hazard Pointer'ы.
- Динамическая линковка RPC-функций: Процесс сообщает демону битовую карту функций, которые он умеет исполнять; демон регистрирует его в lock-free реестре в разделяемой памяти.
- Многоадресная рассылка: Один вызов может быть исполнен всеми зарегистрированными процессами (
RPC_SEND_ALL), только первым (RPC_SEND_FIRST), только последним (RPC_SEND_LAST) или по кругу (RPC_SEND_RR); результаты собираются черезlibsrpc_lastreq_num()/libsrpc_lastreq_get(). - Синхронизация и очереди: Lock-free MPMC-очередь на процесс и POSIX-семафоры в разделяемой памяти для усыпления/пробуждения потоков-исполнителей.
- Встроенный загрузчик (Embedded Loader): Статически скомпилированный загрузчик внутри библиотеки, запускающий демона из
memfdчерезfexecve()— без файла на диске. - Опциональная подмена аллокатора: Перехват
malloc/calloc/realloc/freeс переключением на пул разделяемой памяти «на лету» (по умолчанию отключён, см.DISABLE_ALLOC).
- Язык программирования: C17
- Система сборки: CMake (версия 3.10+), Make
- ОС: Linux (x86_64; используются
memfd_create,fexecve, абстрактные UNIX-сокеты,MAP_FIXED_NOREPLACE,MADV_DONTFORK) - Зависимости:
- Стандартные библиотеки:
pthread(потоки, семафоры),dl(динамическая загрузка). - Утилита
xxd— на этапе сборки, для встраивания загрузчика. - Внешних зависимостей и git-подмодулей нет: аллокатор
tlsf_txnвключён в дерево проекта.
- Стандартные библиотеки:
simplerpc/
├── CMakeLists.txt # Основной файл конфигурации CMake
├── Makefile # Вспомогательный Makefile (обёртка над CMake)
├── doc/
│ └── ARCHITECTURE.md # Описание внутреннего устройства
├── src/ # Исходный код ядра библиотеки libsrpc
│ ├── libsrpc.h # Публичное API
│ ├── libsrpc_rpc_functions.h # X-макрос RPC_LIST — список RPC-функций
│ ├── libsrpc.c # Кодогенерация обёрток, диспетчер, реестр функций
│ ├── libsrpc_daemon.c # Запуск демона (spawn) и его главный цикл
│ ├── libsrpc_loader.c # Исходный код встроенного загрузчика
│ ├── libsrpc_unix_socket.c # Транспортный уровень на базе UNIX-сокетов
│ ├── libsrpc_shmem.c # Управление разделяемой памятью и аллокатором
│ ├── libsrpc_shm_gc.c # Сборщик мусора разделяемой памяти
│ ├── libsrpc_proc.c # Дескрипторы процессов в разделяемой памяти
│ ├── lf_mpmc_queue.c # Lock-free MPMC-очередь запросов
│ ├── libsrpc_list_spin.c # Односвязный список (spinlock на запись)
│ ├── libsrpc_fixblockalloc.c # Пул блоков фиксированного размера (локальный)
│ ├── libsrpc_pthread.c # Обёртки создания потоков, привязка к CPU
│ ├── libsrpc_wrapper.h # Обёртки POSIX-семафоров (pshared)
│ ├── libsrpc_errno.c # Коды и строки ошибок libsrpc
│ ├── libsrpc_local.h # Внутренние структуры: контекст, запрос, ответ
│ ├── libsrpc_private.h # Идентификаторы RPC и реестр функций
│ └── macro.h, argfunc.h # Препроцессорная кодогенерация
├── libs/ # Сторонние исходники (в дереве, без подмодулей)
│ └── tlsf_txn/ # Транзакционный аллокатор TLSF v4.1 (+ тесты, README)
├── example/ # Демонстрационные приложения
│ ├── app.c # Вызывающий процесс (caller)
│ ├── log.c # Исполнитель log_write()
│ ├── clc.c # Исполнитель calc_add(), он же вызывающий log_write()
│ └── all_exit.c # Широковещательная остановка исполнителей
└── bench/ # Замеры производительности
├── bench_ipc_baseline.c # Базовые линии: pipe, socketpair
├── bench_srpc_server.c # Процесс-исполнитель для замеров
├── bench_srpc_client.c # Замеры задержки и пропускной способности
└── run_bench.sh # Сценарий запуска замеров
Подмодулей в проекте нет, достаточно обычного клонирования.
git clone https://github.com/dsn76/simplerpc.git
cd simplerpcВ проекте предусмотрен Makefile, который автоматизирует вызов CMake.
make allДанная команда создаст директорию build, сконфигурирует проект с помощью CMake и скомпилирует статическую библиотеку аллокатора libtlsf.a, динамическую библиотеку libsrpc.so, примеры и бенчмарки.
Для ручной сборки через CMake:
mkdir -p build && cd build
cmake ..
makeОчистка: make clean (удаляет каталог build).
Режим сборки задаётся переменной BUILD_TYPE:
release(по умолчанию): оптимизация-O3.debug: отладочная информация-O0 -ggdb3 -DDEBUG(включает подробный выводDBG_PRINT).
Дополнительно поддерживаются следующие параметры CMake:
| Параметр | По умолчанию | Назначение |
|---|---|---|
BUILD_TYPE |
release |
Режим сборки: debug / release |
ENABLE_SANITIZER |
OFF |
AddressSanitizer + UndefinedBehaviorSanitizer (только для debug) |
DISABLE_ALLOC |
ON |
Отключает подмену стандартного malloc/calloc/realloc/free на аллокатор разделяемой памяти |
DBG_LVL |
2 |
Уровень сообщений: 0 — выкл., 1 — ERR, 2 — ERR+WRN, 3 — ERR+WRN+INF |
MPMCQ_DEGREE |
10 |
Ёмкость MPMC-очереди как степень двойки (2^10 = 1024) |
SHMEM_SIZE_KB |
4096 |
Размер разделяемой памяти в килобайтах |
SHMEM_BASE_VADR |
0x200000000000 |
Базовый виртуальный адрес отображения разделяемой памяти |
Пример:
cmake -DBUILD_TYPE=debug -DDBG_LVL=3 -DSHMEM_SIZE_KB=16384 ..Важно: при каждой конфигурации CMake штампует метку
BUILD_TS, из которой формируются имя демона, имя абстрактного сокета и сигнатура разделяемой памяти. Приложения, собранные с разными сборкамиlibsrpc.so, взаимодействовать друг с другом не будут — это защита от несовместимости версийRPC_LIST. После пересборки библиотеки нужно перезапустить все участвующие процессы.
Все функции, доступные для удалённого вызова, перечисляются в одном X-макросе src/libsrpc_rpc_functions.h:
#define RPC_LIST \
XF(RPC_SEND_ALL, int, testlocal) \
XF(RPC_SEND_ALL, pid_t, log_write, const char*) \
XF(RPC_SEND_ALL, int, calc_add, int, int) \
XF(RPC_SEND_ALL, void, all_exit) \Формат: XF(политика рассылки, тип возврата, имя, типы параметров...). Политика: RPC_SEND_ALL, RPC_SEND_FIRST, RPC_SEND_LAST, RPC_SEND_RR. Функции с переменным числом параметров не допускаются.
Отдельного «серверного» API нет. Процесс, который определил функцию из RPC_LIST в своём коде, автоматически становится её исполнителем — сильный символ перекрывает слабый alias из libsrpc.so. Процесс, который функцию не определил, при вызове получает заглушку и отправляет RPC-запрос:
/* Процесс-исполнитель: определяет функцию — и становится её сервером. */
pid_t log_write(const char* msg) {
fprintf(stderr, "LOG: '%s'\n", msg);
return getpid();
}/* Процесс-вызывающий: просто вызывает, ничего не определяя. */
char *str = libsrpc_shmem_malloc(1024);
snprintf(str, 1024, "Hello from pid=%d", getpid());
pid_t pid = log_write(str); /* уйдёт по RPC во все исполнители */
/* Сбор результатов от всех исполнителей (RPC_SEND_ALL): */
for (int i = 0, num = libsrpc_lastreq_num(); i < num; i++) {
int rc = libsrpc_lastreq_get(i, &pid, sizeof(pid));
if (!rc) printf("pid[%d]=%d\n", i, pid);
}
libsrpc_shmem_free(str);Указатели, передаваемые в RPC-функции, должны адресовать разделяемый пул (
libsrpc_shmem_malloc) — библиотека копирует только сами аргументы, но не данные за указателями.
Таймаут ожидания ответа задаётся в микросекундах:
libsrpc_timeout_oneshot_set()(следующий вызов),libsrpc_timeout_func_set()(конкретная функция),libsrpc_timeout_global_set()(все вызовы). По умолчанию 1 с.
При линковке приложения, которое только экспортирует RPC-функции, обязателен флаг
-Wl,--no-as-needed: без ссылок на символыlibsrpc.soлинковщик уберётDT_NEEDED, и конструктор библиотеки не выполнится.
В директории example/ представлены четыре приложения. Фоновый демон-координатор запускать не нужно — он поднимается автоматически первым же процессом, слинкованным с libsrpc.so, и завершается после отключения последнего клиента.
log— процесс-исполнитель, предоставляетlog_write().clc— процесс-исполнительcalc_add(), при этом сам вызываетlog_write()по RPC.app— вызывающий процесс: выделяет строку в разделяемом пуле, вызываетlog_write()иcalc_add(), печатает результаты.all_exit— вызываетall_exit(), широковещательно останавливая всех исполнителей.
Запуск исполнителей (в отдельных терминалах):
./build/example/log
./build/example/clcЗапуск клиента:
./build/example/appОстановка исполнителей:
./build/example/all_exitВ процессе выполнения клиент выделит память в разделяемом пуле (libsrpc_shmem_malloc), запишет туда строку и инициирует RPC-вызов функции логирования, которая выполнится в процессе log. Функция calc_add() при этом исполнится в процессе clc.
Каталог bench/ содержит замеры задержки RPC-вызова в сравнении с базовыми механизмами IPC:
make all
./bench/run_bench.sh build 4000Сценарий поднимает процесс-исполнитель, прогоняет базовые линии (pipe, socketpair), затем однопоточный и многопоточный round-trip RPC и печатает распределение задержек (min/p50/p90/p99/max) и пропускную способность.
- doc/ARCHITECTURE.md — внутреннее устройство, компоненты, жизненный цикл RPC-вызова.
- libs/tlsf_txn/README.md — документация транзакционного аллокатора TLSF.
Код libsrpc распространяется под лицензией Apache-2.0. Подробности смотрите в файле LICENSE.