KasperskyOS Community Edition 1.1
[Topic sc_using_ipc][Topic sc_ipc_channels]

Обзор: создание IPC-каналов

Есть два способа создания IPC-каналов: статический и динамический.

Статическое создание IPC-каналов проще в реализации, поскольку для него можно использовать init-описание.

Динамическое создание IPC-каналов позволяет изменять топологию взаимодействия процессов "на лету". Это требуется, если неизвестно, какой именно сервер содержит службу, необходимую клиенту. Например, может быть неизвестно, на какой именно накопитель нужно будет записывать данные.

Статическое создание IPC-канала

Статический способ имеет следующие особенности:

  • клиент и сервер находятся в остановленном состоянии в момент создания IPC-канала;
  • создание инициируются родительским процессом, запускающим клиента и сервера (обычно это Einit);
  • созданный IPC-канал невозможно удалить;
  • чтобы получить IPC-дескриптор и идентификатор службы (riid) после создания IPC-канала, клиент и сервер должны использовать интерфейс локатора сервисов (coresrv/sl/sl_api.h).

Динамическое создание IPC-канала

Динамический способ имеет следующие особенности:

  • клиент и сервер уже запущены в момент создания IPC-канала;
  • создание инициируются совместно клиентом и сервером;
  • созданный IPC-канал может быть удален;
  • клиент и сервер получают IPC-дескриптор и идентификатор службы (riid) сразу после успешного создания IPC-канала.
В начало
[Topic ipc_channels_overview]

Создание IPC-каналов с помощью init.yaml

Здесь собраны init-описания, демонстрирующие особенности создания IPC-каналов. Примеры задания свойств и аргументов процессов через init-описания разбираются в отдельной статье.

В примерах в составе KasperskyOS Community Edition может использоваться формат init-описания с макросами (init.yaml.in).

Файл с init-описанием обычно называется init.yaml, хотя может иметь любое имя.

Соединение и запуск процесса-клиента и процесса-сервера

В следующем примере будут запущены два процесса: класса Client и класса Server. Имена процессов не указаны, поэтому они будут совпадать с именами классов процессов. Имена исполняемых файлов также не указаны, они также будут совпадать с именами классов. Процессы будут соединены IPC-каналом с именем server_connection.

init.yaml

entities:

- name: Client

connections:

- target: Server

id: server_connection

- name: Server

В начало
[Topic ipc_channel_create_einit]

Динамическое создание IPC-каналов

При динамическом создании IPC-канала используются функции:

Динамическое создание IPC-канала осуществляется по следующему сценарию:

  1. Запускаются процессы: клиент, сервер и сервер имен.
  2. Сервер подключается к серверу имен с помощью вызова NsCreate() и публикует имя сервера, имя интерфейса и имя службы с помощью вызова NsPublishService().
  3. Клиент подключается к серверу имен с помощью вызова NsCreate() и выполняет поиск имени сервера и имени службы по имени интерфейса с помощью вызова NsEnumServices().
  4. Клиент запрашивает доступ к службе с помощью вызова KnCmConnect(), передавая в качестве аргументов найденные имя сервера и имя службы.
  5. Сервер вызывает функцию KnCmListen() для проверки наличия запросов на доступ к службе.
  6. Сервер принимает запрос клиента на доступ к службе с помощью вызова KnCmAccept(), передавая в качестве аргументов имя клиента и имя службы, которые получены при вызове KnCmListen().

Пункты 2 и 3 могут быть опущены, если клиент заранее знает имя сервера и имя службы.

Сервер может снимать с публикации на сервере имен ранее опубликованные службы с помощью вызова NsUnPublishService().

Сервер может отклонять запросы доступа к службам с помощью вызова KnCmDrop().

Для использования сервера имен политика безопасности решения должна разрешать взаимодействие процесса класса kl.core.NameServer и процессами, между которыми необходимо динамически создавать IPC-каналы.

В начало
[Topic ipc_channel_create_dynamic]

Использование служб из состава KasperskyOS Community Edition

В этом разделе

Добавление службы в решение

В начало
[Topic sc_using_sdk_endpoints]

Добавление службы в решение

Чтобы программа Client могла использовать ту или иную функциональность через механизм IPC, необходимо:

  1. Найти в составе KasperskyOS Community Edition исполняемый файл (условно назовем его Server), реализующий нужную функциональность. (Под функциональностью мы здесь понимаем одну или несколько служб, имеющих самостоятельные IPC-интерфейсы)
  2. Подключить CMake-пакет, содержащий файл Server и его клиентскую библиотеку.
  3. Добавить исполняемый файл Server в образ решения.
  4. Изменить init-описание так, чтобы при старте решения программа Einit запускала новый серверный процесс из исполняемого файла Server и соединяла его IPC-каналом с процессом, запускаемым из файла Client.

    Необходимо указать корректное имя IPC-канала, чтобы транспортные библиотеки могли идентифицировать этот канал и найти его IPC-дескрипторы. Корректное имя IPC-канала, как правило, совпадает с именем класса серверного процесса. VFS при этом является исключением.

  5. Изменить PSL-описание так, чтобы разрешить запуск серверного процесса и IPC-взаимодействие между клиентом и сервером.
  6. Подключить в исходном коде программы Client заголовочный файл с методами сервера.
  7. Скомпоновать программу Client с клиентской библиотекой.

Пример добавления GPIO-драйвера в решение

В составе KasperskyOS Community Edition есть файл gpio_hw, реализующий функциональность GPIO-драйвера.

Следующие команды подключают CMake‑пакет gpio:

.\CMakeLists.txt

...

find_package (gpio REQUIRED COMPONENTS CLIENT_LIB ENTITY)

include_directories (${gpio_INCLUDE})

...

Добавление исполняемого файла gpio_hw в образ решения производится с помощью переменной gpio_HW_ENTITY, имя которой можно найти в конфигурационном файле пакета – /opt/KasperskyOS-Community-Edition-<version>/sysroot-aarch64-kos/lib/cmake/gpio/gpio-config.cmake:

einit\CMakeLists.txt

...

set (ENTITIES Client ${gpio_HW_ENTITY})

...

В init-описание нужно добавить следующие строки:

init.yaml.in

...

- name: client.Client

connections:

- target: kl.drivers.GPIO

id: kl.drivers.GPIO

- name: kl.drivers.GPIO

path: gpio_hw

В PSL-описание нужно добавить следующие строки:

security.psl.in

...

execute src=Einit, dst=kl.drivers.GPIO

{

grant()

}

request src=client.Client, dst=kl.drivers.GPIO

{

grant()

}

response src=kl.drivers.GPIO, dst=client.Client

{

grant()

}

...

В коде программы Client нужно подключить заголовочный файл, в котором объявлены методы GPIO-драйвера:

client.c

...

#include <gpio/gpio.h>

...

Наконец, нужно скомпоновать программу Client с клиентской библиотекой GPIO:

client\CMakeLists.txt

...

target_link_libraries (Client ${gpio_CLIENT_LIB})

...

Для корректной работы GPIO‑драйвера может понадобиться добавить в решение компонент BSP. Чтобы не усложнять этот пример, мы не рассматриваем здесь BSP. Подробнее см. пример gpio_output: /opt/KasperskyOS-Community-Edition-<version>/examples/gpio_output

В начало
[Topic using_sdk_endpoints_examples][Topic sc_using_new_endpoints]

Обзор: структура IPC-сообщения

В KasperskyOS все взаимодействия между процессами статически типизированы. Допустимые структуры IPC-сообщения определяются описанием интерфейсов процесса-получателя сообщения (сервера).

Корректное IPC-сообщение (как запрос, так и ответ) содержит фиксированную часть и арену.

Фиксированная часть сообщения

Фиксированная часть сообщения содержит аргументы фиксированного размера, а также RIID и MID.

Аргументы фиксированного размера – это аргументы любых IDL-типов, кроме типа sequence.

RIID и MID идентифицируют вызываемый интерфейс и метод:

  • RIID (Runtime Implementation ID) является порядковым номером вызываемой службы процесса (начиная с нуля).
  • MID (Method ID) является порядковым номером метода в содержащем его интерфейсе (начиная с нуля).

Тип фиксированной части сообщения генерируется компилятором NK на основе IDL-описания интерфейса. Для каждого метода интерфейса генерируется отдельная структура. Также генерируются типы union для хранения любого запроса к процессу, компоненту или интерфейсу. Подробнее см. Пример генерации транспортных методов и типов.

Арена

Арена представляет собой буфер для хранения аргументов переменного размера (IDL‑тип sequence).

Проверка структуры сообщения модулем безопасности

Перед тем как вызывать связанные с сообщением правила, подсистема Kaspersky Security Module проверяет отправляемое сообщение на корректность. Проверяются как запросы, так и ответы. Если сообщение имеет некорректную структуру, оно будет отклонено без вызова связанных с ним методов моделей безопасности.

Формирование структуры сообщения

В составе KasperskyOS Community Edition поставляются следующие инструменты, позволяющие упростить для разработчика создание и упаковку IPC-сообщения:

Формирование простейшего IPC-сообщения показано в примерах echo и ping (/opt/KasperskyOS-Community-Edition-<version>/examples/).

В начало
[Topic ipc_message_structure_overview]

Нахождение IPC-дескриптора

Клиентский и серверный IPC-дескрипторы требуется находить, если для используемой службы нет готовых транспортных библиотек (например, вы написали собственную службу). Для самостоятельной работы с IPC-транспортом нужно предварительно инициализировать его с помощью метода NkKosTransport_Init(), передав в качестве второго аргумента IPC-дескриптор используемого канала.

Подробнее см. примеры echo и ping (/opt/KasperskyOS-Community-Edition-<version>/examples/),

Для использования служб, которые реализованы в исполняемых файлах в составе KasperskyOS Community Edition, нет необходимости находить IPC-дескриптор. Вся работа с транспортом, включая нахождение IPC-дескрипторов, выполняется поставляемыми транспортными библиотеками.

См. примеры gpio_*, net_*, net2_* и multi_vfs_* (/opt/KasperskyOS-Community-Edition-<version>/examples/).

Нахождение IPC-дескриптора при статическом создании канала

При статическом создании IPC-канала как клиент, так и сервер могут сразу после своего запуска узнать свои IPC-дескрипторы с помощью методов ServiceLocatorRegister() и ServiceLocatorConnect(), указав имя созданного IPC-канала.

Например, если IPC-канал имеет имя server_connection, то на клиентской стороне необходимо вызвать:

#include <coresrv/sl/sl_api.h>

Handle handle = ServiceLocatorConnect("server_connection");

На серверной стороне необходимо вызвать:

#include <coresrv/sl/sl_api.h>

nk_iid_t iid;

Handle handle = ServiceLocatorRegister("server_connection", NULL, 0, &iid);

Подробнее см. примеры echo и ping (/opt/KasperskyOS-Community-Edition-<version>/examples/), а так же заголовочный файл /opt/KasperskyOS-Community-Edition-<version>/sysroot-aarch64-kos/include/coresrv/sl/sl_api.h.

Нахождение IPC-дескриптора при динамическом создании канала

Как клиент, так и сервер получают свои IPC-дескрипторы сразу при успешном динамическом создании IPC-канала.

Клиентский IPC-дескриптор является одним из выходных (out) аргументов метода KnCmConnect(). Серверный IPC-дескриптор является выходным аргументом метода KnCmAccept(). Подробнее см. заголовочный файл /opt/KasperskyOS-Community-Edition-<version>/sysroot-aarch64-kos/include/coresrv/cm/cm_api.h.

В начало
[Topic ipc_find_ipc_desc]

Нахождение идентификатора службы (riid)

Идентификатор службы (riid) требуется находить на клиентской стороне, если для используемой службы нет готовых транспортных библиотек (например, вы написали собственную службу). Для вызова методов сервера необходимо на клиентской стороне предварительно вызвать метод инициализации прокси-объекта, передав в качестве третьего аргумента идентификатор службы. Например, для интерфейса Filesystem:

Filesystem_proxy_init(&proxy, &transport.base, riid);

Подробнее см. примеры echo и ping (/opt/KasperskyOS-Community-Edition-<version>/examples/),

Для использования служб, которые реализованы в исполняемых файлах в составе KasperskyOS Community Edition, нет необходимости находить идентификатор службы. Вся работа с транспортом выполняется поставляемыми транспортными библиотеками.

См. примеры gpio_*, net_*, net2_* и multi_vfs_* (/opt/KasperskyOS-Community-Edition-<version>/examples/).

Нахождение идентификатора службы при статическом создании канала

При статическом создании IPC-канала клиент может узнать идентификатор нужной службы с помощью метода ServiceLocatorGetRiid(), указав дескриптор IPC-канала и полное квалифицированное имя службы. Например, если экземпляр компонента OpsComp содержит службу FS, то на клиентской стороне необходимо вызвать:

#include <coresrv/sl/sl_api.h>

nk_iid_t riid = ServiceLocatorGetRiid(handle, "OpsComp.FS");

Подробнее см. примеры echo и ping (/opt/KasperskyOS-Community-Edition-<version>/examples/), а так же заголовочный файл /opt/KasperskyOS-Community-Edition-<version>/sysroot-aarch64-kos/include/coresrv/sl/sl_api.h.

Нахождение идентификатора службы при динамическом создании канала

Клиент получает идентификатор службы сразу при успешном динамическом создании IPC-канала. Клиентский IPC-дескриптор является одним из выходных (out) аргументов метода KnCmConnect(). Подробнее см. заголовочный файл /opt/KasperskyOS-Community-Edition-<version>/sysroot-aarch64-kos/include/coresrv/cm/cm_api.h.

В начало
[Topic ipc_find_riid]

Пример генерации транспортных методов и типов

При сборке решения компилятор NK на основе EDL-, CDL- и IDL-описаний генерирует набор специальных методов и типов, упрощающих формирование, отправку, прием и обработку IPC-сообщений.

В качестве примера рассмотрим класс процессов Server, предоставляющий службу FS, которая содержит единственный метод Open():

Server.edl

entity Server

/* OpsComp - именованный экземпляр компонента Operations */

components {

OpsComp: Operations

}

Operations.cdl

component Operations

/* FS - локальное имя службы, реализующей интерфейс Filesystem */

endpoints {

FS: Filesystem

}

Filesystem.idl

package Filesystem

interface {

Open(in string<256> name, out UInt32 h);

}

На основе этих описаний будут сгенерированы файлы Server.edl.h, Operations.cdl.h, и Filesystem.idl.h содержащие следующие методы и типы:

Методы и типы, общие для клиента и сервера

  • Абстрактные интерфейсы, содержащие указатели на реализации входящих в них методов.

    В нашем примере будет сгенерирован один абстрактный интерфейс – Filesystem:

    typedef struct Filesystem {

    const struct Filesystem_ops *ops;

    } Filesystem;

    typedef nk_err_t

    Filesystem_Open_fn(struct Filesystem *, const

    struct Filesystem_Open_req *,

    const struct nk_arena *,

    struct Filesystem_Open_res *,

    struct nk_arena *);

    typedef struct Filesystem_ops {

    Filesystem_Open_fn *Open;

    } Filesystem_ops;

  • Набор интерфейсных методов.

    При вызове интерфейсного метода в запросе автоматически проставляются соответствующие значения RIID и MID.

    В нашем примере будет сгенерирован единственный интерфейсный метод Filesystem_Open:

    nk_err_t Filesystem_Open(struct Filesystem *self,

    struct Filesystem_Open_req *req,

    const

    struct nk_arena *req_arena,

    struct Filesystem_Open_res *res,

    struct nk_arena *res_arena)

Методы и типы, используемые только на клиенте

  • Типы прокси-объектов.

    Прокси-объект используется как аргумент интерфейсного метода. В нашем примере будет сгенерирован единственный тип прокси-объекта Filesystem_proxy:

    typedef struct Filesystem_proxy {

    struct Filesystem base;

    struct nk_transport *transport;

    nk_iid_t iid;

    } Filesystem_proxy;

  • Функции для инициализации прокси-объектов.

    В нашем примере будет сгенерирована единственная инициализирующая функция Filesystem_proxy_init:

    void Filesystem_proxy_init(struct Filesystem_proxy *self,

    struct nk_transport *transport,

    nk_iid_t iid)

  • Типы, определяющие структуру фиксированной части сообщения для каждого конкретного метода.

    В нашем примере будет сгенерировано два таких типа: Filesystem_Open_req (для запроса) и Filesystem_Open_res (для ответа).

    typedef struct __nk_packed Filesystem_Open_req {

    __nk_alignas(8)

    struct nk_message base_;

    __nk_alignas(4) nk_ptr_t name;

    } Filesystem_Open_req;

    typedef struct Filesystem_Open_res {

    union {

    struct {

    __nk_alignas(8)

    struct nk_message base_;

    __nk_alignas(4) nk_uint32_t h;

    };

    struct {

    __nk_alignas(8)

    struct nk_message base_;

    __nk_alignas(4) nk_uint32_t h;

    } res_;

    struct Filesystem_Open_err err_;

    };

    } Filesystem_Open_res;

Методы и типы, используемые только на сервере

  • Тип, содержащий все службы компонента, а также инициализирующая функция. (Для каждого компонента сервера.)

    При наличии вложенных компонентов этот тип также содержит их экземпляры, а инициализирующая функция принимает соответствующие им инициализированные структуры. Таким образом, при наличии вложенных компонентов, их инициализацию необходимо начинать с самого вложенного.

    В нашем примере будет сгенерирована структура Operations_component и функция Operations_component_init:

    typedef struct Operations_component {

    struct Filesystem *FS;

    };

    void Operations_component_init(struct Operations_component *self,

    struct Filesystem *FS)

  • Тип, содержащий все службы, предоставляемые сервером непосредственно; все экземпляры компонентов, входящие в сервер; а также инициализирующая функция.

    В нашем примере будет сгенерирована структура Server_entity и функция Server_entity_init:

    #define Server_entity Server_component

    typedef struct Server_component {

    struct : Operations_component *OpsComp;

    } Server_component;

    void Server_entity_init(struct Server_entity *self,

    struct Operations_component *OpsComp)

  • Типы, определяющие структуру фиксированной части сообщения для любого метода конкретного интерфейса.

    В нашем примере будет сгенерировано два таких типа: Filesystem_req (для запроса) и Filesystem_res (для ответа).

    typedef union Filesystem_req {

    struct nk_message base_;

    struct Filesystem_Open_req Open;

    };

    typedef union Filesystem_res {

    struct nk_message base_;

    struct Filesystem_Open_res Open;

    };

  • Типы, определяющие структуру фиксированной части сообщения для любого метода любой службы конкретного компонента.

    При наличии вложенных компонентов эти типы также содержат структуры фиксированной части сообщения для любых методов любых служб, включенных во все вложенные компоненты.

    В нашем примере будет сгенерировано два таких типа: Operations_component_req (для запроса) и Operations_component_res (для ответа).

    typedef union Operations_component_req {

    struct nk_message base_;

    Filesystem_req FS;

    } Operations_component_req;

    typedef union Operations_component_res {

    struct nk_message base_;

    Filesystem_res FS;

    } Operations_component_res;

  • Типы, определяющие структуру фиксированной части сообщения для любого метода любой службы конкретного компонента, экземпляр которого входит в сервер.

    При наличии вложенных компонентов эти типы также содержат структуры фиксированной части сообщения для любых методов любых служб, включенных во все вложенные компоненты.

    В нашем примере будет сгенерировано два таких типа: Server_entity_req (для запроса) и Server_entity_res (для ответа).

    #define Server_entity_req Server_component_req

    typedef union Server_component_req {

    struct nk_message base_;

    Filesystem_req OpsComp_FS;

    } Server_component_req;

    #define Server_entity_res Server_component_res

    typedef union Server_component_res {

    struct nk_message base_;

    Filesystem_res OpsComp_FS;

    } Server_component_res;

  • Dispatch-методы (диспетчеры) для отдельного интерфейса, компонента или класса процессов.

    Диспетчеры анализируют полученный запрос (значения RIID и MID), вызывают реализацию соответствующего метода, после чего сохраняют ответ в буфер. В нашем примере будут сгенерированы диспетчеры Filesystem_interface_dispatch, Operations_component_dispatch и Server_entity_dispatch.

    Диспетчер класса процессов обрабатывает запрос и вызывает методы, реализуемые этим классом. Если запрос содержит некорректный RIID (например, относящийся к другой службе, которой нет у этого класса процессов) или некорректный MID, диспетчер возвращает NK_EOK или NK_ENOENT.

    nk_err_t Server_entity_dispatch(struct Server_entity *self,

    const

    struct nk_message *req,

    const

    struct nk_arena *req_arena,

    struct nk_message *res,

    struct nk_arena *res_arena)

    В специальных случаях можно использовать диспетчеры интерфейса и компонента. Они принимают дополнительный аргумент – ID реализации интерфейса (nk_iid_t). Запрос будет обработан только если переданный аргумент и RIID из запроса совпадают, а MID корректен. В противном случае диспетчеры возвращают NK_EOK или NK_ENOENT.

    nk_err_t Operations_component_dispatch(struct Operations_component *self,

    nk_iid_t iidOffset,

    const

    struct nk_message *req,

    const

    struct nk_arena *req_arena,

    struct nk_message *res,

    struct nk_arena *res_arena)

    nk_err_t Filesystem_interface_dispatch(struct Filesystem *impl,

    nk_iid_t iid,

    const

    struct nk_message *req,

    const

    struct nk_arena *req_arena,

    struct nk_message *res,

    struct nk_arena *res_arena)

В начало
[Topic transport_code_overview]