Содержание
- Разработка под KasperskyOS
- Запуск процессов
- Файловые системы и сеть
- IPC и транспорт
- Создание IPC-каналов
- Добавление в решение службы из состава KasperskyOS Community Edition
- Создание и использование собственных служб
Обзор: 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-описании
Ключ |
Обязательный |
Значение |
---|---|---|
|
Да |
Имя класса процесса (из EDL-описания). |
|
Нет |
Имя процесса. Если его не указать, то будет взято имя класса процесса. У каждого процесса должно быть уникальное имя. Можно запустить несколько процессов одного класса, но с разными именами. |
|
Нет |
Имя исполняемого файла в ROMFS (в образе решения). Если его не указать, то будет взято имя класса процесса без префиксов и точек. Например, процессы классов Можно запустить несколько процессов из одного исполняемого файла. |
|
Нет |
Список словарей IPC-каналов процесса. Этот список задает статически создаваемые IPC-каналы, клиентскими IPC-дескрипторами которых будет владеть процесс. По умолчанию этот список пуст. (Помимо статически создаваемых IPC-каналов процессы могут использовать динамически создаваемые IPC-каналы.) |
|
Нет |
Список параметров запуска программы (параметры функции |
|
Нет |
Словарь переменных окружения программы. Ключами в этом словаре являются имена переменных окружения. Максимальный размер значения переменной окружения составляет 1024 байта. |
Ключи словаря IPC-канала процесса приведены в таблице ниже.
Ключи словаря IPC-канала в init-описании
Ключ |
Обязательный |
Значение |
---|---|---|
|
Да |
Имя IPC-канала, которое может быть задано как конкретным значением, так и ссылкой вида
|
|
Да |
Имя процесса, который будет владеть серверным дескриптором IPC-канала. |
Примеры init-описаний
Здесь приведены примеры init-описаний, демонстрирующие различные аспекты запуска процессов.
Система сборки может автоматически создавать init-описание на основе шаблона init.yaml.in
.
Запуск клиента и сервера и создание IPC-канала между ними
В этом примере будут запущены процесс класса Client
и процесс класса Server
. Имена процессов не указаны, поэтому они будут совпадать с именами классов процессов. Имена исполняемых файлов также не указаны, они также будут совпадать с именами классов процессов. Процессы будут соединены IPC-каналом с именем server_connection
.
init.yaml
Запуск процессов из заданных исполняемых файлов
В этом примере будут запущены процесс класса Client
из исполняемого файла с именем cl
, процесс класса ClientServer
из исполняемого файла с именем csr
и процесс класса MainServer
из исполняемого файла с именем msr
. Имена процессов не указаны, поэтому они будут совпадать с именами классов процессов.
init.yaml
Запуск двух процессов из одного исполняемого файла
В этом примере будут запущены процесс класса Client
из исполняемого файла с именем Client
, а также два процесса классов MainServer
и BkServer
из исполняемого файла с именем srv
. Имена процессов не указаны, поэтому они будут совпадать с именами классов процессов.
init.yaml
Запуск двух серверов одного класса и клиента и создание IPC-каналов между клиентом и серверами
В этом примере будут запущены процесс класса Client
(с именем Client
) и два процесса класса Server
с именами UserServer
и PrivilegedServer
. Клиент будет соединен с серверами IPC-каналами с именами server_connection_us
и server_connection_ps
. Имена исполняемых файлов не указаны, поэтому они будут совпадать с именами классов процессов.
init.yaml
Установка параметров запуска и переменных окружения программ
В этом примере будут запущены процесс класса VfsFirst
(с именем VfsFirst
) и процесс класса VfsSecond
(с именем VfsSecond
). Программа, которая будет исполняться в контексте процесса VfsFirst
, будет запущена с параметром -f /etc/fstab
, а также получит переменную окружения ROOTFS
со значением ramdisk0,0 / ext2 0
и переменную окружения UNMAP_ROMFS
со значением 1
. Программа, которая будет исполняться в контексте процесса VfsSecond
, будет запущена с параметром -l devfs /dev devfs 0
. Имена исполняемых файлов не указаны, поэтому они будут совпадать с именами классов процессов.
init.yaml
Запуск процессов с помощью системной программы ExecutionManager
Компонент ExecutionManager предоставляет интерфейс на языке C++ для создания, запуска и остановки процессов в решениях, построенных на базе KasperskyOS.
Интерфейс компонента ExecutionManager не подходит для использования в коде, написанном на языке C. Для управления процессами на языке C используйте интерфейс task.h библиотеки libkos.
API компонента ExecutionManager представляет собой надстройку над IPC, которая позволяет упростить процесс разработки программ. ExecutionManager является отдельной системной программой, доступ к которой осуществляется через IPC, но при этом разработчикам предоставляется клиентская библиотека, которая скрывает необходимость использования IPC-вызовов напрямую.
Программный интерфейс компонента ExecutionManager описан в статье "Компонент ExecutionManager".
Сценарий использования компонента ExecutionManager
Здесь и далее клиентом называется приложение, использующее API компонента ExecutionManager для управления другими приложениями.
Типовой сценарий использования компонента ExecutionManager включает следующие шаги:
- Добавление программы ExecutionManager в решение. Чтобы добавить ExecutionManager в решение, необходимо:
- Добавить следующие команды в корневой файл CMakeLists.txt:
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
.
- Компоновка исполняемого файла клиента с клиентской прокси-библиотекой ExecutionManager, для чего необходимо в файле
CMakeLists.txt
для сборки клиента добавить следующую команду:target_link_libraries (<имя CMake-цели для сборки клиента> ${execution_manager_EXECMGR_PROXY}) - Добавление разрешений для необходимых событий в описание политики безопасности решения:
- Чтобы программа ExecutionManager могла запускать другие процессы, политика безопасности решения должна разрешать следующие взаимодействия для класса процессов
execution_manager.ExecMgrEntity
:- События безопасности вида
execute
для всех классов запускаемых процессов. - Доступ ко всем службам программы
VFS
. - Доступ ко всем службам программы
BlobContainer
. - Доступ к службам ядра
Sync
,Task
,VMM
,Thread
,HAL
,Handle
,FS
,Notice
,CM
иProfiler
(их описания находятся в директорииsysroot-*-kos/include/kl/core
из состава SDK).
- События безопасности вида
- Чтобы клиент мог обращаться к программе ExecutionManager, политика безопасности решения должна разрешать следующие взаимодействия для класса клиентского процесса:
- Доступ к соответствующим службам программы ExecutionManager (их описания находятся в директории
sysroot-*-kos/include/kl/execution_manager
из состава SDK).
- Доступ к соответствующим службам программы ExecutionManager (их описания находятся в директории
- Чтобы программа ExecutionManager могла запускать другие процессы, политика безопасности решения должна разрешать следующие взаимодействия для класса процессов
- Использование API программы ExecutionManager в коде клиента.
Для этого необходимо использовать заголовочный файл
component/package_manager/kos_ipc/package_manager_proxy.h
. Подробнее см. "Компонент ExecutionManager".
Обзор: программа Env
Системная программа Env
предназначена для установки параметров запуска и переменных окружения программ. Если программа Env
включена в решение, то процессы, соединенные IPC-каналом с процессом Env
, при своем запуске автоматически отправляют IPC-запросы этой программе и получают параметры запуска и переменные окружения.
Использование системной программы Env
является устаревшим способом установки параметров запуска и переменных окружения программ. Установку параметров запуска и переменных окружения программ нужно выполнять через файл init.yaml.in
или init.yaml
.
Если значение параметра запуска или переменной окружения программы задано как через программу Env
, так и через файл init.yaml.in
или init.yaml
, то будет применяться значение, заданное через программу Env
.
Чтобы использовать программу Env
в решении, необходимо:
- Разработать код программы
Env
, используя макросы и функции из заголовочного файлаsysroot-*-kos/include/env/env.h
из состава KasperskyOS SDK. - Собрать исполняемый файл программы
Env
, скомпоновав его с библиотекойenv_server
из состава KasperskyOS SDK. - В init-описании указать, что необходимо запустить процесс
Env
и соединить с ним другие процессы (Env
при этом является сервером). Имя IPC-канала задается макросомENV_SERVICE_NAME
, определенным в заголовочном файлеenv.h
. - Включить исполняемый файл
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
В началоПримеры установки параметров запуска и переменных окружения программ с помощью 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
Пример установки переменных окружения программы
Исходный код программы Env
, которая при запуске процесса с именем Vfs3
передаст ему две переменных окружения программы: ROOTFS=ramdisk0,0 / ext2 0
и UNMAP_ROMFS=1
:
env.c
Файловые системы и сеть
В KasperskyOS работа с файловыми системами и сетью выполняется через отдельную системную программу, реализующую виртуальную файловую систему (англ. Virtual File System, VFS).
В составе SDK компонент VFS представлен набором исполняемых файлов, библиотек, файлов формальной спецификации и заголовочных файлов. Подробнее см. раздел "Состав компонента VFS".
Основной сценарий взаимодействия с системной программой VFS происходит следующим образом:
- Прикладная программа соединяется IPC-каналом с системной программой VFS и при сборке компонуется с клиентской библиотекой компонента VFS.
- В прикладном коде POSIX-вызовы для работы с файловыми системами и сетью преобразуются в вызовы функций клиентской библиотеки.
Ввод и вывод в файловые дескрипторы для стандартных потоков ввода-вывода (stdin, stdout и stderr) также преобразуется в обращения к VFS. Если прикладная программа не скомпонована с клиентской библиотекой компонента VFS, то вывод в stdout невозможен. В таком случае возможен только вывод в стандартный поток ошибок (stderr), который в этом случае осуществляется без использования VFS через специальные методы ядра KasperskyOS.
- Клиентская библиотека выполняет IPC-запросы к системной программе VFS.
- Системная программа VFS принимает IPC-запросы и вызывает соответствующие реализации файловых систем (которые, в свою очередь могут выполнять IPC-запросы к драйверам устройств) или сетевые драйверы.
- После обработки запроса, системная программа VFS выполняет ответы на IPC-запросы прикладной программы.
Использование нескольких программ VFS
В решение можно добавить несколько копий системной программы VFS, разделив таким образом информационные потоки разных системных и прикладных программ. Также можно разделить информационные потоки в рамках одной прикладной программы. Подробнее см. "Разделение информационных потоков с помощью VFS-бэкендов".
Включение функциональности VFS в прикладную программу
Функциональность компонента VFS можно полностью включить в прикладную программу, избавляясь при этом от необходимости передавать каждый запрос через IPC. Подробнее см. "Включение функциональности VFS в программу".
При этом использование функциональности VFS по IPC позволяет разработчику решения:
- контролировать вызовы методов для работы с сетью и файловыми системами с помощью политики безопасности решения;
- соединить несколько клиентских программ с одной программой VFS;
- соединить одну клиентскую программу с двумя программами VFS для раздельной работы с сетью и файловыми системами.
Состав компонента 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Функции, реализуемые библиотекой vfs_net
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Если в VFS нет реализации вызванной функции, возвращается код ошибки EIO
.
Создание IPC-канала до VFS
В этом примере процесс Client
использует файловые системы и сетевой стек, а процесс VfsFsnet
обрабатывает IPC-запросы процесса Client
, связанные с использованием файловых систем и сетевого стека. Такой подход используется в тех случаях, когда не требуется разделение информационных потоков, связанных с файловыми системами и сетевым стеком.
Имя IPC-канала должно задаваться макросом _VFS_CONNECTION_ID
, определенным в заголовочном файле sysroot-*-kos/include/vfs/defs.h
из состава KasperskyOS SDK.
Init-описание примера:
init.yaml
Включение функциональности VFS в программу
В этом примере программа Client
включает функциональность программы VFS для работы с сетевым стеком (см. рис. ниже).
Библиотеки компонента VFS в составе программы
Выполняется компиляция файла реализации client.c
и компоновка с библиотеками vfs_local
, vfs_implementation
и dnet_implementation
:
CMakeLists.txt
Если бы программа Client
использовала файловые системы, то нужно было бы выполнить компоновку с библиотеками vfs_local
и vfs_fs
, а также с библиотеками реализации этих файловых систем. Кроме того, нужно было бы добавить в решение драйвер блочного устройства.
Обзор: параметры запуска и переменные окружения 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
.ROOTFS = <запись в формате fstab>
Переменная окружения
ROOTFS
позволяет монтировать заданную файловую систему в корневую директорию. При использовании параметра запуска-f
комбинация переменных окруженияROOTFS
иUNMAP_ROMFS
дает возможность выполнить поиск файлаfstab
не в ROMFS-образе, а в файловой системе, заданной через переменную окруженияROOTFS
.VFS_CLIENT_MAX_THREADS
Переменная окружения
VFS_CLIENT_MAX_THREADS
позволяет переопределить параметр конфигурирования SDKVFS_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
:
Для исполняемого файла VfsSdCardFs
:
Для исполняемого файла VfsNet
:
Монтирование файловых систем при запуске VFS
При запуске программы VFS по умолчанию монтируется только файловая система RAMFS в корневую директорию. Если требуется монтировать другие файловые системы, это можно сделать не только с помощью вызова функции mount()
, но и установив параметры запуска и переменные окружения программы VFS.
Файловые системы ROMFS
и squashfs
предназначены только для чтения, поэтому для монтирования этих файловых систем нужно указать параметр ro
.
Использование параметра запуска -l
Одним из способов монтировать файловую систему является установка для программы VFS параметра запуска -l <запись в формате fstab>
.
В этих примерах при запуске программы VFS будут монтированы файловые системы devfs и ROMFS:
init.yaml.(in)
CMakeLists.txt
Использование файла fstab из ROMFS-образа
При сборке решения можно добавить файл fstab
в ROMFS-образ. Этот файл можно использовать для монтирования файловых систем, установив для программы VFS параметр запуска -f <путь к файлу fstab>
.
В этих примерах при запуске программы VFS будут монтированы файловые системы, заданные через файл fstab
, который был добавлен при сборке решения в ROMFS-образ:
init.yaml.(in)
CMakeLists.txt
Использование "внешнего" файла fstab
Если файл fstab
находится не в ROMFS-образе, а в другой файловой системе, то для использования этого файла необходимо установить для программы VFS следующие параметры запуска и переменные окружения:
ROOTFS
. Эта переменная окружения позволяет монтировать в корневую директорию файловую систему, содержащую файлfstab
.UNMAP_ROMFS
. Если эта переменная окружения определена, то поиск файлаfstab
будет выполнен в файловой системе, заданной через переменную окруженияROOTFS
.-f
. Этот параметр запуска используется, чтобы монтировать файловые системы, указанные в файлеfstab
.
В этих примерах при запуске программы VFS в корневую директорию будет монтирована файловая система ext2, в которой должен находиться файл fstab
по пути /etc/fstab
:
init.yaml.(in)
CMakeLists.txt
Разделение информационных потоков с помощью VFS-бэкендов
В этом примере применяется паттерн безопасной разработки, предусматривающий отделение информационных потоков, связанных с использованием файловых систем, от информационных потоков, связанных с использованием сетевого стека.
Процесс Client
использует файловые системы и сетевой стек. Процесс VfsFirst
обеспечивает работу с файловыми системами, а процесс VfsSecond
дает возможность работать с сетевым стеком. Через переменные окружения программ, исполняющихся в контекстах процессов Client
, VfsFirst
и VfsSecond
, заданы VFS-бэкенды, которые обеспечивают раздельное использование файловых систем и сетевого стека. В результате этого IPC-запросы процесса Client
, связанные с использованием файловых систем, обрабатываются процессом VfsFirst
, а IPC-запросы процесса Client
, связанные с использованием сетевого стека, обрабатываются процессом VfsSecond
(см. рис. ниже).
Схема взаимодействия процессов
Init-описание примера:
init.yaml
Создание 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
Компоновка программы Client
Создание статической библиотеки VFS-бэкенда:
CMakeLists.txt
Компоновка программы Client
со статической библиотеки VFS-бэкенда:
CMakeLists.txt
Установка параметров запуска и переменных окружения программ
Init-описание примера:
init.yaml
Динамическая настройка сетевого стека
Чтобы изменить параметры сетевого стека, заданные по умолчанию, нужно использовать функцию sysctl()
или sysctlbyname()
, объявленные в заголовочном файле sysroot-*-kos/include/sys/sysctl.h
из состава KasperskyOS SDK. Параметры, которые можно изменить, приведены в таблице ниже.
Настраиваемые параметры сетевого стека
Название параметра |
Описание параметра |
---|---|
|
Максимальное время жизни (англ. Time To Live, TTL) отправляемых IP-пакетов. Не влияет на протокол ICMP. |
|
Если имеет значение |
|
Значение MSS (в байтах), которое применяется, если только взаимодействующая сторона не сообщила это значение при открытии TCP-соединения, или не задействован режим "Path MTU Discovery" (RFC 1191). Также это значение MSS передается взаимодействующей стороне при открытии TCP-соединения. |
|
Минимальное значение MSS, в байтах. |
|
Если имеет значение |
|
Число повторных отправок проверочных сообщений (англ. Keep-Alive Probes, KA) без ответа, после выполнения которых TCP-соединение считается закрытым. Если имеет значение |
|
Время неактивности TCP-соединения, по истечении которого начинают отправляться KA. Задается в условных единицах, которые можно перевести в секунды, разделив на значение параметра |
|
Время между повторными отправками KA при отсутствии ответа. Задается в условных единицах, которые можно перевести в секунды, разделив на значение параметра |
|
Размер буфера для принимаемых по протоколу TCP данных, в байтах. |
|
Размер буфера для отправляемых по протоколу TCP данных, в байтах. |
|
Размер буфера для принимаемых по протоколу UDP данных, в байтах. |
|
Размер буфера для отправляемых по протоколу UDP данных, в байтах. |
Пример настройки MSS:
Создание 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-канала.
Добавление в решение службы из состава KasperskyOS Community Edition
Чтобы программа Client
могла использовать ту или иную функциональность через механизм IPC, необходимо:
- Найти в составе KasperskyOS Community Edition исполняемый файл (условно назовем его
Server
), реализующий нужную функциональность. (Под функциональностью мы здесь понимаем одну или несколько служб, имеющих самостоятельные IPC-интерфейсы) - Подключить CMake-пакет, содержащий файл
Server
и его клиентскую библиотеку. - Добавить исполняемый файл
Server
в образ решения. - Изменить init-описание так, чтобы при старте решения программа
Einit
запускала новый серверный процесс из исполняемого файлаServer
и соединяла его IPC-каналом с процессом, запускаемым из файлаClient
.Необходимо указать корректное имя IPC-канала, чтобы транспортные библиотеки могли идентифицировать этот канал и найти его IPC-дескрипторы. Корректное имя IPC-канала, как правило, совпадает с именем класса серверного процесса. VFS при этом является исключением.
- Изменить PSL-описание так, чтобы разрешить запуск серверного процесса и IPC-взаимодействие между клиентом и сервером.
- Подключить в исходном коде программы
Client
заголовочный файл с методами сервера. - Скомпоновать программу
Client
с клиентской библиотекой.
Пример добавления GPIO-драйвера в решение
В составе KasperskyOS Community Edition есть файл gpio_hw
, реализующий функциональность GPIO-драйвера.
Следующие команды подключают CMake‑пакет gpio:
.\CMakeLists.txt
Добавление исполняемого файла gpio_hw
в образ решения производится с помощью переменной gpio_HW_ENTITY
, имя которой можно найти в конфигурационном файле пакета – /opt/KasperskyOS-Community-Edition-<version>/sysroot-aarch64-kos/lib/cmake/gpio/gpio-config.cmake:
einit\CMakeLists.txt
В init-описание нужно добавить следующие строки:
init.yaml.in
В PSL-описание нужно добавить следующие строки:
security.psl.in
В коде программы Client
нужно подключить заголовочный файл, в котором объявлены методы GPIO-драйвера:
client.c
Наконец, нужно скомпоновать программу Client
с клиентской библиотекой GPIO:
client\CMakeLists.txt
Для корректной работы GPIO‑драйвера может понадобиться добавить в решение компонент BSP. Чтобы не усложнять этот пример, мы не рассматриваем здесь BSP. Подробнее см. пример gpio_output: /opt/KasperskyOS-Community-Edition-<version>/examples/gpio_output
Обзор: структура 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 поставляются:
- Компилятор NK, позволяющий сгенерировать транспортные методы и типы.
- Библиотека
libkos
, которая предоставляет API для работы с IPC-транспортом.
Реализация простейшего IPC-взаимодействия показана в примерах echo и ping (/opt/KasperskyOS-Community-Edition-<version>/examples/
).
Получение 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
, то на клиентской стороне необходимо вызвать:
На серверной стороне необходимо вызвать:
Подробнее см. примеры 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-канал может быть создан снова.
В началоПолучение идентификатора службы (riid)
Идентификатор службы (riid) требуется получить на клиентской стороне, если для используемой службы нет готовых транспортных библиотек (например, вы написали собственную службу). Для вызова методов сервера необходимо на клиентской стороне предварительно вызвать метод инициализации прокси-объекта, передав в качестве третьего параметра идентификатор службы. Например, для интерфейса Filesystem
:
Подробнее см. примеры 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
, то на клиентской стороне необходимо вызвать:
Подробнее см. примеры 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
.
Пример генерации транспортных методов и типов
При сборке решения компилятор NK на основе EDL-, CDL- и IDL-описаний генерирует набор специальных методов и типов, упрощающих формирование, отправку, прием и обработку IPC-сообщений.
В качестве примера рассмотрим класс процессов Server
, предоставляющий службу FS
, которая содержит единственный метод Open()
:
Server.edl
Operations.cdl
Filesystem.idl
На основе этих описаний будут сгенерированы файлы 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
: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
(для ответа).typedef union Server_component_req { struct nk_message base_; Filesystem_req OpsComp_FS; } Server_component_req; 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)
Работа с ареной 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
), предназначены для использования на стороне клиента. Остальные константы могут использоваться как на стороне клиента, так и на стороне сервера.
Примеры создания арены:
Заполнение арены данными перед передачей через 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()
, то отменять резервирование участка не требуется, так как такая арена на протяжении всего своего жизненного цикла содержит один участок, занимающий всю арену.
Примеры заполнения арены данными:
Получение данных из арены после приема через 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-сообщения.
Пример получения данных из арены:
Дополнительные возможности API
Чтобы получить размер арены, нужно вызвать функцию nk_arena_capacity()
.
Чтобы получить размер использованной части арены, нужно вызвать функцию nk_arena_allocated_size()
.
Чтобы проверить, является ли корректным дескриптор участка арены, нужно использовать макрос nk_arena_validate()
или функцию __nk_arena_validate()
.
Сведения о функциях и макросах API
Функции и макросы arena.h
Функция/Макрос |
Сведения о функции/макросе |
---|---|
|
Назначение Инициализирует дескриптор арены. Параметры
Значения макроса Код инициализации дескриптора арены. |
|
Назначение Инициализирует дескриптор арены. Параметры
Возвращаемые значения Нет. |
|
Назначение Создает и инициализирует дескриптор арены. Параметры
Возвращаемые значения Дескриптор арены. |
|
Назначение Создает в стеке буфер, а также создает и инициализирует дескриптор арены. Параметры
Значения макроса Дескриптор арены. |
|
Назначение Инициализирует дескриптор арены, содержащей только один участок. Параметры
Значения макроса Дескриптор арены. |
|
Назначение Отменяет резервирование участков арены. Параметры
Возвращаемые значения Нет. |
|
Назначение Резервирует участок арены заданного размера с заданным выравниванием. Параметры
Возвращаемые значения В случае успеха возвращает |
|
Назначение Позволяет получить размер арены. Параметры
Возвращаемые значения Размер арены в байтах. Дополнительные сведения Если параметр имеет значение |
|
Назначение Проверяет, является ли корректным дескриптор участка арены. Параметры
Значения макроса Имеет значение
Имеет значение
Имеет значение |
|
Назначение Проверяет, является ли корректным дескриптор участка арены. Параметры
Возвращаемые значения Возвращает
Возвращает
Возвращает |
|
Назначение Позволяет получить указатель на участок арены и размер этого участка. Параметры
Возвращаемые значения Указатель на участок арены или |
|
Назначение Позволяет получить размер использованной части арены. Параметры
Возвращаемые значения Размер использованной части арены в байтах. Дополнительные сведения Если параметр имеет значение |
|
Назначение Резервирует участок арены для заданного числа объектов заданного типа и копирует эти объекты в зарезервированный участок. Параметры
Значения макроса В случае успеха имеет значение |
|
Назначение Резервирует участок арены с заданным выравниванием для данных заданного размера и копирует эти данные в зарезервированный участок. Параметры
Возвращаемые значения В случае успеха возвращает |
|
Назначение Инициализирует дескриптор арены, содержащей только один участок. Параметры
Значения макроса Нет. |
|
Назначение Резервирует участок арены для заданного числа объектов заданного типа. Параметры
Значения макроса В случае успеха имеет значение адреса зарезервированного участка арены, иначе имеет значение |
|
Назначение Позволяет получить адрес участка арены и число объектов заданного типа, которое вмещается в этом участке. Параметры
Значения макроса В случае успеха имеет значение адреса участка арены, иначе имеет значение Дополнительные сведения Если размер участка арены не кратен размеру типа объектов, для которых этот участок предназначен, имеет значение |
|
Назначение Уменьшает размер участка арены. Параметры
Значения макроса В случае успеха имеет значение адреса уменьшенного участка арены, иначе имеет значение Дополнительные сведения Если требуемый размер участка арены превышает текущий, имеет значение Если выравнивание участка арены, который нужно уменьшить, не удовлетворяет типу объектов, для которых предназначен уменьшенный участок, имеет значение Если участок арены, который нужно уменьшить, является последним в арене, то освободившаяся часть этого участка становится доступной для резервирования последующих участков. |
|
Назначение Уменьшает размер участка арены. Параметры
Возвращаемые значения В случае успеха возвращает адрес уменьшенного участка арены, иначе возвращает Дополнительные сведения Если требуемый размер участка арены превышает текущий, возвращает Если выравнивание участка арены, который нужно уменьшить, не удовлетворяет заданному выравниванию, возвращает Если участок арены, который нужно уменьшить, является последним в арене, то освободившаяся часть этого участка становится доступной для резервирования последующих участков. |
Транспортный код на языке 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 |
Тип С++ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Работа с транспортным кодом на C++
Сценарии разработки клиента и сервера, которые обмениваются IPC-сообщениями, представлены в разделах "Статическое создание IPC-каналов при разработке на языке C++" и "Динамическое создание IPC-каналов при разработке на языке C++"
В началоСтатическое создание IPC-каналов при разработке на языке C++
Чтобы реализовать клиентскую программу, вызывающую метод службы, предоставляемой серверной программой, необходимо:
- Подключить сгенерированный заголовочный файл описания (
*.edl.cpp.h
) клиентской программы. - Подключить сгенерированные заголовочные файлы описаний используемых интерфейсов (
*.idl.cpp.h
). - Подключить заголовочные файлы:
/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
- Инициализировать объект приложения, вызвав функцию
kosipc::MakeApplicationAutodetect()
. (Также можно использовать функцииkosipc::MakeApplication()
иkosipc::MakeApplicationPureClient()
.) - Получить клиентский IPC-дескриптор канала и идентификатор службы (
riid
) вызвав функциюkosipc::ConnectStaticChannel()
.Функция принимает имя IPC-канала (из файла init.yaml) и квалифицированное имя службы (из CDL- и EDL-описаний компонента решения).
- Инициализировать прокси-объект для используемой службы, вызвав функцию
MakeProxy()
.
Пример
Чтобы реализовать серверную программу, предоставляющую службы другим программам, необходимо:
- Подключить сгенерированный заголовочный файл
*.edl.cpp.h
, содержащий описание компонентной структуры программы, включая все предоставляемые службы. - Подключить заголовочные файлы:
/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
- Создать классы, содержащие реализации интерфейсов, которые данная программа и её компоненты предоставляют в виде служб.
- Инициализировать объект приложения, вызвав функцию
kosipc::MakeApplicationAutodetect()
. - Инициализировать структуру
kosipc::components::Root
, которая описывает компонентную структуру программы и описания интерфейсов всех предоставляемых программой служб. - Связать поля структуры
kosipc::components::Root
с объектами, реализующими соответствующие службы.Поля структуры
Root
повторяют иерархию компонентов и служб, заданную совокупностью CDL- и EDL-файлов. - Получить серверный IPC-дескриптор канала, вызвав функцию
ServeStaticChannel()
.Функция принимает имя IPC-канала (из файла init.yaml) и структуру, созданную на шаге 5.
- Создать объект
kosipc::EventLoop
, вызвав функциюMakeEventLoop()
. - Запустить цикл диспетчеризации входящих IPC-сообщений, вызвав метод
Run()
объектаkosipc::EventLoop
.
Пример
Динамическое создание IPC-каналов при разработке на языке C++
Динамическое создание IPC-канала на стороне клиента включает следующие шаги:
- Подключить к клиентской программе сгенерированный заголовочный файл описания (
*.edl.cpp.h
). - Подключить сгенерированные заголовочные файлы описаний используемых интерфейсов (
*.idl.cpp.h
). - Подключить заголовочные файлы:
/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
- Получить указатели на имя сервера и квалифицированное имя службы с помощью сервера имен – специального сервиса ядра, представленного программой
NameServer
. Для этого необходимо подключиться к серверу имен вызовом функцииNsCreate()
и найти сервер, предоставляющий требуемую службу, используя функциюNsEnumServices()
. Подробнее см. "Динамическое создание IPC-каналов (cm_api.h, ns_api.h)". - Создать объект приложения, вызвав функцию
kosipc::MakeApplicationAutodetect()
. (Также можно использовать функцииkosipc::MakeApplication()
иkosipc::MakeApplicationPureClient()
.) - Создать прокси-объект для требуемой службы, вызвав функцию
MakeProxy()
. В качестве входного параметра функцииMakeProxy()
использовать вызов функцииkosipc::ConnectDynamicChannel()
. В функциюkosipc::ConnectDynamicChannel()
передать указатели на имя сервера и квалифицированное имя службы, полученные на шаге 4.
После успешной инициализации прокси-объекта клиенту доступен вызов методов требуемой службы.
Пример
Динамическое создание IPC-канала на стороне сервера включает следующие шаги:
- Подключить к серверной программе сгенерированный заголовочный файл (
*.edl.cpp.h
), содержащий описание компонентной структуры сервера, включая все предоставляемые службы. - Подключить заголовочные файлы:
/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
- Создать классы, содержащие реализации интерфейсов, которые сервер предоставляет в виде служб. Создать и инициализировать объекты этих классов.
- Создать объект приложения, вызвав функцию
kosipc::MakeApplicationAutodetect()
. - Создать и инициализировать объект класса
kosipc::components::Root
, который описывает структуру компонентов и служб сервера. Эта структура генерируется из описаний в CDL- и EDL-файлах. - Cвязать объект класса
kosipc::components::Root
с объектами классов, созданными на шаге 3. - Создать и инициализировать объект класса
kosipc::EventLoop
, который реализует цикл диспетчеризации входящих IPC-сообщений, вызвав функциюMakeEventLoop()
. В качестве входного параметра функцииMakeEventLoop()
использовать вызов функцииServeDynamicChannel()
. В функциюServeDynamicChannel()
передать объект классаkosipc::components::Root
, созданный на шаге 5. - Запустить цикл диспетчеризации входящих IPC-сообщений в отдельном потоке, вызвав метод
Run()
объектаkosipc::EventLoop
. - Создать и инициализировать объект, который реализует обработчик приема входящих запросов на динамическое создание IPC-канала.
При создании объекта можно использовать класс
kosipc::SimpleConnectionAcceptor
, который является стандартной реализацией интерфейсаkosipc::IConnectionAcceptor
. (Интерфейсkosipc::IConnectionAcceptor
определен в файле/opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/connection_acceptor.h
.) В этом случае обработчик будет реализовать следующую логику: если запрашиваемая клиентом служба опубликована на сервере, то запрос от клиента будет принят, иначе отклонен.Если необходимо создать собственный обработчик, то следует реализовать свою логику обработки запросов в методе
OnConnectionRequest()
, унаследованном от интерфейсаkosipc::IConnectionAcceptor
. Этот метод будет вызываться сервером при получении от клиента запроса на динамическое создание IPC-канала. - Создать объект класса
kosipc::EventLoop
, который реализует цикл приема входящих запросов на динамическое создание IPC-канала, вызвав функциюMakeEventLoop()
. В качестве входного параметра функцииMakeEventLoop()
использовать вызов функцииServeConnectionRequests()
. В функциюServeConnectionRequests()
передать объект, созданный на шаге 9.Цикл приема входящих запросов на динамическое создание IPC-канала может быть только один. Цикл должен работать в одном потоке. Цикл приема входящих запросов на динамическое создание IPC-канала должен быть создан после создания цикла диспетчеризации входящих IPC-сообщений (см. на шаг 7).
- Запустить цикл приема входящих запросов на динамическое соединение в текущем потоке, вызвав метод
Run()
объектаkosipc::EventLoop
.
Пример
При необходимости можно создать и инициализировать несколько объектов класса kosipc::components::Root
, объединенных в список объектов типа ServiceList
с помощью метода AddServices()
. Использование нескольких объектов позволяет, например, разделять компоненты и службы сервера на группы или публиковать службы под разными именами.
Пример