Windows-приложения создают порты завершения вызовом Windows-функции CreateIoCompletionPort с указанием NULL вместо описателя порта завершения. Это приводит к выполнению системного сервиса NtCreateIoComple-tion. Объект IoCompletion исполнительной системы, построенный на основе синхронизующего объекта ядра, называется очередью. Таким образом, системный сервис создает объект «порт завершения» и инициализирует объект «очередь» в памяти, выделенной для порта. (Указатель на порт ссылается и на объект «очередь», так как последний находится в начальной области памяти порта.) Максимальное число сопоставленных с портом потоков, которые могут быть активны, указывается в объекте «очередь» при его инициализации; это значение, которое было передано в CreateIoCompletionPort. Для инициализации объекта «очередь» порта завершения NtCreateIoCompletion вызывает функцию KeInitializeQueue.
Когда приложение обращается к CreateIoCompletionPort для связывания описателя файла с портом, вызывается системный сервис NtSetInformationFile, которому передается описатель этого файла. При этом класс информации для NtSetInformationFile устанавливается как FileCompletionInformation, и, кроме того, эта функция принимает описатель порта завершения и параметр CompletionKey, ранее переданный в CreateIoCompletionPort. Функция NtSetInformationFile производит разыменование описателя файла для получения объекта «файл» и создает структуру данных контекста завершения.
Указатель на эту структуру NtSetInformationFile помещает в поле Com-pletionContext объекта «файл». По завершении асинхронной операции ввода-вывода для объекта «файл» диспетчер ввода-вывода проверяет, отличается ли поле CompletionContext от NULL. Если да, он создает пакет завершения и ставит его в очередь порта завершения вызовом KeInsertQueue; при этом в качестве очереди, в которую помещается пакет, указывается порт. (Здесь объект «порт завершения» — синоним объекта «очередь».)
Когда серверный поток вызывает GetQueuedCompletionStatus, выполняется системный сервис NtRemoveIoCompletion. После проверки параметров и преобразования описателя порта завершения в указатель на порт NtRemoveIoCompletion вызывает KeRemoveQueue.
Как видите, KeRemoveQueue и KeInsertQueue — это базовые функции, обеспечивающие работу порта завершения. Они определяют, следует ли активизировать поток, ждущий пакет завершения ввода-вывода. Объект «очередь» поддерживает внутренний счетчик активных потоков и хранит такое значение, как максимальное число активных потоков. Если при вызове потоком KeRemoveQueue текущее число активных потоков равно максимуму или превышает его, данный поток будет включен (в порядке LIFO) в список потоков, ждущих пакет завершения. Список потоков отделен от объекта «очередь». B блоке управления потоком имеется поле для указателя на очередь, сопоставленную с объектом «очередь»; если это поле пустое, поток не связан с очередью.
Windows отслеживает потоки, ставшие неактивными из-за ожидания на каких-либо объектах, отличных от порта завершения, по указателю на очередь, присутствующему в блоке управления потоком. Процедуры планировщика, в результате выполнения которых поток может быть блокирован (KeWaitForSingleObject, KeDelayExecutionThread и т. д.), проверяют этот указатель. Если он не равен NULL, они вызывают функцию KiActivateWaiterQueue, которая уменьшает счетчик числа активных потоков, сопоставленных с очередью. Если конечное число меньше максимального и в очереди есть хотя бы один пакет завершения, первый поток из списка потоков очереди пробуждается и получает самый старый пакет. И напротив, всякий раз, когда после блокировки пробуждается поток, связанный с очередью, планировщик выполняет функцию KiUnwaitTbread, увеличивающую счетчик числа активных потоков очереди.
Наконец, в результате вызова Windows-функции PostQueuedCompletionStatus выполняется системный сервис NtSetIoCompletion, который просто вставляет с помощью KeInsertQueue специальный пакет в очередь порта завершения.
Порт завершения в действии показан на рис. 9-21. Хотя к обработке пакетов завершения готовы два потока, максимум, равный 1, допускает активизацию только одного потока, связанного с портом завершения. Таким образом, на этом порте завершения блокируется два потока.
Утилита Driver Verifier (о которой мы уже рассказывали в главе 7) предоставляет несколько параметров для проверки правильности операций, связанных с вводом-выводом. Ha рис. 9-22 в окне Driver Verifier Manager (Диспетчер проверки драйверов) в Windows Server 2003 эти параметры помечены флажками.
Даже если вы не указываете никаких параметров, Verifier наблюдает за работой выбранных для верификации драйверов, следя за недопустимыми операциями, в том числе за вызовом функций пула памяти ядра при неправильном уровне IRQL, попытками повторного освобождения свободной памяти и запроса блоков памяти нулевого размера.
Рис. 9-22. Параметры Driver Verifier, относящиеся к операциям ввода-вывода
Параметры проверки ввода-вывода перечислены ниже.
• I/O Verification (Проверка ввода-вывода) Если этот параметр выбран, диспетчер ввода-вывода выделяет память под IRP-пакеты для проверяемых драйверов из специального пула и отслеживает его использование. Кроме того, Verifier вызывает крах системы по окончании обработки IRP с неправильным состоянием и при передаче неверного объекта «устройство» диспетчеру ввода-вывода. (B Windows 2000 этот параметр назывался I/O Verification Level 1).
• I/O Verification Level 2 (Проверка ввода-вывода уровня 2) Этот параметр существует только в Windows 2000; он просто ужесточает проверку операций обработки IRP и использования стека.
• Enhanced I/O Verification (Расширенная проверка ввода-вывода) Этот параметр впервые появился в Windows XP и включает мониторинг всех IRP для контроля того, что драйверы корректно помечают их при асинхронной обработке, что они правильно управляют блоками стека устройства и что они удаляют каждый объект «устройство» только раз. B дополнение Verifier случайным образом посылает драйверам ложные IRP, связанные с управлением электропитанием и WMI, изменяет порядок перечисления устройств и изменяет состояние IRP, связанных с PnP и электропитанием, по окончании их обработки; последнее позволяет выявить драйверы, возвращающие неверное состояние из своих процедур диспетчеризации.
• DMA Checking (Проверка DMA) DMA — аппаратно поддерживаемый механизм, позволяющий устройствам передавать данные в физическую память или получать их из нее без участия процессора. Диспетчер ввода-вывода поддерживает ряд функций, используемых драйверами для планирования DMA-операций и управления ими. Данный параметр включает проверку правильности применения этих функций и буферов, предоставляемых диспетчером ввода-вывода для DMA-операций.
• Disk Integrity Verification (Проверка целостности диска) После включения этого параметра, доступного только в Windows Server 2003, Verifier ведет мониторинг операций чтения и записи на дисках и проверяет контрольные суммы соответствующих данных. По окончании операций чтения с диска Verifier проверяет ранее сохраненные контрольные суммы и вызывает крах системы, если новая и старая контрольные суммы не совпадают, так как это свидетельствует о повреждении диска на аппаратном уровне.
• SCSI Verification (Проверка SCSI) Этот параметр появился в Windows XP и не виден в диалоговом окне параметров Driver Verifier. Однако он включается, когда вы выбираете для проверки минипорт-драйвер SCSI и отмечаете хотя бы один из других параметров. Тогда Verifier следит, как минипорт-драйвер SCSI использует функции, предоставляемые драйвером библиотеки SCSI-минипорта — storport.sys или scsiport.sys. При этом проверяется, что драйвер не обрабатывает запрос более одного раза, что он не передает недопустимые аргументы и что на выполнение операций не уходит больше определенного времени. (Подробнее о минипорт-драйверах SCSI см. в главе 10.)