Семьи Набора I/O
В Наборе I/O семьи являются наборами классов, определяющих и реализующих абстракции, характерные для всех устройств определенного типа. Они обеспечивают программируемые интерфейсы и универсальный код поддержки для разработки драйверов, которые являются элементами (провайдеры) или клиенты таких семей.
В этой главе описываются много понятий, связанных с семьями I/O Kit:
Отношение драйверов семьям
Семьи как библиотеки, включая управление версиями и загрузку библиотек
Программируемая структура семей и соглашений о присвоении имен
Кроме того, эта глава предлагает некоторые подсказки для тех, кто хочет записать их собственным семьям I/O Kit. Для ссылки на текущий набор семей I/O Kit, предоставленных Apple, см. приложение Ссылка семьи Набора I/O
Драйверы и семьи
Семья I/O Kit является библиотекой, реализующей некоторый протокол шины (например, PCI или USB) или некоторый единый набор служб. Но поддержка, которую предоставляет семья, универсальна. Семья не включает ни одних из подробных данных для того, чтобы достигнуть аппаратные средства, потому что это не может сделать предположения об определенных аппаратных средствах под общим уровнем, который это представляет. Это - ответственность писателя драйвера записать код, образующий мост между бетоном и кратким обзором — т.е. между аппаратными средствами и абстракцией, определенной семьей. Драйвер должен расширить семью, чтобы поддерживать определенные аппаратные средства или получить определенные функции.
Возьмите семью SCSI Parallel в качестве примера. Семья SCSI Parallel инкапсулирует Интерфейс Параллели SCSI 5 спецификаций, которые четко определены. Одна из вещей, которые описывает спецификация, - то, как пойти о сканировании шины и обнаружении устройств. Поскольку это - дорогая работа, много контроллеров Параллели SCSI включают встроенное микропрограммное обеспечение, которое может кэшировать информацию об обнаруженных устройствах. Для использования в своих интересах этой оптимизации кэширования Вы могли разработать свой драйвер контроллера — элемент семьи SCSI Parallel — так, чтобы это переопределило функциональность сканирования для взаимодействия со встроенным микропрограммным обеспечением.
Семьи обычно выполняют определенные универсальные задачи, такие как сканирование шин, запросы клиентов, организация очередей и проверка команд, восстановление с ошибок, и соответствие и загрузка драйверов. Драйверы делают задачи, посягающие на аппаратные средства в некотором роде. Когда команда завершается, чтобы продолжить пример семьи SCSI Parallel, основное задание драйвера контроллера Параллели SCSI, как элемент семьи SCSI Parallel, должно получить команды SCSI от своей семьи, выполнить каждую команду на аппаратных средствах и отправить уведомление.
Некоторые семьи I/O Kit ясно разграничены спецификациями, которые они инкапсулируют. Другие семьи, такие как семья Audio, как легко не определяются, потому что нет никакой единственной спецификации, предписывающей, что должна включать семья. В случаях, таких как они, Apple тщательно выбрал набор абстракций для слияния в семью для создания его гибким и достаточно всесторонним. Все семьи должны распространить свои возможности, и это до более высоких уровней штабеля драйвера для управления этими возможностями.
Драйвер является и провайдером и клиентом в его отношениях семьям I/O Kit. Драйвер, который является провайдером для семьи (через ее кусок) является также элементом той семьи; это должно наследоваться от определенного класса в семье, описывающей службу, которую это экспортирует (однако, увеличенный). С другой стороны, драйвер является клиентом семьи, службу которой он импортирует (через кусок семьи). Например, драйвер диска SCSI наследовался бы от семейства систем хранения, а не семьи SCSI Parallel, к которой это будет клиент (см. рисунок 6-1). Драйвер мыши USB наследовался бы от семьи HID (Human Interface Devices) и будет клиентом семьи USB. Драйвер Звуковой карты PCI наследовался бы от семьи Audio и будет клиентом семьи PCI.
Семьи как библиотеки
Семьи реализованы как библиотеки, упакованные как расширения ядра (KEXTs). Они указывают свои атрибуты определения в информационном списке свойств и установлены в /System/Library/Extensions
. Семьи, механически, мало отличаются, чем обычные драйверы.
Две связанных характеристики отличают семью от драйвера. Во-первых, драйвер выражает зависимость от семьи, использующей OSBundleLibraries
свойство; во-вторых, семья загружается только как побочный продукт драйвера, перечисляющего его как библиотеку. Драйвер указывает библиотеки, от которых он зависит как элементы OSBundleLibraries
словарь. Набор I/O гарантирует, что эти библиотеки будут загружены в ядро, прежде чем это загрузит драйвер и соединит его с его семьями. Обратите внимание на то, что сами библиотеки объявляют библиотеки (расширения ядра и само ядро), на котором они зависят с помощью OSBundleLibraries
свойство.
Вы указываете библиотеку как пару ключ/значение в OSBundleLibraries
словарь, где ключ является идентификатором пакета (CFBundleIdentifier
) из библиотеки и значения самая ранняя версия библиотеки, с которой драйвер совместим. Все версии выражены в 'vers'
стиль ресурса. Перечисление 6-1 дает пример от информационного списка свойств драйвера AppleUSBAudio.
Свойство Listing 6-1 The OSBundleLibraries
<key>OSBundleLibraries</key> |
<dict> |
<key>com.apple.iokit.IOAudioFamily</key> |
<string>1.0.0</string> |
<key>com.apple.iokit.IOUSBFamily</key> |
<string>1.8</string> |
</dict> |
Несмотря на то, что Набор I/O загружает библиотеки, прежде чем он загрузит драйвер, указывающий эти зависимости и загружающий библиотеки в надлежащем порядке зависимости, нет никакой гарантии о порядке, в котором он загружает библиотеки, не имеющие никаких взаимозависимостей.
Обычно разработчики должны объявить зависимости для своего драйвера устройства или любого другого расширения ядра. (Если KEXT не имеет исполнимой программы, объявление зависимости является ненужным.), Какие зависимости они должны объявить, зависит, на котором должны быть разрешены символы. При включении заголовочного файла семьи или другой библиотеки, или если заголовок косвенно заканчивается включая библиотеку, необходимо объявить ту зависимость. Если Вы не уверенный, существует ли зависимость, объявите его так или иначе.
Управление версиями библиотеки
Быть доступным для загрузки и соединения в ядро, семью или другую библиотеку должно объявить ее информацию о совместимости с помощью двух свойств: CFBundleVersion
и OSBundleCompatibleVersion
. CFBundleVersion
свойство определяет прямой предел совместимости — т.е. текущая версия. OSBundleCompatibleVersion
свойство определяет обратный предел совместимости путем идентификации последнего CFBundleVersion
- определенная версия библиотеки, повредившей совместимость на уровне двоичных кодов с предыдущими версиями.
Каждый раз, когда Вы пересматриваете драйвер или семью, необходимо постепенно увеличить Ваш CFBundleVersion
оцените соответственно. Вы сбрасываете OSBundleCompatibleVersion
значение (к току CFBundleVersion
) только, когда версия делает двоичный файл несовместимым с предыдущими версиями, как тогда, когда Вы удаляете функцию или другой символ, или изменяете класс, таким образом что vtable изменения макета. Если Вы пишете семье I/O Kit, удостоверьтесь, что Вы указываете OSBundleCompatibleVersion
свойство для Вашей библиотеки; иначе, драйверы и другие расширения ядра не могут объявить зависимость от него и таким образом не могут соединиться против него.
И для драйверов и для семей (и, действительно, все расширения ядра), удостоверяются, что Вы также устанавливаете версию в модуле ядра и что это значение эквивалентно CFBundleVersion
в информационном списке свойств. Вы устанавливаете версию в исполнимой программе через MODULE_VERSION
установка в XCode, в Специализированном списке Настроек цели (Вы находите это в представлении Build цели).
Загрузка библиотеки
Менеджер KEXT функционирует как загрузчик ядра и компоновщика. Во время начальной загрузки или каждый раз, когда система обнаруживает недавно подключенное устройство, Набор I/O начинает процесс соответствия для нахождения подходящего драйвера для устройства. Когда такой драйвер найден, это - задание менеджера KEXT, чтобы загрузить драйвер в ядро и соединить его с библиотеками, от которых это зависит.
Но прежде чем это может сделать это, менеджер KEXT должен гарантировать, что те библиотеки и все другие библиотеки, от которых зависят те библиотеки, загружаются сначала. Чтобы сделать это, менеджер создает дерево зависимостей всех библиотек и других модулей ядра, требуемых для драйвера. Это создает это дерево с помощью содержания OSBundleLibraries
свойство, сначала драйвера и затем каждой требуемой библиотеки.
После того, как это создает дерево зависимостей, проверки менеджера KEXT, если уже загружаются библиотеки, которые являются самыми удаленными от драйвера в дереве. Если какая-либо из этих библиотек не загружается, менеджер загружает его и вызывает его подпрограмму запуска (подпрограмма варьируется согласно типу KEXT). Это тогда продолжает дерево зависимостей точно так же — соединение, загрузку, и запуск — пока все требуемые библиотеки не были соединены и загружены. Посмотрите рисунок 6-2 для иллюстрации этой процедуры.
Если менеджер KEXT встречается с проблемой, инициализирующей библиотеку, или это не находит библиотеку с совместимой версией (на основе значения OSBundleCompatibleVersion
), это останавливает и (обычно) возвращает код неисправности. Модули, уже загруженные, остаются загруженными некоторое время. Обычно разгрузка модулей не происходит сразу, когда они не используются. Набор I/O включает функцию, отслеживающую время простоя и разгружающую модули после определенного периода безделья.
Программируемая структура семей
Несмотря на то, что семьи I/O Kit имеют тенденцию очень отличаться друг от друга, у них есть некоторые структурные элементы вместе. Во-первых, IOService является общим суперклассом для всех семей I/O Kit; по крайней мере один важный класс в каждой семье, и возможно больше, наследовался от IOService (см. Базовые классы Набора I/O для получения дополнительной информации). И у каждой семьи есть один или несколько классов, представляющих интерфейс драйверам.
Типичные классы
Семья обычно определяет два класса для драйверов:
Класс, описывающий кусок, взаимодействует через интерфейс для драйверов, которые являются клиентами семьи
Суперкласс для драйверов, которые являются элементами семьи, и таким образом провайдерами к ее кускам
Другими словами, семьи I/O Kit обычно определяют восходящий интерфейс и нисходящий интерфейс. Эти интерфейсы требуются для разделения на уровни объектов драйвера, вовлеченных в соединение I/O. Восходящий интерфейс — интерфейс куска — представляет остальной части системы аппаратные абстракции и определения правила, инкапсулировавшие семьей. Нисходящий интерфейс обеспечивает интерфейс разделения на подклассы для задействованных драйверов. Вместе, интерфейсы определяют вызовы в семью и вниз вызовы, которые задействованные драйверы, как ожидают, выполнят.
В дополнение к этим двум классам семьи обычно определяют много служебных классов и поддерживают классы. Приложение Ссылка семьи Набора I/O описывает некоторые из этих классов.
Некоторые семьи указывают подклассы для определенных вариантов клиентских или задействованных драйверов. Семейство систем хранения, например, определяет универсальный класс блочной системы хранения для объектов куска (IOBlockStorageDevice) и затем также обеспечивает определенные подклассы для определенных вариантов: IOCDBlockStorageDevice и IODVDBlockStorageDevice. Кроме того, семьи могут включать классы для интерфейсов устройства (как подклассы IOUserClient), а также команды, определенные для семьи (как подклассы IOCommand). У семей могут также быть различные классы помощника и заголовочные файлы для специфичных для семьи определений типа.
Когда существует мало потребности в таких драйверах, некоторые семьи не включают общедоступный кусок или класс провайдера для драйверов. И Apple не предоставил семьям для всех типов аппаратных средств. Если Вы находите, что Набор I/O не имеет семьи или интерфейса для Ваших потребностей, можно всегда создавать драйвер, наследовавшийся непосредственно от IOService. Такие драйверы «семьи меньше» иногда необходимы, если возможное применение для драйвера - немногие. Они должны включить абстракции и диапазон функциональности, найденной в семьях, а также специфичном для аппаратных средств коде, типичном для драйверов. Помимо прямого наследования от IOService, драйверы семьи меньше часто используют классы помощника Набора I/O, такие как IOWorkLoop, классы источника события, IOMemoryCursor и IOMemoryDescriptor.
Именование и кодирование соглашений
Обычно позиция Apple на именовании класса в семьях - то, что имя должно указать то, что представляет класс. Часто, это имя диктует спецификация для аппаратных средств. Например, семья PCI определяет класс IOPCIBridge для драйверов, которые являются провайдерами для семьи. Причина этого имени проста: мост PCI (поскольку спецификация ясно дает понять) - то, чем управляют драйверы контроллера PCI. Когда нет никакого ясного прецедента именования для классов семьи, Набор I/O следует соглашению о присвоении имен IOFamilyNameDevice для куска (клиент) классы и IOFamilyNameController для классов провайдера.
Если Вы пишете Вашей собственной семье I/O Kit, Apple рекомендует следовать тем же рекомендациям по именованию для классов. И существует несколько других общих соглашений о присвоении имен знать. Каждый класс, функция, тип, и т.д. должен иметь префикс, назначающий поставщика, пишущего программное обеспечение. Обязательно не используйте любой из префиксов, которые Apple резервирует для себя (Таблица 6-1).
Префикс | Значение |
---|---|
OS, OS | libkern или другая служба ядра |
IO, io | Семья I/O Kit или I/O Kit |
MK, знак, mach_ | Ядро Маха |
Apple, APPLE, яблоко, AAPL, aapl, com_apple_ | Поддержка оборудования Apple (например, предоставленный Apple драйверы) |
Кроме того, частные, внутренние символы должны иметь подчеркивание «_» префикс, после соглашения, используемого Apple. Не получайте доступ к этому частному APIs от KEXT. Как с драйверами, использование обратной нотации DNS (заменяющий подчеркиваниями в течение многих периодов) настоятельно рекомендовано для предотвращения конфликтов имен.
Создание семьи Набора I/O
Могли бы быть случаи при размышлении стоящим записать собственной семье I/O Kit. Обычно это происходит, когда существует стандарт или протокол, для которого не существует никакая семья, и Вы различаете потребность в функциональной совместимости среди драйверов для устройств на основе этого протокола или стандарта. Примером мог бы быть стандарт IEEE488 для оборудования лаборатории и плоттеров.
Если Вы решаете реализовать семью, вот несколько инструкций для помощи Вам:
Вначале, запишите семье и коду драйвера вместе; еще не волнуйтесь о подразделении функциональности и интерфейса между драйвером и семьей. Просто концентрат, на придумывая хороший объектно-ориентированный проект, определяя, какие объекты необходимы и какие отношения они должны иметь.
После того, как Вы будете иметь рабочий драйвер и решите штабель для определенного устройства, разделите код семейства от специфичного для аппаратных средств кода. Один подход, который мог бы быть полезен для определения местоположения универсального семьей кода, специально для сложных семей, должен записать два или больше драйвера для различных аппаратных средств и затем краткого обзора далеко общий код.
Определите то, на что объекты куска семьи похожи к драйверам — т.е. APIs, который будут видеть Ваши клиенты. Чтобы сделать это, смотрите на спецификацию и инкапсулируйте важные функции (не необходимо включать редко или никогда используемые функции). Следует иметь в виду, что куски большинства семей делают очень мало. Чаще всего они инкапсулируют арбитражные подробные данные и обращение.
Определите суперкласс для драйверов, которые будут элементами Вашей семьи.
Сохраните разделение разделения на уровни семьи воздухонепроницаемым. Семья не должна включать заголовки ни от какой другой семьи или драйвера и не должна определять суперкласс клиентов.
Могут также быть ситуации, которые могли бы потребовать создание «суперсемьи»: семья, расширяющая существующую семью в пути, подобном подклассу, но с большой разницей; его целью является общность, а не специфика. Сторонние поставщики могли бы хотеть иметь суперсемью для содержания кода, характерного для драйверов на основе различных протоколов шины. Это избавило бы от необходимости загружать код, который не необходим. Например, у поставщика мыши мог бы быть драйвер, способный к управлению и USB и мыши ADB. Если система требует мыши USB, Вы не хотите иметь специфичный для ADB код, загруженный также. Таким образом поставщик мог бы записать суперсемье, действующей как сервис библиотека; это выделило бы уровни кода, определенного для протокола шины в подсемьи и помещать остающийся код в суперсемью. Только код, определенный для в настоящее время используемой шины, был бы загружен.