【转帖】管理硬件优先级

 

1.1 管理硬件优先级
特定设备或中间层驱动程序例程运行的IRQL决定了它能调用哪些内核模式的支持例程。例如,有些支持例程要求调用者运行在为DISPATCH_LEVEL的IRQL上。其他例程在调用者运行在提高的(raised)IRQL(即高于PASSIVE_LEVEL的IRQL)时不能被安全地调用。

表16.1列出了最常见的标准驱动程序例程被调用的缺省IRQL以及Kernel定义的IRQL值(由低到高)。

表16.1 驱动程序例程的缺省IRQL

IRQL(由低到高)
屏蔽掉的中断
运行在此IRQL的支持例程

PASSIVE_LEVEL

Dispatch、DriverEntry、AddDevice、Reinitialize、Unload例程、驱动程序创建的线程、工作者线程(work-thread)回调、文件系统驱动程序



DISPATCH_LEVEL
DISPATCH_LEVEL和APC_LEVEL中断被屏蔽掉了。设备、时钟和电源错误中断仍可发生
StartIo、AdapterControl、AdapterListControl、ControllerControl、IoTimer、Cancel(持有撤消自旋锁时)、DpcForIsr、CustomTimerDpc、CustomDpc例程



DIRQL
驱动程序中断对象中所有IRQL<=DIRQL的中断。时钟和电源错误中断仍可发生
ISR、SyncCritSection例程


当运行在下列三种IRQL之一时,由最低层驱动程序处理IRP:

§    PASSIVE_LEVEL:没有处理器中断被屏蔽掉,在驱动程序的Dispatch例程中。

DriverEntry、AddDevice、Reinitialize和Unload例程也运行在PASSIVE_LEVEL,此外还有驱动程序创建的系统线程

§    DISPATCH_LEVEL:处理器的DISPATCH_LEVEL和APC_LEVEL中断被屏蔽掉了,在StartIo例程中。

AdapterControl、AdapterListControl、ControllerControl、IoTimer、Cancel(持有撤消自旋锁时)、DpcForIsr、CustomTimerDpc和CustomDpc例程也都运行在DISPATCH_LEVEL。

§    Device IRQL(DIRQL):处理器上所有低于或等于驱动程序中断对象的SynchronizeIrql的中断都被屏蔽掉了,在ISR和SyncCritSection例程中。

当运行在下列两种IRQL时,由更高层驱动程序处理IRP:

§    PASSIVE_LEVEL:没有处理器中断被屏蔽掉,在驱动程序的Dispatch例程中。

DriverEntry、AddDevice、Reinitialize和Unload例程也运行在PASSIVE_LEVEL,此外还有驱动程序创建的系统线程、工作者线程回调或文件系统驱动程序。

§    DISPATCH_LEVEL:处理器的DISPATCH_LEVEL和APC_LEVEL中断被屏蔽掉了,在驱动程序的IoCompletion例程中。

IoTimer、Cancel和CustomTimerDpc例程也都运行在DISPATCH_LEVEL。

有时,海量存储设备的中间层和最低层驱动程序在等于APC_LEVEL的IRQL上被调用。特别是,这种情况会在文件系统驱动程序向低层驱动程序发送IRP_MJ_READ请求导致页错误时发生。

大多数标准驱动程序例程运行在仅能使它们调用适当的支持例程的IRQL上。例如,当设备驱动程序运行在等于DISPATCH_LEVEL的IRQL上时,它必须调用AllocateAdapter或IoAllocateController。由于多数设备驱动程序从StartIo例程中调用这些例程,因此它们通常运行在DISPATCH_LEVEL。

应注意的是,对于没有StartIo例程的设备驱动程序,因为它建立并管理自己的IRP队列,所以当它应该调用AllocateAdapter(或IoAllocateController)时,不一定非要运行在等于DISPATCH_LEVEL的IRQL上。这样的驱动程序必须在调用KeRaiseIrql和调用KeLowerIrql之间调用AllocateAdapter,于是当它调用AllocateAdapter时,就能运行在要求的IRQL上,而且当调用例程重新获得控制时,能够恢复初始IRQL。

为了能在适当的IRQL调用支持例程并能在驱动程序中成功地管理硬件优先级,应当注意下列情况:

§    用低于当前IRQL的输入NewIrql值调用KeRaiseIrql会导致一个致命错误。调用KeLowerIrql以期望恢复初始IRQL(也就是,在调用KeRaiseIrql之后)也会导致一个致命错误。

§    当运行在提高的IRQL上时,用Kernel定义的调度者对象调用KeWaitForSingleObject或KeWaitForMultipleObjects以在非零时间段中等待会导致一个致命错误。只有运行在非任意线程和PASSIVE_LEVEL的驱动程序例程(如驱动程序创建的线程、DriverEntry例程和Reinitialize例程、或像大多数设备I/O控制请求那样的同步I/O操作的Dispatch例程)能在非零时间段中安全地等待时间、信号量、互斥体或定时器。

§    即使运行在PASSIVE_LEVEL上,可分页代码也决不能在输入Wait参数为TRUE的情况下,用它调用KeSetEvent、KeReleaseSemaphore或KeReleaseMutex。这样的调用会导致一个致命的页错误。

§    运行在高于APC_LEVEL的IRQL上的例程既不能从页式存储池中分配内存,也不能安全地访问页式存储池中的内存。如果这样的例程引起了一个页错误,这个错误将是致命的。

§    当驱动程序调用KeAcquireSpinLockAtDpcLevel和KeRelaeseSpinLockFromDpcLevel时,它必须运行在DISPATCH_LEVEL上。

当驱动程序调用KeAcquireSpinLock时,它可以运行在低于DISPATCH_LEVEL的IRQL上,但是它必须通过调用KeRelaeseSpinLock释放这个自旋锁。也就是说,通过调用KeRelaeseSpinLockFromDpcLevel释放由调用KeAcquireSpinLock获得的自旋锁是编程错误。

当驱动程序运行在高于DISPATCH_LEVEL的IRQL上时,它绝对不能调用KeAcquireSpinLockAtDpcLevel、KeRelaeseSpinLockFromDpcLevel、KeAcquireSpinLock或KeRelaeseSpinLock。

§    如果调用者还没有运行在这些提高后的IRQL上,调用使用了自旋锁的支持例程(如ExInterlockedXxx)会将当前处理器上的IRQL提高到DISPATCH_LEVEL或DIRQL。

§    运行在提高IRQL上的驱动程序代码应该尽快执行。为了获得好的整体性能,例程运行的IRQL越高,就越应该将例程执行速度调得尽可能快。例如,调用KeRaiseIrql的驱动程序应当尽快地做逆调用KeLowerIrql。

请使用在线DDK参见“使用自旋锁”部分和例程相应的参考部分。
exallocatepool();
exinitializeworkitem();
exqueueworkitem();
内核驱动与文件创建读写
目的:
在内核驱动中创建文件,并进行读写。用于保存驱动运行中的信息。

方式:
可以用两种方式实现该操作。
1、 在应用程序中创建文件,驱动运用IOCTL将信息传给应用层,应用程序对文件进行读写。Tdi_fw就是利用这种方式来记录驱动过滤信息,Tdi_fw的应用程序创建一个线程,循环利用IOCTL来读取驱动信息,并保存到文件。
2、 在驱动中完成所有的这些操作。在这里我们主要讨论这种方式。

实现:
利用ZwCreateFile创建磁盘文件,利用ZwReadFile、ZwWriteFile读写文件。
创建文件代码如下:
RtlInitUnicodeString (&usname,L"//SystemRoot//System32//LogFiles//passthru.log");
InitializeObjectAttributes(&oa, &usname, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
Status = ZwCreateFile(&hfile, GENERIC_WRITE, &oa, &iostatus, NULL,
   FILE_ATTRIBUTE_NORMAL,FILE_SHARE_READ, FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);

写文件的代码如下:
ZwWriteFile(hfile, NULL, NULL, NULL, &iostatus, PrintContent, count, NULL, NULL);

这些代码倒是不难,网上找一些例子就搞定了。
注意两点:
1、 记得要在结束时,ZwClose(hFile)。
2、 系统刚启动时,最好将文件创建于C:,或者系统文件下。否则会创建文件失败。
真正的问题在下面,这些Zw函数都只能运行在PASSIVE_LEVEL上。在DISPATCH_LEVEL上调用这几个函数会出现BSOD。这时问题就来了,我们在PtReceive函数里面也需要保存一些运行的信息,而PtReceive处于DISPATCH_LEVEL的级别。这个问题需要解决。

这里有两种方式:
1.运用工作队列WorkItem,WorkItem能排队注册的回调函数。当例程处于DISPATCH_LEVEL级别时将回调函数塞入队列,当进程降低到PASSIVE_LEVEL时,这些队列中的回调函数将会被系统调用。
2.运用PsCreateSystemThread方式注册一个线程,在注册一个事件,申请一段内存。
在我们有信息写入文件的时候,先将信息写入内存,然后Set这个事件。在这个线程中循环KeWaitForSingleObject这个Event。然后在调用ZwWriteFile将信息写入文件。这里要注意一点的是文件读写的同步问题。我实现的就是这种方案。




4月20日补充:
1、可以用用RtlStringCbPrintfA代替sprintf函数,对内存进行字符串初始化。例子如下:

#include "ntstrsafe.h"

CHAR pszDest[30];
ULONG cbDest = 30;
LPCSTR pszFormat = "%s %d + %d = %d.";
CHAR* pszTxt = "The answer is";

RtlStringCbPrintfA(pszDest, cbDest, pszFormat, pszTxt, 1, 2, 3);
KdPrint(("%s",pszDest));
需要注意的是这个函数还有另一个版本RtlStringCbPrintfW,专门用来对UNICODE进行超作。其他可能对我们比较有用的函数是RtlStringCbCopyA和RtlStringCbCatA用于拷贝和连接。操作都很简单,它们本身就是驱动下的sprintf的替代版本。

2、发现一个问题,就是安装完驱动重启后,在DriverEntry调用那个ZwCreateFile函数会返回失败。而在系统启动以后安装Passthru,就不会出现这个问题。原因是在操作系统启动时,系统初始化Passthru,调用我们的DriverEntry函数。这时候磁盘还没完全初始化好无法访问磁盘。
这个问题其实对我们来说没有什么影响,我们需要记录信息的时候,一定是在应用程序客户端已经开启后的事情了。这时候磁盘早已启动好了,我们不用担心。
这里的解决方法是,在应用程序一启动,打开驱动设备的时候,才ZwCreateFile。这样一定是可以的。

3、 运用WorkItem进行系统回调函数排队的方法也不难。在NDIS下可以使用NdisInitializeWorkItem和NdisScheduleWorkItem函数来排队我们在高irql下的不能执行的动作。等到进程下降到passive_level时,这些已经排队了的回调函数就会Dequeue。
这个方法我今天试过了,可以使用。但是从效率来讲,并没有什么突出的地方。而且根据CSDN的介绍,工作队列有两个需要注意的地方。一是不能执行太长的操作,会死锁。2是在排队工作项之前,最好释放所有的mutex、lock、semaphore,否则很容易死锁。反正这两种操作都不错(另一种线程的方法参见前面文档)。随便选一个用用就行了。
Regmon(2)


if(NT_SUCCESS(ntStatus))
{
创建符号连接,以便GUI能够指名,访问这个驱动/设备
创建每个需要处理的方法的分发点
DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] =
DriverObject->MajorFunction[IRP_MJ_CREATE] =
DriverObject->MajorFunction[IRP_MJ_CLOSE] =
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = RegmonDispatch;
#if DBG
DriverObject->DriverUnload = RegmonUnload;
#end if
}

if(!NT_SUCCESS(ntStatus))
{
DbgPrint((“Regmon: Failed   to create our device!/n”));
发生错误,清理环境(资源等)
if(GUIDevice) IoDeleteDevice(GUIDevice);
IoDeleteSymbolicLink(&deviceLinkUnicodeString);
return ntStatus;
}

初始化互斥量
MUTEX_INIT(StoreMutex);
MUTEX_INIT(HashMutex);
MUTEX_INIT(FilterMutex);

初始化根键长度
for(I = 0; I < NUMROOTKEYS; I ++)
{
RootKey[I].RootNameLen = strlen(RootKey[I].RootName;
}
for(I = 0; I < 2; I ++)
{
CurrentUser[I].RootNameLen = strlen(CurrentUser[I].RootName;
}

系统表数据结构指针,一个NTOSKRNL导出
ServiceTable = KeServiceDescriptorTable; 用户自定义的结构
DbgPrint((“HookRegistry:Servicetable:%x/n”, ServiceTable));
获取进程名称的偏移
ProcessNameOffset = GetProcessNameOffset(); 这个函数是牛人自己写的。

分配初始化的输出缓冲区
Store = ExAllocatePool(PagePool, sizeof(*Store));
if(!Store)
{
IoDeleteDevice(GUIDevice);
IoDeleteSymbolicLink(&deviceLinkUnicodeString);
return STATUS_INSUFFICENT_RESOURCES;
}
Store->Len = 0;
Store->Next = NULL;
NumStore = 1;

如果我们是个启动设备则开始记日志
if(startType != SERVICE_DEMAND_START)
{
初始化日志
BoolLogging = TRUE;

KeInitializeEvent(&LoggingEvent, SynchronizationEvent, FALSE);
GUIActive = TRUE;
Fileltet = BootFilter;
RegmonUpdateFilters();
HookRegisters();

告诉用户,日志模式已经开启
RtlInitUnicodeString(&bootMessageUnicodeString, bootMessage);
ZwDisplayString(&bootMessageUnicodeString);

注册关闭通知
IoRegisterShutdonwNotification注册一个驱动提供的系统关闭通知函数,在系统被完全关闭之前调用。
IoRegisterShutdown Notification(GUIDevice);

}
return STATUS_SUCCESS;
}








RegmonDispatch
这个函数处理设备的请求。需要显示处理的请求来自GUI。当然,当GUI建立,关闭合驱动的通信时,也需要处理Create,Close。
NTSTAUTS RegmonDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION irpStack; 当前堆栈
PVOID inputBuffer;
PVOID outputBuffer;
ULONG inputBufferLength;
ULONG outputBufferLength;
ULONG ioControlCode;
PSTORE_BUF old;
WORK_QUEUE_ITEM workItem;

设置处理成功
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;

获取Irp中当前位置的指针,这就是代码和参数定位的地方
irpStack = IoGetCurrentIrpStackLocation(Irp);

inputBuffer = Irp->AssociatedIrp.SystemBuffer;
inputBufferLength = iprStack->Parameters.DeviceIoControl.InputBufferLength;
outputBuffer = Irp->AssociatedIrp.SystemBuffer;
outputBufferLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;

switch(irpStack->MajorFunction)
{
case IRP_MJ_CREATE:
       DbgPrint((“Regmon: IRP_MJ_CREATE/n”));
       关闭启动日志
       if(BootLogging)
       {
         BootLogging = FALSE;
         IoUnregisterShutdownNotification(DeviceObject);
         MUTEX_WAIT(StoreMutex);
         ExInitializeWorkItem(&workItem, RegmonCloseBootLog, 0);
这个函数已经过期,应该使用IoAllocateWorkItem。返回一个指向私有结构IO_WORKITEM的指针。驱动不应该以任何形式访问这个结构!
         ExQueueWorkItem(&workItem, CriticalWorkQueue);
         这个函数也过期了,应该使用IoQueueWorkItem。插入一个指定的工作项到队列中,这个队列由系统工作线程访问,系统工作线程从队列中移出一个工作项,并调用其中的回调函数。
         KeWaitForSingleObject(&LoggingEvent, Executive, KernelMode, FALSE, NULL);
         MUTEX_RELEASE(StoreMutex);
       }
       Sequence = 0;
       GUIActive = TRUE;
       DbgPrint((“GUI Active: %d/n”, GUIActive));
       break;
}
}

还能继续小
Project->Settings打开Link属性页,将Object/library modules:下面编辑框中的各种lib全部删除,然后打上msvcrt.lib kernel32.lib user32.lib
Project->Settings的Link属性页里,在Project Options下面的编辑框里加上一句:/ALIGN:4096

这都是些简洁的方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值