Создание аппаратных средств, доступных для приложений

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

Эта глава делает несколько вещей помочь Вам в этом отношении. Это описывает архитектурные аспекты OS X и Дарвинского ядра, лежащих в основе транспорта данных через границу, разделяющую ядро и пространство пользователя. Это описывает альтернативный APIs на OS X для перекрестного граничного транспорта между штабелем драйвера и приложением. И это описывает как к решению для самокрутки путем записи пользовательскому пользовательскому клиенту, наконец взятия Вас в подробном туре посредством реализации в качестве примера.

Лучше понять информацию представило в этом разделе, рекомендуется познакомиться с материалом inIOKit Основные принципы и соответствующие разделы Руководства по программированию Ядра.

Передача данных в и из ядра

Дарвинское ядро дает Вам, несколько способов позволить Вашему ядру кодировать связываются с кодом приложения. Определенное пространство пользователя ядра транспортирует API для использования, зависит от обстоятельств.

Наконец, существуют транспортные механизмы Набора I/O и APIs, позволяющий коду драйвера связаться с кодом приложения. В этом разделе описываются аспекты среды ядра, дающие начало этим механизмам, и обсуждает альтернативы, доступные Вам.

Проблемы с перекрестной границей I/O

Важная функция ядра OS X является защитой памяти. Каждый процесс в системе, включая ядро, имеет свое собственное адресное пространство, которое другие процессы не свободны к доступу неограниченным способом. Защита памяти важна для системной устойчивости. Это достаточно плохо, когда пользовательский процесс отказывает, потому что некоторый другой процесс повредил свою память. Когда ядро теряет работоспособность по той же причине, но это катастрофически — системный катастрофический отказ —.

В основном (но не исключительно) из-за защиты памяти, существуют определенные аспекты ядра, влияющие, как перекрестный граничный I/O имеет место или должен иметь место:

  • Ядро является ведомым устройством приложения. Код в ядре (такой как в драйвере) пассивен в этом, это только реагирует на запросы от процессов в пространстве пользователя. Драйверы не должны инициировать действие I/O самостоятельно.

  • Ресурсам ядра обескураживают в пространстве пользователя. Коду приложения нельзя доверить ресурсы ядра, такие как буферы памяти ядра и потоки ядра. Этот вид воздействия оставляет целую систему уязвимой; приложение может повредить критические области физической памяти или сделать что-то глобально катастрофическое с потоком ядра, разрушив всю систему. Для избавления от необходимости передающие ресурсы ядра к пространству пользователя система обеспечивает несколько транспортных механизмов пространства пользователя ядра для диапазона программируемых обстоятельств.

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

  • Каждый переход пространства пользователя ядра подвергается хиту производительности. Транспортные механизмы ядра используют ресурсы и таким образом требуют потерю производительности. Каждое прохождение от ядра до пространства пользователя (или наоборот) включает издержки вызовов RPC Маха, вероятное выделение ресурсов ядра, и возможно другие дорогие операции. Цель состоит в том, чтобы использовать эти механизмы максимально эффективно.

  • Ядро должно содержать только код, который должен быть там. Добавление ненужного кода к ядру — в частности кодирует, который работал бы точно также в пользовательском процессе — чрезмерно увеличивает размер ядра, потенциально дестабилизирует его, излишне соединяет проводом вниз физическую память (делающий его недоступный к приложениям) и ухудшает полную производительность системы. Посмотрите Кодирование в Ядре для более полного объяснения того, почему необходимо всегда стремиться избежать помещать код в ядро.

Mac OS 9 сравненных

На Mac OS 9, аппаратные средства доступа приложений в пути, полностью отличающемся от способа, которым это сделано на OS X. Различие в подходе в основном вследствие различий в архитектуре, особенно в отношении между приложением и драйвером.

В отличие от OS X, Mac OS 9 не поддерживает неприкосновенный барьер между адресным пространством приложения и адресным пространством ничего, что было бы найдено в ядре OS X. Приложение имеет доступ к адресу любого другого процесса в системе, включая тот из драйвера.

Этот доступ влияет, как вызываются подпрограммы завершения. Структуру позади всего I/O на Mac OS 9 систем вызывают блоком параметра. Блок параметра содержит поля, обычно требуемые для передачи DMA:

  • Адрес узла

  • Адрес Target

  • Направление передачи

  • Подпрограмма завершения и связанные данные

Подпрограмма завершения реализована приложением для обработки любых возвращенных результатов. Драйвер поддерживает связанный список блоков параметра как запросы I/O или задания для механизма DMA для выполнения. Когда задание завершается, аппаратные средства инициировали прерывание, запрашивая драйвер вызвать подпрограмму завершения приложения. Код приложения, реализовывая подпрограмму завершения работает во «время прерывания» — т.е. в контексте аппаратного прерывания. Это приводит к большей вероятности, что программная ошибка в подпрограмме завершения может разрушить или подвесить всю систему.

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

Программирование альтернатив

Набор I/O дает Вам несколько готовых альтернатив для выполнения перекрестного граничного I/O, не имея необходимость добавлять код к ядру:

  • Интерфейсы устройства семьи I/O Kit

  • POSIX APIs

  • Свойства I/O Registry

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

Интерфейсы Устройства семьи Набора I/O

Интерфейс устройства является оборотом того, что известно как пользовательский клиент в ядре. Интерфейс устройства является библиотекой или плагином, через интерфейс которого приложение может получить доступ к устройству. Приложение может вызвать любую из функций, определяемых интерфейсом, чтобы связаться с или управлять устройством. В свою очередь, библиотека или плагин говорят с пользовательским объектом клиента (экземпляр подкласса IOUserClient) в штабеле драйвера в ядре. (См. Архитектуру Пользовательских Клиентов для полного описания этих типов объектов драйвера.)

Несколько семей I/O Kit обеспечивают интерфейсы устройства для приложений и других клиентов пространства пользователя. Эти семьи включают (но не ограничиваются), SCSI, HID, USB и семьи FireWire. (Проверьте заголовочные файлы в платформу Набора I/O для обнаружения о полном списке семей, обеспечивающих интерфейсы устройства.), Если Ваш драйвер является элементом одной из этих семей, Ваши клиенты пространства пользователя должны только использовать интерфейс устройства семьи для доступа к аппаратным средствам, которыми управляет драйвер.

Посмотрите Нахождение и Доступ к Устройствам в Доступе к Аппаратным средствам Из Приложений для подробного представления процедуры для получения и использования интерфейсов устройства.

Используя POSIX APIs

Для каждого хранения, сети и последовательного устройства Набор I/O динамично создает файл устройств в файловой системе /dev каталог, когда это обнаруживает устройство и находит драйвер для него, или в системе, запускается или как часть его продолжающегося процесса соответствия. Если Ваш драйвер устройства является элементом Хранения Набора I/O, Сети или семей Serial, то Ваши клиенты могут получить доступ к Вашим водительским службам при помощи POSIX подпрограммы I/O. Они могут просто использовать Реестр I/O для обнаружения файла устройств, связанного с устройством средства управления драйвером. Затем с тем файлом устройств в качестве параметра, они вызывают POSIX функции I/O, чтобы открыть и закрыть устройство и читать и данные записи в него.

Поскольку Набор I/O динамично генерирует содержание /dev каталог как устройства присоединяется и отсоединяется, Вы никогда не должны твердый код имя файла устройств или ожидать, что он останется тем же каждый раз, когда Ваше выполнение приложения. Для получения пути к файлу устройств необходимо использовать устройство, соответствующее для получения пути устройства из Реестра I/O. Как только Вы нашли корректный путь, можно использовать функции POSIX для доступа к устройству. Для получения информации об использовании Реестра I/O для нахождения путей файла устройств посмотрите Аппаратные средства Доступа Из Приложений.

Доступ к свойствам устройства

Реестр I/O является базой динамических данных, которой использование Набора I/O для хранения текущих свойств и отношений драйвера возражает в системе OS X. APIs в ядре и в пространстве пользователя предоставляет доступ к Реестру I/O, позволяя коду получить и установить свойства объектов в Реестре. Этот общий доступ делает возможным ограниченная форма общения между драйвером и приложением.

Все объекты драйвера в ядре происходят из IOService, который является поочередно подклассом класса IORegistryEntry. Методы IORegistryEntry позволяют коду в ядре искать Реестр I/O определенные записи и получить и установить свойства тех записей. Дополнительный набор функций (определенный в IOKitLib.h) существуйте в платформе Набора I/O. Приложения могут использовать функции, чтобы выбрать данные, хранившие как свойства объекта драйвера или отправить данные в объект драйвера.

Этот устанавливающий свойство механизм подходит для ситуаций, где следующие условия являются истиной:

  • Драйвер не должен выделять постоянные ресурсы для завершения транзакции.

  • Приложение передает — копией — ограниченный объем данных (менее чем страница)

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

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

  • Вы управляете драйвером в ядре (и таким образом может реализовать setProperties метод, описанный ниже).

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

Общая процедура для отправки данных от приложения до объекта драйвера как свойство запускается с установления соединения с драйвером. Процедура для этого, описанного в Основном Соединении и Процедуре I/O, состоит из трех шагов:

  1. Получение ведущего порта Набора I/O

  2. Получение экземпляра драйвера

  3. Создание соединения

Как только у Вас есть соединение, сделайте следующие шаги:

  1. Вызовите IOConnectSetCFProperties функция, передающая в соединении и Базовом контейнерном объекте Основы, таком как CFDictionary.

    Базовый объект Основы содержит данные, которые Вы хотите передать драйверу. Обратите внимание на то, что можно вызвать IOConnectSetCFProperty вместо этого, если Вы хотите передать только сингл, объект Основы Ядра типа значения, такой как CFString или CFNumber и что ключ значения. Оба вызова функции вызывают вызов IORegistryEntry::setProperties метод в драйвере.

  2. В драйвере реализуйте setProperties метод.

    Прежде чем это вызовет этот метод, Набор I/O преобразовывает Базовый объект Основы, переданный в пользовательским процессом к соответствующему libkern контейнерному объекту (такому как OSDictionary). В его реализации этого метода объект драйвера извлекает данные из libkern контейнерного объекта и делает с ним, что ожидается.

Базовый объект Основы, переданный в пользовательским процессом, должен, конечно, иметь libkern эквивалент. Таблица 4-1 показывает допустимые Базовые типы Основы и их соответствующие объекты libkern.

Таблица 4-1  Соответствующая Базовая Основа и libkern контейнерные типы

Базовая основа

libkern

CFDictionary

OSDictionary

CFArray

OSArray

CFSet

OSSet

CFString

OSString

CFData

OSData

CFNumber

OSNumber

CFBoolean

OSBoolean

Следующий пример (Перечисление 4-1) показывает, как семья Serial Набора I/O использует механизм установки свойства Реестра I/O, чтобы позволить пользовательскому процессу сделать поток драйвера неактивным, пока последовательный порт не свободен использовать (когда существуют устройства, такие как модем и факс, конкурирующий за порт).

Перечисление 4-1  , Управляющее использованием последовательного устройства setProperties

IOReturn IOSerialBSDClient::
setOneProperty(const OSSymbol *key, OSObject *value)
{
    if (key == gIOTTYWaitForIdleKey) {
        int error = waitForIdle();
        if (ENXIO == error)
            return kIOReturnOffline;
        else if (error)
            return kIOReturnAborted;
        else
            return kIOReturnSuccess;
    }
 
    return kIOReturnUnsupported;
}
 
IOReturn IOSerialBSDClient::
setProperties(OSObject *properties)
{
    IOReturn res = kIOReturnBadArgument;
 
    if (OSDynamicCast(OSString, properties)) {
        const OSSymbol *propSym =
            OSSymbol::withString((OSString *) properties);
        res = setOneProperty(propSym, 0);
        propSym->release();
    }
    else if (OSDynamicCast(OSDictionary, properties)) {
        const OSDictionary *dict = (const OSDictionary *) properties;
        OSCollectionIterator *keysIter;
        const OSSymbol *key;
 
        keysIter = OSCollectionIterator::withCollection(dict);
        if (!keysIter) {
            res = kIOReturnNoMemory;
            goto bail;
        }
 
        while ( (key = (const OSSymbol *) keysIter->getNextObject()) ) {
            res = setOneProperty(key, dict->getObject(key));
            if (res)
                break;
        }
 
        keysIter->release();
    }
 
bail:
    return res;
}

Пользовательские пользовательские клиенты

Если Вы не можете сделать свои аппаратные средства должным образом доступными для приложений с помощью стандартных интерфейсов устройства Набора I/O, POSIX APIs или свойства I/O Registry, то необходимо будет, вероятно, записать пользовательскому пользовательскому клиенту. Чтобы сделать этот вывод, необходимо было сначала ответить «нет» на следующие вопросы:

  • Если Ваше устройство элемент семьи I/O Kit, та семья обеспечивает интерфейс устройства?

  • Действительно ли Ваше устройство является сериалом, сетями или устройством хранения?

  • Действительно ли свойства I/O Registry достаточны для потребностей приложения? (Если необходимо переместить огромные объемы данных, или если Вы не управляете кодом драйвера, тогда они, вероятно, не.)

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

Запись пользовательского пользовательского клиента

В этом разделе рассматриваются архитектуру пользовательских пользовательских клиентов, предлагает соображения для их проекта и описывает API и процедуры для реализации пользовательского пользовательского клиента. Посмотрите заключительный раздел A Guided Tour Through a User Client для экскурсии через довольно сложный пользовательский клиент.

Архитектура пользовательских клиентов

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

Пользовательский клиент фактически состоит из двух частей, одной части для каждой стороны границы, разделяющей ядро от пространства пользователя (см. Руководство по программированию Ядра для детального обсуждения границы пространства пользователя ядра). Эти части связываются друг с другом через интерфейсы, соответствующие установленному протоколу. В целях этого обсуждения ядро половина соединения является пользовательским надлежащим клиентом; часть на стороне приложения вызывают интерфейсом устройства. Рисунок 4-1 иллюстрирует этот проект

  Архитектура рисунка 4-1 пользовательских клиентов
Architecture of user clients

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

  • Пользовательский клиент является объектом драйвера (категория, включающая куски, а также драйверы). Пользовательский клиент является таким образом объектом C++, полученным из IOService, базового класса для объектов драйвера Набора I/O, самого в конечном счете получающего из libkern базового класса OSObject.

    Из-за его наследования от IOService объект драйвера, такой как пользовательский клиент участвует в жизненном цикле драйвера (инициализация, запуск, присоединение, зондирование, и т.д.) и в определенном штабеле драйвера имеет отношения клиентского провайдера с другими объектами драйвера в ядре. К пользовательскому провайдеру клиента — драйвер, предоставляющий услуги ему и объект, с которым приложение связывается — пользовательские клиентские взгляды точно так же, как другой клиент в ядре.

  • Интерфейс устройства является библиотекой пространства пользователя или другой исполнимой программой, связанной с приложением или другим пользовательским процессом. Это компилируется от любого кода, который может вызвать функции в платформе Набора I/O и или соединяется непосредственно в Мужественное приложение или косвенно загружается приложением через динамическую совместно используемую библиотеку, или плагин такой, как предоставлено Базовой Основой вводит CFBundle и CFPlugIn. (См. Реализацию Стороны пользователя Соединения для получения дополнительной информации.)

Пользовательские классы пользователя-клиента обычно наследовались от класса помощника IOUserClient. (Они могли также наследоваться от класса пользователя-клиента семьи I/O Kit, самого наследовавшегося от IOUserClient, но это не рекомендуемый подход; для объяснения, почему, посмотрите введение в раздел Creating a User Client Subclass.) Интерфейсная устройством сторона соединения использует функции C и вводит определенный в платформе Набора I/O IOKitLib.h.

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

Типы транспорта пользователя-клиента

Набор I/O APIs включает несколько различных типов транспорта через границу между ядром и пространством пользователя:

  • Передача невведенных данных: Этот механизм использует массивы структур, содержащих указатели на методы для вызова в объекте драйвера; методы должны соответствовать прототипам для примитивных функций с параметрами, только указывающими общий тип (скаляр для единственного, 32-разрядного значения или структура для группы значений), число скалярных параметров, размер структур и направление (ввод или вывод). Передача невведенных данных с помощью этого механизма может быть синхронной или асинхронной.

  • Совместное использование памяти: Это - форма памяти, отображающейся, в котором или больше страниц памяти отображаются в адресное пространство двух задач — в этом случае, драйвер и порядок подачи заявки. Или процесс может тогда получить доступ или изменить данные, хранившие на тех поделившихся страницах. Механизм пользователя-клиента для общей памяти использует объекты IOMemoryDescriptor на стороне ядра и буферных указателях vm_address_t на стороне пользователя для отображения аппаратных регистров на пространство пользователя. Этот метод передачи данных предназначается для аппаратных средств, которые не основаны на DMA и идеальны для перемещения больших объемов данных между аппаратными средствами и приложением. Пользовательские процессы могут также отобразить свою память в адресное пространство ядра.

  • Отправка уведомлений: Этот механизм передает порты уведомлений в и из ядра для отправки уведомлений между ядром и пользовательскими процессами. Эти методы используются в асинхронной передаче данных.

Важный момент для учета - то, что реализация пользовательского клиента не ограничивается только одним из упомянутых выше механизмов. Это может использовать двух или больше из них; например, это могло бы, использовал синхронный механизм невведенных данных для программирования механизма DMA и общей памяти для фактической передачи данных.

Синхронный по сравнению с асинхронной передачей данных

Два стиля передачи невведенных данных возможны с пользователем-клиентом Набора I/O APIs: Синхронный и асинхронный. У каждого есть его сильные места и недостатки, и каждый более подходит для определенных характеристик аппаратных средств и пространства пользователя API. Несмотря на то, что асинхронная модель I/O несколько сопоставима с путем Mac OS 9 аппаратных средств доступа приложений, это отличается в некотором отношении. Старшим значащим из этих различий является аспект архитектуры, совместно использованной с синхронной моделью: В OS X клиент обеспечивает поток, на котором вызывают подпрограммы завершения I/O, но ядро управляет потоком. Подпрограммы завершения I/O выполняются вне контекста ядра.

Следующее обсуждение сравнивает синхронное (блокирование) I/O и асинхронный (неблокирование с завершением) модели I/O с архитектурной точки зрения и без обсуждения определенного APIs. Для обзора того APIs посмотрите Создание Пользовательского Клиентского Подкласса.

В синхронном I/O пользовательский процесс выпускает запрос I/O по потоку, вызывающему в ядро и блоки, пока запрос I/O не был обработан (завершенный). Фактическая работа I/O завершается на потоке цикла работы драйвера. Когда I/O завершается, пользовательский клиент будит пользовательский поток, дает ему любые результаты операции I/O и возвращает управление потока к пользовательскому процессу. После обработки результата I/O поток поставляет другой запрос I/O пользовательскому клиенту, и процесс запускается снова.

Рисунок 4-2  Синхронный I/O между приложением и пользовательским клиентом
Synchronous I/O between application and user client

Характеристика определения синхронной модели - то, что клиент превращает вызов функции в ядро, не возвращающееся, пока I/O не завершился. Главный недостаток синхронного подхода - то, что поток, выпускающий запрос I/O, не может больше выполнять работу, пока не завершается I/O. Однако возможно прервать блокирующие синхронные подпрограммы при помощи сигналов, например. В этом случае пользовательский клиент должен знать, что сигналы могли бы быть отправлены и как обработать их. Это должно быть подготовлено реагировать соответственно во всех возможных ситуациях, такой как тогда, когда операция I/O происходит, когда получен сигнал.

В асинхронном I/O у клиента пользовательского процесса есть по крайней мере два потока, вовлеченные в передачи I/O. Один поток поставляет запрос I/O пользовательскому клиенту и возвратам сразу. Клиент также предоставляет поток пользовательскому клиенту для поставки уведомлений о завершениях I/O. Пользовательский клиент поддерживает связанный список этих уведомлений от предшествующих операций I/O. Если там ожидают уведомления, пользовательский клиент вызывает подпрограмму завершения потока уведомления, передающую в результатах операции I/O. Иначе, при отсутствии уведомлений в этом списке, пользовательский клиент помещает поток уведомления для сна.

Рисунок 4-3  Асинхронный I/O между приложением и пользовательским клиентом
Asynchronous I/O between application and user client

Пользовательский процесс должен создать и управлять дополнительными потоками в этой модели с помощью некоторого средства пользовательского уровня, такими как BSD pthreads. Эта необходимость указывает на основной недостаток асинхронной модели: проблема управления потоком в многопоточной среде. Это - что-то, что трудно сделать правильно. Другая проблема с асинхронным I/O связана с производительностью; с этим типом I/O существует два распространения в прямом и обратном направлениях пространства пользователя ядра на I/O. Один способ смягчить эту проблему состоит в том, чтобы обработать уведомления завершения в пакетном режиме и иметь уведомления потоки процессы несколько из них сразу. Для асинхронного подхода Вы также могли бы рассмотреть базирование потока I/O клиента на объекте цикла выполнения (CFRunLoop); этот объект является превосходным мультиплексором, позволяя Вам иметь различные источники событий пространства пользователя.

Таким образом, какая модель для I/O является лучше, синхронной или асинхронной? Как со многими аспектами проекта, ответ является определенным, “это зависит”. Это зависит от любого кода унаследованного приложения, с которым Вы работаете, это зависит от изощренности Вашего программирования потока, и это зависит от уровня I/O. Когда число операций в секунду I/O ограничивается (хорошо под 1 000 в секунду), асинхронный подход хорош. Иначе, рассмотрите синхронную модель I/O, пользующуюся лучшим преимуществом архитектуры OS X.

Факторы в пользовательском клиентском проекте

Прежде чем Вы начнете писать код своего пользовательского клиента, не торопитесь для размышления о его проекте. Думайте о том, что пользовательский клиент, как предполагается, делает, и что является лучшим программируемым интерфейсом для выполнения этого. При хранении некоторых точек повышенными в Проблемах С Перекрестной границей I/O в памяти, рассмотрите следующие вопросы:

  • Каков будет эффект Вашего проекта на производительности, имея в виду, что каждый переход пространства пользователя ядра облагает пошлиной производительности?

    Если Ваш пользовательский клиент необходимо быть нужна самое большее одна граница, пересекающаяся для каждого запроса I/O, API разработан должным образом. Идеально, можно обработать многократные запросы I/O в пакетном режиме в единственном пересечении.

  • Ваш проект помещает какой-либо код в ядро, которое могло работать точно также в пространстве пользователя?

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

  • API Вашего интерфейса устройства (сторона пространства пользователя пользовательского клиента) представляет аппаратные подробные данные клиентам?

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

Следующие разделы описывают эти и другие проблемы более подробно.

Диапазон доступности

Проект стороны пользователя пользовательского соединения клиента зависит от вероятного числа и природы приложений (и другие пользовательские процессы), которые хотят связаться с Вашим драйвером. Если Вы разрабатываете свой пользовательский клиент только для одного определенного приложения, и то приложение основывается на Мужественном объектном коде, то можно включить соединение и код I/O в само приложение. Посмотрите Основное Соединение и Процедуру I/O для общей процедуры.

Однако писатель драйвера часто хочет свой драйвер, доступный больше чем одним приложением. Драйвер мог быть предназначен для использования семейством заявок, поданных единственным разработчиком программного обеспечения. Или любое число приложений — даже те, о которых Вы в настоящее время не знаете — должны быть в состоянии получить доступ к службам драйвера. В этих ситуациях необходимо поместить код, связанный со стороной пользователя пользовательского соединения клиента в отдельный модуль, такой как совместно используемая библиотека или плагин. Этот модуль, известный как интерфейс устройства, должен абстрагировать общее соединение и функциональность I/O и представить дружественный программируемый интерфейс клиентам приложения.

Так скажем, Вы решили поместить свое соединение и код I/O в интерфейс устройства; теперь необходимо решить то, что формируется, этот интерфейс устройства должен взять. Соединение и код I/O должны вызвать функции, определяемые в платформе Набора I/O, содержащей Мужественную динамическую совместно используемую библиотеку; следовательно, все интерфейсы устройства должны быть созданы как исполняемый код на основе Мужественного формата объектных файлов. Интерфейс устройства может быть упакован как пакет, содержащий динамическую совместно используемую библиотеку или как плагин. Другими словами, общий выбор API между CFBundle (или NSBundle Какао) или CFPlugIn.

Решение между пакетом и плагином обусловлено природой приложений, которые будут клиентами Вашего пользовательского клиента. Если существует хороший шанс, что основанные на CFM приложения захотят получить доступ к Вашему драйверу, необходимо использовать APIs CFBundle, потому что CFBundle обеспечивает возможности перекрестной архитектуры. Если Вы требуете более мощной абстракции для доступности устройства, и клиенты приложения вряд ли будут основаны на CFM, можно использовать CFPlugIn APIs. Как исторический очерк, семьи Набора I/O используют CFPlugIns для своих интерфейсов устройства, потому что эти типы плагинов обеспечивают больший диапазон доступности, позволяя сторонним разработчикам создать подобные драйверу модули в пространстве пользователя.

Если только одно приложение будет клиентом Вашего пользовательского пользовательского клиента, но то приложение основывается на объектном коде CFM-PEF, необходимо создать Мужественный пакет (использующий CFBundle или APIs NSBundle) как интерфейс устройства для приложения.

В большинстве случаев можно безопасно выбрать CFBundle (или NSBundle) для интерфейса устройства. В дополнение к их возможности вызова перекрестной архитектуры они связываются, APIs упрощает создавать интерфейс устройства.

Проект унаследованных приложений

Основным фактором в проекте Вашего пользовательского клиента является API приложений, в настоящее время получающих доступ к аппаратным средствам на других платформах, таких как Mac OS 9 или Windows. Разработчики, портирующие эти приложения на OS X, будут (понятно) обеспокоены тем, как трудно это должно будет заставить их приложения работать с пользовательским пользовательским клиентом. Они, вероятно, захотят отодвинуться такое количество связанного с аппаратными средствами кода своего приложения, как они могут к OS X, но это может не быть просто сделать.

Например, если приложение, API основывается на инициированных прерыванием асинхронных обратных вызовах, такой как на Mac OS 9, что API не подходит для OS X, где поток основного прерывания должен остаться в ядре. Несмотря на то, что Набор I/O действительно имеет APIs для асинхронного I/O, этот APIs значительно отличается, чем те в Mac OS 9. Кроме того, предпочтительный подход для OS X должен использовать синхронные вызовы. Таким образом, это могло бы быть хорошей возможностью для разработчика приложений обновить его архитектуру аппаратного API.

Если разработчики приложений решают радикально перепроектировать свои аппаратные средства API, проект того, что API должен влиять на выбор, сделанный для транспорта пространства пользователя ядра. Например, если бы высокоуровневый API является асинхронным с обратными вызовами, логический выбор должен был бы базировать новое приложение API на асинхронном невведенном передающем данные API Набора I/O. С другой стороны, если высокоуровневый API уже синхронен, чем должен ясно использоваться синхронный невведенный передающий данные API Набора I/O. Синхронный API намного проще и более чист для реализации, и, если сделано должным образом не страдает мудрый производительностью по сравнению с асинхронным подходом.

Оборудование.

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

Наконец, существует проблема аппаратного управления памятью. Возможно, самым важным аспектом устройства, с точки зрения проекта пользователя-клиента, являются свои возможности управления памятью. Эти возможности влияют, как приложения могут получить доступ к аппаратным регистрам. Аппаратные средства могут использовать любой PIO (Запрограммированный ввод/вывод) или DMA (Прямой доступ к памяти). С PIO CPU самостоятельно перемещает данные между устройством и системой (физическая) память; с DMA контроллер шины берет на себя эту роль, освобождая микропроцессор для других задач. Почти все аппаратные средства теперь используют DMA, но некоторые более старые устройства все еще используют PIO.

Самый простой подход к управлению приложениями устройства должен отобразить ресурсы устройства (такие как аппаратные регистры или кадровые буферы) в адресное пространство порядка подачи заявки. Однако этот подход излагает значительную угрозу безопасности и должен только быть опробован с осторожностью. Кроме того, отображение регистров в пространство пользователя не является выполнимой опцией с аппаратными средствами DMA, потому что пользовательский процесс не будет иметь доступа к физической памяти, в которой это нуждается. Аппаратные средства DMA требуют, чтобы работа I/O была выполнена в ядре, где виртуальная память APIs приводит к доступу к физическим адресам. Если Ваше устройство использует управление памятью DMA, это возложено на Вас найти, что самый эффективный путь к Вашему драйверу выполняет работу I/O.

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

  • Полное управление памятью PIO. Поскольку Вы не требуете прерываний или доступа физической памяти, можно отобразить аппаратные регистры в адресное пространство приложения и управлять устройством оттуда.

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

Посмотрите Отображающиеся Регистры Устройства и RAM В Пространство пользователя для получения дополнительной информации об этих двух отображающих память подходах.

Следующие два подхода к проектированию являются надлежащими аппаратным средствам, использующим DMA для управления памятью. С DMA Ваш код требует физических адресов регистров и должен иметь дело с прерываниями, сообщенными аппаратными средствами. Если Ваши аппаратные средства соответствуют этому описанию, необходимо использовать проект на основе невведенного передающего данные механизма класса IOUserClient и обработать I/O в пользовательском клиенте и драйвере. Существует два типа такого проекта:

  • Основанный на функции пользовательский клиент. Этот вид пользовательского клиента определяет дополнительные наборы функций с обеих сторон границы пространства пользователя ядра (например, WriteBlockToDevice или ScanImage). Вызывание одной функции на стороне пользователя приводит к вызову функции на стороне ядра. Если набор функций не является маленьким, Вы не должны проявлять этот подход, потому что это поместило бы слишком много кода в ядро.

  • Основанные на регистре файлы задачи. Файл задачи является массивом, обрабатывающим ряд в пакетном режиме команд для получения и установки значений регистра и адресов. Пользовательский клиент реализует только четыре «примитивных» функции, воздействующие на содержание файла задачи. Файлы задачи полностью объяснены в следующем разделе, Файлах Задачи.

Когда у Вас есть аппаратные средства DMA, пользовательские клиенты, использующие основанные на регистре файлы задачи, являются рекомендуемым подходом к проектированию. Посмотрите Передающие Невведенные Данные Синхронно для примеров.

Файлы задачи

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

На стороне ядра все, в чем Вы нуждаетесь, является четырьмя «примитивными» методами, выполняющими основные операции, возможные с аппаратными регистрами:

  • Получите значение в регистре x

  • Регистр набора x для оценки y

  • Получите адрес в регистре x

  • Регистр набора x для обращения y

Этот маленький набор методов ограничивает объем кода в ядре, выделенном I/O для клиента и перемещающем большую часть этого кода в сторону пространства пользователя проекта. Интерфейс устройства представляет интерфейс более функционально ориентированному приложению. Эти функции реализованы, однако, для «разбивания» функциональных запросов на ряд команд регистра.

Посмотрите Передающие Невведенные Данные Синхронно для примера файлов задачи.

Сценарий проекта

Первый шаг в определении подхода для взятия должен смотреть на полную архитектуру системы и решить, являются ли какие-либо существующие решения для пространства пользователя ядра I/O надлежащими. Если Вы решаете, что нуждаетесь в пользовательском пользовательском клиенте, то анализируете то, что является подходом к проектированию для взятия, который является самым надлежащим пространству пользователя API и аппаратные средства.

Как пример, скажите, что у Вас есть плата PCI с возможностями цифровой обработки сигналов (DSP). Нет никакой семьи I/O Kit для устройств этого типа, таким образом, Вы знаете, что не может быть никакого интерфейса устройства семьи, который можно использовать. Теперь, скажем, карта использует и управление памятью DMA и прерывания, таким образом, должен быть код в ядре для обработки этих вещей; следовательно, драйвер должен быть записан, чтобы сделать это, и вероятно пользовательский клиент. Поскольку большая сумма данных DSP должна быть перемещена в и от карты, свойства I/O Registry не являются верным решением. Таким образом пользовательский пользовательский клиент необходим.

На другой платформе существует код пространства пользователя, вручающий от обработки задач к DSP. Этот код работает и над Mac OS 9 и над Windows. К счастью, существующий API абсолютно синхронен; может быть только один выдающийся запрос на поток. Этот аспект API делает его логическим шагом для адаптации кода, чтобы реализовать синхронную передачу данных в пользовательском клиенте и отобразить память карты в адресное пространство приложения для передач DMA.

Реализация стороны пользователя соединения

Конечно, если Вы пишете код для стороны ядра пользовательского соединения клиента, Вы, вероятно, собираетесь записать дополнительный код стороны пользователя. Во-первых, необходимо познакомиться с функциями C в платформе Набора I/O, особенно те в IOKitLib.h. Это подпрограммы, вокруг которых Вы структурируете свой код. Но прежде, чем установить руку в клавиатуру, займите несколько минут для решения то, на что код пространства пользователя собирается быть похожим, и как это будет соединенным.

Основное Соединение и Процедура I/O

Необходимо выполнить определенные задачи в коде стороны пользователя для соединения, создаете ли Вы библиотеку или включаете соединение и функциональность I/O в единственном клиенте приложения. Этот раздел суммирует те задачи, все из которых включают призывающий функции, определяемые IOKitLib.h.

Определение общих типов

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

Как иллюстрация, Перечисление 4-2 показывает содержание общего заголовочного файла проекта SimpleUserClient:

  Определения Общего типа перечисления 4-2 для SimpleUserClient

typedef struct MySampleStruct
{
    UInt16 int16;
    UInt32 int32;
} MySampleStruct;
 
enum
{
    kMyUserClientOpen,
    kMyUserClientClose,
    kMyScalarIStructImethod,
    kMyScalarIStructOmethod,
    kMyScalarIScalarOmethod,
    kMyStructIStructOmethod,
    kNumberOfMethods
};
Получите Ведущий порт Набора I/O

Запустите путем вызова IOMasterPort функция, чтобы заставить порт «ведущего устройства» Маха использовать для связи с Набором I/O. В текущей версии OS X необходимо запросить ведущий порт по умолчанию путем передачи константы MACH_PORT_NULL.

kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort);
Получите экземпляр драйвера

Затем, найдите экземпляр водительского класса в Реестре I/O. Запустите путем вызова IOServiceMatching функция для создания соответствующего словаря для соответствия против всех устройств, которые являются экземплярами указанного класса. Все, что необходимо сделать, предоставить имя класса драйвера. Соответствующая информация, используемая в соответствующем словаре, может варьироваться в зависимости от искавшей категории обслуживания.

classToMatch = IOServiceMatching(kMyDriversIOKitClassName);

Затем вызовите IOServiceGetMatchingServices, передача в соответствующем словаре получена на предыдущем шаге. Эта функция возвращает объект итератора, для которого Вы используете в вызове IOIteratorNext получить каждый последующий экземпляр в списке. Перечисление 4-3 иллюстрирует, как Вы могли бы сделать это.

  Итерация перечисления 4-3 через список экземпляров формирователя тока

    kernResult = IOServiceGetMatchingServices(masterPort, classToMatch, &iterator);
 
    if (kernResult != KERN_SUCCESS)
    {
        printf("IOServiceGetMatchingServices returned %d\n\n", kernResult);
        return 0;
    }
 
    serviceObject = IOIteratorNext(iterator);
    IOObjectRelease(iterator);

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

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

Создайте соединение

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

Для создания соединения вызвать IOServiceOpen, передача в экземпляре драйвера получена на предыдущем шаге вместе с текущей задачей Маха. Этот вызов вызывает newUserClient в экземпляре драйвера, приводящем к инстанцированию, инициализации и присоединению пользовательского клиента. Если драйвер указывает IOUserClientClass свойство в его информационном списке свойств, значении по умолчанию newUserClient реализация делает эти вещи для драйвера. В почти всех случаях необходимо указать IOUserClientClass свойство и полагается на реализацию по умолчанию.

Перечисление 4-4 показывает, как проект SimpleUserClient получает порт Маха, получает экземпляр драйвера и создает соединение с пользовательским клиентом.

Перечисление 4-4  , Открывающее соединение драйвера через пользовательский клиент

    // ...
    kern_return_t   kernResult;
    mach_port_t     masterPort;
    io_service_t    serviceObject;
    io_connect_t    dataPort;
    io_iterator_t   iterator;
    CFDictionaryRef classToMatch;
    // ...
    kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort);
 
    if (kernResult != KERN_SUCCESS)
    {
        printf( "IOMasterPort returned %d\n", kernResult);
        return 0;
    }
    classToMatch = IOServiceMatching(kMyDriversIOKitClassName);
 
    if (classToMatch == NULL)
    {
        printf( "IOServiceMatching returned a NULL dictionary.\n");
        return 0;
    }
    kernResult = IOServiceGetMatchingServices(masterPort, classToMatch,
                                            &iterator);
 
    if (kernResult != KERN_SUCCESS)
    {
        printf("IOServiceGetMatchingServices returned %d\n\n", kernResult);
        return 0;
    }
 
 
    serviceObject = IOIteratorNext(iterator);
 
    IOObjectRelease(iterator);
 
    if (serviceObject != NULL)
    {
        kernResult = IOServiceOpen(serviceObject, mach_task_self(), 0,
                                    &dataPort);
 
        IOObjectRelease(serviceObject);
 
        if (kernResult != KERN_SUCCESS)
        {
            printf("IOServiceOpen returned %d\n", kernResult);
            return 0;
        }
    // ...
Откройте пользовательский клиент

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

Основная процедура для запроса пользовательского клиента открыться подобна запросу I/O: интерфейс приложения или устройства вызывает IOConnectMethod функция, передающая в индексе пользовательскому клиенту IOExternalMethod массив. Проект SimpleUserClient определяет enum константы и для открытого и для дополнительных близких команд, использующихся в качестве индексов в пользовательском клиенте IOExternalMethod массив.

enum{    kMyUserClientOpen,    kMyUserClientClose,    // ...};

Тогда приложение (или интерфейсная устройством библиотека) вызывает один из IOConnectMethod функции; любая из этих функций может использоваться, потому что никакие входные данные не передаются в, и никакие выходные данные ожидается. Проект SimpleUserClient использует IOConnectMethodScalarIScalarO функция (см. Перечисление 4-5, принимающее предшествующий программируемый контекст, показанный в Перечислении 4-4).

Перечисление 4-5  , Запрашивающее пользовательский клиент открываться

// ...
    kern_return_t   kernResult;
// ...
    kernResult = IOConnectMethodScalarIScalarO(dataPort, kMyUserClientOpen,
                                                0, 0);
    if (kernResult != KERN_SUCCESS)
        {
            IOServiceClose(dataPort);
            return kernResult;
        }
    // ...

Если результат вызова не, поскольку этот пример показывает KERN_SUCCESS, тогда приложение знает, что устройство используется другим приложением. Приложение (или интерфейс устройства) тогда закрывает соединение с пользовательским клиентом и перезванивает результат его вызывающей стороне. Обратите внимание на то, что вызов IOServiceClose результаты в вызове clientClose в пользовательском объекте клиента в ядре.

Для полного описания открытого завершения, семантического для осуществления монопольного доступа к устройствам, посмотрите Монопольный Доступ к устройствам и открыть Method.

Отправьте и получите данные

Как только Вы открыли пользовательский клиент, пользовательский процесс может начать отправлять данные в него и получать данные от него. Пользовательский процесс инициирует все действие I/O, и пользовательский клиент (и его провайдер, драйвер) «ведомые устройства» его, отвечая на запросы. Для передачи невведенных данных пользовательский процесс должен использовать IOConnectMethod функции, определяемые в IOKitLib.h. Имена этих функций указывают общие типы параметров (скаляр и структура) и направление передачи (ввод и вывод). Таблица 4-2 перечисляет эти функции.

Таблица 4-2  функции IOConnectMethod

Функция

Описание

IOConnectMethodScalarIScalarO

Один или более скалярных входных параметров, один или несколько скалярных выходных параметров

IOConnectMethodScalarIStructureO

Один или более скалярных входных параметров, одна структура вывела параметр

IOConnectMethodScalarIStructureI

Один или более скалярных входных параметров, одна структура ввела параметр

IOConnectMethodStructureIStructureO

Одна структура ввела параметр, один выходной параметр структуры

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

Например, IOConnectMethodScalarIStructureO функция определяется как:

kern_return_t
IOConnectMethodScalarIStructureO(
    io_connect_t    connect,
    unsigned int    index,
    IOItemCount     scalarInputCount,
    IOByteCount *   structureSize,
    ... );

Параметры этой функции подобны тем из другого IOConnectMethod функции.

  • Параметр подключения является объектом соединения, полученным через IOServiceOpen вызовите (dataPort во фрагменте кода в Перечислении 4-4).

  • Индексный параметр является индексом в пользовательский клиент IOExternalMethod массив.

  • scalarInputCount параметр является числом скалярных входных значений.

  • structureSize параметр является размером возвращенной структуры.

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

Перечисление 4-6  , Запрашивающее I/O с IOConnectMethodScalarIStructureO функция

kern_return_t
MyScalarIStructureOExample(io_connect_t dataPort)
{
    MySampleStruct  sampleStruct;
    int             sampleNumber1 = 154;    // This number is random.
    int             sampleNumber2 = 863;    // This number is random.
    IOByteCount     structSize = sizeof(MySampleStruct);
    kern_return_t   kernResult;
 
    kernResult = IOConnectMethodScalarIStructureO(dataPort,
                        kMyScalarIStructOmethod, // method index
                        2,  // number of scalar input values
                        &structSize, // size of ouput struct
                        sampleNumber1,  // scalar input value
                        sampleNumber2,  // another scalar input value
                        &sampleStruct   // pointer to output struct
                        );
 
    if (kernResult == KERN_SUCCESS)
    {
        printf("kMyScalarIStructOmethod was successful.\n");
        printf("int16 = %d, int32 = %d\n\n", sampleStruct.int16,
                (int)sampleStruct.int32);
        fflush(stdout);
    }
    return kernResult;
}
Закройте соединение

При окончании действия I/O сначала дайте близкую команду пользовательскому клиенту для имения его, закрывают ее провайдера. Команда принимает форму, подобную этому, раньше давал открытую команду. Вызовите IOConnectMethod функция, передающая в константе, которая будет использоваться в качестве индекса в пользовательский клиент IOExternalMethod массив. В проекте SimpleUserClient этот вызов является следующим:

kernResult = IOConnectMethodScalarIScalarO(dataPort, kMyUserClientClose, 0,
                                            0);

Наконец, закройте соединение и высвободите любые ресурсы. Для этого просто вызовите IOServiceClose на Вашем io_connect_t соединение.

Аспекты проекта для интерфейсов устройства

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

Одна причина этого была подчеркнута прежде: Код в ядре может быть дренажом на производительности и источнике нестабильности. Но другая причина должна быть так же важна для разработчиков. Код пространства пользователя намного проще отладить, чем код ядра.

Создание пользовательского клиентского подкласса

При создании пользовательского клиента для драйвера необходимо создать подкласс IOUserClient. В дополнение к выполнению определенных задач, которые должны сделать все подклассы IOUserClient, необходимо записать код, который является определенным для того, как пользовательский клиент передает данные:

  • Используя механизм невведенных данных, синхронно (блокирующий)

  • Используя механизм невведенных данных асинхронно (неблокирование, с вызовом подпрограммы завершения)

  • Используя отображающий память APIs (для аппаратных средств PIO)

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

Этот раздел не покрывает аспекты разделения на подклассы классов домашнего пользователя-клиента. Такие классы имеют тенденцию быть сложными и тесно интегрированными в другие классы семьи. Конечно, Вы могли смотреть на код реализации с открытым исходным кодом, чтобы понять, как созданного и интегрированного, но все еще разделение на подклассы клиента домашнего пользователя не является рекомендуемым подходом.

Основы проекта пользователя-клиента

Пользовательский клиент является, прежде всего, объектом драйвера. Это в «вершине» штабеля драйвера между его провайдером (драйвер) и его клиентом (пользовательский процесс). Как объект драйвера, это должно участвовать в жизненный цикл драйвера путем реализации надлежащих методов: start, open, и т.д. (см. Основные принципы IOKit для описания жизненного цикла драйвера). Это должно связаться с его провайдером в надлежащие моменты. И это должно также поддержать соединение со своим клиентом (пользовательский процесс) вместе с любым состоянием, связанным с тем соединением; дополнительно, когда клиент уходит, это - пользовательская ответственность клиента очистить.

Учитывая тесную связь между драйвером и его пользовательским клиентом, рекомендуется включать исходные файлы для пользовательского клиента в проекте для драйвера. Если Вы хотите Набор I/O, в ответ на IOServiceOpen быть вызванным в пространстве пользователя, для автоматического выделения, запускается, и присоединяет экземпляр подкласса пользователя-клиента, указывает IOUserClientClass свойство в информационном списке свойств Вашего драйвера. Значение свойства должно быть полным именем класса Вашего пользовательского клиента. Также Ваш класс драйвера может реализовать метод IOService newUserClient для создания присоедините и запустите экземпляр подкласса IOUserClient.

В пользовательском заголовочном файле клиента объявите методы жизненного цикла, что Вы являетесь переопределяющими; они могут включать start, message, terminate, и finalize. Эти сообщения распространены, штабель драйвера, от драйвера возражают самый близкий к аппаратным средствам пользовательскому клиенту. Также объявите open и close методы; сообщения, вызывающие эти методы, распространены в противоположном направлении и порождаются приложением или самим интерфейсом устройства. open метод, в котором пользовательский клиент открывает его провайдера, особенно важен как место, где осуществляется монопольный доступ к устройствам. Для больше на пользовательском клиенте open и close методы, посмотрите Монопольный Доступ к устройствам и открыть Method; для роли приложения в этом посмотрите, Открывают User Client.

Существует одна определенная вещь отметить о start метод. В Вашей реализации этого метода проверьте, что переданным - в объекте провайдера является экземпляр Вашего водительского класса (использование OSDynamicCast) и присвойте его переменной экземпляра. Ваш пользовательский клиент должен отправить несколько сообщений в его провайдера в течение времени, оно загружается, таким образом, полезно сохранить ссылку на провайдера удобной.

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

Инициализация

Класс IOUserClient определяет initWithTask метод для инициализации экземпляров пользователя-клиента. Реализация по умолчанию просто вызывает IOService init метод, игнорируя параметры. initWithTask метод имеет четыре параметра:

  • Задача Маха клиента, открывшего соединение (тип task_t)

  • Маркер безопасности, который будет передан clientHasPrivilege метод, когда Вы пытаетесь определить, разрешают ли клиенту сделать безопасные операции (для которого им нужен эффективный UID нуля),

  • Тип, который будет передан clientHasPrivilege метод, когда Вы пытаетесь определить, разрешают ли клиенту сделать безопасные операции (для которого им нужен эффективный UID нуля).

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

Старший значащий из этих параметров является первым, пользовательская задача. Вероятно, необходимо сохранить эту ссылку как переменную экземпляра так, чтобы можно было легко обработать связанные с соединением действия, связанные с пользовательским процессом. Перечисление 4-7 показывает простую реализацию initWithTask:

Перечисление 4-7  реализация initWithTask

bool
com_apple_dts_SimpleUserClient::initWithTask(task_t owningTask,
                                    void *security_id , UInt32 type)
{
    IOLog("SimpleUserClient::initWithTask()\n");
 
    if (!super::initWithTask(owningTask, security_id , type))
        return false;
 
    if (!owningTask)
    return false;
 
    fTask = owningTask;
    fProvider = NULL;
    fDead = false;
 
    return true;
}
Монопольный Доступ к устройствам и открыть Method

Пользовательский клиент должен позволить устройство совместно использовать или исключительность устройства, как требуется аппаратными средствами. Много видов устройств разработаны, чтобы позволить только одному приложению за один раз получать доступ к устройству. Например, устройство, такое как сканер требует эксклюзивного доступа. С другой стороны, устройство как плата PCI DSP (описанный в сценарии представил Сценарий Проекта INA) разрешает совместное использование своих служб среди многократных клиентов приложения.

Идеальное место для пользовательского клиента, чтобы проверить и осуществить эксклюзивный доступ для устройств находится в open метод. В этой точке в жизненном цикле драйвера пользовательский клиент может попросить, чтобы открылся его провайдер; если провайдер open сбои метода, который означает другое приложение, получают доступ к службам провайдера. Пользовательский клиент отказывает в доступе к запрашивающему приложению путем возврата надлежащего кода результата, kIOReturnExclusiveAccess.

Как со всеми командами, проблемы приложения начальная команда для открытия. Это обрабатывает открытую команду так же, как это делает любую команду, данную путем вызова IOConnectMethod функция. Проект SimpleUserClient определяет enum константы и для открытого и для дополнительных близких команд, использующихся в качестве индексов в пользовательском клиенте IOExternalMethod массив. Тогда приложение (или интерфейс устройства) вызывает один из IOConnectMethod функции для выдачи открытой команды. Если результат вызова не KERN_SUCCESS, тогда интерфейс приложения или устройства знает, что устройство используется другим пользовательским процессом. Посмотрите Открывают User Client для большего количества подробных данных.

Для его части подкласс пользователя-клиента определяет записи в IOExternalMethod массив для открытых и близких команд (Перечисление 4-8).

Перечисление 4-8  IOExternalMethod записи для открытых и близких команд

 static const IOExternalMethod sMethods[kNumberOfMethods] =
    {
        {   // kMyUserClientOpen
            NULL,
            (IOMethod) &com_apple_dts_SimpleUserClient::open,
            kIOUCScalarIScalarO,
            0,
            0
        },
        {   // kMyUserClientClose
            NULL,
            (IOMethod) &com_apple_dts_SimpleUserClient::close,
            kIOUCScalarIScalarO,
            0,
            0
        },
      // ...
   );

В его реализации getTargetAndMethodForIndex метод, когда пользовательский клиент получает индекс kMyUserClientOpen, это возвращает обоих указатель на open метод и целевой объект, на который можно вызвать этот метод (пользовательский клиент сам). Реализация open метод в SimpleUserClient похож на код в Перечислении 4-9.

  Реализация перечисления 4-9 пользователя-клиента open метод

IOReturn
com_apple_dts_SimpleUserClient::open(void)
{
    if (isInactive())
        return kIOReturnNotAttached;
 
    if (!fProvider->open(this))
        return kIOReturnExclusiveAccess;
 
    return kIOReturnSuccess;
}

Эта реализация сначала проверяет на провайдера путем вызова метода IOService isInactive. isInactive возвраты метода true если провайдера завершили и таким образом препятствуют присоединить; в этом случае пользовательский клиент должен возвратиться kIOReturnNotAttached. Иначе, если пользовательскому клиенту присоединили его провайдера, это может вызвать open на нем. Если open вызовите сбои, тогда пользовательские клиентские возвраты kIOReturnExclusiveAccess; иначе это возвращается kIOReturnSuccess.

close метод в SimpleUserClient подобен open метод, за исключением того, что реализация проверяет, открыт ли провайдер перед вызовом close на нем Перечисление 4-10).

  Реализация перечисления 4-10 пользователя-клиента close метод

IOReturn
com_apple_dts_SimpleUserClient::close(void)
{
    IOLog("SimpleUserClient::close()\n");
 
    if (!fProvider)
        return kIOReturnNotAttached;
 
    if (fProvider->isOpen(this))
        fProvider->close(this);
 
    return kIOReturnSuccess;
}

Пользовательский клиент close метод может быть вызван по ряду причин в дополнение к коду пространства пользователя, дающему близкую команду. Пользовательский процесс мог корректно закончить соединение с пользовательским клиентом путем вызова IOServiceClose функционируйте или пользовательский процесс мог умереть, когда clientDied метод вызывается в пользовательском клиенте (см. Очистку). Если неожиданный случай происходит в штабеле драйвера (например, устройство демонтировано), пользовательский клиент получает a didTerminate сообщение, на которое это должно ответить путем вызова close метод. В его реализации close метод, пользовательский клиент, после принятия надлежащих мер предосторожности, должен закрыть своего провайдера для разгрузки его должным образом.

Одно последствие этого открыто-близкого проекта - то, что это до пользовательского провайдера клиента (в open метод), чтобы определить, когда или приемлем ли другой клиент. Например, предположите, что плата PCI DSP имеет аппаратное ограничение, в котором она может поддерживать только 256 клиентов. Вы могли работать вокруг этого ограничения путем мультиплексирования аппаратного доступа среди клиентов, но это будет большой работой для маловероятного случая. Вместо этого Вы могли бы принять решение иметь драйвер карты (пользовательский провайдер клиента) считают число клиентов — постепенное увеличение количества в open метод и постепенное уменьшение его в close метод — и возврат ошибка от open если был превышен клиентский предел.

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

Очистка

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

Для этих предписаний класс IOUserClient определил два метода, clientClose и clientDied. clientClose если клиентский процесс вызывает, метод вызывают IOServiceClose функция. clientDied если клиентский процесс умирает без вызова, метод вызывают IOServiceClose. Типичный ответ пользовательского клиента в любом случае должен вызвать close на его провайдере. Перечисление 4-11 показывает, как класс SimpleUserClient делает это.

  Реализации перечисления 4-11 clientClose и clientDied

IOReturn
com_apple_dts_SimpleUserClient::clientClose(void)
{
 
    // release my hold on my parent (if I have one).
    close();
    terminate();
 
    if (fTask)
    fTask = NULL;
 
    fProvider = NULL;
 
    // DON'T call super::clientClose, which just returns notSupported
 
    return kIOReturnSuccess;
}
 
IOReturn
com_apple_dts_SimpleUserClient::clientDied(void)
{
    IOReturn ret = kIOReturnSuccess;
 
    IOLog("SimpleUserClient::clientDied()\n");
 
    // do any special clean up here
    ret = super::clientDied();
 
    return ret;
}

Передача невведенных данных синхронно

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

Это - основной механизм, короче говоря но описание не учитывает важные подробные данные. Они запускаются с факта, что данные передали в, или из ядра по существу не вводится. Ядро не может знать или предсказать типы данных в пространстве пользователя и так может только принять наиболее обобщенные типы данных. Следующее обсуждение описывает, как пользователь-клиент Набора I/O, API (с обеих сторон границы ядра) размещает это ограничение и как Ваш подкласс IOUserClient должен реализовать свою часть невведенного передающего данные механизма. Поскольку Вы читаете вперед, обращаетесь к рисунку 4-4, графически показывающему отношения среди частей невведенного передающего данные API.

Скаляр и структура

Механизм пользователя-клиента использует только два обобщенных типа для параметров: скаляр и структура. Они далее квалифицированы направлением данных: ввод или вывод.

Методы, вызванные в объекте драйвера через механизм невведенных данных, должны соответствовать IOMethod введите, который сознательно эластичен с точки зрения допустимых параметров:

typedef IOReturn (IOService::*IOMethod)(void * p1, void * p2, void * p3,
                                void * p4, void * p5, void * p6 );

Однако параметры IOMethod метод должен соответствовать одному из четырех обобщенных прототипов, идентифицированных константами, определенными в IOUserClient.h :

kIOUCScalarIScalarO

Скалярный ввод, скалярный вывод

kIOUCScalarIStructO

Скалярный ввод, структура выводится

kIOUCStructIStructO

Ввод структуры, структура выводится

kIOUCScalarIStructI

Скалярный ввод, структура вводится

На стороне пользовательского процесса соединения, IOKitLib.h определяет четыре функции, соответствующие этим константам:

  • IOConnectMethodScalarIScalarO

  • IOConnectMethodScalarIStructureO

  • IOConnectMethodStructureIStructureO

  • IOConnectMethodScalarIStructureI

Для получения дополнительной информации посмотрите раздел Send и Receive Data, обсуждающий, как код в пространстве пользователя использует эти функции.

Включая заголовочный файл общих типов

Удостоверьтесь, что Ваш подкласс пользователя-клиента включает заголовочный файл, который Вы создали для определения типов данных, характерных и для ядра и для кода пространства пользователя. Типы включали бы enum константы для использования в качестве индексов в IOExternalMethod массив и любые структуры, вовлеченные в I/O.

Для больше на этих определениях общего типа, посмотрите Общие типы Определения.

Построение массива IOExternalMethod

Отличительное действие, которое подкласс IOUserClient должен выполнить при реализации его части механизма невведенных данных, идентифицирует метод драйвера, который пользовательский процесс хочет вызванный в определенный момент. Важная часть этой задачи является конструкцией массива указателей на методы для вызова. Однако содержание этого массива является фактически больше, чем простая таблица указателей метода; каждый элемент массива IOExternalMethod структура, которой только один элемент является указателем метода.

Другие элементы IOExternalMethod структура определяет объект, реализовывая метод, чтобы вызвать (цель), идентифицировать общие типы параметров (скаляр или структура, ввод или вывод), и предоставить некоторую информацию о параметрах. Таблица 4-3 описывает IOExternalMethod поля.

Табличные 4-3  поля IOExternalMethod структура

Поле (с типом)

Описание

IOService * object

Объект драйвера реализация метода, обычно пользовательский провайдер клиента («цель»). Может быть NULL если цель должна быть динамично определена во время выполнения.

IOMethod func

Указатель на метод для вызова в цели; включайте имя класса (например, “com_acme_driver_MyDriver:: myMethod”)

IOOptionBits flags

Один из enum константы, определенные в IOUserClient.h для указания общих типов параметра

IOByteCount count0

Если первый параметр определяет скаляр, число скалярных значений; если первый параметр определяет структуру, размер структуры

IOByteCount count1

Если второй параметр определяет скаляр, число скалярных значений; если второй параметр определяет структуру, размер структуры

Можно инициализировать IOExternalMethod массив в любом из вероятных мест в Вашем коде:

  • В статическом контексте

  • В start метод

  • В Вашей реализации IOUserClient getTargetAndMethodForIndex метод

Именно в этом последнем методе Набор I/O запрашивает IOExternalMethod структура для использования. Перечисление 4-12 показывает, как проект SimpleUserClient в качестве примера инициализирует массив.

Перечисление 4-12  , Инициализирующее массив IOExternalMethod структуры

static const IOExternalMethod sMethods[kNumberOfMethods] =
{
    {   // kMyUserClientOpen
        NULL,                   // Target determined at runtime.
        (IOMethod) &com_apple_dts_SimpleUserClient::open,
        kIOUCScalarIScalarO,    // Scalar Input, Scalar Output.
        0,                      // No scalar input values.
        0                       // No scalar output values.
    },
    {   // kMyUserClientClose
        NULL,                   // Target determined at runtime.
        (IOMethod) &com_apple_dts_SimpleUserClient::close,
        kIOUCScalarIScalarO,    // Scalar Input, Scalar Output.
        0,                      // No scalar input values.
        0                       // No scalar output values.
    },
    {   // kMyScalarIStructImethod
        NULL,                   // Target determined at runtime.
        (IOMethod) &com_apple_dts_SimpleDriver::method1,
        kIOUCScalarIStructI,    // Scalar Input, Struct Input.
        1,                      // One scalar input value.
        sizeof(MySampleStruct)  // The size of the input struct.
    },
    {   // kMyScalarIStructOmethod
        NULL,                   // Target determined at runtime.
        (IOMethod) &com_apple_dts_SimpleDriver::method2,
        kIOUCScalarIStructO,    // Scalar Input, Struct Output.
        2,                      // Two scalar input values.
        sizeof(MySampleStruct)  // The size of the output struct.
    },
    {   // kMyScalarIScalarOmethod
        NULL,                   // Target determined at runtime.
        (IOMethod) &com_apple_dts_SimpleDriver::method3,
        kIOUCScalarIScalarO,    // Scalar Input, Scalar Output.
        2,                      // Two scalar input values.
        1                       // One scalar output value.
    },
    {   // kMyStructIStructOmethod
        NULL,                   // Target determined at runtime.
        (IOMethod) &com_apple_dts_SimpleDriver::method4,
        kIOUCStructIStructO,    // Struct Input, Struct Output.
        sizeof(MySampleStruct), // The size of the input struct.
        sizeof(MySampleStruct)  // The size of the output struct.
    },
};
Реализация getTargetAndMethodForIndex

Все подклассы IOUserClient, использующих механизм невведенных данных для передачи данных между приложением и драйвером, должны реализовать getTargetAndMethodForIndex метод (или, для асинхронной поставки, getAsyncTargetAndMethodForIndex). Типичная реализация getTargetAndMethodForIndex должен сделать две вещи:

  • Возвратите непосредственно указатель на надлежащее IOExternalMethod структура, идентифицирующая метод для вызова.

  • Возвратите ссылку на объект, реализующий метод (цель).

Можно или статически присвоить цель IOExternalMethod поле при инициализации структуры, или можно динамично определить цель во время выполнения. Поскольку цель является обычно пользовательским провайдером клиента, часто все, что необходимо сделать, возвратить ссылку на провайдера (предполагающий, что Вы сохранили его как переменную экземпляра). Перечисление 4-13 показывает один подход для того, чтобы сделать это.

Перечисление 4-13  реализовывая getTargetAndMethodForIndex метод

IOExternalMethod *
com_apple_dts_SimpleUserClient::getTargetAndMethodForIndex(IOService **
                                                target, UInt32 index)
{
 
     // Make sure IOExternalMethod method table has been constructed
    // and that the index of the method to call exists
 
    if (index < (UInt32)kNumberOfMethods)
    {
        if (index == kMyUserClientOpen || index == kMyUserClientClose)
        {
            // These methods exist in SimpleUserClient
            *target = this;
        }
        else
        {
            // These methods exist in SimpleDriver
            *target = fProvider;
        }
 
        return (IOExternalMethod *) &sMethods[index];
    }
 
    return NULL;
};
Рисунок 4-4  существенный код для передачи невведенных данных
The essential code for passing untyped data
Проверка

Пользовательский клиент должен полностью проверить все данные, которые это обрабатывает. Это не должно только вслепую доверять своему клиенту, пользовательскому процессу; клиент мог быть злонамеренным. Некоторые проверки проверки могли бы быть внутренней непротиворечивостью среди команд ввода и вывода и буферов, побочных или плохо определенных команд и ошибочных битов регистра. Для длинного и сложного кода Вы могли бы хотеть создать конфигурируемый механизм проверки.

Передача невведенных данных асинхронно

Характеристика определения процедуры, описанной мимоходом Невведенные Данные Синхронно, является поведением потока пространства пользователя, обращающегося с просьбой I/O. Когда клиент в пространстве пользователя запрашивает передачу I/O путем вызова одного из IOConnectMethod когда I/O завершается, функции, поток, переносящий запрос, должны блокировать и ожидать пользовательского клиента для возврата. Это поведение синхронно. Однако класс IOUserClient также обеспечивает APIs для асинхронной передачи данных между пользовательским клиентом и пользовательским процессом. С этим APIs, когда пользовательский процесс вызывает IOConnectMethod функция, это может сразу продолжиться к другим задачам, потому что это не блокируется в функции. Позже, пользовательский клиент вызывает обратный вызов в клиентском приложении, передавая его результат и любые получающиеся данные.

Несмотря на то, что можно выделить поток отдельного приложения для уведомлений о завершениях I/O (как описано в Синхронном По сравнению с Асинхронной передачей данных), поток уведомления не необходим для асинхронного I/O. Фактически, существует альтернатива потоку уведомления, который намного проще реализовать.

Можно выполнить то же асинхронное поведение с помощью цикла выполнения приложения (CFRunLoop). Основной поток каждого приложения имеет объект цикла выполнения, имеющий, получают права на наборе порта Маха, на котором все источники событий, подходящие для приложения (события от нажатия мыши, события дисплея, уведомления пространства пользователя, и т.д.), имеют, отправляют права. Работая в жестком цикле, CFRunLoop проверяет, имеет ли какой-либо из источников событий в его наборе порта Маха незаконченные события и, если они делают, диспетчеризирует событие намеченному месту назначения.

Поскольку выполненные циклы так повсеместны в пространстве пользователя — каждое приложение имеет один — они предлагают простое решение проблемы регистрации завершений I/O от ядра до пространства пользователя. Источник уведомления I/O для пользовательского клиента просто должен быть добавлен к циклу выполнения. Когда I/O завершается, тогда приложение (или интерфейс устройства) просто должно передать этот порт пользовательскому клиенту, а также указатель на подпрограмму завершения для пользовательского клиента для вызова.

В этом разделе описываются общие процедуры, которые пользовательский клиент и приложение должны выполнить для реализации асинхронного использования I/O CFRunLoop и часть APIs в платформе Набора I/O и классе IOUserClient. (Многие из APIs в классе IOUserClient, тегирующегося с «асинхронным», не важны для реализации асинхронного I/O.) Несмотря на то, что этот раздел иллюстрирует процедуру только с одним CFRunLoop (связанный с основным потоком приложения) и один источник цикла выполнения, возможно иметь многократные циклы выполнения и многократные источники цикла выполнения.

Процедура подачи заявки Используя CFRunLoop

Для реализации его части асинхронной невведенной передачи данных приложение или интерфейс устройства должны сначала получить, получают права на порт на наборе порта CFRunLoop приложения, таким образом становясь источником цикла выполнения для того цикла выполнения. Тогда это передает этот порт Маха своему пользовательскому клиенту. Это должно также реализовать функцию обратного вызова, которую вызывает пользовательский клиент, когда завершается I/O.

Следующая процедура детализирует шаги, которые приложение должно завершить для выполнения этого; весь APIs, упомянутый здесь, определяется в IOKitLib.h, за исключением CFRunLoop APIs, которые определяются в CFRunLoop.h в Базовой платформе Основы:

  1. Реализуйте функцию, соответствующую прототипу обратного вызова IOAsyncCallback (или один из связанных типов обратного вызова; посмотрите IOKitLib.h). Начальный параметр этой функции (void *refcon) идентификатор запроса I/O; последующие параметры для результата и данных возврата (если таковые имеются). Ваша реализация IOAsyncCallback подпрограмма должна должным образом обработать результат и возвратила данные для каждого определенного запроса I/O.

  2. Создайте объект порта уведомлений (который основывается на порту Маха).

    Вызвать IONotificationPortCreate, передача в ведущем порту, полученном от более раннего вызова до IOMasterPort. Этот вызов возвращается IONotificationPortRef объект.

  3. Получите исходный объект цикла выполнения из объекта порта уведомлений.

    Вызовите IONotificationPortGetRunLoopSource функция, передающая в IONotificationPortRef объект. Этот вызов возвращает a CFRunLoopSourceRef объект.

  4. Зарегистрируйте источник цикла выполнения в цикле выполнения приложения.

    Вызвать CFRunLoopAddSource, указывая как параметры ссылку на CFRunLoop приложения, CFRunLoopSourceRef объект, полученный на предыдущем шаге и режиме цикла выполнения kCFRunLoopDefaultMode.

  5. Получите порт Маха, поддерживающий источник цикла выполнения для уведомлений завершения I/O.

    Вызовите IONotificationPortGetMachPort функция, передающая в IONotificationPortRef возразите снова. Этот вызов возвращает порт Маха, введенный как mach_port_t.

  6. Дайте порт уведомлений Маха пользовательскому клиенту.

    Вызвать IOConnectSetNotificationPort, передача в порту и соединении с пользовательским клиентом; два остающихся параметра, тип и ссылка, определяются на семью I/O Kit и таким образом не необходимые в Вашем случае. Вызов IOConnectSetNotificationPort результаты в вызове registerNotificationPort в пользовательском клиенте.

  7. Когда Ваше приложение будет готово к передаче I/O, выпустите запрос I/O путем вызова одного из IOConnectMethod функции. Блок параметра (такой как файл задачи) содержащий запрос I/O должен включать как его первые два поля указатель на IOAsyncCallback подпрограмма обратного вызова, реализованная в приложении или интерфейсе устройства. Это должно также включать a (void *) поле refcon для обеспечения контекста для запроса. Иначе, процедура является точно тем же что касается синхронной невведенной передачи данных за исключением того, что IOConnectMethod функционируйте сразу возвращается.

  8. Когда больше нет передач I/O для создания, приложение или интерфейс устройства должны избавиться от порта и удалить источник цикла выполнения, который это создало и зарегистрировало. Это включает завершение следующих шагов:

    1. Удалите источник цикла выполнения (CFRunLoopSourceRef) от цикла выполнения путем вызова CFRunLoopRemoveSource.

    2. Уничтожьте объект порта уведомлений путем вызова IONotificationPortDestroy. Обратите внимание на то, что этот вызов также выпускает источник цикла выполнения, до которого Вы получили от вызова IONotificationPortGetRunLoopSource на Шаге 3.

Пользовательская клиентская процедура

Процедура подкласс IOUserClient должен следовать за использованием асинхронного I/O APIs и CFRunLoop, подобна синхронному подходу, описанному мимоходом Невведенные Данные Синхронно в этом, часть того же APIs используется. Однако асинхронная процедура I/O (использующий цикл выполнения приложения) отличается по многим значительным подробным данным от синхронного подхода.

  1. Создайте IOExternalMethod выстройте и реализуйте getTargetAndMethodForIndex метод, как Вы были бы в синхронном подходе.

  2. Реализуйте метод IOUserClient registerNotificationPort сохранить ссылку на порт уведомлений Маха, передающийся в. Вспомните, что этот метод вызывается в результате IOConnectSetNotificationPort вызовите в пространстве пользователя, и порт уведомлений является источником цикла выполнения объекта CFRunLoop приложения.

  3. В Вашей реализации IOMethod метод, вызывающийся, делает следующее:

    1. Проверьте IOAsyncCallback поле указателя в блоке параметра. Если это не -NULL, тогда Вы знаете, что это - асинхронный запрос I/O. (Если это - a NULL указатель, затем обработайте запрос синхронно.)

    2. Вызовите IOUserClient setAsyncReference метод, передающий в пустом OSAsyncReference массив, порт уведомлений на CFRunLoop приложения, указателе на подпрограмму обратного вызова приложения и указателе на refcon информацию. setAsyncReference метод инициализирует OSAsyncReference массив со следующими константами (в этом порядке): kIOAsyncReservedIndex, kIOAsyncCalloutFuncIndex, и kIOAsyncCalloutRefconIndex. (Эти типы определяются в заголовочном файле OSMessageNotification.h.)

    3. Отошлите запрос I/O на обработку более низкими объектами в штабеле драйвера и возврате.

  4. Когда операция I/O завершается на цикле работы драйвера, драйвер уведомляет пользовательский клиент и дает ей результат операции I/O и любого получающегося вывода. Уведомлять приложение, пользовательские клиентские вызовы sendAsyncResult, передача в результате и данных от операции I/O. Это результаты вызова в вызове IOAsyncCallback подпрограмма обратного вызова в коде пространства пользователя.

  5. Когда больше нет передач I/O, пользовательский клиент должен в close метод, разъедините его часть асинхронной инфраструктуры I/O путем вызова mach_port_deallocate на порту уведомлений.

Отображение регистров устройства и RAM в пространство пользователя

Если Ваш драйвер имеет аппаратные средства с полным управлением памятью PIO, Ваш пользовательский клиент может отобразить аппаратные регистры в адресное пространство пользовательского процесса. В этом случае пользовательский процесс не требует доступа к физическим адресам. Однако, если аппаратные средства PIO действительно потребуют использования прерываний, то у Вас будет к фактору это требование в Ваш код. Это всегда возможно, даже с аппаратными средствами DMA, для публикации устройства RAM к пространству пользователя.

Пользовательский процесс подает заявку для расширенной памяти путем вызова IOConnectMapMemory, передавая в, среди других параметров, указателя на (что станет), расширенная память в ее собственном адресном пространстве. IOConnectMapMemory результаты вызова в вызове clientMemoryForType метод в пользовательском клиенте. В его реализации этого метода пользовательский клиент возвращает объект IOMemoryDescriptor, который он создал в open или start метод, поддерживающий отображение на аппаратные регистры. Пользовательский процесс получает назад это отображение с точки зрения типов виртуальной памяти vm_address_t и vm_size_t. Это теперь свободно читать из и записать в аппаратные регистры.

Вместо того, чтобы создать объект IOMemoryDescriptor, пользовательский клиент мог (в большинстве случаев) просто создать объект IODeviceMemory куском его провайдера. Затем в clientMemoryForType метод, это возвращает указатель на объект IODeviceMemory. (IODeviceMemory является подклассом IOMemoryDescriptor.), Если это делает это, это должно гарантировать, что флаг без кэширований включен (осуществлением операции ИЛИ надлежащий флаг в IOOptionBits параметр). Кусок обеспечивающего драйвера использует объект IODeviceMemory отобразить регистры физического адресного пространства устройства PCI к виртуальному адресному пространству ядра. Через этот объект это может достигнуть адресное пространство ядра, которое делает большинство драйверов, когда они хотят говорить с аппаратными регистрами. Возврат IODeviceMemory провайдера возражает в clientMemoryForType метод также, как Вы публикуете (PCI) устройство RAM к пространству пользователя.

Удостоверьтесь, что Вы сохраняете IODeviceMemory или любой другой объект IOMemoryDescriptor в Вашей реализации clientMemoryForType таким образом, ссылка на него может быть возвращена к вызывающей стороне.

Если пользовательский клиент создает объект IOMemoryDescriptor в open метод, это должно выпустить объект в close метод; если пользовательский клиент создает объект IOMemoryDescriptor в start метод, это должно выпустить объект в stop метод. Если пользовательский клиент передает объект IODeviceMemory, создаваемый его провайдером, он не должен выпускать его вообще (провайдер должен выпустить его соответственно). К концу I/O интерфейс устройства или приложение должны вызвать IOConnectUnmapMemory.

Например, реализации IOConnectMapMemory и clientMemoryForType, см. Перечисление 4-18 и Перечисление 4-24, соответственно.

Экскурсия через пользовательский клиент

Double X Technologies делает много продуктов. (Это - вымышленная компания, не идите, ища ее в Интернете.) Одним продуктом является плата видеосъемки. Эти аппаратные средства используют механизм DMA для передачи данных на высоких показателях и не разрешают совместное использование устройства. Компания записала OS X (Дарвин) драйвер для него, и теперь хочет сделать устройство и драйвер доступными для как можно большего количества клиентов пространства пользователя. Таким образом, они решают, что должны записать пользовательскому клиенту и дополнительной библиотеке пространства пользователя — интерфейсу устройства — для него.

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

Определения общего типа

Проект для Двойного X драйверов Получения также включает пользователя-клиент заголовочные и исходные файлы. Один из этих заголовочных файлов содержит определения типа, использующиеся и пользовательским клиентом и интерфейсом устройства. Этот файл содержит enum константы, используемые в качестве индексов в массив IOExternalMethod структуры сохраняются пользовательским клиентом. Это также содержит структуры для различных типов файлов задачи, кодов операций и макро-инициализаторов для файлов задачи.

Перечисление 4-14 показывает определение enum константы, используемые в качестве индексов массива метода.

Перечисление 4-14  индексы в IOExternalMethod массив

typedef enum XXCaptureUCMethods {
    kXXCaptureUserClientActivate        // kIOUCScalarIScalarO,  3,  0
    kXXCaptureUserClientDeactivate      // kIOUCScalarIScalar0,  0,  0
    kXXCaptureUserClientExecuteCommand, // kIOUCStructIStructO, -1, -1
    kXXCaptureUserClientAbortCommand,   // kIOUCScalarIScalarO,  1,  0
    kXXCaptureLastUserClientMethod,
} XXCaptureUCMethods;

Как Вы видите, методы, вызванные пользовательским клиентом, выделяют (активируют) и освобождают (деактивировали) ядро и аппаратные ресурсы, и выполняют и прерывают команды. Активировать команда также приводит к вызову пользовательского клиента open метод (для осуществления эксклюзивного доступа) и деактивировать команда вызывает вызов close. Этот сквозной контроль фокусируется на выполнить команде (kXXCaptureUserClientExecuteCommand).

Каждый файл задачи начинается с «кода операции» (или код операции), который указывает тип задачи выполнить с файлом задачи. Двойное X пользовательских клиентов Получения определяет приблизительно дюжину кодов операции как enum константы (см. Перечисление 4-15).

  Коды операций перечисления 4-15 для файлов задачи

typedef enum XXCaptureOpCodes {
    kXXGetAddress = 0,                      //  0
    kXXGetRegister,                         //  1
    kXXSetAddress,                          //  2
    kXXSetRegister,                         //  3
    kXXClearRegister,                       //  4
    kXXSetToRegister,                       //  5
 
    // DMA control commands
    kXXDMATransferBlock,                    //  6
    kXXDMATransferNonBlock,                 //  7
    kXXLastOpCode
} XXCaptureOpCodes;

Коды операции покрывают диапазон специфичных для регистра действий, от получения адреса или регистра оценивают установке адреса или регистра. Существует также два кода операции для двух видов передач DMA: блокирование и неблокирование. Этот раздел фокусируется на файлах задачи с двумя кодами операции: атомарный регистр набора для оценки (kXXSetToRegister) и блокирование передачи DMA (kXXDMATransferBlock).

Двойное X кодов пользователя-клиента определяет файл задачи как структуру типа XXUCCommandData. Первые несколько полей этой структуры резервируются для получения информации о статусе и конфигурационной информации; последнее поле (fCmds) массив XXCaptureCommandBlock структуры. Перечисление 4-16 показывает определения этих структур.

  Файловые структуры задачи перечисления 4-16 используются в некоторых операциях

typedef struct XXUCCommandData {
    IOReturn fErrorCode;    // Out:What type of error
    UInt32 fNumCmds;        // In:Number of commands, Out:Cmd in error
    XXCaptureCommandBlock fCmds[0];
} XXUCCommandData;
 
typedef struct XXCaptureCommandBlock {
    UInt16 fOp;
    UInt16 fReg;
    UInt32 fReserved[3];
} XXCaptureCommandBlock;
 
typedef struct XXCaptureRegisterToValue {
    UInt16 fOp;
    UInt16 fReg;            // In, register address in BAR
    UInt32 fValue;          // In|Out, value of bits to be set
    UInt32 fMask;           // In|Out, mask of bits to be set
    UInt32 fReserved2;      // 0, Do not use
} XXCaptureRegisterToValue;
 
typedef struct XXCaptureDMATransfer {
    UInt16 fOp;
    UInt16 fDirection;      // Direction of transfer
    UInt8 *fUserBuf;
    UInt8 fDevOffset;
    UInt32 fLength;         // In, length in bytes to transfer
} XXCaptureDMATransfer;

Также показанный в этом примере два типа структур, определенных для видов команд, которые мы прослеживаем на этом туре: значение регистра Набора и непрерывное блокирование передача DMA. Типы структуры, соответственно, XXCaptureRegisterToValue и XXCaptureDMATransfer. Когда интерфейс устройства создает файл задачи, содержащий эти структуры команды, он вставляет их XXCaptureCommandBlock выстройте даже при том, что они не имеют того типа. Обратите внимание на то, что XXCaptureCommandBlock концы с некоторым дополнением (fReserved), гарантируя его, чтобы быть, по крайней мере, тем же размером как любая другая блочная структура команды. Просто необходимо сделать надлежащий кастинг на элементе в XXCaptureCommandBlock массив для получения структуры корректного типа.

Общий заголовочный файл также определяет макросы, которые инициализируют команды и помещают их в файл задачи. Перечисление 4-17 показывает макро-инициализаторы для XXCaptureRegisterValue и XXCaptureDMATransfer структуры команды.

  Инициализаторы Макроса перечисления 4-17 для некоторых файлов задачи

#define cmdGetRegister(cmd, reg) do {                               \
    XXCaptureCommandBlock *c = (XXCaptureCommandBlock *) (cmd);     \
    c->fOp   = kXXCaptureGetRegister;                                       \
    c->fReg  = (reg);                                               \
    cmd++;                                                          \
} while (0)
 
#define cmdSetToRegister(cmd, reg, val) do {                        \
    XXCaptureRegisterValue *c = (XXCaptureRegisterValue *) (cmd);       \
    c->fOp    = kXXSetToRegister;                                   \
    c->fReg   = (reg);                                              \
    c->fValue = (val);                                              \
    c->fMask  = (mask);                                             \
    cmd++;                                                          \
} while (0)
 
#define cmdDMA(cmd, op, dir, u, d, len) do {                        \
    XXCaptureDMATransfer *c = (XXCaptureDMATransfer *) (cmd);           \
    c->fOp      = (op);                                             \
    c->fDirection = (dir);                                          \
    c->fUserBuf = (u);                                              \
    c->fDevOffset = (d);                                            \
    c->fLength  = (len);                                            \
    cmd++;                                                          \
} while (0)
 
#define cmdDMABlock(cmd, dir, u, d, len)                            \
        cmdDMA(cmd, kXXDMATransferBlock, dir, u, d, len)#define cmdDMANonBlock(cmd, dir, u, d, len)                         \
        cmdDMA(cmd, kXXDMATransferNonBlock, dir, u, d, len)

#define оператор препроцессора использовался для построения их макросы, потому что это позволяет коду постепенно увеличить индекс автоматически к следующей команде в XXCaptureCommandBlock массив.

Библиотека интерфейса устройства

Библиотека, реализованная Двойным X для функционирования как интерфейс устройства для его аппаратных средств видеосъемки, представляет функциональный программируемый интерфейс приложениям. (Разработчикам приложений не придется знать низкоуровневые подробные данные аппаратных средств.), Когда это получает функциональный запрос (эквивалентный чему-то как “запись n блоки данных”), интерфейс устройства разламывает его на требуемые команды аппаратного регистра, помещает эти команды в файл задачи и отправляет этот файл задачи пользовательскому клиенту для выполнения.

Во-первых, запросы приложения Двойное X интерфейсов устройства для установления соединения с пользовательским клиентом. Интерфейс устройства определяет функцию XXDeviceOpen (не показанный) с этой целью. Эта функция завершает шаги, описанные ранее в следующих разделах:

  1. Получите Ведущий порт Набора I/O

  2. Получите экземпляр драйвера

  3. Создайте соединение

Даже при том, что интерфейс устройства теперь имеет соединение с пользовательским клиентом, передачи I/O еще не могут произойти. Пользовательский клиент должен сначала открыть его провайдера и подготовиться к I/O путем выделения необходимого ядра и аппаратных ресурсов. XXDeviceConnect функция, который вызовы приложения после того, как это открыло соединение, определяется с этой целью (см. Перечисление 4-18).

Перечисление 4-18  , Вызывающее пользовательский клиент, активирует метод, отображая память карты

// knXXCaptureUserClientActivate, kIOUCScalarIScalarO,  3,  0
kern_return_t
XXDeviceConnect(XXDeviceHandle handle,
                 Boolean fieldMode, UInt32 storeSize)
{
    XXDeviceDataRef device = (XXDeviceDataRef) handle;
    kern_return_t ret;
 
    ret = IOConnectMethodScalarIScalarO(device->fUCHandle,
            kXXCaptureUserClientActivate, 3, 0,
            (int) &device->fStatus, (int) fieldMode,
            storeSize);
    if (KERN_SUCCESS != ret)
        goto bail;
 
    ret = IOConnectMapMemory(device->fUCHandle,
                        kXXCaptureUserClientCardRam0,
                        mach_task_self(),
                        (vm_address_t *) &device->fCardRAM,
                        &device->fCardSize,
                        kIOMapAnywhere);
 
bail:
    return ret;
}

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

XXDeviceConnect функционируйте также отображает физическую память карты получения в адресное пространство пользовательского процесса. Это хранит возвращенную адресную информацию в XXDeviceHandle «глобальная» структура. Этот шаг необходим, прежде чем любые передачи DMA смогут иметь место.

Для фактических передач I/O Двойное X интерфейсов устройства определяют много функций, дарящих себе имена и подписи, значение которых разработчик приложений может легко схватить. Этот функциональный интерфейс I/O мог бы состоять из функций с именами такой как XXCaptureWriteData, XXCaptureReadData, и (для аппаратных средств, понимающих блоки), XXCaptureWriteBlocks. Перечисление 4-19 показывает, как интерфейс устройства реализует XXCaptureWriteBlocks функция.

Перечисление 4-19  функция интерфейса устройства для записи блоков данных

#define writeCmdSize \
    (sizeof(XXUCCommandData) + kMaxCommands* sizeof(XXCaptureCommandBlock))
 
int XXCaptureWriteBlocks(XXDeviceHandle device, int blockNo, void *addr,
                            int *numBlocks)
{
    int res;
    int length;
    UInt8 buffer[writeCmdSize];
    XXCaptureCommandBlock *cmd, *getCmd;
    XXUCCommandData *cmds = (XXUCCommandData *) buffer;
 
    cmd = &cmds->fCmds[0];
 
    cmdSetToRegister(cmd, kXXCaptureControlReg, -1, kXXCaptureDMAResetMask);
    cmdDMABlock(cmd, kIODirectionOut, adddr, blockNo, *numBlocks);
    getCmd = cmd;
    cmdGetRegister(cmd, kXXCaptureDMATransferred);
 
    length = (UInt8 *) cmd - (UInt8 *) cmds;
    res = (int) IOConnectMethodStructureIStructureO(device->fUCHandle,
                kXXCaptureUserClientExecuteCommand,
                length, length, cmds, cmds);
 
    if (res)
        goto bail;
 
    res = cmds->fErrorCode;
    *numBlocks = getCmd->fValue;
 
bail:
    return res;
}

Эта функция в основном берет запрос для записи блока данных (чей адрес пространства пользователя и размер предоставляются в параметрах), и создает файл задачи, составленный из трех основанных на регистре команд:

  • cmdSetToRegister макрос создает команду, говорящую аппаратным средствам сбрасывать механизм DMA.

  • cmdDMABlock макрос соединяет команду, программирующую механизм DMA для передачи I/O с помощью передаваемых параметров и указывая направление передачи (kIODirectionOut).

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

XXCaptureWriteBlocks функционируйте затем вызывает IOConnectMethodStructureIStructureO, передача в указателе на XXCaptureCommandBlock файл задачи просто создается. Этот вызов функции приводит к вызову пользовательского клиента execute метод (см. Пользовательский Клиент). Наконец, функция определяет, была ли ошибка в выполнении файла задачи; если было, это извлекает информацию из вывода XXCaptureCommandBlock структура (getCmd) и возвраты эта информация к вызывающему приложению.

Пользовательский клиент

Вспомните к IOConnectMethodStructureIStructureO вызовите в интерфейсе устройства XXCaptureWriteBlocks функция. Через волшебство Маха, обменивающегося сообщениями и Набора I/O, тот вызов выходит на стороне ядра как вызов пользовательского клиента getTargetAndMethodForIndex метод с индексным параметром kXXCaptureUserClientExecuteCommand. IOUserClient разделяют на подклассы для Двойного X реализаций устройства getTargetAndMethodForIndex обработать это как показано inListing 4-20.

Перечисление 4-20  Возвращая указатель на IOExternalMethod структура

#define kAny ((IOByteCount) -1 )
 
static IOExternalMethod sXXCaptureUserClientMethods[] =
{
    // kXXCaptureUserClientActivate,
    { 0, Method(activate),   kIOUCScalarIScalarO,    3,    0 },
    // kXXCaptureUserClientDeactivate,
    { 0, Method(deactivate), kIOUCScalarIScalarO,    0,    0 },
    // kXXCaptureUserClientExecuteCommand,
    { 0, Method(execute),    kIOUCStructIStructO, kAny, kAny },
    // kXXCaptureUserClientAbortCommand,
    { 0, Method(abort),      kIOUCScalarIScalarO,    1,    0 },
};
 
IOExternalMethod *XXCaptureUserClient::
getTargetAndMethodForIndex(IOService **targetP, UInt32 index)
{
    IOExternalMethod *method = 0;
 
    if (index < kXXCaptureLastUserClientMethod)
    {
        *targetP = this;
        method = &sXXCaptureUserClientMethods[index];
    }
 
    return method;
}

В результате возвращенного индекса и цели, Набор I/O вызывает execute метод в пользовательском клиенте, передающем в указателях на буферы файлов задачи (vInCmd и vOutCmd). Поскольку Вы видите от Перечисления 4-21, execute метод начинается путем объявления локальных переменных и присвоения параметров им.

Перечисление 4-21  execute метод — подготовка данных

// kXXCaptureUserClientExecuteCommand, kIOUCStructIStructO, -1, -1
IOReturn XXCaptureUserClient::
execute(void *vInCmd,  void *vOutCmd,
        void *vInSize, void *vOutSizeP, void *, void *)
{
    XXCaptureCommandBlock *cmd;
    UInt32 index, numCmds, cmdSize;
    bool active;
 
    XXUCCommandData *inCmdBuf = (XXUCCommandData *) vInCmd;
    XXUCCommandData *outCmdBuf = (XXUCCommandData *) vOutCmd;
    UInt32 *outSizeP = (UInt32 *) vOutSizeP;
    UInt32  inSize = (UInt32) vInSize, outSize = *outSizeP;
    IOReturn ret = kIOReturnInternalError;
    UInt32 numOutCmd;

Прежде чем это выполнит любую работу I/O, execute метод выполняет ряд проверки, проверяет параметры. Это проверяет размеры блоков команды ввода и вывода и гарантирует, что они являются внутренне непротиворечивыми. Это также проверяет на блоки команды с побочными данными, такие как регистры, биты которых не могут быть установлены. Перечисление 4-22 иллюстрирует, как пользовательский клиент выполняет один такой начинать работу kXXSetToRegister команда.

Перечисление 4-22  проверка начинает работу kXXSetToRegister команда

        case kXXSetToRegister: {
            XXCaptureRegisterToValue *regCmd;
            regCmd = (XXCaptureRegisterToValue *) cmd;
 
            if ( !isValidBitsForRegister(cmd->fReg, regCmd->fMask)
            {
                DebugLog(("%s(%x)::execute() "
                          "can't set bit %x for reg(%d)\n",
                          getName(), (int) this,
                          (int) regCmd->fValue, (int) cmd->fReg));
                ret = kIOReturnBadArgument;
                goto bail;
            }
            break;
        }

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

После завершения фазы проверки пользовательский клиент выполняет запрос I/O. Перечисление 4-23 показывает как execute метод обрабатывает kXXSetToRegister и kXXContTransferBlock команды.

Перечисление 4-23  execute метод — выполнение команды I/O

    cmd = inCmdBuf->fCmds;
    for (index = 0; index < numCmds; index += cmdSize, cmd += cmdSize)
    {
        cmdSize = 1;            // Setup default command size
 
        switch (cmd->fOp)
        case kXXSetToRegister: {
            XXCaptureRegisterToValue *regCmd =
                (XXCaptureRegisterToValue *) &inCmdBuf->fCmds[index];
            fNub->toValueBitAtomic(cmd->fReg, regCmd->fValue, regCmd->fMask);
            break;
        }
        case kXXGetRegister: {
            XXCaptureRegisterValue *out =
                (XXCaptureRegisterValue *) &outCmdBuf->fCmds[index];
 
            out->fValue = fNub->getReg(regP);
            break;
        }
        case kXXDMATransferBlock:
        case kXXDMATransferNonBlock: {
 
            XXCaptureDMATransfer *c =
                (XXCaptureDMATransfer *) &inCmdBuf->fCmds[index];
 
            DMARequest *req;
            bool blocking = c->fOp == kXXDMATransferBlock;
 
            req = fProvider->createDMARequest(
                        (vm_address_t) c->fUserBuf,
                        fClient,
                        c->fDevOffset,
                        c->fDirection,
                        c->fLength,
                        this,
                        (XXDMAEngine::Completion)
                            &XXCaptureUserClient::dmaCompleteGated,
                        (void *) blocking);
            if (!req) {
                ret = kIOReturnError;
                goto bail;
            }
            ret = fGate->runAction(gatedFunc(runDMAGated),
                (void *) req, (void *) blocking);
            break;
        }
 
            // other code here ...
        }
bail:
    outCmdBuf->fErrorCode = ret;
    outCmdBuf->fNumCmds = index;
 
    return kIOReturnSuccess;
}

Если команда “установлена регистр для оценки” (kXXSetToRegister), execute вызывает метод, реализованный его вызванным провайдером toValueBitAtomic. Этот метод устанавливает указанный регистр в указанное значение атомарным способом.

Если команда, “получают значение регистра” (kXXGetRegister), execute вызывает его провайдера для получения значения указанного регистра (в этом случае, содержа число переданных блоков). Код присваивает это значение надлежащему полю (fValue) из kXXGetRegister команда.

Если команда является “программой механизм DMA для непрерывного блокирования передача I/O” (kXXDMATransferBlock), execute метод программирует механизм DMA путем завершения следующих шагов:

  1. Это создает запрос DMA путем вызова createDMARequest метод, реализованный объектом, представляющим механизм DMA.

  2. Это программирует механизм DMA путем выполнения runDMAGated метод в логическом элементе команды.

  3. Это помещает результат операции I/O и число блоков, переданных в надлежащих полях выходного файла задачи; если ошибка произошла, fNumCmds поле этой структуры (XXUCCommandData) содержит индекс команды, вызывающей ошибку.

Прежде, чем оставить этот пример, давайте смотреть на пользовательскую роль клиента в подготовке общей памяти для передач DMA. Вспомните как интерфейс устройства в XXDeviceConnect функция, вызванная IOConnectMapMemory отобразить аппаратные регистры платы видеосъемки в ее адресное пространство. IOConnectMapMemory вызовите вызывает вызов clientMemoryForType метод в пользовательском клиенте. Перечисление 4-24 показывает, как Двойное X пользовательских клиентов реализует этот метод для возврата IOMemoryDescriptor.

  Реализация перечисления 4-24 clientMemoryForType

IOReturn XXCaptureUserClient::
clientMemoryForType(UInt32 type, UInt32 *flags,
                    IOMemoryDescriptor **memory)
{
    IOMemoryDescriptor *mem;
    IOReturn ret;
 
    switch(type)
    {
    case kXXCaptureUserClientCardRam0:
        mem = fNub->getDeviceRAM();
        break;
 
    default:
        ret = kIOReturnUnsupported;
        goto bail;
    }
 
    if (!mem)
    {
        ret = kIOReturnNoMemory;
        goto bail;
    }
 
    mem->retain();
    *memory = mem;
    ret = kIOReturnSuccess;
 
bail:
 
    return ret;
}

Если тип памяти, которую требуют, в этом методе пользовательский клиент просто получает дескриптор памяти ее провайдера kXXCaptureUserClientCardRam0. Этот объект дескриптора памяти является экземпляром класса IODeviceMemory, наследовавшегося от IOMemoryDescriptor. Это представляет регистры устройства PCI (карта видеосъемки, в этом случае) и, более подробно, отображение тех физических адресов в виртуальное адресное пространство ядра. Прежде, чем возвратить объект IODeviceMemory, эта реализация сохраняет его. Когда пользовательский клиент возвращает этот объект IODeviceMemory (косвенно), Набор I/O преобразовывает его в адресное пространство приложения с точки зрения vm_address_t указатель памяти и длина.