Смекни!
smekni.com

Профилировщик приложений (стр. 3 из 4)

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

Инициализируется работа таймера:

Таймер необходим для того, чтобы с определённым интервалом обновлять хранимую информацию.

Для этого создаётся объект ядра «таймер»:

status = _ExAllocatePool( g_pTimer, NonPagedPool, sizeof( KTIMER ) );

KeInitializeTimerEx( g_pTimer, SynchronizationTimer );

Замечание: память под объекты ядра должна выделяться исключительно в нестраничном пуле (ключевое слово NonPagedPool).

Таймеры могут быть двух типов:

SynchronizationTimer — по истечении указанного временного интервала или очередного периода, он переводится в сигнальное состояние, пока один из потоков, ждущих его, не будет пробуждён. Тогда же таймер переводится в несигнальное состояние.

NotificationTimer — по истечении указанного временного интервала или очередного периода, он переводится в сигнальное состояние, причём пробуждаются все потоки ожидающие на нём. Такой таймер остаётся в сигнальном состоянии до тех пор, пока он не будет явно переведён в несигнальное.

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

status = _ExAllocatePool( g_pTimerDpc, NonPagedPool, sizeof( KDPC ) );

KeInitializeDpc( g_pTimerDpc, OnTime, NULL );

Далее, в силу того, что в данном драйвере по таймеру должны выполняться действия, требующие пользовательского контекста, необходимо их вынести из функции OnTimer(), которая является DPC-процедурой, а следовательно, во время её выполнения доступен лишь системный контекст. Тем не менее, необходимо обеспечить приемлемую синхронность выполнения необходимой работы с моментом извлечения DPC-объекта функции из очереди для обработки. Для этого создадим поток, который будет посвящён ожиданию некоторого события:

OBJECT_ATTRIBUTES objectAttributes;

InitializeObjectAttributes( &objectAttributes, NULL, OBJ_KERNEL_HANDLE,

NULL, NULL );

status = PsCreateSystemThread( &hThread, THREAD_ALL_ACCESS, &objectAttributes,

NULL, NULL, UpdateThreadFunc, NULL );

KeInitializeEvent( g_pUpdateEvent, SynchronizationEvent, FALSE );

Замечание: объекты ядра «события» по своему типу идентичны объектам ядра «таймер».

При поступлении этого события поток будет обновлять системную информацию о процессах и их потоках. Объект этого события будем переводить в сигнальное состояние в функции OnTimer(). Данный способ синхронизации позволил обеспечить выполнение необходимых действий через заданный интервалом с точностью до милисекунды, что следует из нижеприведённых сообщений, перехваченных программой DebugView от отладочной версии драйвера:

0.00075233 [Spectator] ^^^^^^^^ OnTime ^^^^^^^^

0.00116579 [Spectator] ======== LockInfo ========

0.00118814 [Spectator] ======== ReloadInfo ========

0.99727142 [Spectator] ^^^^^^^^ OnTime ^^^^^^^^

1.00966775 [Spectator] ======== LockInfo ========

1.00968981 [Spectator] ======== ReloadInfo ========

1.99729049 [Spectator] ^^^^^^^^ OnTime ^^^^^^^^

2.05610037 [Spectator] ======== LockInfo ========

2.05632067 [Spectator] ======== ReloadInfo ========

2.99727035 [Spectator] ^^^^^^^^ OnTime ^^^^^^^^

2.99741030 [Spectator] ======== LockInfo ========

2.99743295 [Spectator] ======== ReloadInfo ========

3.99727631 [Spectator] ^^^^^^^^ OnTime ^^^^^^^^

3.99739385 [Spectator] ======== LockInfo ========

3.99741673 [Spectator] ======== ReloadInfo ========

4.99728107 [Spectator] ^^^^^^^^ OnTime ^^^^^^^^

4.99742365 [Spectator] ======== LockInfo ========

4.99744749 [Spectator] ======== ReloadInfo ========

5.99728870 [Spectator] ^^^^^^^^ OnTime ^^^^^^^^

5.99742651 [Spectator] ======== LockInfo ========

5.99744844 [Spectator] ======== ReloadInfo ========

Здесь OnTime – момент входа в процедуру таймера OnTimer, LockInfo – момент, когда пробудился поток, отвечающий за обновление информации, ReloadInfo – момент, когда информация была действительно обновлена.

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

После всех этих действий, наконец, запускается таймер:

LARGE_INTEGER dueTime = RtlConvertLongToLargeInteger( 0 );

BOOLEAN existed = KeSetTimerEx( g_pTimer, dueTime, g_timerPeriod, g_pTimerDpc );

Здесь dueTime – время до первого вызова процедуры OnTime(), а g_timerPeriod – период дальнейших вызовов.

Вконце концов, в процедуре DriverEntry происохдит обнуление счётчика пользовательских приложений-клиентов, получивших описатель данного драйвера: pDeviceExtension->clientCount = 0;

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

3.1.2 DriverUnload

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

3.1.3 DispatchCreate и DispatchClose

В этих функциях происходит учёт количества открытых описателей данного драйвера полученных с помощью API-вызова CreateFile(). Сколько описателей было открыто – столько же должно быть закрыто API-вызовом CloseHandle(). Иначе драйвер по окончании работы пользовательского приложения останется в операционной системе, что, естественно, крайне не желательно.

3.1.4 DispatchDeviceControl

Эта процедура обслуживает IOCTL-запросы от пользовательских приложений посылаемые API-вызовом DeviceIoControl(). В данном курсовом проекте взаимодействие с драйвером большею частью и построено на их применении, здесь реализована основная функциональность драйвера: то, для чего он и предназначался. Поэтому данная процедура наиболее объёмна.

Сначала, назависимо от конкретного IOCTL-запроса, получается указатель на ячейку IRP-стека IRP-пакета, предназначенную для объекта устройства драйвера:

PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp );

Далее, из этой ячейки извлекается код IOCTL-запроса, на основе которого с помощью оператора switch происходит дополнительная диспетчеризация IRP-пакета.

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

Длины (в байтах) пользовательских буферов, входного и выходного, извлекаются из поля Parameters ячейки IRP-стека: Parameters.DeviceIoControl.InputBufferLength и Parameters.DeviceIoControl.OutputBufferLength соответственно. А адрес системного буфера извлекается из заголовка IRP-пакета: AssociatedIrp.SystemBuffer.

3.1.4.1 IOCTL_LAST_CLIENT. Входные данные: [нет]

Выходные данные: [нет]

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

3.1.4.2 IOCTL_LOCK_INFO и IOCTL_UNLOCK_INFO. Входные данные: [нет]

Выходные данные: [нет]

Первый IOCTL-запрос из этой служит для захвата пользовательским приложением системной информации в монопольное пользование. Другой – соответствено, для осовбождения этого ресурса. В них просто вызываются одноимённые функции LockInfo() и UnlockInfo(), о которых было рассказано ранее, когда речь шла о процедуре DriverEntry данного раздела.

3.1.4.4 IOCTL_PROCESS_FIRST и IOCTL_PROCESS_NEXT. Входные данные: [нет]

Выходные данные: структура с базовой информацие о процессе.

Эта пара IOCTL-запросов позволяет их инициатору последовательно проссматривать структуры, описывающие запущенные процессы в системе. Каждый из них вызывает одноимённую функцию ProcessFirst() и ProcessNext() соответственно. Первая функция устанавливает указатель на первую запись, а вторая перемещает указатель на следующую, если такая имеется. Результатом выполнения каждой из этих функций является заполненная структура с информацией оп процессе, если не достигнут конец списка. В том случае, когда конец списка всё-таки достигается, IRP-пакет, тем не менее, помечается как успешно обработанный, но значение количества переданных байтов устанавливается равным нулю, что и позволяет пользовательскому приложению правильно распознать такую ситуацию и своевременно прекратить посылать драйверу дальнейшие IOCTL_PROCESS_NEXT-запросы.

3.1.4.5 IOCTL_THREAD_FIRST и IOCTL_THREAD_NEXT. Входные данные: [нет]

Выходные данные: структура с базовой информацие о потоке.

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

3.1.4.6 IOCTL_OPEN_THREAD. Входные данные: права доступа, уникальный идентификатор целевого потока.

Выходные данные: описатель целевого потока.

При обработке данного IOCTL-запроса осуществляется попытка открыть описатель потока, имеющего указанный идентификатор с правами, которые были запрошены пользовательским приложением-клиентом.

3.1.4.6 IOCTL_CLOSE_THREAD. Входные данные: описатель целевого потока.

Выходные данные: [нет].

Во время обработки этого IOCTL-запроса предпринимается попытка закрыть описатель потока, открытый ранее с помощью IOCTL_OPEN_THREAD-запроса.