[Previous] [Next]

Последовательность обслуживания запросов ввода/вывода

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

Предварительная обработка Диспетчером ввода/вывода

На данном этапе производится не связанная с устройством подготовка запроса и его предварительная верификация.

  1. Подсистема Win32 (ограничимся этой подсистемой) преобразует запрос в системный сервисный вызов (native system service call). Диспетчер системного сервиса переходит в режим ядра и управление передается Диспетчеру ввода/вывода.
  2. Диспетчер ввода/вывода выделяет память под структуру данных, известную под именем I/O RequestPacket (IRP), пакет запроса на ввод/вывод (пакет IRP). В следующей главе эта структура описывается детальнее, но в первом приближении, вполне можно считать пакет IRP рабочим рецептом (предписанием), выдаваемым драйверу и, соответственно, устройству, которое тот обслуживает. Структура данных IRP заполняется необходимой информацией, включая код, которым обозначается тип запроса на ввод/вывод.
  3. Диспетчер ввода/вывода осуществляет некоторую проверку правильности аргументов, переданных из запроса (инициированного из кода пользовательского режима). Проверка включает верификацию дескриптора файла (имеется в виду, что доступ к устройству из программного кода пользовательского режима осуществляется как доступ к файлу — по файловому дескриптору, ассоциированному с нужным устройством, например, посредством вызова ReadFile). Кроме того, выполняется проверка прав доступа к этому файловому объекту, проверка, предоставил ли драйвер процедуру для обслуживания такого типа запроса, и проверка адресов пользовательских буферных областей памяти, необходимых для обслуживания запроса.
  4. В случае, если запрос является операцией буферизованного ввода/вывода (buffered I/O), Диспетчер ввода/вывода получает область памяти в области нестранично организованной памяти (нестраничном пуле) под создание буфера, после чего копирует данные из области пользовательского буфера в этот системный буфер. В случае, если запрос к устройству требует прямого ввода/вывода (direct I/O), производится блокирование пользовательского буфера в физической памяти и создается список дескрипторов страниц (MDL список), через которые драйвер имеет возможность доступа к этому пространству физической памяти.
  5. Диспетчер ввода/вывода производит вызов необходимых рабочих процедур драйвера (dispatch routines).

Предварительная обработка в драйвере

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

  1. Выполняет дополнительную проверку параметров входящего запроса. Драйвер может устанавливать зависящие от устройства ограничения, которые не могут быть известны Диспетчеру ввода/вывода. Например, пользователь может затребовать передачу большого объема данных, который превышает возможности обслуживаемого устройства (или драйвера).
  2. В случае, если запрос не требует работы с физическим устройством (например, считывание 0 байт данных), то вызываемая драйверная процедура может завершить обработку и "отправить" IRP пакет обратно Диспетчеру ввода/вывода, пометив его как обработанный.
  3. В случае, если запрос требует для завершения обработки реального обращения к обслуживаемому физическому устройству, рабочая процедура может пометить IRP пакет как незавершенный (pending). Таким образом, Диспетчеру ввода/вывода сообщается, что необходимо поместить в очередь запрос на вызов процедуры StartIo как только устройство освободится — возможно, что устройство еще обрабатывает предыдущий запрос. Вообще говоря, этап общения с устройством может быть реализован весьма экзотическими приемами, тут как раз место, где фантазия разработчиков разыгрывается не на шутку.

Старт операции ввода/вывода

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

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

  1. Проверяет код IRP функции (чтение, запись и т.п.) и выполняет установочные действия для данного типа операций.
  2. В случае, если устройство (то есть объект устройства, которому адресован IRP пакет) по логике работы олицетворяет только одну из функций сложного реального устройства, запрашивает исключительный доступ к заранее созданному объекту контроллера, планируя при этом вызов соответствующей процедуры ControllerControl.
  3. В случае, если запрос требует DMA операций, выполняет надлежащие операции над объектом адаптера и планирует вызов процедуры AdapterControl (которая будет вызвана, сразу же, как только условия исключительного доступа к соответствующему DMA каналу будут удовлетворены).
  4. Использует процедуру SynchCritSection для выполнения безопасного доступа к тем ресурсам, которые могут потребоваться процедуре обработки прерываний.
  5. Возвращает управление Диспетчеру ввода/вывода в ожидании сигналов прерывания от устройства.

Пример драйвера с использованием механизма System Queuing подробно рассматривается в главе 11.

Процедура обслуживания прерываний ISR

При возникновении прерываний, диспетчер прерываний (из состава кода ядра) производит вызов драйверной ISR процедуры. Процедура ISR обычно выполняет следующие действия:

  1. Проверяет, ожидалось ли прерывание (относится ли поступившее прерывание к обслуживающему устройству).
  2. Освобождает (завершает) прерывание.
  3. В случае, если ранее была начата операция программируемого ввода/вывода (не DMA), но передача данных еще не завершена окончательно, процедура ISR могла бы начать операцию передачи следующей порции данных и завершить свою работу, пока она не будет вызвана по поводу следующего прерывания.
  4. В случае, если ранее была начата операция DMA и остались еще не переданные данные, то ISR могла бы запланировать вызов DPC процедуры для настройки DMA аппаратуры и передачи следующей порции данных.
  5. В случае, если произошла ошибка или передача данных не была завершена, то ISR могла бы запланировать вызов DPC процедуры для того, чтобы выполнить пост-обработку при более низком уровне IRQL.

Пост-обработка, выполняемая драйвером

Диспетчер DPC выполняет вызовы DPC процедур драйвера для того, чтобы решать задачи пост-обработки, а именно:

  1. В случае, если выполнялся набор операции по переносу данных и некоторая часть данных еще не была передана, DPC процедура производит установку аппаратуры, производит "старт" устройства и, в ожидании нового прерывания, возвращает управление Диспетчеру ввода/вывода. При этом IRP пакет остается пока в состоянии 'pending' (о его завершении будет объявлено позже — по окончании переноса).
  2. В случае, если произошла ошибка или превышено время ожидание отклика (таймаут), DPC процедура может записать это событие во внутреннюю очередь, поддерживаемую для данного объекта устройства, и затем либо сделать повторную попытку, либо прервать обработку запроса на ввод/вывод. Очереди IRP пакетов для устройств, Device Queue, могут поддерживаться драйверами как альтернатива системным очередям (System Queuing).
  3. DPC процедура освобождает необходимые для переноса ресурсы, удерживаемые драйвером (среди которых могут быть DMA ресурсы).
  4. DPC процедура помещает размер переданных данных и информацию о финальном состоянии в IRP пакет.
  5. Наконец, если обработка IRP пакета действительно завершена, DPC процедура сообщает Диспетчеру ввода/вывода об окончании обработки текущего IRP запроса тем, что помечает его как завершенный (вместо 'pending' — ожидающий обработки) и делает вызов IoStartNextPacket. Это указывает Диспетчеру ввода/вывода на то, что следует переходить к вызову процедуры StartIo для следующего IRP пакета, если таковой ожидает обработки.

Пост-обработка, выполняемая Диспетчером ввода/вывода

После того как DPC процедура или одна из рабочих процедур помечают IRP пакет как завершенный, Диспетчер ввода/вывода (разумеется, когда получит управление) осуществляет завершающие действия, которые в литературе и документации DDK собирательно называются "очисткой" (cleanup). Это означает:

  1. В том случае, если выполнялась операция записи при буферизованном вводе/выводе (buffered I/O), Диспетчер ввода/вывода освобождает буферную область, использованную во время только что завершенного переноса данных.
  2. В случае, если выполнялась процедура прямого ввода/вывода (direct I/O), Диспетчер ввода/вывода отменяет фиксацию (lock) в оперативной памяти тех страниц, в которых размещался пользовательский буфер.
  3. Диспетчер ввода/вывода помещает в очередь запрос к программному потоку, инициатору первоначального запроса, на выполнение асинхронного процедурного вызова (asynchronous procedure call, APC), работающего в режиме ядра. Этот APC вызов будет выполнять программный код Диспетчера ввода/вывода в контексте потока-инициатора запроса на операцию ввода/вывода.
  4. Выполняющийся в режиме ядра программный АРС-поток производит перенос информации о состоянии и размере переданных данных в пространство пользовательского приложения.
  5. В случае, если выполнялась процедура чтения категории буферизованного ввода/вывода (buffered I/O), процедура APC производит копирование содержимого буферной области, размещенной в нестраничном пуле памяти, в область памяти, предоставленную пользовательским приложением в качестве рабочего буфера. Затем освобождается системный буфер в нестраничном пуле.
  6. В случае, если исходный запрос был сделан для выполнения асинхронного ввода/вывода, процедура APC устанавливает ассоциированное событие (event) и/или файловый объект в состояние, сигнализирующее пользователю об окончании обработки запроса. (Если читатель не знаком с асинхронным вводом/выводом, то может получить доступ к его описанию в документации MSDN по ключевым словам OVERLAPPED, CancelIo, ReadFileEx и т.п.)
  7. В случае, если исходный запрос содержал процедуру завершения (как, например, при использовании функций пользовательского режима ReadFileEx/WriteFileEx в пользовательском приложении), APC процедура производит планирование вызова в дальнейшем APC процедуры пользовательского режима, которая выполнит вызов процедуры завершения, указанных в параметрах вызовов ReadFileEx/WriteFileEx.