Фреймворк организации сервисов и стандартизованных интерфейсов драйверов устройств
Системная библиотека позволяет пользовательским процессам функционировать в качестве менеджеров ресурсов, которые можно динамически запускать и останавливать.
Менеджеры ресурсов, как правило, предназначены для обеспечения интерфейса к различным типам устройств: как к реальным физическим (например, последовательный порт, сетевая карта и жесткий диск), так и к виртуальным логическим (например, /dev/null
, сетевая файловая система или псевдотерминал).
В других операционных системах эти функции пересекаются с интерфейсами драйверов устройств и системными сервисами. Однако в отличие от драйверов, менеджеры ресурсов не требуют специальных механизмов взаимодействия с ядром и работают на уровне привилегий любой пользовательской программы.
Поскольку ЗОСРВ «Нейтрино» является распределенной микроядерной системой, в которой почти все внеядерные функции обеспечиваются запускаемыми пользователем программами, требуется хорошо определенный и эффективный интерфейс между клиентскими и серверными процессами. Все функции менеджера ресурсов основаны на передаче сообщений и не требуют наличия неявного интерфейса с микроядром.
По своей сути, менеджер ресурсов является серверной программой пользовательского уровня, которая принимает сообщения от клиентов и, при необходимости, может взаимодействовать с оборудованием.
Связь между менеджером ресурсов и клиентскими процессами, которые используют соответствующий ресурс, осуществляется посредством отображения в пространство имен менеджера процессов. В результате этой операции устанавливается соответствие символьным именем и серверным процессом. Менеджер ресурсов регистрирует свое имя в пространстве имен с помощью передачи менеджеру процессов сообщения о том, что он отвечает за обработку запросов к некоторой точке монтирования (или ниже нее, если речь идет о файловой системе). Это позволяет выполнить связывание сервиса с пространством имен, что перекликается с UNIX-концепцией всё есть файл.
Так, например, менеджер ресурсов devc-ser8250 может одновременно управлять последовательным портом и обслуживать клиентские запросы через префикс (символьное имя) /dev/ser1
пространства имён. Таким образом, запрос на передачу данных в последовательный порт выполняется с помощью открытия ресурса с именем /dev/ser1
.
Выбор менеджера ресурсов в качестве интерфейса реализации системного сервиса имеет смысл по нескольким причинам:
/dev/stats
и при чтении из него возвращает текст с описанием статистики своего использования. Утилита cat по имени файла позволяет вывести эти сведения в stdout:
Менеджеры ресурсов можно условно разделить на два типа:
Менеджеры ресурсов данного типа создают только одиночные записи в пространстве имен, каждая из которых представляет некоторое отдельное устройство. В менеджерах ресурсов этого типа, как правило, основная часть работы по предоставлению POSIX-интерфейса к устройству выполняется за счет библиотеки менеджеров ресурсов (интегрирована в системную библиотеку).
Например, драйвер последовательного порта регистрирует такие имена, как /dev/ser1
и /dev/ser2
. Когда пользователь применяет команду ls -l /dev
, библиотека неявно выполняет обработку ответов на возникающие сообщения _IO_STAT
(их посылает утилита ls). По этой причине разработчик последовательного порта может сосредоточиться на деталях управления оборудованием и меньше отвлекаться на интерфейсные вопросы.
Менеджеры ресурсов данного типа регистрируют в пространстве имен точку монтирования. Она представляет собой часть пути, ниже которой располагаются ресурсы, о которых известно лишь менеджеру ресурсов. Например, если менеджер ресурсов обслуживает точку монтирования /mount
, то при проверке пути /mount/home/user1
:
Несколько примеров менеджеров ресурсов файловых систем:
/net
, представляющего узлы, доступные в рамках протокола распределенной сети Qnet
Как только менеджер ресурсов зарегистрирует свой префикс, он будет получать сообщения при каждой попытке клиента выполнить для этого путевого имени вызов open(), read(), write() и т.п. Например, после того как devc-ser8250 создаст префикс /dev/ser1
, и клиентская программа выполнит следующий код:
fd = open( "/dev/ser1", O_RDONLY );
системная библиотека на стороне клиента сгенерирует сообщение с типом _IO_CONNECT
и передаст его менеджеру ресурсов. Через некоторое время клиентская программа может выполнить команду:
read( fd, buf, BUFSIZ );
В этом случае системная библиотека сгенерирует сообщение _IO_READ
и отправит его менеджеру ресурсов. Ключевым фактором здесь является осуществление взаимодействий между клиентом и сервером с помощью штатного механизма обмена сообщениями. Такой подход имеет ряд уникальных особенностей:
![]() | Все драйверы устройств и файловые системы в ЗОСРВ «Нейтрино» реализуются в виде менеджеров ресурсов. Это делает их в общем случае прозрачно доступными по сети. Кроме того, это означает, что пользовательский менеджер ресурсов потенциально обладает такой же функциональностью, что и собственного драйвера и сервисы операционной системы. |
Представить архитектуру менеджеров ресурсов можно следующим псевдокодом:
1. Инициализация структуры dispatch2. Регистрация префикса в пространстве имен менеджера процессов procntoWHILE True3. Получение сообщенияSWITCH 16-битный_тип_сообщенияCASE _IO_CONNECT:Вызов обработчика io_open()CASE _IO_READ:Вызов обработчика io_read()CASE _IO_WRITE:Вызов обработчика io_write()CASE *:Обработка других типов сообщений и выполнение соответствующих операцийENDSWITCHENDWHILE
Таким образом, архитектура включает три ключевых компонента:
Конструкция вида switch ... case требуется для каждого менеджера ресурсов. Однако, системная библиотека поставляется набор библиотечных функций, которые автоматизируют создание и обслуживание таких конструкций. Включая обработчики сообщений разных типов со стандартизованной логикой (обычно это возвращение корректного кода ошибки).
Существует две категории сообщений, предназначенных для менеджера ресурсов:
Сообщения установления соединения генерируются клиентом при вызове библиотечных функций, использующих символьное имя префикса (например, сообщение с типом _IO_CONNECT
генерируется функцией open() системной библиотеки). Обработка таких сообщений может включать проверку разрешений (имеет ли клиент право на доступ к данному устройству) и настройку контекста для данного запроса. Данный контекст называется блок управления открытым контекстом (англ. OCB, Open Control Block).
Сообщения ввода/вывода используют OCB, создаваемый между клиентом и менеджером ресурсов, для выполнения последующих операций ввода/вывода (например, обработку сообщений с типом _IO_READ
, генерируемых клиентским вызовом read()).
Такое разделение имеет веское обоснование. Было бы крайне неэффективно передавать полное символьное имя префикса с каждым сообщением, следующим после установления соединения с префиксом (или ресурсом, если речь идет о менеджере файловой системы). Обработчик io_open() также должен выполнять некоторые операции (например, проверку разрешений) лишь однократно, а не с каждым поступающим сообщением. Допустим, что при обработке запроса от read() клиенту переданы 4096 байт данных из 20 Мбайт общего объема данных в файле, ассоциированном с префиксом. В этом случае менеджер ресурсов при последовательных вызовах функции read() со стороны клиента должен иметь доступ к некоторой контекстной информации, ассоциированной с сессией клиента. Например, для хранения сведений о позиции чтения/записи в файле. Эту задачу позволяет решить OCB.
Библиотека resmgr, являющаяся частью системной библиотеки, предоставляет достаточно лаконичное API, позволяющее значительно упростить разработку менеджеров ресурсов. Поскольку расширение функциональности системы с помощью пользовательских менеджеров является типовой и достаточно частой задачей в системах, основанных на микроядре, затраты на их проектирование необходимо минимизировать.
Рассмотрим основные подходы к автоматизации, заложенные в библиотеку resmgr.
Если существуют типы сообщений, которые менеджер ресурсов по некоторым причинам не хочет обрабатывать (например, если он не поддерживает функцию вроде lseek()), системная библиотека может выполнять действия по умолчанию, заложенные в стандартном обработчике для данного типа сообщений.
Предусмотрено два уровня действий по умолчанию:
ENOSYS
, что сообщает о том, что данная функциональность сервером не поддерживается.
Другой удобной возможностью разделяемой библиотеки resmgr является автоматическая обработка сообщений dup().
Допустим, что клиентская программа выполнила следующий код:
fd = open( "/dev/device", O RDONLY );...fd2 = dup( fd );...fd3 = dup( fd );...close( fd3 );...close( fd2 );...close( fd );
В этом случае будет сгенерировано сообщение _IO_CONNECT
для вызова open() и два сообщения _IO_DUP
для двух вызовов dup(). После этого, когда клиент выполнит вызовы close(), он сгенерирует три сообщения _IO_CLOSE
.
Так как функции dup() создают дубликаты файловых дескрипторов, новые OCB не будут созданы для каждого из них. Отсюда, освобождение памяти при каждом сообщении _IO_CLOSE
также не требуется. В противном случае, первая же операция закрытия освободила бы весь пользовательский контекст.
Библиотека предоставляет стандартные обработчики, которые отслеживают вызовы open(), dup() и close() и выполняют работу только по последней операции закрытия.
Менеджеры ресурсов могут быть спроектированы как многопоточными, чтобы несколько потоков одновременно ожидали сообщения и имели возможность обрабатывать их одновременно.
Такое управление потоками является еще одной функцией, библиотеки менеджеров ресурсов. Она следит не только за количеством созданных и ожидающих потоков, но и за их оптимальным количеством.
Предусмотрен набор функций вида dispatch_*(), которые:
Для сохранения пропускной способности сети и поддержки атомарных операций микроядро поддерживает составные сообщения. Составное (комбинированное) сообщение состоит из нескольких буферов, объединенных и передаваемых как единое целое.
Например, функция readblock() позволяет потоку атомарно выполнять операции lseek() и read(). В клиентской библиотеке это достигается путем объединения сообщений _IO_LSEEK
и _IO_READ
в одно. При получении такого сообщения библиотека обработает оба поступивших сообщения, превращая обработку запросов от клиентской функции readblock() в атомарную на уровне IPC.
Составные сообщения также полезны для функции stat(). Вызов stat() может быть реализован как комбинированная отправка сообщения в составе функций open(), fstat() и close(). Вместо создания нескольких отдельных сообщений для каждой из этих функций, библиотека объединяет их в одно составное сообщение, что повышает общую производительность коммуникаций (особенно при их сетевом характере). Также это упрощает сам менеджер ресурсов, поскольку в этом случае для обработки stat() не требуется отдельного сообщения установления соединения.
Библиотека менеджеров ресурсов самостоятельно выполняет композицию и декомпозицию составных сообщений и их передачу различным функциям обработки (по типам каждого отдельного сообщения). Это приводит к минимизации усилий, связанных с написанием менеджеров ресурсов.
Поскольку большое число сообщений, принимаемых менеджером ресурсов, связано с общим набором атрибутов, в ОС предусмотрен еще один уровень стандартной обработки сообщений. Этот уровень связан с библиотекой функций iofunc_*() и позволяет менеджеру ресурсов автоматически обрабатывать такие функции, как stat(), chmod(), chown(), lseek() и т.п. Еще одним преимуществом является то, что обработчики iofunc_*() реализуют POSIX-семантику при обработке сообщений, что так же уменьшает работу программиста.
Необходимо рассмотреть три основных структуры, которыми управляет менеджер ресурсов:
Их взаимосвязь представлена на схеме:
Первая структура уже рассматривалась ранее в параграфе Типы сообщений. Она содержит данные, ассоциированные с конкретной сессией пользователя, и создается при каждом новом открытии префикса.
Поскольку менеджер ресурсов может обслуживать более чем один префикс (например, devc-ser8250 может отвечать за /dev/ser1
, /dev/ser2
, /dev/ser3
и т.д.), атрибутивная запись является уникальным идентификатором префикса. Она содержит данные, ассоциированные с каждым конкретным устройством. В частности, она включает идентификатор владельца и его группы, время последнего изменения и т.п.
Третья структура используется в основном лишь менеджерами файловых систем — она содержит общие для всего монтируемого устройства данные.
При наличии нескольких клиентских сессий к различным обслуживаемым префиксам, взаимосвязь этих структур будет выглядеть следующим образом:
По умолчанию функции iofunc_*() работают на основе допущения, что в структуры OCB и атрибутивной записи не инкапсулированы в пользовательские типы данных. Это предположение является безопасным по следующим причинам:
Как уже было отмечено, стандартные структуры должны быть заданы первым полем в типах данных, в которые их необходимо инкапсулировать. Это позволяет применять к ним функций iofunc_*(), передавая указатели на пользовательские типы данных. рассмотрим это на примере атрибутивной записи (понимая, что аналогичную процедуру можно произвести и для OCB):
Расширение атрибутивной записи позволяет иметь во всех обработчиках сообщений доступ к данным, уникальным для конкретного префикса (файла устройства). В расширенном OCB можно хранить данные, ассоциированные с пользовательской сессией. Доступ к обоим видам данных из обработчиков производится без осуществления поиска, что существенно экономит вычислительные ресурсы.
Системная библиотека содержит стандартные обработчики iofunc_*() для следующих клиентских вызовов:
Благодаря поддержке механизмов регистрации префиксов в пространстве имен, наличию простого интерфейса менеджеров ресурсов, а также набора стандартных обработчиков для решения типовых задач, системная библиотека обеспечивает простоту разработки драйверов для нового оборудования и гибкость в расширении перечня системных сервисов.
Предыдущий раздел: перейти