Обработка событий

Драйвер устройства работает в, возможно, самой хаотической из сред в операционной системе. Много устройств требуют, чтобы последовательность команд выполнила единственную операцию I/O. Однако многократные потоки могут ввести водительский код в любом месте и в любое время, ли через клиент запросы I/O, аппаратные прерывания или события тайм-аута; в многопроцессорной системе потоки могут даже работать одновременно. Драйвер должен быть подготовлен обработать их всех.

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

Циклы работы

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

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

Набор I/O обеспечивает классы для обработки этих источников событий: IOInterruptEventSource, IOTimerEventSource, и IOCommandGate. (Вы обрабатываете питание и структурные события с помощью механизма, предоставленного IOCommandGate объекты.) Каждый из классов источника события определяет механизм, определенный для типа события для вызова единственной функции в защищенном контексте цикла работы. Если потоку, переносящему событие, нужен доступ к водительские критические данные, это должно сделать так через объект одного из этих классов.

Обычно клиентские драйверы устанавливают свои циклы работы, источники событий и обработчики событий в их start функция. Во избежание мертвых блокировок и условий состязания, весь код, получающий доступ к тем же данным, должен совместно использовать единственный цикл работы, регистрируя их источники событий в них так, чтобы использовался единственный механизм пропускания. Циклы работы могут быть безопасно совместно использованы среди несвязанных объектов, конечно, и часто совместно используются объектами на разных уровнях в единственном штабеле драйвера. Циклы работы могут также быть выделены для использования определенным драйвером и его клиентами. Посмотрите Совместно используемые и Специализированные Циклы Работы для получения дополнительной информации.

Функциональность предложений механизма цикла работы Набора I/O, примерно подобная той из Вертикали, Восстанавливает менеджера, Тайм менеджер и Задержанный Диспетчер задач Mac OS 9.

Архитектура цикла работы

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

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

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

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

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

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

Все службы I/O Kit могут легко совместно использовать цикл работы своего провайдера. Основа драйвера Реестр, представляя логическую плату компьютера, всегда содержит цикл работы, таким образом, драйвер уверен в наличии цикла работы, даже если это не создает сам тот. Весь драйвер должен сделать, вызывают функцию IOService getWorkLoop получить доступ к циклу работы его провайдера.

Таким образом весь штабель объектов драйвера или подмножество таких объектов, может совместно использовать один цикл работы. Рисунок 7-1 показывает, как цикл работы, совместно использованный многократными объектами драйвера, использует источники событий для управления доступом к его механизму пропускания.

  Объекты Драйвера рисунка 7-1, совместно использующие цикл работы
Driver objects sharing a work loop

Большинство драйверов не создаст свой собственный цикл работы. Если аппаратные средства непосредственно не повышают прерывания в Вашем драйвере, или если прерывания редко происходят в Вашем драйвере, то Вам не нужен Ваш собственный цикл работы. Однако драйвер, берущий прямые прерывания — другими словами, который взаимодействует непосредственно с контроллером прерываний — должен создать свой собственный специализированный цикл работы. Примерами таких драйверов являются драйверы контроллера PCI (или любой подобный драйвер с классом провайдера IOPCIDevice) и драйверы RAID-контроллера. Даже эти циклы работы могут быть совместно использованы водительскими клиентами, однако, таким образом, важно понять, что в любом случае, драйвер не должен предполагать, что это имеет монопольное использование цикла работы. Это означает, что драйвер должен редко включать или отключать все события на своем цикле работы, начиная с выполнения так может влиять на другие службы I/O Kit с помощью цикла работы.

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

Примеры получения циклов работы

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

Для создания специализированного цикла работы для драйвера переопределите getWorkLoop функция. Перечисление 7-1 иллюстрирует ориентированную на многопотоковое исполнение реализацию getWorkLoop это создает цикл работы лениво и безопасно.

Перечисление 7-1  , Создающее специализированный цикл работы

protected:
    IOWorkLoop *cntrlSync;/* Controllers Synchronizing context */
// ...
IOWorkLoop * AppleDeviceDriver::getWorkLoop()
{
    // Do we have a work loop already?, if so return it NOW.
    if ((vm_address_t) cntrlSync >> 1)
        return cntrlSync;
 
    if (OSCompareAndSwap(0, 1, (UInt32 *) &cntrlSync)) {
        // Construct the workloop and set the cntrlSync variable
        // to whatever the result is and return
        cntrlSync = IOWorkLoop::workLoop();
    }
    else while ((IOWorkLoop *) cntrlSync == (IOWorkLoop *) 1)
        // Spin around the cntrlSync variable until the
        // initialization finishes.
        thread_block(0);
 
    return cntrlSync;
}

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

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

Источники событий

Цикл работы может иметь любое число источников событий, добавленных к нему. Источник события является объектом, соответствующим типу события, которое драйвер устройства, как могут ожидать, обработает; существуют в настоящее время источники событий для аппаратных прерываний, событий таймера и команд I/O. Набор I/O определяет класс для каждых из этих типов событий: IOInterruptEventSource, IOTimerEventSource, и IOCommandGate, соответственно. Каждый из этих классов непосредственно наследовался от абстрактного класса IOEventSource.

Объект источника события действует как очередь для событий, поступающих от определенного источника события, и вручает от тех событий контексту цикла работы, когда это просит у них работу. При создании объекта источника события Вы указываете функцию обратного вызова (также известный как функция «действия»), чтобы быть вызванными для обработки события. Подобный механизму цели/действия среды Какао, Набор I/O хранит как переменные экземпляра в источнике события цель события (объект драйвера, обычно) и действие для выполнения. Подпись обработчика должна соответствовать Action прототип объявляется в заголовочном файле класса источника события. Как требуется цикл работы спрашивает каждый из своих источников событий поочередно (путем вызова их checkForWork функция) для событий для обработки. Если источник события имеет событие с очередями, цикл работы выполняет код обработчика для того события в его собственном защищенном контексте. Обратите внимание на то, что при регистрации источника события в цикле работы источник события предоставлен сигнальным семафором цикла работы, который это использует для пробуждения цикла работы. (Для получения дополнительной информации о как сны цикла работы и следы, посмотрите threadMain функция в IOWorkLoop документация.)

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

Процедура для добавления источников событий к циклу работы подобна для каждого типа источника события. Это включает четыре простых шага:

  1. Получите свой цикл работы.

  2. Создайте объект источника события.

  3. Добавьте объект к циклу работы.

  4. Включите источник события.

Избавление от источника события также имеет общий процедурный образец:

  1. Отключите источник события.

  2. Удалите его из цикла работы.

  3. Выпустите источник события.

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

Обработка прерываний

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

Обработка прерываний в наборе I/O

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

Два типа событий инициировали прерывание:

  • Основанные на команде события, такие как входящие сетевые пакеты и чтения носителей

  • Асинхронные события, такие как нажатия клавиатуры

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

  1. Прямое прерывание

  2. Таймеры и страница

  3. Реальное время (мультимедиа)

  4. Косвенные прерывания (драйверы)

  5. Менеджер окон

  6. Пользовательские потоки (включая запросы I/O)

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

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

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

Набор I/O не запрещает доступ к контексту прямого прерывания, и фактически обеспечивает отдельный интерфейс программирования с этой целью (см. Используя Обработчиков прерываний Без Циклов Работы). Однако использованию прямых прерываний строго обескураживают.

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

Рисунок 7-2 иллюстрирует некоторые из этих понятий. Это показывает события, происходящие из других источников, поставляемых соответствующим объектам источника события, «присоединенным» к циклу работы. Как с любым объектом источника события, каждый источник события прерывания действует как очередь для событий того типа; когда существует событие в очереди, объект сигнализирует цикл работы, что это имеет работу для него. Цикл работы (т.е. специализированный поток) пробуждает и запрашивает каждый установленный источник события поочередно. Если источник события имеет работу, цикл работы выполняет подпрограмму завершения для события (в этом случае, прерывание) в его собственном защищенном потоке. Предыдущее сообщение — клиентский поток, выполняющий код источника события — блокируется, пока подпрограмма не заканчивает обрабатывать событие. Тогда цикл работы перемещается в следующий источник события прерывания и, если существует работа, выполняет подпрограмму завершения для того прерывания в его защищенном контексте. Когда больше нет работы, чтобы сделать, сны цикла работы.

Рисунок 7-2  цикл работы и его источники событий
A work loop and its event sources

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

Установка обработчика прерываний, присоединенного к циклу работы

Драйвер обычно создает объект источника события прерывания — обычно IOInterruptEventSource или IOFilterInterruptEventSource класс — в start функция путем вызова метода создания фабрики для класса (например, interruptEventSource). Этот метод указывает сам драйвер как цель и идентифицирует функцию членства действия (соответствующий Action введите определенный для класса источника события) быть вызванным как подпрограмма завершения для источника события. Метод фабрики также связывает драйвер с провайдером, имеющим дело со средством аппаратного прерывания (обычно кусок, такой как IOPCIDevice). Драйвер тогда регистрирует источник события в цикле работы через IOWorkLoop addEventSource функция.

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

Перечисление 7-2  , Добавляющее источник события прерывания к циклу работы

myWorkLoop = (IOWorkLoop *)getWorkLoop();
 
interruptSource = IOInterruptEventSource::interruptEventSource(this,
    (IOInterruptEventAction)&MyDriver::interruptOccurred,
    provider);
 
if (!interruptSource) {
    IOLog("%s: Failed to create interrupt event source!\n", getName());
    // Handle error (typically by returning a failure result).
    }
 
if (myWorkLoop->addEventSource(interruptSource) != kIOReturnSuccess) {
    IOLog("%s: Failed to add interrupt event source to work loop!\n",
        getName());
    // Handle error (typically by returning a failure result).
}

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

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

Вы уничтожаете источник события прерывания в водительской функции деактивации (обычно stop). Перед выпуском IOInterruptEventSource объект, необходимо отключить его и затем удалить его из цикла работы. Перечисление 7-3 дает пример того, как сделать это.

Перечисление 7-3  , избавляющееся IOInterruptEventSource объект

if (interruptSource) {
    interruptSource->disable();
    myWorkLoop->removeEventSource(interruptSource);
    interruptSource->release();
    interruptSource = 0;
}

Источники событий прерывания фильтра

Поддержки Набора I/O совместно использовали прерывания, где драйверы совместно используют единственную линию прерывания. С этой целью это определяет IOFilterInterruptEventSource класс, подкласс IOInterruptEventSource. Apple настоятельно рекомендует, чтобы сторонние писатели драйвера устройства базировали свои источники событий прерывания на IOFilterInterruptEventSource класс вместо IOFilterInterruptEventSource класс. Последний класс не гарантирует, что совместное использование линий прерывания безопасно.

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

Перечисление 7-4 показывает, как установить и использовать IOFilterInterruptEventSource.

Перечисление 7-4  , Создающее IOFilterInterruptEventSource

bool myDriver::start(IOService * provider)
{
    // stuff happens here
 
    IOWorkLoop * myWorkLoop = (IOWorkLoop *) getWorkLoop();
    if (!myWorkLoop)
        return false;
 
    // Create and register an interrupt event source. The provider will
    // take care of the low-level interrupt registration stuff.
    //
    interruptSrc =
        IOFilterInterruptEventSource::filterInterruptEventSource(this,
                    (IOInterruptEventAction) &myDriver::interruptOccurred,
                    (IOFilterInterruptAction) &myDriver::checkForInterrupt,
                    provider);
    if (myWorkLoop->addEventSource(interruptSrc) != kIOReturnSuccess) {
        IOLog("%s: Failed to add FIES to work loop.\n", getName());
    }
    // and more stuff here...
}
 
 
bool myDriver::checkForInterrupt(IOFilterInterruptEventSource * src)
{
    // check if this interrupt belongs to me
 
    return true; // go ahead and invoke completion routine
}
 
 
void myDriver::interruptOccurred(IOInterruptEventSource * src, int cnt)
{
    // handle the interrupt
}

Если Ваша подпрограмма фильтра ( checkForInterrupt подпрограмма в Перечислении 7-4) возвраты true, Набор I/O автоматически запустит Вашу подпрограмму обработчика прерываний на Вашем цикле работы. Прерывание останется отключенным в аппаратных средствах до Вашей процедуры обработки прерывания (interruptOccurred в Перечислении 7-4), завершается.

Используя обработчиков прерываний без циклов работы

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

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

Больше информации об этом предмете является предстоящим.

Обработка событий таймера

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

Драйвер создает IOTimerEventSource с обратным вызовом Action функционируйте и время, в которое можно вызвать ту функцию, и затем можно зарегистрировать ее в цикле работы для работы. Когда тайм-аут передаст, источник события планируется с циклом работы. Когда цикл работы запрашивает его для работы, источник события закрывает логический элемент цикла работы (путем взятия блокировки цикла работы), вызывает функцию обратного вызова, и затем выпускает блокировку цикла работы для открытия логического элемента. Перечисление 7-5 показывает, как создать и зарегистрировать источник события таймера.

  Создание перечисления 7-5 и регистрация источника события таймера

myWorkLoop = (IOWorkLoop *)getWorkLoop();
 
timerSource = IOTimerEventSource::timerEventSource(this,
    (IOTimerEventSource::Action)&MyDriver::timeoutOccurred);
 
if (!timerSource) {
    IOLog("%s: Failed to create timer event source!\n", getName());
    // Handle error (typically by returning a failure result).
    }
 
if (myWorkLoop->addEventSource(timerSource) != kIOReturnSuccess) {
    IOLog("%s: Failed to add timer event source to work loop!\n", getName());
    // Handle error (typically by returning a failure result).
}
 
timerSource->setTimeoutMS(MYDRIVER_TIMEOUT);

Часто драйвер хочет установить таймер и выпустить запрос I/O одновременно. Если запрос I/O завершается, прежде чем событие таймера инициировано, драйвер должен сразу отменить таймер. Если событие таймера инициировано сначала, драйвер обычно переиздает тайм-аут запрос I/O (в котором времени это сбрасывает таймер).

Если Вы хотите, чтобы событие таймера было текущим, необходимо сбросить таймер к желаемому интервалу в Action обработчик. IOTimerEventSource класс не имеет механизма для установки периодических таймеров. Класс действительно обеспечивает несколько функций для установки относительных и абсолютных интервалов таймера при различных гранулярностях (наносекунды, микросекунды, и т.д.). Фрагмент кода в использовании Перечисления 7-5 setTimeoutMS установить таймер с определенным интервалом миллисекунды тайм-аута.

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

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

Перечисление 7-6  , Избавляющееся от источника события таймера

if (timerSource) {
    timerSource->cancelTimeout();
    myWorkLoop->removeEventSource(timerSource);
    timerSource->release();
    timerSource = 0;
}

Запросы I/O и Логические элементы Команды

Клиентское использование драйвера IOCommandGate объекты выпустить запросы I/O к драйверу. Доступ контрольно-пропускной службы команды к блокировке цикла работы, и таким образом это сериализирует доступ к данным, вовлеченным в запросы I/O. Это не требует, чтобы контекстное переключение потока гарантировало однопоточный доступ. IOCommandGate источник события просто берет блокировку цикла работы, прежде чем это выполнит Action подпрограмма; путем выполнения так, это предотвращает другие источники событий на том же цикле работы от планирования. Это делает его эффективным механизмом для передач I/O.

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

Вызовы и вниз вызывают

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

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

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

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

До закрытия логического элемента команды необходимо соответственно подготовить запрос I/O. Запрос I/O включает три вещи: сама команда (который специфичен для семьи), память, вовлеченная в передачу (определенный как IOMemoryDescriptor возражают), и функция для вызова для обработки запроса в контексте логического элемента команды. Посмотрите Управляющие Данные для получения информации о IOMemoryDescriptors и связанных объектах.

Логические элементы команды должны быть закрыты на самый краткий период, в течение которого выполняется наименьшее количество возможного объема работы. Чем дольше логический элемент команды содержит блокировку цикла работы, тем больше вероятность конкуренции. Как со всеми источниками событий, функция логического элемента команды не должна выделять память или любой другой неограниченный ресурс из-за опасности блокировать. Вместо этого клиент должен предварительно выделить требуемые ресурсы, прежде чем управление будет передано контексту цикла работы. Например, это могло выделить пул ресурсов в start функция.

Вы создаете IOCommandGate объект путем вызова commandGate метод фабрики, указывая как параметры объектного «владельца» источника события (обычно this) и указатель на функцию, соответствующую Action прототип. Вы тогда регистрируете логический элемент команды в использовании цикла работы клиента IOWorkLoop addEventSource функция. Перечисление 7-7 дает пример этой процедуры.

  Создание перечисления 7-7 и регистрация логического элемента команды

workLoop = (IOWorkLoop *)getWorkLoop();
commandGate = IOCommandGate::commandGate(this,
                 (IOCommandGate::Action)receiveMsg);
if (!commandGate ||
    (workLoop->addEventSource(commandGate) != kIOReturnSuccess) ) {
    kprintf("can't create or add commandGate\n");
    return false;
}

IOCommandGate класс обеспечивает две альтернативы для инициирования выполнения запроса I/O в логическом элементе команды. Каждый runCommand функция членства и другой runAction функция членства. Эти функции работают так же. Когда клиент хочет вызвать Action функция, вместо того, чтобы вызвать его непосредственно, это вызывает логический элемент команды runCommand или runAction функция, передающая во всех обязательных аргументах. Логический элемент команды тогда захватывает блокировку цикла работы (т.е. это закрывает логический элемент команды), вызывает Action функция, и затем открывает логический элемент.

То, где две функции отличаются, находится в их гибкости. runCommand функция использует тот же механизм цели/действия, используемый другими классами источника события. В этом механизме, создаваемом IOCommandGate объект инкапсулирует (указатель на) Action функционируйте, а также цель (или «владелец») возражают, что реализует эту функцию. В этой модели, только одном Action функция может быть вызвана для запроса I/O.

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

Перечисление 7-8 иллюстрирует один использование runCommand функционируйте для выпуска запроса I/O.

Перечисление 7-8  Выпуская I/O запрашивает через логический элемент команды

void ApplePMU::enqueueCommand ( PMUrequest * request )
{
    commandGate->runCommand(request);
}
 
void receiveMsg ( OSObject * theDriver, void * newRequest, void *, void *, void * )
{
    ApplePMU * PMUdriver = (ApplePMU *) theDriver;
    PMUrequest * theRequest = (PMUrequest*)newRequest;
 
    // Inserts the request in the queue:
    theRequest->prev = PMUdriver->queueTail;
    theRequest->next = NULL;
    if ( PMUdriver->queueTail != NULL ) {
        PMUdriver->queueTail->next = theRequest;
    }
    else {
        PMUdriver->queueHead = theRequest;
    }
    PMUdriver->queueTail =  theRequest;
 
    // If we can, we process the next request in the queue:
    if ( (PMUdriver->PGE_ISR_state == kPMUidle) && !PMUdriver->adb_reading) {
        PMUdriver->CheckRequestQueue();
    }
}

В этом примере, runCommand функция используется для косвенного вызова логического элемента команды Action функция, receiveMsg. Одна важная тактика, которая - этот пример шоу то, как задержать обработку запросы I/O — когда выделение памяти и других ресурсов могло бы быть необходимым — до больше событий, ставится в очередь в логическом элементе команды. receiveMsg если больше запросов не находится на рассмотрении, a вызовов, функция стоит в очереди каждый входящий запрос и CheckRequestQueue функция для выполнения фактической работы I/O.

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

Вы уничтожаете источник события логического элемента команды в водительской функции деактивации (обычно stop). Перед выпуском IOCommandGate объект, необходимо удалить его из цикла работы. Перечисление 7-9 дает пример того, как сделать это.

Перечисление 7-9  , избавляющееся IOCommandGate объект

if (commandGate) {
    myWorkLoop->removeEventSource(commandGate);
    commandGate->release();
    commandGate = 0;
}