[Python, Параллельное программирование, Разработка под Linux] Клиент-серверный IPC при помощи Python multiprocessing

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
08-Дек-2020 21:31

Статья отражает личный опыт разработки расширения ядра для Linux.В ней рассмотрен способ выполнения привилегированных системных вызовов процессом пространства ядра по запросам управляющей программы пользовательского пространства через строго описанный API.Исходный код написан на Python для реального коммерческого приложения, но для публикации абстрагирован от конкретных задач.Введение
«Межпроцессное взаимодействие (англ. inter-process communication, IPC) — обмен данными между потоками одного или разных процессов. Реализуется посредством механизмов, предоставляемых ядром ОС или процессом, использующим механизмы ОС и реализующим новые возможности IPC». — Википедия
У процессов, могут быть разные причины для обмена информацией. На мой взгляд все они являются следствием политики безопасности ядра Unix.Как известно, ядро Unix — это автономная система, которая функционирует без вмешательства человека. Собственно говоря, пользователь — это объект операционной системы, который появился чтобы обезопасить ядро от несанкционированного вмешательства.Обеспечение безопасности ядра заключается в разделении адресного пространства операционной системы на пространство ядра и пространство пользователя. Отсюда два режима работы системы — режим пользователя и режим ядра. Причем, смена режимов — это переключение между двумя пространствами.В режиме пользователя недоступны области памяти, зарезервированные ядром, и системные вызовы, которые изменяют состояние системы.Тем не менее таким доступом обладает суперпользователь.Предпосылки параллелизмаЕсли ваша программа не использует привилегированные системные вызовы, вам незачем переключаться в пространство ядра, а значит можно писать монолит без параллелизма.В противном случае вам придётся запускать свою программу под рутом.На этом можно было бы закончить, если бы не проблема доступа к эксклюзивным ресурсам пользовательского пространства из пространства ядра.Представьте, что вы обращаетесь к ресурсу или к переменной окружения, которая присутствует только в пользовательском пространстве.Например, служба, которая вам нужна, зависит от окружения рабочего стола, которое доступно только когда пользовательская сессия активна. В этом случае объект, который вам нужен просто не существует в пространстве ядра. Для того, чтобы получить к нему доступ, вам нужен процесс в пользовательском пространстве, а для системных вызовов — процесс в пространстве ядра.При этом вы можете запросить у процесса в пространстве ядра исполнение системного вызова из пользовательского процесса при помощи одного из методов IPC.Таблица методов межпроцессного взаимодействияМетодРеализуется ОС или процессомНеименованный каналВсе ОС, совместимые со стандартом POSIX.Разделяемая памятьВсе ОС, совместимые со стандартом POSIX.Очередь сообщений (Message queue)Большинство ОС.СигналБольшинство ОС; в некоторых ОС, например, в Windows, сигналы доступны только в библиотеках, реализующих стандартную библиотеку языка Си, и не могут использоваться для IPC.Почтовый ящикНекоторые ОС.СокетБольшинство ОС.Именованный каналВсе ОС, совместимые со стандартом POSIX.Проецируемый в память файл (mmap)Все ОС, совместимые со стандартом POSIX. При использовании временного файла возможно возникновение гонки. ОС Windows также предоставляет этот механизм, но посредством API, отличающегося от API, описанного в стандарте POSIX.Обмен сообщениями (без разделения)Используется в парадигме MPI, Java RMI, CORBA и других.ФайлВсе ОС.СемафорВсе ОС, совместимые со стандартом POSIX.КаналВсе ОС, совместимые со стандартом POSIX.Для своего приложения я выбрал сокеты и написал API для коммуникации между процессами.Таким образом в распоряжении конечного пользователя оказывается программа, которая не требует прав суперпользователя, но запрашивает исполнение системных вызовов у процесса ядра через соответствующий сокет.При этом процесс в пространстве ядра, запускается при загрузке системы и остаётся активным всегда, прослушивая сокет на наличие входящих дейтаграмм.Историческая справкаТрадиционно процессы, которые запускаются при загрузке системы и остаются активными в фоне, классифицируются как daemon. Имена исполняемых файлов таких программ по соглашению заканчиваются на «d». Пример: systemd.Программы пользовательского пространства, взаимодействующие с daemon можно назвать управляющими, что также по соглашению отражено в их названиях. Пример: systemctl.Известны и другие примеры: ssh и sshd.В более современной интерпретации их называют клиентами и серверами с тем отличием, что последние взаимодействуют по сетевым протоколам. Однако, это не мешает нам использовать сокеты исключительно для локальных процессов когда сокет принимает дейтаграммы через конвейер.Структура проектаДля сервера и клиента я использую одинаковую структуру.
.
├── core
│   ├── api.py
│   └── __init__.py
├── main.py
core — это пакет, в который можно положить модули с любой логикой. В модуле api реализованы методы обращения процессов друг к другу.Реализация API клиента
from multiprocessing.connection import Client
from multiprocessing.connection import Listener
# адрес сервера (процесса в пространстве ядра)
# для исходящих запросов
daemon = ('localhost', 6000)
# адрес клиента (этого процесса) для входящих
# ответов от сервера
cli = ('localhost', 6001)
def send(request: dict) -> bool or dict:
    """
    Принимет словарь аргументов удалённого метода.
    Отправляет запрос, после чего открывет сокет
    и ждет на нем ответ от сервера.
    """
    with Client(daemon) as conn:
        conn.send(request)
    with Listener(cli) as listener:
        with listener.accept() as conn:
            try:
                return conn.recv()
            except EOFError:
                return False
def hello(name: str) -> send:
    """
    Формирует уникальный запрос и вызывает функцию send
    для его отправки.
    """
    return send({
        "method": "hello",
        "name": name
    })
В модуле connection пакета multiprocessing есть два класса, реализующих API высокого уровня над низкоуровнивым аналогом стандартной библиотеки — socket.Client — класс, который содержит методы отправки дейтаграмм.Listener принимает дейтаграммы.Отправляемые запросы содержат название целевого метода сервера.Причем запросы не требуют никаких преобразований на сервере, ведь он тоже написан на Python, который интерпретирует поступающие данные также, как и клиент. Всё это происходит «под капотом» и не может не радовать.Использование APIВ main.py я импортирую модуль api для дальнейшего использования.
from core import api
response = api.hello("World!")
print(response)
Этот код представлен для демонстрации. В работе я использовал Сlick Framework для создания СLI приложения с опциями, которые вызывают методы API.Реализация API сервераПо идее метод должен выполнять системный вызов, который требует привилегий ядра. Иначе всё это теряет смысл.
def hello(request: dict):
    """
    Привилегированный системный вызов.
    """
    return " ".join(["Hello", request["name"])
Использование API
from core import api
from multiprocessing.connection import Listener
from multiprocessing.connection import Client
# адрес сервера (этого процесса) для входящих запросов
daemon = ('localhost', 6000)
# адрес клиента для исходящих ответов
cli = ('localhost', 6001)
while True:
    with Listener(daemon) as listener:
        with listener.accept() as conn:
            request = conn.recv()
            if request["method"] == "hello":
                response = api.connect(request)
            with Client(cli) as conn:
                conn.send(response)
Сервер должен быть активен всегда, поэтому я запускаю его в бесконечно цикле.Таким образом он всегда слушает порт 6000 и, при поступлении дейтаграммы, анализирует запрос. Затем он вызывает указанный в запросе метод и возвращает результат исполнения клиенту.ДополнительноСоветую снабдить свой сервер пакетом systemd, который позволяет программам на Python писать лог в journald.Для сборки вы можете использовать pyinstaller — он запакует ваш код в бинарный файл со всеми зависимостями. Не забудьте про соглашение о наименовании исполняемых файлов, упомянутое ранее.Отдельная тема — создание установочного скрипта или репозитория. Если у вас есть опыт в этом, прошу поделится им в комментариях.Спасибо за внимание!
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_python, #_parallelnoe_programmirovanie (Параллельное программирование), #_razrabotka_pod_linux (Разработка под Linux), #_python, #_linux, #_ipc, #_python, #_parallelnoe_programmirovanie (
Параллельное программирование
)
, #_razrabotka_pod_linux (
Разработка под Linux
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 24-Ноя 20:16
Часовой пояс: UTC + 5