Программирование драйверов Windows

Системные программные потоки


Процесс в операционной системе Windows является единицей владения. Процессу принадлежат, в частности, программные потоки, которые уже и являются единицами исполнения. Для каждого потока установлен независимый программный счетчик и контекст исполнения, который включает состояние регистров центрального процессора, значение уровня IRQL, от которого зависит, когда этот поток получит возможность распорядиться системным процессором.

Потоки пользовательского режима хорошо известны программистам пользовательских приложений. Несколько отличаются от них потоки режима ядра. Системный поток есть такой поток, который выполняется исключительно в режиме ядра. Он не имеет контекста пользовательского режима и не может получить доступ в пользовательское адресное пространство. Соответственно, программный код рабочих процедур (в частности, код процедуры обработки IOCTL запросов, который может пользоваться виртуальными адресами пользовательского приложения) не может считаться системным потоком, хотя и относится к коду драйвера режима ядра. Системные программные потоки созданы специальными вызовами и не имеют возможности интерпретировать виртуальные пользовательские адреса (ниже 0x80000000) ни в одном из пользовательских контекстов. Системный поток не имеет корней в пользовательском режиме. Данная особенность накладывает основное ограничение, свойственное системным программным потокам, &#8212 они могут пользоваться только адресами системного адресного пространства. Все остальные адреса (виртуальные адреса пользовательских приложений), переданные им каким-нибудь способом, будут не просто бесполезны &#8212 их использование может привести к краху операционной системы.

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

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

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

Таблица 10.1. Прототип вызова PsCreateSystemThread



NTSTATUS PsCreateSystemThread IRQL == PASSIVE_LEVEL
Параметры Создает системный программный поток
OUT PHANDLE pThreadHandle Указатель на переменную для сохранения дескриптора нового программного потока
IN ULONG DesiredAccess THREAD_ALL_ACCESS (или 0L) для создаваемого драйвером потока
IN POBJECT_ATTRIBUTES Attrib NULL для создаваемого драйвером потока
IN HANDLE ProcessHandle NULL для создаваемого драйвером потока
OUT PCLIENT_ID ClientId NULL для создаваемого драйвером потока
IN PKSTART_ROUTINE StartAddr Стартовая функция потока &#8212 точка входа в поток
IN PVOID Context Аргумент, передаваемый в стартовую функцию
Возвращаемое значение • STATUS_SUCCESS &#8212 поток создан

• STATUS_Xxx &#8212 код ошибки
Не последнюю роль в потребности использовать программные потоки в драйвере играет и тот факт, что (стандартно) их код выполняется на уровне PASSIVE_LEVEL.


Как поступить, если разработчик драйвера желает протоколировать события в драйвере, записывая их в файл на диске, включая события, происходящие при повышенных приоритетах IRQL? Ведь функция ZwCreateFile и ZwWriteFile

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

Системные программные потоки создаются вызовом PsCreateSystemThread

(таблица 10.1), а завершиться они должны самостоятельно &#8212 выполнением вызова PsTerminateSystemThread.

Таблица 10.2. Прототип вызова PsTerminateSystemThread

NTSTATUS PsTerminateSystemThread IRQL == PASSIVE_LEVEL
Параметры Вызывается системным программным потоком при окончании работы
IN NTSTATUS ExitStatus Код завершения потока
Возвращаемое значение STATUS_SUCCESS &#8212 поток прекращен
Когда драйвер выгружается, он должен быть уверен, что все системные потоки, созданные драйвером, завершены. Зачастую, это означает, что должен быть использован какой-либо из механизмов сигнализации, при помощи которого можно было уведомить поток о необходимости завершить свою работу и дождаться этого момента.

Создаваемые драйвером системные программные потоки имеют при создании уровень IQRL равный PASSIVE_LEVEL в диапазоне приоритетов Normal (см. таблицу 6.2), хотя могут иметь любой приоритет в диапазоне Normal и RealTime.

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

VOID ThreadStartRoutine (PVOID pContext) { : KeSetPriorityThread ( KeGetCurrentThread(), LOW_REALTIME_PRIORITY); }

Заметим, что численное значение LOW_REALTIME_PRIORITY в заголовочных файлах DDK установлено равным 16 (это нижняя граница диапазона RealTime).

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


Содержание раздела