KasperskyOS Community Edition 1.2

Содержание

Разработка под KasperskyOS

В этом разделе справки

Запуск процессов

Файловые системы и сеть

IPC и транспорт

В начало
[Topic developing][Topic sc_application_start]

Обзор: Einit и init.yaml

Инициализирующая программа Einit

При запуске решения ядро KasperskyOS находит в образе решения и запускает исполняемый файл с именем Einit, то есть инициализирующую программу. Инициализирующая программа выполняет следующие действия:

  • создает и запускает процессы при запуске решения;
  • создает IPC-каналы между процессами при запуске решения (статически создает IPC-каналы).

Процесс инициализирующей программы имеет класс Einit.

Генерация исходного кода инициализирующей программы

В составе KasperskyOS SDK поставляется утилита einit, которая позволяет генерировать исходный код инициализирующей программы на языке C. Стандартным способом использования утилиты einit является интеграция ее вызова в один из шагов сборочного скрипта, в результате которого генерируется файл einit.c, содержащий исходный код инициализирующей программы. На одном из следующих шагов сборочного скрипта необходимо скомпилировать файл einit.c в исполняемый файл Einit и включить в образ решения.

Для инициализирующей программы не требуется создавать файлы формальной спецификации. Эти файлы поставляются в составе KasperskyOS SDK и автоматически применяются при сборке решения. Однако класс процесса Einit должен быть указан в файле security.psl.

Утилита einit генерирует исходный код инициализирующей программы основе init-описания, представляющего собой текстовый файл, который обычно имеет имя init.yaml.

Синтаксис init.yaml

Init-описание содержит данные в формате YAML, которые идентифицируют:

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

Эти данные представляют собой словарь с ключом entities, содержащий список словарей процессов. Ключи словаря процесса приведены в таблице ниже.

Ключи словаря процесса в init-описании

Ключ

Обязательный

Значение

name

Да

Имя класса процесса (из EDL-описания).

task

Нет

Имя процесса. Если его не указать, то будет взято имя класса процесса. У каждого процесса должно быть уникальное имя.

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

path

Нет

Имя исполняемого файла в ROMFS (в образе решения). Если его не указать, то будет взято имя класса процесса без префиксов и точек. Например, процессы классов Client и net.Client, для которых не указано имя исполняемого файла, будут запущены из файла Client.

Можно запустить несколько процессов из одного исполняемого файла.

connections

Нет

Список словарей IPC-каналов процесса. Этот список задает статически создаваемые IPC-каналы, клиентскими IPC-дескрипторами которых будет владеть процесс. По умолчанию этот список пуст. (Помимо статически создаваемых IPC-каналов процессы могут использовать динамически создаваемые IPC-каналы.)

args

Нет

Список параметров запуска программы (параметры функции main()). Максимальный размер одного элемента списка составляет 1024 байта.

env

Нет

Словарь переменных окружения программы. Ключами в этом словаре являются имена переменных окружения. Максимальный размер значения переменной окружения составляет 1024 байта.

Ключи словаря IPC-канала процесса приведены в таблице ниже.

Ключи словаря IPC-канала в init-описании

Ключ

Обязательный

Значение

id

Да

Имя IPC-канала, которое может быть задано как конкретным значением, так и ссылкой вида

{var: <имя константы>, include: <путь к заголовочному файлу>}.

target

Да

Имя процесса, который будет владеть серверным дескриптором IPC-канала.

В начало
[Topic einit_overview]

Примеры init-описаний

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

Система сборки может автоматически создавать init-описание на основе шаблона init.yaml.in.

Запуск клиента и сервера и создание IPC-канала между ними

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

init.yaml

entities: - name: Client connections: - target: Server id: server_connection - name: Server

Запуск процессов из заданных исполняемых файлов

В этом примере будут запущены процесс класса Client из исполняемого файла с именем cl, процесс класса ClientServer из исполняемого файла с именем csr и процесс класса MainServer из исполняемого файла с именем msr. Имена процессов не указаны, поэтому они будут совпадать с именами классов процессов.

init.yaml

entities: - name: Client path: cl - name: ClientServer path: csr - name: MainServer path: msr

Запуск двух процессов из одного исполняемого файла

В этом примере будут запущены процесс класса Client из исполняемого файла с именем Client, а также два процесса классов MainServer и BkServer из исполняемого файла с именем srv. Имена процессов не указаны, поэтому они будут совпадать с именами классов процессов.

init.yaml

entities: - name: Client - name: MainServer path: srv - name: BkServer path: srv

Запуск двух серверов одного класса и клиента и создание IPC-каналов между клиентом и серверами

В этом примере будут запущены процесс класса Client (с именем Client) и два процесса класса Server с именами UserServer и PrivilegedServer. Клиент будет соединен с серверами IPC-каналами с именами server_connection_us и server_connection_ps. Имена исполняемых файлов не указаны, поэтому они будут совпадать с именами классов процессов.

init.yaml

entities: - name: Client connections: - id: server_connection_us target: UserServer - id: server_connection_ps target: PrivilegedServer - task: UserServer name: Server - task: PrivilegedServer name: Server

Установка параметров запуска и переменных окружения программ

В этом примере будут запущены процесс класса VfsFirst (с именем VfsFirst) и процесс класса VfsSecond (с именем VfsSecond). Программа, которая будет исполняться в контексте процесса VfsFirst, будет запущена с параметром -f /etc/fstab, а также получит переменную окружения ROOTFS со значением ramdisk0,0 / ext2 0 и переменную окружения UNMAP_ROMFS со значением 1. Программа, которая будет исполняться в контексте процесса VfsSecond, будет запущена с параметром -l devfs /dev devfs 0. Имена исполняемых файлов не указаны, поэтому они будут совпадать с именами классов процессов.

init.yaml

entities: - name: VfsFirst args: - -f - /etc/fstab env: ROOTFS: ramdisk0,0 / ext2 0 UNMAP_ROMFS: 1 - name: VfsSecond args: - -l - devfs /dev devfs 0
В начало
[Topic using_einit]

Запуск процессов с помощью системной программы ExecutionManager

Компонент ExecutionManager предоставляет интерфейс на языке C++ для создания, запуска и остановки процессов в решениях, построенных на базе KasperskyOS.

Интерфейс компонента ExecutionManager не подходит для использования в коде, написанном на языке C. Для управления процессами на языке C используйте интерфейс task.h библиотеки libkos.

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

Программный интерфейс компонента ExecutionManager описан в статье "Компонент ExecutionManager".

Сценарий использования компонента ExecutionManager

Здесь и далее клиентом называется приложение, использующее API компонента ExecutionManager для управления другими приложениями.

Типовой сценарий использования компонента ExecutionManager включает следующие шаги:

  1. Добавление программы ExecutionManager в решение. Чтобы добавить ExecutionManager в решение, необходимо:
    find_package (execution_manager REQUIRED) include_directories (${execution_manager_INCLUDE}) add_subdirectory (execution_manager)

    Для работы программы ExecutionManager необходима программа BlobContainer. Эта программа автоматически добавляется в решение при добавлении ExecutionManager.

    • Компонент ExecutionManager поставляется в составе SDK в виде набора статических библиотек и заголовочных файлов и собирается под конкретное решение с помощью CMake-команды create_execution_manager_entity() из CMake-библиотеки execution_manager.

      Чтобы собрать программу ExecutionManager, необходимо в корневой директории проекта создать директорию с именем execution_manager, а в ней создать файл CMakeLists.txt, в котором содержится команда create_execution_manager_entity().

      CMake-команда create_execution_manager_entity() принимает следующие параметры:

      Обязательный параметр ENTITY, в котором указывается имя исполняемого файла для программы ExecutionManager.

      Опциональные параметры:

      • DEPENDS - дополнительные зависимости для сборки программы ExecutionManager.
      • MAIN_CONN_NAME - имя IPC-канала для соединения с процессом ExecutionManager. Должно совпадать со значением переменной mainConnection при обращении к API ExecutionManager в коде клиента.
      • ROOT_PATH - путь к корневой директории для служебных файлов программы ExecutionManager, по умолчанию "/ROOT".
      • VFS_CLIENT_LIB - имя клиентской транспортной библиотеки для подключения программы ExecutionManager к программе VFS.
    include (execution_manager/create_execution_manager_entity) create_execution_manager_entity( ENTITY ExecMgrEntity MAIN_CONN_NAME ${ENTITY_NAME} ROOT_PATH "/root" VFS_CLIENT_LIB ${vfs_CLIENT_LIB})
    • При сборке решения (файл CMakeLists.txt для программы Einit) добавить следующие исполняемые файлы в образ решения:
      • исполняемый файл программы ExecutionManager;
      • исполняемый файл программы BlobContainer.
  2. Компоновка исполняемого файла клиента с клиентской прокси-библиотекой ExecutionManager, для чего необходимо в файле CMakeLists.txt для сборки клиента добавить следующую команду:
    target_link_libraries (<имя CMake-цели для сборки клиента> ${execution_manager_EXECMGR_PROXY})
  3. Добавление разрешений для необходимых событий в описание политики безопасности решения:
    1. Чтобы программа ExecutionManager могла запускать другие процессы, политика безопасности решения должна разрешать следующие взаимодействия для класса процессов execution_manager.ExecMgrEntity:
      • События безопасности вида execute для всех классов запускаемых процессов.
      • Доступ ко всем службам программы VFS.
      • Доступ ко всем службам программы BlobContainer.
      • Доступ к службам ядра Sync, Task, VMM, Thread, HAL, Handle, FS, Notice, CM и Profiler (их описания находятся в директории sysroot-*-kos/include/kl/core из состава SDK).
    2. Чтобы клиент мог обращаться к программе ExecutionManager, политика безопасности решения должна разрешать следующие взаимодействия для класса клиентского процесса:
      • Доступ к соответствующим службам программы ExecutionManager (их описания находятся в директории sysroot-*-kos/include/kl/execution_manager из состава SDK).
  4. Использование API программы ExecutionManager в коде клиента.

    Для этого необходимо использовать заголовочный файл component/package_manager/kos_ipc/package_manager_proxy.h. Подробнее см. "Компонент ExecutionManager".

В начало
[Topic app_static_start]

Обзор: программа Env

Системная программа Env предназначена для установки параметров запуска и переменных окружения программ. Если программа Env включена в решение, то процессы, соединенные IPC-каналом с процессом Env, при своем запуске автоматически отправляют IPC-запросы этой программе и получают параметры запуска и переменные окружения.

Использование системной программы Env является устаревшим способом установки параметров запуска и переменных окружения программ. Установку параметров запуска и переменных окружения программ нужно выполнять через файл init.yaml.in или init.yaml.

Если значение параметра запуска или переменной окружения программы задано как через программу Env, так и через файл init.yaml.in или init.yaml, то будет применяться значение, заданное через программу Env.

Чтобы использовать программу Env в решении, необходимо:

  1. Разработать код программы Env, используя макросы и функции из заголовочного файла sysroot-*-kos/include/env/env.h из состава KasperskyOS SDK.
  2. Собрать исполняемый файл программы Env, скомпоновав его с библиотекой env_server из состава KasperskyOS SDK.
  3. В init-описании указать, что необходимо запустить процесс Env и соединить с ним другие процессы (Env при этом является сервером). Имя IPC-канала задается макросом ENV_SERVICE_NAME, определенным в заголовочном файле env.h.
  4. Включить исполняемый файл Env в образ решения.

Исходный код программы Env

В исходном коде программы Env используются следующие макросы и функции из заголовочного файла env.h:

  • ENV_REGISTER_ARGS(name,argarr) – установить параметры запуска argarr для программы, которая будет исполняться в контексте процесса с именем name.
  • ENV_REGISTER_VARS(name,envarr) – установить переменные окружения envarr для программы, которая будет исполняться в контексте процесса с именем name.
  • ENV_REGISTER_PROGRAM_ENVIRONMENT(name,argarr,envarr) – установить параметры запуска argarr и переменные окружения envarr для программы, которая будет исполняться в контексте процесса с именем name.
  • envServerRun() – инициализировать серверную часть программы Env, чтобы она могла отвечать на IPC-запросы.

Примеры использования программы Env

В начало
[Topic env_overview]

Примеры установки параметров запуска и переменных окружения программ с помощью Env

Использование системной программы Env является устаревшим способом установки параметров запуска и переменных окружения программ. Установку параметров запуска и переменных окружения программ нужно выполнять через файл init.yaml.in или init.yaml.

Если значение параметра запуска или переменной окружения программы задано как через программу Env, так и через файл init.yaml.in или init.yaml, то будет применяться значение, заданное через программу Env.

Пример установки параметров запуска программы

Исходный код программы Env, которая при запуске процесса с именем NetVfs передаст ему два параметра запуска программы: -l devfs /dev devfs 0 и -l romfs /etc romfs ro:

env.c

#include <env/env.h> #include <stdlib.h> int main(int argc, char** argv) { const char* NetVfsArgs[] = { "-l", "devfs /dev devfs 0", "-l", "romfs /etc romfs ro" }; ENV_REGISTER_ARGS("NetVfs", NetVfsArgs); envServerRun(); return EXIT_SUCCESS; }

Пример установки переменных окружения программы

Исходный код программы Env, которая при запуске процесса с именем Vfs3 передаст ему две переменных окружения программы: ROOTFS=ramdisk0,0 / ext2 0 и UNMAP_ROMFS=1:

env.c

#include <env/env.h> #include <stdlib.h> int main(int argc, char** argv) { const char* Vfs3Envs[] = { "ROOTFS=ramdisk0,0 / ext2 0", "UNMAP_ROMFS=1" }; ENV_REGISTER_VARS("Vfs3", Vfs3Envs); envServerRun(); return EXIT_SUCCESS; }
В начало
[Topic using_env_app]

Файловые системы и сеть

В KasperskyOS работа с файловыми системами и сетью выполняется через отдельную системную программу, реализующую виртуальную файловую систему (англ. Virtual File System, VFS).

В составе SDK компонент VFS представлен набором исполняемых файлов, библиотек, файлов формальной спецификации и заголовочных файлов. Подробнее см. раздел "Состав компонента VFS".

Основной сценарий взаимодействия с системной программой VFS происходит следующим образом:

  1. Прикладная программа соединяется IPC-каналом с системной программой VFS и при сборке компонуется с клиентской библиотекой компонента VFS.
  2. В прикладном коде POSIX-вызовы для работы с файловыми системами и сетью преобразуются в вызовы функций клиентской библиотеки.

    Ввод и вывод в файловые дескрипторы для стандартных потоков ввода-вывода (stdin, stdout и stderr) также преобразуется в обращения к VFS. Если прикладная программа не скомпонована с клиентской библиотекой компонента VFS, то вывод в stdout невозможен. В таком случае возможен только вывод в стандартный поток ошибок (stderr), который в этом случае осуществляется без использования VFS через специальные методы ядра KasperskyOS.

  3. Клиентская библиотека выполняет IPC-запросы к системной программе VFS.
  4. Системная программа VFS принимает IPC-запросы и вызывает соответствующие реализации файловых систем (которые, в свою очередь могут выполнять IPC-запросы к драйверам устройств) или сетевые драйверы.
  5. После обработки запроса, системная программа VFS выполняет ответы на IPC-запросы прикладной программы.

Использование нескольких программ VFS

В решение можно добавить несколько копий системной программы VFS, разделив таким образом информационные потоки разных системных и прикладных программ. Также можно разделить информационные потоки в рамках одной прикладной программы. Подробнее см. "Разделение информационных потоков с помощью VFS-бэкендов".

Включение функциональности VFS в прикладную программу

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

При этом использование функциональности VFS по IPC позволяет разработчику решения:

  • контролировать вызовы методов для работы с сетью и файловыми системами с помощью политики безопасности решения;
  • соединить несколько клиентских программ с одной программой VFS;
  • соединить одну клиентскую программу с двумя программами VFS для раздельной работы с сетью и файловыми системами.

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

Состав компонента VFS

Создание IPC-канала до VFS

Включение функциональности VFS в программу

Обзор: параметры запуска и переменные окружения VFS

Монтирование файловых систем при запуске VFS

Разделение информационных потоков с помощью VFS-бэкендов

Создание VFS-бэкенда

Динамическая настройка сетевого стека

В начало
[Topic sc_filesystems_and_net]

Состав компонента VFS

Компонент VFS реализует виртуальную файловую систему. В составе KasperskyOS SDK компонент VFS представлен набором исполняемых файлов, библиотек, файлов формальной спецификации и заголовочных файлов, позволяющих использовать файловые системы и/или сетевой стек.

Библиотеки VFS

CMake-пакет vfs содержит следующие библиотеки:

  • vfs_fs – содержит реализации файловых систем devfs, ramfs и ROMFS, а также позволяет добавить в VFS реализации других файловых систем.
  • vfs_net – содержит реализацию файловой системы devfs и сетевого стека.
  • vfs_imp – содержит библиотеки vfs_fs и vfs_net.
  • vfs_remote – клиентская транспортная библиотека, которая преобразует локальные вызовы в IPC-запросы к VFS и принимает IPC-ответы.
  • vfs_server – серверная транспортная библиотека VFS, которая принимает IPC-запросы, преобразует их в локальные вызовы и отправляет IPC-ответы.
  • vfs_local – используется для включения функциональности VFS в программу.

Исполняемые файлы VFS

CMake-пакет precompiled_vfs содержит следующие исполняемые файлы:

  • VfsRamFs;
  • VfsSdCardFs;
  • VfsNet.

Исполняемые файлы VfsRamFs и VfsSdCardFs включают в себя библиотеки vfs_server, vfs_fs, vfat и lwext4. Исполняемый файл VfsNet включает в себя библиотеки vfs_server и vfs_imp.

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

Файлы формальной спецификации и заголовочные файлы VFS

В директории sysroot-*-kos/include/kl из состава KasperskyOS SDK находятся следующие файлы VFS:

  • файлы формальной спецификации VfsRamFs.edl, VfsSdCardFs.edl, VfsNet.edl и VfsEntity.edl и сгенерированные из них заголовочные файлы;
  • файл формальной спецификации Vfs.cdl и сгенерированный из него заголовочный файл Vfs.cdl.h;
  • файлы формальной спецификации Vfs*.idl и сгенерированные из них заголовочные файлы.

API библиотеки libc, поддерживаемый VFS

Функциональность VFS доступна программам через API, предоставляемый библиотекой libc.

Функции, реализуемые библиотеками vfs_fs и vfs_net, приведены в таблицах ниже. Символом * отмечены функции, которые включаются в библиотеку vfs_fs опционально (в зависимости от параметров сборки библиотеки).

Функции, реализуемые библиотекой vfs_fs

mount()

unlink()

ftruncate()

lsetxattr()*

umount()

rmdir()

chdir()

fsetxattr()*

open()

mkdir()

fchdir()

getxattr()*

openat()

mkdirat()

chmod()

lgetxattr()*

read()

fcntl()

fchmod()

fgetxattr()*

readv()

statvfs()

fchmodat()

listxattr()*

write()

fstatvfs()

chroot()

llistxattr()*

writev()

getvfsstat()

fsync()

flistxattr()*

stat()

pipe()

fdatasync()

removexattr()*

lstat()

futimens()

pread()

lremovexattr()*

fstat()

utimensat()

pwrite()

fremovexattr()*

fstatat()

link()

sendfile()

acl_set_file()*

lseek()

linkat()

getdents()

acl_get_file()*

close()

symlink()

sync()

acl_delete_def_file()*

rename()

symlinkat()

ioctl()

 

renameat()

unlinkat()

setxattr()*

 

Функции, реализуемые библиотекой vfs_net

read()

bind()

getsockname()

recvfrom()

readv()

listen()

gethostbyname()

recvmsg()

write()

connect()

getnetbyaddr()

send()

writev()

accept()

getnetbyname()

sendto()

fstat()

poll()

getnetent()

sendmsg()

close()

shutdown()

setnetent()

ioctl()

fcntl()

getnameinfo()

endnetent()

sysctl()

fstatvfs()

getaddrinfo()

getprotobyname()

 

pipe()

freeaddrinfo()

getprotobynumber()

 

futimens()

getifaddrs()

getsockopt()

 

socket()

freeifaddrs()

setsockopt()

 

socketpair()

getpeername()

recv()

 

Если в VFS нет реализации вызванной функции, возвращается код ошибки EIO.

В начало
[Topic vfs_overview]

Создание IPC-канала до VFS

В этом примере процесс Client использует файловые системы и сетевой стек, а процесс VfsFsnet обрабатывает IPC-запросы процесса Client, связанные с использованием файловых систем и сетевого стека. Такой подход используется в тех случаях, когда не требуется разделение информационных потоков, связанных с файловыми системами и сетевым стеком.

Имя IPC-канала должно задаваться макросом _VFS_CONNECTION_ID, определенным в заголовочном файле sysroot-*-kos/include/vfs/defs.h из состава KasperskyOS SDK.

Init-описание примера:

init.yaml

- name: Client connections: - target: VfsFsnet id: {var: _VFS_CONNECTION_ID, include: vfs/defs.h} - name: VfsFsnet
В начало
[Topic client_and_vfs_ipc_channel]

Включение функциональности VFS в программу

В этом примере программа Client включает функциональность программы VFS для работы с сетевым стеком (см. рис. ниже).

Библиотеки компонента VFS в составе программы

Выполняется компиляция файла реализации client.c и компоновка с библиотеками vfs_local, vfs_implementation и dnet_implementation:

CMakeLists.txt

project (client) include (platform/nk) # Установка флагов компиляции project_header_default ("STANDARD_GNU_11:YES" "STRICT_WARNINGS:NO") # Генерация файла Client.edl.h nk_build_edl_files (client_edl_files NK_MODULE "client" EDL "${CMAKE_SOURCE_DIR}/resources/edl/Client.edl") add_executable (Client "src/client.c") add_dependencies (Client client_edl_files) # Компоновка с библиотеками VFS target_link_libraries (Client ${vfs_LOCAL_LIB} ${vfs_IMPLEMENTATION_LIB} ${dnet_IMPLEMENTATION_LIB}

Если бы программа Client использовала файловые системы, то нужно было бы выполнить компоновку с библиотеками vfs_local и vfs_fs, а также с библиотеками реализации этих файловых систем. Кроме того, нужно было бы добавить в решение драйвер блочного устройства.

В начало
[Topic client_and_vfs_linked]

Обзор: параметры запуска и переменные окружения VFS

Параметры запуска программы VFS

  • -l <запись в формате fstab>

    Параметр запуска -l монтирует заданную файловую систему.

  • -f <путь к файлу fstab>

    Параметр -f монтирует файловые системы, указанные в файле fstab. Если переменная окружения UNMAP_ROMFS не определена, то поиск файла fstab будет выполнен в ROMFS-образе. Если переменная окружения UNMAP_ROMFS определена, то поиск файла fstab будет выполнен в файловой системе, заданной через переменную окружения ROOTFS.

Примеры использования параметров запуска программы VFS

Переменные окружения программы VFS

  • UNMAP_ROMFS

    Если переменная окружения UNMAP_ROMFS определена, то ROMFS-образ будет удален из памяти. Это позволяет сэкономить память, а также при использовании параметра запуска -f дает возможность выполнить поиск файла fstab не в ROMFS-образе, а в файловой системе, заданной через переменную окружения ROOTFS.

    Пример использования переменной окружения UNMAP_ROMFS

  • ROOTFS = <запись в формате fstab>

    Переменная окружения ROOTFS позволяет монтировать заданную файловую систему в корневую директорию. При использовании параметра запуска -f комбинация переменных окружения ROOTFS и UNMAP_ROMFS дает возможность выполнить поиск файла fstab не в ROMFS-образе, а в файловой системе, заданной через переменную окружения ROOTFS.

    Пример использования переменной окружения ROOTFS

  • VFS_CLIENT_MAX_THREADS

    Переменная окружения VFS_CLIENT_MAX_THREADS позволяет переопределить параметр конфигурирования SDK VFS_CLIENT_MAX_THREADS.

  • _VFS_NETWORK_BACKEND=<имя VFS-бэкенда>:<имя IPC-канала до процесса VFS>

    Переменная окружения _VFS_NETWORK_BACKEND задает VFS-бэкенд для работы с сетевым стеком. Можно указать имя стандартного VFS-бэкенда: client (для программы, исполняющейся в контексте клиентского процесса), server (для программы VFS, исполняющейся в контексте серверного процесса) или local, а также имя пользовательского VFS-бэкенда. Если используется VFS-бэкенд local, то имя IPC-канала не указывается (_VFS_NETWORK_BACKEND=local:). Может быть указано более одного IPC-канала через запятую.

  • _VFS_FILESYSTEM_BACKEND=<имя VFS-бэкенда>:<имя IPC-канала до процесса VFS>

    Переменная окружения _VFS_FILESYSTEM_BACKEND задает VFS-бэкенд для работы с файловыми системами. Имя VFS-бэкенда и имя IPC-канала до процесса VFS задаются так же, как и в переменной окружения _VFS_NETWORK_BACKEND.

Значения по умолчания для параметров запуска и переменных окружения VFS

Для исполняемого файла VfsRamFs:

ROOTFS = ramdisk0,0 / ext4 0 VFS_FILESYSTEM_BACKEND = server:kl.VfsRamFs

Для исполняемого файла VfsSdCardFs:

ROOTFS = mmc0,0 / fat32 0 VFS_FILESYSTEM_BACKEND = server:kl.VfsSdCardFs -l nodev /tmp ramfs 0 -l nodev /var ramfs 0

Для исполняемого файла VfsNet:

VFS_NETWORK_BACKEND = server:kl.VfsNet VFS_FILESYSTEM_BACKEND = server:kl.VfsNet -l devfs /dev devfs 0
В начало
[Topic vfs_args_and_envs_overview]

Монтирование файловых систем при запуске VFS

При запуске программы VFS по умолчанию монтируется только файловая система RAMFS в корневую директорию. Если требуется монтировать другие файловые системы, это можно сделать не только с помощью вызова функции mount(), но и установив параметры запуска и переменные окружения программы VFS.

Файловые системы ROMFS и squashfs предназначены только для чтения, поэтому для монтирования этих файловых систем нужно указать параметр ro.

Использование параметра запуска -l

Одним из способов монтировать файловую систему является установка для программы VFS параметра запуска -l <запись в формате fstab>.

В этих примерах при запуске программы VFS будут монтированы файловые системы devfs и ROMFS:

init.yaml.(in)

... - name: VfsFirst args: - -l - devfs /dev devfs 0 - -l - romfs /etc romfs ro ...

CMakeLists.txt

... set_target_properties (${vfs_ENTITY} PROPERTIES EXTRA_ARGS " - -l - devfs /dev devfs 0 - -l - romfs /etc romfs ro") ...

Использование файла fstab из ROMFS-образа

При сборке решения можно добавить файл fstab в ROMFS-образ. Этот файл можно использовать для монтирования файловых систем, установив для программы VFS параметр запуска -f <путь к файлу fstab>.

В этих примерах при запуске программы VFS будут монтированы файловые системы, заданные через файл fstab, который был добавлен при сборке решения в ROMFS-образ:

init.yaml.(in)

... - name: VfsSecond args: - -f - fstab ...

CMakeLists.txt

... set_target_properties (${vfs_ENTITY} PROPERTIES EXTRA_ARGS " - -f - fstab") ...

Использование "внешнего" файла fstab

Если файл fstab находится не в ROMFS-образе, а в другой файловой системе, то для использования этого файла необходимо установить для программы VFS следующие параметры запуска и переменные окружения:

  1. ROOTFS. Эта переменная окружения позволяет монтировать в корневую директорию файловую систему, содержащую файл fstab.
  2. UNMAP_ROMFS. Если эта переменная окружения определена, то поиск файла fstab будет выполнен в файловой системе, заданной через переменную окружения ROOTFS.
  3. -f. Этот параметр запуска используется, чтобы монтировать файловые системы, указанные в файле fstab.

В этих примерах при запуске программы VFS в корневую директорию будет монтирована файловая система ext2, в которой должен находиться файл fstab по пути /etc/fstab:

init.yaml.(in)

... - name: VfsThird args: - -f - /etc/fstab env: ROOTFS: ramdisk0,0 / ext2 0 UNMAP_ROMFS: 1 ...

CMakeLists.txt

... set_target_properties (${vfs_ENTITY} PROPERTIES EXTRA_ARGS " - -f - /etc/fstab" EXTRA_ENV " ROOTFS: ramdisk0,0 / ext2 0 UNMAP_ROMFS: 1") ...
В начало
[Topic mount_on_start]

Разделение информационных потоков с помощью VFS-бэкендов

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

Процесс Client использует файловые системы и сетевой стек. Процесс VfsFirst обеспечивает работу с файловыми системами, а процесс VfsSecond дает возможность работать с сетевым стеком. Через переменные окружения программ, исполняющихся в контекстах процессов Client, VfsFirst и VfsSecond, заданы VFS-бэкенды, которые обеспечивают раздельное использование файловых систем и сетевого стека. В результате этого IPC-запросы процесса Client, связанные с использованием файловых систем, обрабатываются процессом VfsFirst, а IPC-запросы процесса Client, связанные с использованием сетевого стека, обрабатываются процессом VfsSecond (см. рис. ниже).

Схема взаимодействия процессов

Init-описание примера:

init.yaml

entities: - name: Client connections: - target: VfsFirst id: VFS1 - target: VfsSecond id: VFS2 env: _VFS_FILESYSTEM_BACKEND: client:VFS1 _VFS_NETWORK_BACKEND: client:VFS2 - name: VfsFirst env: _VFS_FILESYSTEM_BACKEND: server:VFS1 - name: VfsSecond env: _VFS_NETWORK_BACKEND: server:VFS2
В начало
[Topic client_and_two_vfs]

Создание VFS-бэкенда

В этом примере показано, как создать и использовать собственный VFS-бэкенд.

Процесс Client использует файловые системы fat32 и ext4. Процесс VfsFirst обеспечивает работу с файловой системой fat32, а процесс VfsSecond дает возможность работать с файловой системой ext4. Через переменные окружения программ, исполняющихся в контекстах процессов Client, VfsFirst и VfsSecond, заданы VFS-бэкенды, которые обеспечивают обработку IPC-запросов процесса Client процессом VfsFirst или VfsSecond в зависимости от того, какую файловую систему использует процесс Client. В результате этого IPC-запросы процесса Client, связанные с использованием файловой системы fat32, обрабатываются процессом VfsFirst, а IPC-запросы процесса Client, связанные с использованием файловой системы ext4, обрабатываются процессом VfsSecond (см. рис. ниже).

На стороне процесса VfsFirst файловая система fat32 монтируется в директорию /mnt1. На стороне процесса VfsSecond файловая система ext4 монтируется в директорию /mnt2. Пользовательский VFS-бэкенд custom_client, используемый на стороне процесса Client, позволяет отправлять IPC-запросы по IPC-каналу VFS1 или VFS2 в зависимости от того, начинается ли путь к файлу с /mnt1 или нет. При этом пользовательский VFS-бэкенд использует в качестве посредника стандартный VFS-бэкенд client.

Схема взаимодействия процессов

Исходный код VFS-бэкенда

Этот файл реализации содержит исходный код VFS-бэкенда custom_client, использующего стандартные VFS-бэкенды client:

backend.c

#include <vfs/vfs.h> #include <stdio.h> #include <stdlib.h> #include <platform/compiler.h> #include <pthread.h> #include <errno.h> #include <string.h> #include <getopt.h> #include <assert.h> /* Код управления файловыми дескрипторами */ #define MAX_FDS 50 struct entry { Handle handle; bool is_vfat; }; struct fd_array { struct entry entries[MAX_FDS]; int pos; pthread_rwlock_t lock; }; struct fd_array fds = { .pos = 0, .lock = PTHREAD_RWLOCK_INITIALIZER }; int insert_entry(Handle fd, bool is_vfat) { pthread_rwlock_wrlock(&fds.lock); if (fds.pos == MAX_FDS) { pthread_rwlock_unlock(&fds.lock); return -1; } fds.entries[fds.pos].handle = fd; fds.entries[fds.pos].is_vfat = is_vfat; fds.pos++; pthread_rwlock_unlock(&fds.lock); return 0; } struct entry *find_entry(Handle fd) { pthread_rwlock_rdlock(&fds.lock); for (int i = 0; i < fds.pos; i++) { if (fds.entries[i].handle == fd) { pthread_rwlock_unlock(&fds.lock); return &fds.entries[i]; } } pthread_rwlock_unlock(&fds.lock); return NULL; } /* Структура пользовательского VFS-бэкенда */ struct context { struct vfs wrapper; pthread_rwlock_t lock; struct vfs *vfs_vfat; struct vfs *vfs_ext4; }; struct context ctx = { .wrapper = { .dtor = _vfs_backend_dtor, .disconnect_all_clients = _disconnect_all_clients, .getstdin = _getstdin, .getstdout = _getstdout, .getstderr = _getstderr, .open = _open, .read = _read, .write = _write, .close = _close, } }; /* Реализация методов пользовательского VFS-бэкенда */ static bool is_vfs_vfat_path(const char *path) { char vfat_path[5] = "/mnt1"; if (memcmp(vfat_path, path, sizeof(vfat_path)) != 0) return false; return true; } static void _vfs_backend_dtor(struct vfs *vfs) { ctx.vfs_vfat->dtor(ctx.vfs_vfat); ctx.vfs_ext4->dtor(ctx.vfs_ext4); } static void _disconnect_all_clients(struct vfs *self, int *error) { (void)self; (void)error; ctx.vfs_vfat->disconnect_all_clients(ctx.vfs_vfat, error); ctx.vfs_ext4->disconnect_all_clients(ctx.vfs_ext4, error); } static Handle _getstdin(struct vfs *self, int *error) { (void)self; Handle handle = ctx.vfs_vfat->getstdin(ctx.vfs_vfat, error); if (handle != INVALID_HANDLE) { if (insert_entry(handle, true)) { *error = ENOMEM; return INVALID_HANDLE; } } return handle; } static Handle _getstdout(struct vfs *self, int *error) { (void)self; Handle handle = ctx.vfs_vfat->getstdout(ctx.vfs_vfat, error); if (handle != INVALID_HANDLE) { if (insert_entry(handle, true)) { *error = ENOMEM; return INVALID_HANDLE; } } return handle; } static Handle _getstderr(struct vfs *self, int *error) { (void)self; Handle handle = ctx.vfs_vfat->getstderr(ctx.vfs_vfat, error); if (handle != INVALID_HANDLE) { if (insert_entry(handle, true)) { *error = ENOMEM; return INVALID_HANDLE; } } return handle; } static Handle _open(struct vfs *self, const char *path, int oflag, mode_t mode, int *error) { (void)self; Handle handle; bool is_vfat = false; if (is_vfs_vfat_path(path)) { handle = ctx.vfs_vfat->open(ctx.vfs_vfat, path, oflag, mode, error); is_vfat = true; } else handle = ctx.vfs_ext4->open(ctx.vfs_ext4, path, oflag, mode, error); if (handle == INVALID_HANDLE) return INVALID_HANDLE; if (insert_entry(handle, is_vfat)) { if (is_vfat) ctx.vfs_vfat->close(ctx.vfs_vfat, handle, error); *error = ENOMEM; return INVALID_HANDLE; } return handle; } static ssize_t _read(struct vfs *self, Handle fd, void *buf, size_t count, bool *nodata, int *error) { (void)self; struct entry *found_entry = find_entry(fd); if (found_entry != NULL && found_entry->is_vfat) return ctx.vfs_vfat->read(ctx.vfs_vfat, fd, buf, count, nodata, error); return ctx.vfs_ext4->read(ctx.vfs_ext4, fd, buf, count, nodata, error); } static ssize_t _write(struct vfs *self, Handle fd, const void *buf, size_t count, int *error) { (void)self; struct entry *found_entry = find_entry(fd); if (found_entry != NULL && found_entry->is_vfat) return ctx.vfs_vfat->write(ctx.vfs_vfat, fd, buf, count, error); return ctx.vfs_ext4->write(ctx.vfs_ext4, fd, buf, count, error); } static int _close(struct vfs *self, Handle fd, int *error) { (void)self; struct entry *found_entry = find_entry(fd); if (found_entry != NULL && found_entry->is_vfat) return ctx.vfs_vfat->close(ctx.vfs_vfat, fd, error); return ctx.vfs_ext4->close(ctx.vfs_ext4, fd, error); } /* Конструктор пользовательского VFS-бэкенда. ctx.vfs_vfat и ctx.vfs_ext4 инициализируются * как стандартные бэкенды с именем "client". */ static struct vfs *_vfs_backend_create(Handle client_id, const char *config, int *error) { (void)config; ctx.vfs_vfat = _vfs_init("client", client_id, "VFS1", error); assert(ctx.vfs_vfat != NULL && "Can't initialize client backend!"); assert(ctx.vfs_vfat->dtor != NULL && "VFS FS backend has not set the destructor!"); ctx.vfs_ext4 = _vfs_init("client", client_id, "VFS2", error); assert(ctx.vfs_ext4 != NULL && "Can't initialize client backend!"); assert(ctx.vfs_ext4->dtor != NULL && "VFS FS backend has not set the destructor!"); return &ctx.wrapper; } /* Регистрация пользовательского VFS-бэкенда под именем custom_client */ static void _vfs_backend(create_vfs_backend_t *ctor, const char **name) { *ctor = &_vfs_backend_create; *name = "custom_client"; } REGISTER_VFS_BACKEND(_vfs_backend)

Компоновка программы Client

Создание статической библиотеки VFS-бэкенда:

CMakeLists.txt

... add_library (backend_client STATIC "src/backend.c") ...

Компоновка программы Client со статической библиотеки VFS-бэкенда:

CMakeLists.txt

... add_dependencies (Client vfs_backend_client backend_client) target_link_libraries (Client pthread ${vfs_CLIENT_LIB} "-Wl,--whole-archive" backend_client "-Wl,--no-whole-archive" backend_client ) ...

Установка параметров запуска и переменных окружения программ

Init-описание примера:

init.yaml

entities: - name: vfs_backend.Client connections: - target: vfs_backend.VfsFirst id: VFS1 - target: vfs_backend.VfsSecond id: VFS2 env: _VFS_FILESYSTEM_BACKEND: custom_client:VFS1,VFS2 - name: vfs_backend.VfsFirst args: - -l - ahci0 /mnt1 fat32 0 env: _VFS_FILESYSTEM_BACKEND: server:VFS1 - name: vfs_backend.VfsSecond - -l - ahci1 /mnt2 ext4 0 env: _VFS_FILESYSTEM_BACKEND: server:VFS2
В начало
[Topic vfs_backends]

Динамическая настройка сетевого стека

Чтобы изменить параметры сетевого стека, заданные по умолчанию, нужно использовать функцию sysctl() или sysctlbyname(), объявленные в заголовочном файле sysroot-*-kos/include/sys/sysctl.h из состава KasperskyOS SDK. Параметры, которые можно изменить, приведены в таблице ниже.

Настраиваемые параметры сетевого стека

Название параметра

Описание параметра

net.inet.ip.ttl

Максимальное время жизни (англ. Time To Live, TTL) отправляемых IP-пакетов. Не влияет на протокол ICMP.

net.inet.ip.mtudisc

Если имеет значение 1, то задействован режим "Path MTU Discovery" (RFC 1191), влияющий на максимальный размер TCP-сегмента (англ. Maximum Segment Size, MSS). В этом режиме значение MSS определяется ограничениями узлов сети. Если режим "Path MTU Discovery" не задействован, то значение MSS не превышает заданного параметром net.inet.tcp.mssdflt.

net.inet.tcp.mssdflt

Значение MSS (в байтах), которое применяется, если только взаимодействующая сторона не сообщила это значение при открытии TCP-соединения, или не задействован режим "Path MTU Discovery" (RFC 1191). Также это значение MSS передается взаимодействующей стороне при открытии TCP-соединения.

net.inet.tcp.minmss

Минимальное значение MSS, в байтах.

net.inet.tcp.mss_ifmtu

Если имеет значение 1, то значение MSS при открытии TCP-соединения рассчитывается, исходя из максимального размера блока передаваемых данных (англ. Maximum Transmission Unit, MTU) задействованного сетевого интерфейса. Если имеет значение 0, то значение MSS при открытии TCP-соединения рассчитывается, исходя из MTU того сетевого интерфейса, который имеет наибольшее значение этого параметра среди всех имеющихся сетевых интерфейсов (кроме loopback-интерфейса).

net.inet.tcp.keepcnt

Число повторных отправок проверочных сообщений (англ. Keep-Alive Probes, KA) без ответа, после выполнения которых TCP-соединение считается закрытым. Если имеет значение 0, то число отправок KA не ограничено.

net.inet.tcp.keepidle

Время неактивности TCP-соединения, по истечении которого начинают отправляться KA. Задается в условных единицах, которые можно перевести в секунды, разделив на значение параметра net.inet.tcp.slowhz.

net.inet.tcp.keepintvl

Время между повторными отправками KA при отсутствии ответа. Задается в условных единицах, которые можно перевести в секунды, разделив на значение параметра net.inet.tcp.slowhz.

net.inet.tcp.recvspace

Размер буфера для принимаемых по протоколу TCP данных, в байтах.

net.inet.tcp.sendspace

Размер буфера для отправляемых по протоколу TCP данных, в байтах.

net.inet.udp.recvspace

Размер буфера для принимаемых по протоколу UDP данных, в байтах.

net.inet.udp.sendspace

Размер буфера для отправляемых по протоколу UDP данных, в байтах.

Пример настройки MSS:

static const int mss_max = 1460; static const int mss_min = 100; static const char* mss_max_opt_name = "net.inet.tcp.mssdflt"; static const char* mss_min_opt_name = "net.inet.tcp.minmss"; int main(void) { ... if ((sysctlbyname(mss_max_opt_name, NULL, NULL, &mss_max, sizeof(mss_max)) != 0) || (sysctlbyname(mss_min_opt_name, NULL, NULL, &mss_min, sizeof(mss_min)) != 0)) { ERROR(START, "Can't set tcp default maximum/minimum MSS value."); return EXIT_FAILURE; } }
В начало
[Topic vfs_net_stack_dyn_conf][Topic sc_using_ipc]

Создание IPC-каналов

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

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

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

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

Статическое создание IPC-каналов имеет следующие особенности:

  • Клиент и сервер еще не запущены в момент создания IPC-канала.
  • Создание IPC-канала выполняется родительским процессом, запускающим клиента и сервера (обычно это Einit).
  • В случае удаления IPC-канал невозможно восстановить.
  • Чтобы получить IPC-дескриптор и идентификатор службы (riid) после создания IPC-канала, клиент и сервер должны использовать API, определенный в заголовочном файле sysroot-*-kos/include/coresrv/sl/sl_api.h из состава KasperskyOS SDK.

Статически создаются IPC-каналы, заданные в init-описании.

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

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

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

Добавление в решение службы из состава KasperskyOS Community Edition

Чтобы программа 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-сообщения определяются IDL-описаниями серверов.

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

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

Фиксированная часть IPC-сообщения содержит RIID, MID и опционально параметры интерфейсных методов фиксированного размера.

Параметры фиксированного размера – это параметры, которые имеют IDL-типы фиксированного размера.

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

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

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

Арена IPC-сообщения

Арена IPC-сообщения (далее также арена) содержит параметры интерфейсных методов (и/или элементы этих параметров) переменного размера.

Параметры переменного размера – это параметры, которые имеют IDL‑типы переменного размера.

Подробнее см. "Работа с ареной IPC-сообщений".

Максимальный размер IPC-сообщения

Максимальный размер IPC-сообщения определяется параметрами ядра KasperskyOS. На большинстве поддерживаемых KasperskyOS аппаратных платформ совокупный размер фиксированной части и арены IPC-сообщения не может превышать 4, 8 или 16 МБ.

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

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

Реализация IPC-взаимодействия

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

Реализация простейшего 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> … ServiceId id; Handle handle = ServiceLocatorRegister("server_connection", NULL, 0, &id);

Подробнее см. примеры 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-каналу невозможно.

Получение IPC-дескриптора при динамическом создании канала

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

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

Если динамически созданный IPC-канал больше не требуется, его клиентский и серверный дескрипторы нужно закрыть. При необходимости IPC-канал может быть создан снова.

В начало
[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]

Работа с ареной IPC-сообщений

Общие сведения об арене

С точки зрения разработчика решения на базе KasperskyOS арена IPC-сообщений представляет собой байтовый буфер в памяти процесса, предназначенный для хранения передаваемых через IPC данных переменного размера, то есть входных, выходных и error-параметров интерфейсных методов (и/или элементов этих параметров), которые имеют IDL-типы переменного размера. Также арена используется при обращении к модулю безопасности Kaspersky Security Module для хранения входных параметров методов интерфейса безопасности (и/или элементов этих параметров), которые имеют IDL-типы переменного размера. (Параметры интерфейсных методов постоянного размера хранятся в фиксированной части IPC-сообщения.) Арены используются как на стороне клиента, так и на стороне сервера. Одна арена предназначена либо для передачи, либо для приема через IPC данных переменного размера, но не для передачи и приема этих данных одновременно, то есть условно арены можно разделить на арены IPC-запросов (содержат входные параметры интерфейсных методов) и арены IPC-ответов (содержат выходные и error-параметры интерфейсных методов).

Через IPC передается только использованная часть арены, то есть занятая данными. (При отсутствии данных арена не передается.) Использованная часть арены включает один или несколько участков. В одном участке арены хранится массив объектов одного типа, например, массив однобайтовых объектов или массив структур. В разных участках арены могут храниться массивы объектов разного типа. Адрес начала арены должен быть выравнен на границу 2^N-байтовой последовательности, где 2^N – значение, которое больше либо равно размеру наибольшего примитивного типа в арене (например, наибольшего поля примитивного типа в структуре). Адрес участка арены также должен быть выравнен на границу 2^N-байтовой последовательности, где 2^N – значение, которое больше либо равно размеру наибольшего примитивного типа в участке арены.

Необходимость наличия нескольких участков в арене возникает, если интерфейсный метод имеет несколько входных, выходных или error-параметров переменного размера, а также если несколько элементов входных, выходных или error-параметров интерфейсного метода имеют переменный размер. Например, если интерфейсный метод имеет входной параметр IDL-типа sequence и входной параметр IDL-типа bytes, то в арене IPC-запросов будет как минимум два участка, но могут потребоваться и дополнительные участки, если параметр IDL-типа sequence состоит из элементов IDL-типа переменного размера (например, string, то есть элементами последовательности являются строковые буферы). Также, к примеру, если интерфейсный метод имеет один выходной параметр IDL-типа struct, который содержит два поля типа bytes и string, то в арене IPC-ответов будет два участка.

Из-за выравнивания адресов участков арены между этими участками могут появляться неиспользованные промежутки, поэтому размер использованной части арены может превышать размер помещенных в нее данных.

API для работы с ареной

Набор функций и макросов для работы с ареной определен в заголовочном файле sysroot-*-kos/include/nk/arena.h из состава KasperskyOS SDK. Также функция для копирования строки в арену объявлена в заголовочном файле sysroot-*-kos/include/coresrv/nk/transport-kos.h из состава KasperskyOS SDK.

Сведения о функциях и макросах, определенных в заголовочном файле sysroot-*-kos/include/nk/arena.h, приведены в таблице ниже. В этих функциях и макросах арена и участок арены идентифицируются дескриптором арены (тип nk_arena) и дескриптором участка арены (тип nk_ptr_t) соответственно. Дескриптор арены представляет собой структуру, содержащую три указателя: на начало арены, на начало неиспользованной части арены и на конец арены. Дескриптор участка арены представляет собой структуру, содержащую смещение участка арены в байтах (относительно начала арены) и размер участка арены в байтах. (Тип дескриптора участка арены определен в заголовочном файле sysroot-*-kos/include/nk/types.h. из состава KasperskyOS SDK.)

Создание арены

Чтобы передавать через IPC параметры интерфейсных методов переменного размера, нужно создать арены как на стороне клиента, так и на стороне сервера. (При обработке IPC-запросов на стороне сервера с использованием функции NkKosDoDispatch(), определенной в заголовочном файле sysroot-*-kos/include/coresrv/nk/transport-kos-dispatch.h из состава KasperskyOS SDK, арены IPC-запросов и IPC-ответов создаются автоматически.)

Чтобы создать арену, нужно создать буфер (в стеке или куче) и инициализировать дескриптор арены.

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

Чтобы инициализировать дескриптор арены, используя указатель на уже созданный буфер, нужно использовать функцию или макрос API:

  • макрос NK_ARENA_INITIALIZER();
  • функцию nk_arena_init();
  • функцию nk_arena_create();
  • макрос NK_ARENA_FINAL();
  • макрос nk_arena_init_final().

Тип указателя не имеет значения, поскольку в коде функций и макросов API этот указатель приводится к указателю на однобайтовый объект.

Макрос NK_ARENA_INITIALIZER() и функции nk_arena_init() и nk_arena_create() инициализируют дескриптор арены, которая может содержать один и более участков. Макросы NK_ARENA_FINAL() и nk_arena_init_final() инициализируют дескриптор арены, которая на протяжении всего своего жизненного цикла содержит только один участок, занимающий всю арену.

Чтобы создать буфер в стеке и инициализировать дескриптор одним шагом, нужно использовать макрос NK_ARENA_AUTO(). Этот макрос создает арену, которая может содержать один и более участков, а адрес буфера, созданного этим макросом, имеет достаточное выравнивание для помещения в этот буфер данных примитивного типа максимального размера.

Размер арены должен быть достаточен, чтобы с учетом выравнивания адресов участков вместить параметры переменного размера для IPC-запросов или IPC-ответов одного интерфейсного метода или множества интерфейсных методов, соответствующих одному интерфейсу, компоненту или классу процессов. Автоматически генерируемый транспортный код (заголовочные файлы *.idl.h, *.cdl.h, *.edl.h) содержит константы *_arena_size, значения которых гарантированно соответствуют достаточным размерам арен в байтах.

Заголовочные файлы *.idl.h, *.cdl.h, *.edl.h содержат следующие константы *_arena_size:

  • <имя интерфейса>_<имя интерфейсного метода>_req_arena_size – размер арены IPC-запросов для указанного интерфейсного метода указанного интерфейса;
  • <имя интерфейса>_<имя интерфейсного метода>_res_arena_size – размер арены IPC-ответов для указанного интерфейсного метода указанного интерфейса;
  • <имя интерфейса>_req_arena_size – размер арены IPC-запросов для любого интерфейсного метода указанного интерфейса;
  • <имя интерфейса>_res_arena_size – размер арены IPC-ответов для любого интерфейсного метода указанного интерфейса.

Заголовочные файлы *.cdl.h, *.edl.h дополнительно содержат следующие константы *_arena_size:

  • <имя компонента>_component_req_arena_size – размер арены IPC-запросов для любого интерфейсного метода указанного компонента;
  • <имя компонента>_component_res_arena_size – размер арены IPC-ответов для любого интерфейсного метода указанного компонента.

Заголовочные файлы *.edl.h дополнительно содержат следующие константы *_arena_size:

  • <имя класса процессов>_entity_req_arena_size – размер арены IPC-запросов для любого интерфейсного метода указанного класса процессов;
  • <имя класса процессов>_entity_res_arena_size – размер арены IPC-ответов для любого интерфейсного метода указанного класса процессов.

Константы, содержащие размер арены IPC-запросов или IPC-ответов для одного интерфейсного метода (<имя интерфейса>_<имя интерфейсного метода>_req_arena_size и <имя интерфейса>_<имя интерфейсного метода>_res_arena_size), предназначены для использования на стороне клиента. Остальные константы могут использоваться как на стороне клиента, так и на стороне сервера.

Примеры создания арены:

/* Пример 1 */ alignas(8) char reqBuffer[Write_WriteInLog_req_arena_size]; struct nk_arena reqArena = NK_ARENA_INITIALIZER( reqBuffer, reqBuffer + sizeof(reqBuffer)); /* Пример 2 */ struct nk_arena res_arena; char res_buf[kl_rump_DhcpcdConfig_GetOptionNtpServers_res_arena_size]; nk_arena_init(&res_arena, res_buf, res_buf + sizeof(res_buf)); /* Пример 3 */ char req_buffer[kl_CliApplication_Run_req_arena_size]; struct nk_arena req_arena = nk_arena_create(req_buffer, sizeof(req_buffer)); /* Пример 4 */ nk_ptr_t ptr; const char *cstr = "example"; nk_arena arena = NK_ARENA_FINAL(&ptr, cstr, strlen(cstr)); /* Пример 5 */ const char *path = "path_to_file"; size_t len = strlen(path); /* Структура для сохранения фиксированной части IPC-запроса */ struct kl_VfsFilesystem_Rmdir_req req; struct nk_arena req_arena; nk_arena_init_final(&req_arena, &req.path, path, len); /* Пример 6 */ struct nk_arena res_arena = NK_ARENA_AUTO(kl_Klog_component_res_arena_size);

Заполнение арены данными перед передачей через IPC

Перед передачей IPC-запроса на стороне клиента или IPC-ответа на стороне сервера арену нужно заполнить данными. Если для создания арены используется макрос NK_ARENA_FINAL() или nk_arena_init_final(), то резервировать участок арены не требуется, а нужно только заполнить этот участок данными. Если для создания арены используется макрос NK_ARENA_INITIALIZER() или NK_ARENA_AUTO() либо функция nk_arena_init() или nk_arena_create(), то в арене необходимо зарезервировать один или несколько участков, чтобы поместить в них данные. Чтобы зарезервировать участок арены, нужно использовать функцию или макрос API:

  • функцию __nk_arena_alloc();
  • макрос nk_arena_store();
  • функцию __nk_arena_store();
  • макрос nk_arena_alloc();
  • функцию NkKosCopyStringToArena().

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

Макрос nk_arena_store() и функции __nk_arena_store() и NkKosCopyStringToArena() не только резервируют участок арены, но и копируют данные в этот участок.

Макрос nk_arena_alloc() позволяет получить адрес зарезервированного участка арены. Также адрес участка арены можно получить, используя функцию __nk_arena_get() или макрос nk_arena_get(), которые дополнительно через выходной параметр передают размер арены.

Зарезервированный участок арены можно уменьшить. Для этого нужно использовать макрос nk_arena_shrink() или функцию _nk_arena_shrink().

Чтобы отменить текущее резервирование участков арены для последующего резервирования новых участков под другие данные (после отправки IPC-сообщения), нужно вызвать функцию nk_arena_reset(). Если для создания арены используется макрос NK_ARENA_FINAL() или nk_arena_init_final(), то отменять резервирование участка не требуется, так как такая арена на протяжении всего своего жизненного цикла содержит один участок, занимающий всю арену.

Примеры заполнения арены данными:

/* Пример 1 */ char req_buffer[kl_rump_NpfctlFilter_TableAdd_req_arena_size]; struct nk_arena req_arena = NK_ARENA_INITIALIZER(req_buffer, req_buffer + sizeof(req_buffer)); /* Структура для сохранения фиксированной части IPC-запроса */ struct kl_rump_NpfctlFilter_TableAdd_req req; if (nk_arena_store(char, &req_arena, &req.tid, tid, tidlen)) return ENOMEM; if (nk_arena_store(char, &req_arena, &req.cidrAddr, cidr_addr, cidr_addrlen)) return ENOMEM; /* Пример 2 */ char req_arena_buf[StringMaxSize]; struct nk_arena req_arena = NK_ARENA_INITIALIZER(req_arena_buf, req_arena_buf + sizeof(req_arena_buf)); /* Структура для сохранения фиксированной части IPC-запроса */ kl_drivers_FBConsole_SetFont_req req; size_t buf_size = strlen(fileName) + 1; char *buf = nk_arena_alloc(char, &req_arena, &req.fileName, buf_size); memcpy(buf, fileName, buf_size); /* Пример 3 */ char reqArenaBuf[kl_core_DCM_req_arena_size]; struct nk_arena reqArena = NK_ARENA_INITIALIZER(reqArenaBuf, reqArenaBuf + sizeof(reqArenaBuf)); /* Структура для сохранения фиксированной части IPC-запроса */ kl_core_DCM_Subscribe_req req; rc = NkKosCopyStringToArena(&reqArena, &req.endpointType, endpointType); if (rc != rcOk) return rc; rc = NkKosCopyStringToArena(&reqArena, &req.endpointName, endpointName); if (rc != rcOk) return rc; rc = NkKosCopyStringToArena(&reqArena, &req.serverName, serverName); if (rc != rcOk) return rc; /* Пример 4 */ unsigned counter = 0; nk_ptr_t *paths; /* Резервирование участка арены для дескрипторов других участков арены */ paths = nk_arena_alloc(nk_ptr_t, resArena, &res->logRes, msgCount); while(...) { ... /* Резервирование участков арены с сохранением их дескрипторов в * ранее зарезервированном участке арены с адресом paths */ char *str = nk_arena_alloc( char, resArena, &paths[counter], stringLength + 1); if (str == NK_NULL) return NK_ENOMEM; snprintf(str, (stringLength + 1), "%s", buffer); ... counter++; }

Получение данных из арены после приема через IPC

Перед получением IPC-запроса на стороне сервера или IPC-ответа на стороне клиента для арены, в которую будут помещены полученные через IPC-данные, нужно отменить текущее резервирование участков, вызвав функцию nk_arena_reset(). Это требуется сделать, даже если для создания арены используется макрос NK_ARENA_FINAL() или nk_arena_init_final(). (Макросы NK_ARENA_INITIALIZER() и NK_ARENA_AUTO(), а также функции nk_arena_init() и nk_arena_create() создают арену без зарезервированных участков. Перед однократном использовании такой арены для сохранения полученных через IPC данных вызывать функцию nk_arena_reset() не требуется.)

Чтобы получить указатели на участки арены и размеры этих участков, нужно использовать функцию __nk_arena_get() или макрос nk_arena_get(), передавая через входной параметр соответствующие дескрипторы участков арены, полученные из фиксированной части и арены IPC-сообщения.

Пример получения данных из арены:

struct nk_arena res_arena; char res_buf[kl_rump_DhcpcdConfig_Version_res_ver_size]; nk_arena_init(&res_arena, res_buf, res_buf + sizeof(res_buf)); /* Структура для сохранения IPC-запроса */ struct kl_rump_DhcpcdConfig_Version_req req; req.buflen = buflen; /* Структура для сохранения IPC-ответа */ struct kl_rump_DhcpcdConfig_Version_res res; /* Вызов интерфейсного метода */ if (kl_rump_DhcpcdConfig_Version(dhcpcd.proxy, &req, NULL, &res, &res_arena) != NK_EOK) return -1; size_t ptrlen; char *ptr = nk_arena_get(char, &res_arena, &res.ver, &ptrlen); memcpy(buf, ptr, ptrlen);

Дополнительные возможности API

Чтобы получить размер арены, нужно вызвать функцию nk_arena_capacity().

Чтобы получить размер использованной части арены, нужно вызвать функцию nk_arena_allocated_size().

Чтобы проверить, является ли корректным дескриптор участка арены, нужно использовать макрос nk_arena_validate() или функцию __nk_arena_validate().

Сведения о функциях и макросах API

Функции и макросы arena.h

Функция/Макрос

Сведения о функции/макросе

NK_ARENA_INITIALIZER()

Назначение

Инициализирует дескриптор арены.

Параметры

  • [in] _start – указатель на начало арены.
  • [in] _end – указатель на конец арены.

Значения макроса

Код инициализации дескриптора арены.

nk_arena_init()

Назначение

Инициализирует дескриптор арены.

Параметры

  • [out] self – указатель на дескриптор арены.
  • [in] start – указатель на начало арены.
  • [in] end – указатель на конец арены.

Возвращаемые значения

Нет.

nk_arena_create()

Назначение

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

Параметры

  • [in] start – указатель на начало арены.
  • [in] size – размер арены в байтах.

Возвращаемые значения

Дескриптор арены.

NK_ARENA_AUTO()

Назначение

Создает в стеке буфер, а также создает и инициализирует дескриптор арены.

Параметры

  • [in] size – размер арены в байтах. Должен быть задан константой.

Значения макроса

Дескриптор арены.

NK_ARENA_FINAL()

Назначение

Инициализирует дескриптор арены, содержащей только один участок.

Параметры

  • [out] ptr – указатель на дескриптор участка арены.
  • [in] start – указатель на начало арены.
  • [in] count – число объектов в участке арены.

Значения макроса

Дескриптор арены.

nk_arena_reset()

Назначение

Отменяет резервирование участков арены.

Параметры

  • [in,out] self – указатель на дескриптор арены.

Возвращаемые значения

Нет.

__nk_arena_alloc()

Назначение

Резервирует участок арены заданного размера с заданным выравниванием.

Параметры

  • [in,out] self – указатель на дескриптор арены.
  • [out] ptr – указатель на дескриптор участка арены.
  • [in] size – размер участка арены в байтах.
  • [in] align – значение, задающее выравнивание участка арены. Адрес участка арены может быть невыравненным (align=1) или выравненным (align=2,4,...,2^N) на границу 2^N-байтовой последовательности (например, двухбайтовой, четырехбайтовой).

Возвращаемые значения

В случае успеха возвращает NK_EOK, иначе возвращает код ошибки.

nk_arena_capacity()

Назначение

Позволяет получить размер арены.

Параметры

  • [in] self – указатель на дескриптор арены.

Возвращаемые значения

Размер арены в байтах.

Дополнительные сведения

Если параметр имеет значение NK_NULL, возвращает 0.

nk_arena_validate()

Назначение

Проверяет, является ли корректным дескриптор участка арены.

Параметры

  • [in] type – тип объектов, для которых предназначен участок арены.
  • [in] arena – указатель на дескриптор арены.
  • [in] ptr – указатель на дескриптор участка арены.

Значения макроса

Имеет значение 0 при ненулевом размере арены, если выполняются все следующие условия:

  1. Смещение, указанное в дескрипторе участка арены, не превышает размер арены.
  2. Размер, указанный в дескрипторе участка арены, не превышает размер арены, уменьшенный на смещение, указанное в дескрипторе участка арены.
  3. Размер, указанный в дескрипторе участка арены, кратен размеру типа объектов, для которых предназначен этот участок арены.

Имеет значение 0 при нулевом размере арены, если выполняются все следующие условия:

  1. Смещение, указанное в дескрипторе участка арены, равно нулю.
  2. Размер, указанный в дескрипторе участка арены, равен нулю.

Имеет значение -1 при нарушении хотя бы одного условия как в случае с ненулевым, так и в случае с нулевым размером арены, или если параметр ptr имеет значение NK_NULL.

__nk_arena_validate()

Назначение

Проверяет, является ли корректным дескриптор участка арены.

Параметры

  • [in] self – указатель на дескриптор арены.
  • [in] ptr – указатель на дескриптор участка арены.

Возвращаемые значения

Возвращает 0 при ненулевом размере арены, если выполняются все следующие условия:

  1. Смещение, указанное в дескрипторе участка арены, не превышает размер арены.
  2. Размер, указанный в дескрипторе участка арены, не превышает размер арены, уменьшенный на смещение, указанное в дескрипторе участка арены.

Возвращает 0 при нулевом размере арены, если выполняются все следующие условия:

  1. Смещение, указанное в дескрипторе участка арены, равно нулю.
  2. Размер, указанный в дескрипторе участка арены, равен нулю.

Возвращает -1 при нарушении хотя бы одного условия как в случае с ненулевым, так и в случае с нулевым размером арены, или если параметр ptr имеет значение NK_NULL.

__nk_arena_get()

Назначение

Позволяет получить указатель на участок арены и размер этого участка.

Параметры

  • [in] self – указатель на дескриптор арены.
  • [in] ptr – указатель на дескриптор участка арены.
  • [out] size – размер участка арены в байтах.

Возвращаемые значения

Указатель на участок арены или NK_NULL, если хотя бы один параметр имеет значение NK_NULL.

nk_arena_allocated_size()

Назначение

Позволяет получить размер использованной части арены.

Параметры

  • [in] self – указатель на дескриптор арены.

Возвращаемые значения

Размер использованной части арены в байтах.

Дополнительные сведения

Если параметр имеет значение NK_NULL, возвращает 0.

nk_arena_store()

Назначение

Резервирует участок арены для заданного числа объектов заданного типа и копирует эти объекты в зарезервированный участок.

Параметры

  • [in] type – тип объектов, которые нужно скопировать в участок арены.
  • [in,out] arena – указатель на дескриптор арены.
  • [out] ptr – указатель не дескриптор участка арены.
  • [in] src – указатель на буфер с объектами, которые нужно скопировать в участок арены.
  • [in] count – число объектов, которые нужно скопировать в участок арены.

Значения макроса

В случае успеха имеет значение 0, иначе имеет значение -1.

__nk_arena_store()

Назначение

Резервирует участок арены с заданным выравниванием для данных заданного размера и копирует эти данные в зарезервированный участок.

Параметры

  • [in,out] self – указатель на дескриптор арены.
  • [out] ptr – указатель не дескриптор участка арены.
  • [in] src – указатель на буфер с данными, которые нужно скопировать в участок арены.
  • [in] size – размер данных, которые нужно скопировать в участок арены, в байтах.
  • [in] align – значение, задающее выравнивание участка арены. Адрес участка арены может быть невыравненным (align=1) или выравненным (align=2,4,...,2^N) на границу 2^N-байтовой последовательности (например, двухбайтовой, четырехбайтовой).

Возвращаемые значения

В случае успеха возвращает 0, иначе возвращает -1.

nk_arena_init_final()

Назначение

Инициализирует дескриптор арены, содержащей только один участок.

Параметры

  • [out] arena – указатель на дескриптор арены.
  • [out] ptr – указатель на дескриптор участка арены.
  • [in] start – указатель на начало арены.
  • [in] count – число объектов, для которых предназначен участок арены.

Значения макроса

Нет.

nk_arena_alloc()

Назначение

Резервирует участок арены для заданного числа объектов заданного типа.

Параметры

  • [in] type – тип объектов, для которых предназначен участок арены.
  • [in,out] arena – указатель на дескриптор арены.
  • [out] ptr – указатель на дескриптор участка арены.
  • [in] count – число объектов, для которых предназначен участок арены.

Значения макроса

В случае успеха имеет значение адреса зарезервированного участка арены, иначе имеет значение NK_NULL.

nk_arena_get()

Назначение

Позволяет получить адрес участка арены и число объектов заданного типа, которое вмещается в этом участке.

Параметры

  • [in] type – тип объектов, для которых предназначен участок арены.
  • [in] arena – указатель на дескриптор арены.
  • [in] ptr – указатель на дескриптор участка арены.
  • [out] count – указатель на число объектов, которое вмещается в участке арены.

Значения макроса

В случае успеха имеет значение адреса участка арены, иначе имеет значение NK_NULL.

Дополнительные сведения

Если размер участка арены не кратен размеру типа объектов, для которых этот участок предназначен, имеет значение NK_NULL.

nk_arena_shrink()

Назначение

Уменьшает размер участка арены.

Параметры

  • [in] type – тип объектов, для которых предназначен уменьшенный участок арены.
  • [in,out] arena – указатель на дескриптор арены.
  • [in,out] ptr – указатель на дескриптор участка арены.
  • [in] count – число объектов, для которых предназначен уменьшенный участок арены.

Значения макроса

В случае успеха имеет значение адреса уменьшенного участка арены, иначе имеет значение NK_NULL.

Дополнительные сведения

Если требуемый размер участка арены превышает текущий, имеет значение NK_NULL.

Если выравнивание участка арены, который нужно уменьшить, не удовлетворяет типу объектов, для которых предназначен уменьшенный участок, имеет значение NK_NULL.

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

_nk_arena_shrink()

Назначение

Уменьшает размер участка арены.

Параметры

  • [in,out] self – указатель на дескриптор арены.
  • [in,out] ptr – указатель на дескриптор участка арены.
  • [in] size – размер уменьшенного участка арены в байтах.
  • [in] align – значение, используемое функцией для проверки выравнивания участка арены, который нужно уменьшить. Адрес участка арены может быть невыравненным (align=1) или выравненным (align=2,4,...,2^N) на границу 2^N-байтовой последовательности (например, двухбайтовой, четырехбайтовой).

Возвращаемые значения

В случае успеха возвращает адрес уменьшенного участка арены, иначе возвращает NK_NULL.

Дополнительные сведения

Если требуемый размер участка арены превышает текущий, возвращает NK_NULL.

Если выравнивание участка арены, который нужно уменьшить, не удовлетворяет заданному выравниванию, возвращает NK_NULL.

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

В начало
[Topic ipc_arena]

Транспортный код на языке C++

Перед чтением этого раздела, нужно ознакомиться со сведениями о механизме IPC в KasperskyOS и об IDL-, CDL-, EDL-описаниях.

Чтобы реализовать взаимодействие процессов, необходим транспортный код, отвечающий за формирование, отправку, прием и обработку IPC-сообщений.

У разработчика решения на базе KasperskyOS нет необходимости самостоятельно писать транспортный код. Вместо этого можно использовать специальные инструменты и библиотеки, поставляемые в составе KasperskyOS SDK. Эти библиотеки позволяют разработчику компонента решения сгенерировать транспортный код на основе IDL-, CDL-, EDL-описаний, относящихся к этому компоненту.

Транспортный код

Для генерации транспортного кода на языке C++ в составе KasperskyOS SDK поставляется компилятор nkppmeta.

Компилятор nkppmeta позволяет генерировать транспортные C++ прокси-объекты (proxy) и стабы (stub) для использования как клиентом, так и сервером.

Прокси-объекты используются клиентом для упаковки параметров вызываемого метода в IPC-запрос, выполнения IPC-запроса и распаковки IPC-ответа.

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

Генерация транспортного кода для разработки на C++

Для генерации транспортных прокси-объектов и стабов с помощью генератора nkppmeta при сборке решения используются CMake-команды add_nk_idl(), add_nk_cdl() и add_nk_edl().

Типы С++ в файле *.idl.cpp.h

Каждый интерфейс определяется в IDL-описании. Это описание задает имя интерфейса, сигнатуры интерфейсных методов и типы данных для параметров интерфейсных методов.

Для генерации транспортного кода при сборке решения используются CMake-команда add_nk_idl(), которая создает CMake-цель для генерации заголовочных файлов для заданного IDL-файла при помощи компилятора nkppmeta.

Генерируемые заголовочные файлы содержат представление на языке C++ для интерфейса и типов данных, описанных в IDL-файле, а также методы, необходимые для использования прокси-объектов и стабов.

Соответствие типов данных, объявленных в IDL-файле, типам C++ приведены в таблице ниже.

Соответствие типов IDL типам C++

Тип IDL

Тип С++

SInt8

int8_t

SInt16

int16_t

SInt32

int32_t

SInt64

int64_t

UInt8

uint8_t

UInt16

uint16_t

UInt32

uint32_t

UInt64

uint64_t

Handle

Handle (определен в coresrv/handle/handletype.h)

string

std::string

union

std::variant

struct

struct

array

std::array

sequence

std::vector

bytes

std::vector<std::byte>

Работа с транспортным кодом на C++

Сценарии разработки клиента и сервера, которые обмениваются IPC-сообщениями, представлены в разделах "Статическое создание IPC-каналов при разработке на языке C++" и "Динамическое создание IPC-каналов при разработке на языке C++"

В начало
[Topic cpp_proxy_stubs]

Статическое создание IPC-каналов при разработке на языке C++

Чтобы реализовать клиентскую программу, вызывающую метод службы, предоставляемой серверной программой, необходимо:

  1. Подключить сгенерированный заголовочный файл описания (*.edl.cpp.h) клиентской программы.
  2. Подключить сгенерированные заголовочные файлы описаний используемых интерфейсов (*.idl.cpp.h).
  3. Подключить заголовочные файлы:
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/api.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/connect_static_channel.h
  4. Инициализировать объект приложения, вызвав функцию kosipc::MakeApplicationAutodetect(). (Также можно использовать функции kosipc::MakeApplication() и kosipc::MakeApplicationPureClient().)
  5. Получить клиентский IPC-дескриптор канала и идентификатор службы (riid) вызвав функцию kosipc::ConnectStaticChannel().

    Функция принимает имя IPC-канала (из файла init.yaml) и квалифицированное имя службы (из CDL- и EDL-описаний компонента решения).

  6. Инициализировать прокси-объект для используемой службы, вызвав функцию MakeProxy().

Пример

// Создание и инициализация объекта приложения kosipc::Application app = kosipc::MakeApplicationAutodetect(); // Создание и инициализация прокси-объекта auto proxy = app.MakeProxy<IDLInterface>( kosipc::ConnectStaticChannel(channelName, endpointName)) // Вызов метода требуемой службы proxy->DoSomeWork();

Чтобы реализовать серверную программу, предоставляющую службы другим программам, необходимо:

  1. Подключить сгенерированный заголовочный файл *.edl.cpp.h, содержащий описание компонентной структуры программы, включая все предоставляемые службы.
  2. Подключить заголовочные файлы:
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/event_loop.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/api.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/serve_static_channel.h
  3. Создать классы, содержащие реализации интерфейсов, которые данная программа и её компоненты предоставляют в виде служб.
  4. Инициализировать объект приложения, вызвав функцию kosipc::MakeApplicationAutodetect().
  5. Инициализировать структуру kosipc::components::Root, которая описывает компонентную структуру программы и описания интерфейсов всех предоставляемых программой служб.
  6. Связать поля структуры kosipc::components::Root с объектами, реализующими соответствующие службы.

    Поля структуры Root повторяют иерархию компонентов и служб, заданную совокупностью CDL- и EDL-файлов.

  7. Получить серверный IPC-дескриптор канала, вызвав функцию ServeStaticChannel().

    Функция принимает имя IPC-канала (из файла init.yaml) и структуру, созданную на шаге 5.

  8. Создать объект kosipc::EventLoop, вызвав функцию MakeEventLoop().
  9. Запустить цикл диспетчеризации входящих IPC-сообщений, вызвав метод Run() объекта kosipc::EventLoop.

Пример

// Создание объектов классов, которые реализуют интерфейсы, // предоставляемые сервером в виде служб MyIDLInterfaceImp_1 impl_1; MyIDLInterfaceImp_2 impl_2; // Создание и инициализация объекта приложения kosipc::Application app = kosipc::MakeApplicationAutodetect(); // Создание и инициализация объекта root, описывающего // компоненты и службы сервера kosipc::components::Root root; // Связывание объекта root с объектами классов, реализующими службы сервера root.component1.endpoint1 = &impl_1; root.component2.endpoint2 = &impl_2; // Создание и инициализация объекта, который реализует // цикл диспетчеризации входящих IPC-сообщений kosipc::EventLoop loop = app.MakeEventLoop(ServeStaticChannel(channelName, root)); // Запуск цикла в текущем потоке loop.Run();
В начало
[Topic static_IPC_kosipc]

Динамическое создание IPC-каналов при разработке на языке C++

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

  1. Подключить к клиентской программе сгенерированный заголовочный файл описания (*.edl.cpp.h).
  2. Подключить сгенерированные заголовочные файлы описаний используемых интерфейсов (*.idl.cpp.h).
  3. Подключить заголовочные файлы:
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/make_application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/connect_dynamic_channel.h
  4. Получить указатели на имя сервера и квалифицированное имя службы с помощью сервера имен – специального сервиса ядра, представленного программой NameServer. Для этого необходимо подключиться к серверу имен вызовом функции NsCreate() и найти сервер, предоставляющий требуемую службу, используя функцию NsEnumServices(). Подробнее см. "Динамическое создание IPC-каналов (cm_api.h, ns_api.h)".
  5. Создать объект приложения, вызвав функцию kosipc::MakeApplicationAutodetect(). (Также можно использовать функции kosipc::MakeApplication() и kosipc::MakeApplicationPureClient().)
  6. Создать прокси-объект для требуемой службы, вызвав функцию MakeProxy(). В качестве входного параметра функции MakeProxy() использовать вызов функции kosipc::ConnectDynamicChannel(). В функцию kosipc::ConnectDynamicChannel() передать указатели на имя сервера и квалифицированное имя службы, полученные на шаге 4.

После успешной инициализации прокси-объекта клиенту доступен вызов методов требуемой службы.

Пример

NsHandle ns; // Подключение к серверу имен Retcode rc = NsCreate(RTL_NULL, INFINITE_TIMEOUT, &ns); char serverName[kl_core_Types_UCoreStringSize]; char endpointName[kl_core_Types_UCoreStringSize]; // Получение указателей на имя сервера и квалифицированное имя службы rc = NsEnumServices( ns, interfaceName, 0, serverName, kl_core_Types_UCoreStringSize, endpointName, kl_core_Types_UCoreStringSize); // Создание и инициализация объекта приложения kosipc::Application app = kosipc::MakeApplicationAutodetect(); // Создание и инициализация прокси-объекта auto proxy = app.MakeProxy<IDLInterface>( kosipc::ConnectDynamicChannel(serverName, endpointName)) // Вызов метода требуемой службы proxy->DoSomeWork();

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

  1. Подключить к серверной программе сгенерированный заголовочный файл (*.edl.cpp.h), содержащий описание компонентной структуры сервера, включая все предоставляемые службы.
  2. Подключить заголовочные файлы:
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/event_loop.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/make_application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/root_component.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/serve_dynamic_channel.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/simple_connection_acceptor.h
  3. Создать классы, содержащие реализации интерфейсов, которые сервер предоставляет в виде служб. Создать и инициализировать объекты этих классов.
  4. Создать объект приложения, вызвав функцию kosipc::MakeApplicationAutodetect().
  5. Создать и инициализировать объект класса kosipc::components::Root, который описывает структуру компонентов и служб сервера. Эта структура генерируется из описаний в CDL- и EDL-файлах.
  6. Cвязать объект класса kosipc::components::Root с объектами классов, созданными на шаге 3.
  7. Создать и инициализировать объект класса kosipc::EventLoop, который реализует цикл диспетчеризации входящих IPC-сообщений, вызвав функцию MakeEventLoop(). В качестве входного параметра функции MakeEventLoop() использовать вызов функции ServeDynamicChannel(). В функцию ServeDynamicChannel() передать объект класса kosipc::components::Root, созданный на шаге 5.
  8. Запустить цикл диспетчеризации входящих IPC-сообщений в отдельном потоке, вызвав метод Run() объекта kosipc::EventLoop.
  9. Создать и инициализировать объект, который реализует обработчик приема входящих запросов на динамическое создание IPC-канала.

    При создании объекта можно использовать класс kosipc::SimpleConnectionAcceptor, который является стандартной реализацией интерфейса kosipc::IConnectionAcceptor. (Интерфейс kosipc::IConnectionAcceptor определен в файле /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/connection_acceptor.h.) В этом случае обработчик будет реализовать следующую логику: если запрашиваемая клиентом служба опубликована на сервере, то запрос от клиента будет принят, иначе отклонен.

    Если необходимо создать собственный обработчик, то следует реализовать свою логику обработки запросов в методе OnConnectionRequest(), унаследованном от интерфейса kosipc::IConnectionAcceptor. Этот метод будет вызываться сервером при получении от клиента запроса на динамическое создание IPC-канала.

  10. Создать объект класса kosipc::EventLoop, который реализует цикл приема входящих запросов на динамическое создание IPC-канала, вызвав функцию MakeEventLoop(). В качестве входного параметра функции MakeEventLoop() использовать вызов функции ServeConnectionRequests(). В функцию ServeConnectionRequests() передать объект, созданный на шаге 9.

    Цикл приема входящих запросов на динамическое создание IPC-канала может быть только один. Цикл должен работать в одном потоке. Цикл приема входящих запросов на динамическое создание IPC-канала должен быть создан после создания цикла диспетчеризации входящих IPC-сообщений (см. на шаг 7).

  11. Запустить цикл приема входящих запросов на динамическое соединение в текущем потоке, вызвав метод Run() объекта kosipc::EventLoop.

Пример

// Создание объектов классов, которые реализуют интерфейсы, // предоставляемые сервером в виде служб MyIDLInterfaceImp_1 impl_1; MyIDLInterfaceImp_2 impl_2; // Создание и инициализация объекта приложения kosipc::Application app = kosipc::MakeApplicationAutodetect(); // Создание и инициализация объекта root, описывающего // компоненты и службы сервера kosipc::components::Root root; // Связывание объекта root с объектами классов, реализующими службы сервера. // Поля объекта root повторяют описание компонентов и служб, // заданную совокупностью CDL- и EDL-файлов. root.component1.endpoint1 = &impl_1; root.component2.endpoint2 = &impl_2; // Создание и инициализация объекта, который реализует // цикл диспетчеризации входящих IPC-сообщений kosipc::EventLoop loopDynamicChannel = app.MakeEventLoop(ServeDynamicChannel(root)); // Запуск цикла диспетчеризации входящих IPC-сообщений в отдельном потоке std::thread dynChannelThread( [&loopDynamicChannel]() { loopDynamicChannel.Run(); } ); // Создание объекта, реализующего стандартный обработчик приема входящих запросов // на динамическое создание IPC-канала kosipc::SimpleConnectionAcceptor acceptor(root); // Создание объекта, реализующего цикл приема входящих запросов // на динамическое создание IPC-канала kosipc::EventLoop loopDynamicChannel = app.MakeEventLoop(ServeConnectionRequests(&acceptor)); // Запуск цикла приема входящих запросов на динамическое создание IPC-канала в текущем потоке loopConnectionReq.Run();

При необходимости можно создать и инициализировать несколько объектов класса kosipc::components::Root, объединенных в список объектов типа ServiceList с помощью метода AddServices(). Использование нескольких объектов позволяет, например, разделять компоненты и службы сервера на группы или публиковать службы под разными именами.

Пример

// Создание и инициализация объекта group_1 kosipc::components::Root group_1; group_1.component1.endpoint1 = &impl_1; group_1.component2.endpoint2 = &impl_2; // Создание и инициализация объекта group_2 kosipc::components::Root group_2; group_2.component1.endpoint1 = &impl_3; group_2.component2.endpoint2 = &impl_4; // Создание и инициализация объекта group_3 kosipc::components::Root group_3; group_3.component1.endoint1 = &impl_5; // Создание списка объектов ServiceList endpoints; endpoints.AddServices(group_1); endpoints.AddServices(group_2); endpoints.AddServices(group_3.component1.endpoint1, "SomeCustomEndpointName"); // Создание объекта, реализующего обработчик приема входящих запросов // на динамическое создание IPC-канала kosipc::SimpleConnectionAcceptor acceptor(std::move(endpoints)); // Создание объекта, реализующего цикл приема входящих запросов // на динамическое создание IPC-канала kosipc::EventLoop loopDynamicChannel = app.MakeEventLoop(ServeConnectionRequests(&acceptor));
В начало
[Topic dynamic_IPC_kosipc]