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)
Ответ на: комментарий от ckotctvo

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

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

никогда не понадобятся, bigendian мертв

И это повторяй. Мантры и лозунги.

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

В TCP/IP тоже? Я что-то пропустил?

Кстати, правильно говорить bigstartian, end-то у него как раз little.

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

как ntohl/htonl понадобятся.

никогда не понадобятся, bigendian мертв

Так ntohl/htonl - noop на bigendian. Так что наоборот, без них теперь никак.

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

Алсо

а ntohl/htonl никто не делает.

Тебе нейронка код генерировала?

https://github.com/resetius/coroio/blob/25898b10b43ad313f28e2c7869551d64dbe57b02/coroio/address.cpp

TAddress::TAddress(const std::string& addr, int port)
{
    sockaddr_in addr4 = {};
    sockaddr_in6 addr6 = {};
    if (inet_pton(AF_INET, addr.c_str(), &(addr4.sin_addr)) == 1) {
        addr4.sin_port = htons(port);
        addr4.sin_family = AF_INET;
        Addr_ = std::move(addr4);
    } else if (inet_pton(AF_INET6, addr.c_str(), &(addr6.sin6_addr)) == 1) {
        addr6.sin6_port = htons(port);
        addr6.sin6_family = AF_INET6;
        Addr_ = std::move(addr6);
    } else {
        throw std::runtime_error("Cannot parse address: '" + addr + "'");
    }
}
LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Я в курсе этого кода, так как сам его писал. Только это низкий код. Для реализации rpc о его существовании знать не нужно. Для записи структуры в сокет не надо никаких htonl

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

Даже если на C++. Никто давно такой ерундой как ручное создание сокета не занимается, всегда используется какая-то обертка.

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

А на кой хрен мне туда заглядывать?

Затем, что это стандартный публичный API.

Только это низкий код.

Да хоть средний. Если его нужно писать, значит нужен и ntohl / htonl.

Для реализации rpc

Господи, да всем насрать на rpc из ОП, разговор давно уже не про неё.

Для записи структуры в сокет не надо

Для записи - не надо, а для connect - очень даже надо.

Никто давно такой ерундой как ручное создание сокета не занимается,

А если найду? ©™

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

Для записи - не надо, а для connect - очень даже надо.

У меня вот так:

int lst_conn_open(uint32 addr, uint16 port, int flags);

fd = lst_conn_open(0x7F000001, 80, LST_FLAG_NONBLOCK_AFTER);

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

Даже если на C++. Никто давно такой ерундой как ручное создание сокета не занимается, всегда используется какая-то обертка.

А, ну то есть для тебя bigendian мертв, потому что закопан поглубже в обертки. Так бы и сказал. Копиум из стронг хере. Хотя ты буквально не можешь сюда ничего написать не используя bigendian.

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

А, ну то есть для тебя bigendian мертв, потому что закопан поглубже в обертки

таких архитектур нет уже, если бы tcp стек разрабатывался сейчас, то эти ntohl были бы noop на intel

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

таких архитектур нет уже

в твоей эхокамере — разумееется. Только tcp с тобой надолго под всеми обертками. И это не его с тобой закрыли :)

если бы tcp стек разрабатывался сейчас, то эти ntohl были бы noop на intel

Если бы у бабушки был аргумент против дедушки... Ну ты хоть погугли про убийц tcp, которые вылупляются каждый год... но что-то у них не так то ли с пеаром, то ли фатальный недостаток tcp видят только они.

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

Только tcp с тобой надолго под всеми обертками

Ты различаешь архитектуру процессора и соглашение об упаковке байтов в протоколе ? tcp такой потому что создавался 40 лет назад когда процессоры были bigendian, сейчас таких процессоров нет, а накладные расходы на перекладывание байт остались. Очевидно, что для всего что упаковывается внутрь tcp байты перепаковывать не надо, а надо класть так как они располагаются в памяти.

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

tcp такой потому что создавался 40 лет назад когда процессоры были bigendian,

TCP такой, потому что bigendian - это литературно сетевой порядок байт. А процессоры уже тогда были либо little-, либо сразу оба два, о чём ты, походу, даже понятия не имеешь. PDP-11, тот на котором UNIX дошёл до своей эталонной версии, - little endian (большей частью), но как и многие другие процессоры выпущенные позже - поддерживал работу с данными сразу в обоих вариантах.

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

TCP такой, потому что bigendian - это литературно сетевой порядок байт

Я в курсе. Это всего лишь выбор такой сделали тогда. Сейчас этот выбор не имеет смысла. И этим «сетевым» порядком по факту никто не пользуется кроме протоколов 40 летней давности.

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

кроме протоколов 40 летней давности.

Ну да, все современные гоняют жысон, тут ты прав.

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

Написано же - располагается по одинаковым адресам и не копируется. Что ещё нужно для счастья? Просто не ссылаться на внешнее что попало.

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