[Previous] [Next]

Процедуры обратного вызова для синхронизации доступа к ресурсам

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

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

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

Существует три типа callback процедур, поддерживаемых Диспетчером ввода/вывода, о чем ниже.

Процедура ControllerControl

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

В Windows NT эта абстракция реализована в виде объектов контроллера, которые создается вызовом IoAllocateController. Как правило, процедура, запускающая операцию ввода/вывода, выполняет запрос "владения" объектом контроллера при помощи вызова IoAllocateController, одновременно устанавливая процедуру, которая получит управление, как только запрос на владение будет удовлетворен. Эта callback процедура известна в литературе под именем ControllerControl. По завершении обработки запроса ввода/вывода драйвер освобождает контроллер вызовом IoFreeController.

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

Объекты контроллера в модели WDM не поддерживаются, то есть разработчики DDK ограничили применение этой абстракции только драйверами "в-стиле-NT" (legacy драйверами).

Процедура AdapterControl

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

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

Процедуры SynchCritSection

Обслуживание прерывания происходит на одном из уровней аппаратных DIRQL (что зависит от типа устройства), в то время как весь остальной код драйвера работает на уровне приоритетов не выше DISPATCH_LEVEL. В случае, если когда-либо этому относительно низкоприоритетному коду понадобится поработать с ресурсами, которые использует и ISR (процедура обслуживания прерываний) драйвера, то эти действия должны выполняться только внутри callback процедуры SynchCritSection. Тот программный код, которому хочется корректно обратиться к ресурсам, что, возможно, затребует неожиданно вступившая в права высокоприоритетная процедура обслуживания прерываний, должен воспользоваться посредническими услугами callback процедуры SynchCritSection. Запустить эту callback процедуру необходимо вызовом KeSynchronizeExecution из программного кода, который работает на любом из уровней не выше DIRQL прерывания. Сама процедура SynchCritSection будет работать с приоритетом прерывания, поэтому пребывание внутри ее кода следует минимизировать (постоянное требование для кода высоких уровней IRQL).

Таким образом, фрагмент кода с изначально низким уровнем IRQL через процедуру обратного вызова получает возможность сделать работу при уровне DIRQL устройства, не опасаясь, что в это время управление будет передано в ISR процедуру. По окончании SynchCritSection прежнее значение IRQL восстанавливается.