windows驱动注册中断服务程序
一个驱动程序的标准中断服务例程的必要功能和建立一个ISR
的需求。
1.1 ISR
需求
一个产生中断的物理设备的所有驱动程序必须有一个ISR。中断服务例程由内核定义如下:
BOOLEAN
( *PKSERVICE_ROUTINE ) (
IN PKINTERRUPT Interrupt,
IN PVOID ServiceContext
);
ISR
运行在DIRQL
上,特别是在驱动程序用IoConnectInterrupt
注册其ISR
时说明的SynchronizeIrql
层上。当驱动程序的ISR
运行时,所有带一个中等或较低IRQL
值的中断被当前处理器所屏蔽。
当然,另一个带有一个较高的系统分配的DIRQL
的设备可以中断,或者一个高IRQL
系统中断可以在任何时间发生在Windows NT/Windows 2000
机器上。
记住以下情况:
§ 一个驱动程序的ISR
是可中断的。
因为ISR
运行在一个相对高的、系统分配的DIRQL
上,因此在当前处理器上用一个中等或较低的IRQL
屏蔽中断,ISR
应尽可能快的返回控制。
在DIRQL
运行一个ISR
限制了该ISR
可以调用的支持例程。关于IRQL
管理的更多信息,见第16章。关于任何特定支持例程都能被调用的IRQL
的说明信息,见在线DDK。
1.1.1 ISR
性能
Windows NT/Windows 2000在驱动程序ISR方面完全不同于其他一些操作系统。在Windows NT/Windows 2000系统上,如果其ISR能尽可能快的返回控制,而不是试图保持对CPU的控制并在其ISR中做尽可能多的I/O处理,尤其是在SMP机器上,那么驱动程序会有更好的表现。
反之,ISR应从中断处停止设备,并保存一切必要的关于导致中断的操作的状态信息或该操作的环境。ISR应在常驻内存中保存这些信息或环境,这类内存通常位于设备扩展中。这时,它应对驱动程序的DpcForIsr例程或一个CustomDpc排队以完成这个位于一个较低的IRQL(通常是IRQL DISPATCH_LEVEL)上的操作。
ISR
返回一个Boolean
,表明驱动程序的设备是否产生中断。对于共享一个中断向量或DIRQL的设备的驱动程序,每个ISR一旦确定其设备不是中断源,就应返回FALSE。
1.1.2 附加的需求的驱动程序例程
所有拥有一个ISR的驱动程序也必须拥有DpcForIsr或CustomDpc例程。驱动程序也可以有附加的CustomDpc例程,以用来完成特定的中断驱动的I/O操作。
如果任何驱动程序例程与驱动程序的ISR分享数据、设备寄存器或环境信息,该驱动程序还必须有一个或多个SynchCritSection例程。
ISR运行在比DpcForIsr或CustomDpc例程更高的IRQL上。因此,在一台Windows NT/Windows 2000单处理器的机器上,ISR必须在DpcForIsr或CustomDpc例程执行之前返回。当然,在一台SMP机器上,ISR和DpcForIsr(或CustomDpc)可以并行运行。
1.1.3 建立一个ISR
驱动程序通过在设备启动时调用IoConnectInterrupt
注册其ISR
。驱动程序在处理一个PnP IRP_MN_START_DEVICE
请求时作为最终步骤应连接中断。
每个拥有一个ISR
的驱动程序必须为至少一个中断对象指针提供常驻内存。通常,该指针被存放在代表产生中断的物理设备的设备对象的设备扩展中。如果驱动程序创建一个控制器对象,中断对象指针可以存放在控制器扩展中,或者它可以存放在由驱动程序分配的,非页式缓冲池中。
如果下列两者之一为真,驱动程序必须为中断自旋锁提供存储空间以连接其所有设备的所有中断对象:
§ 驱动程序有一个单独ISR
为两个或多个设备处理不同向量的中断。
§ 驱动程序的ISR
处理一个在多个向量上中断的设备
驱动程序在注册其ISR
之前必须通过调用KeInitializeSpinLock初始化中断自旋锁。驱动程序也必须为它处理的、与中断对象指针一样多的IRQ提供存储区间。
1.2 ISR
基本功能
在入口处,ISR被赋予一个指向驱动程序的中断对象的指针和一个指向驱动程序在调用IoConnectIntertupt时建立的任意区域的ServiceContext指针。大多数驱动程序设置ServiceContext指针以代表产生中断的物理设备的设备对象或者该设备对象的设备扩展。在设备扩展中,驱动程序可以为驱动程序的DpcForIsr例程设置状态信息,DpcForIsr例程通常进行几乎所有的I/O处理以满足每个导致设备中断的请求。
在没有重叠设备I/O操作的驱动程序中,ISR应做以下工作:
1.确定中断是否为假。如果是的话,立即返回FALSE以使中断设备的ISR迅速被调用。否则,继续中断处理。
2.从中断处停止设备。
3.收集所有DpcForIsr
(或CustomDpc)例程需要用来完成为当前操作的I/O处理的环境信息。
4.存放该环境信息于DpcForIsr或CustomDpc例程可访问的区域,通常在处理当前导致中断的I/O请求的目标设备对象的设备扩展中。
5.如果驱动程序有一个DpcForIsr
例程,用指向当前IRP
、目标设备对象和存储的环境信息的指针调用IoRequestDpc
。IoRequestDpc
对DpcForIsr
例程排队以便IRQL一低于处理器上的DISPATCH_LEVEL
就运行。
如果驱动程序有一个CustomDpc
例程,用一个指向DPC
对象(与CustomDpc例程连接)的指针和指向任何保存的环境(CustomDpc例程将需要它来完成操作)的指针调用KeInsertQueueDpc
。通常,ISR也传送指向当前IRP的指针与目标设备对象。一旦IRQL低于处理器上的DISPATCH_LEVEL ,CustomDpc例程便运行。
6.返回TRUE以表明其设备产生中断。
通常,一个ISR不做实际的I/O处理以满足一个IRP。相反,它从中断处停止设备,建立必要的状态信息,然后将驱动程序的DpcForIsr 或 CustomDpc排队以便进行任何满足当前导致设备中断请求的必要I/O处理。
考虑以下实现方针:
§ 为了获得可能最短的间隔,一个ISR
必须运行于DIRQL
。
根据上述策略可以为机器中的所有设备增加I/O流量,因为运行于DIRQL屏蔽了所有系统已经分配了一个较低或中等IRQL值的中断。
驱动程序的中断对象的SynchronizeIrql(在驱动程序调用IoConnectInterrupt时被指定)确定驱动程序的ISR与SynchCritSection例程在其上运行的DIRQL。
当一个驱动程序的StartIo例程用驱动程序的SynchCritSection例程调用KeSynchronizeExecution时,该调用者也传送指向与ISR相连的中断对象的指针。因此,来自设备的中断被屏蔽在处理器运行的SynchCritSection例程上。与此同时,KeSynchronizeExecution持有与中断对象相连的中断自旋锁,以使ISR不能从另一个处理器访问设备寄存器或者设备扩展中共享的状态,直到驱动程序的SynchCritSection例程返回控制。
关于使用一个中断自旋锁的KeSynchronizeExecution的调用者的更多信息,见第16章的“使用自旋锁”。
1.3 ISR
重叠I/O
操作功能
当Windows NT/Windows 2000 SMP机器内的ISR和DpcForIsr(或CustomDpc)可以并行运行时,只有代表DpcForIsr或CustomDpc例程的DPC对象的一个实例可以为在任何给定的时刻执行而被排队。
如果相同DPC对象在DpcForIsr(或CustomDpc)例程运行之前通过一个ISR不止一次地排队,相关DpcForIsr或CustomDpc例程仅被调用一次。如果DPC对象在DpcForIsr(或CustomDpc)运行时排队,该例程的两个实例可以在Windows NT/Windows 2000 SMP机器内并行运行。
因此,任何在其设备上重叠I/O操作的驱动程序必须拥有DpcForIsr和/或CustomDpc例程,在这些例程被调用时它们可以完成多个IRP。对于驱动程序的ISR的基本要求与没有重叠I/O操作的设备驱动程序一样。见“ISR基本功能”。
当然,如果一个驱动程序重叠I/O操作,其ISR必须为DpcForIsr或CustomDpc例程设置附加状态。附加状态包括一个关于DPC例程需要完成而没有完成的请求的数量以及相关的环境信息。此外,如果ISR在DPC运行之前被调用以处理另一个中断,ISR必须小心不要覆盖为没有完成的请求存储的环境信息。
因为驱动程序的DPC例程与 ISR共享该状态,其DPC例程必须用一个系统提供的SynchCritSection例程调用KeSynchronizeExecution以访问代表每个DPC例程的共享状态。
关于这些例程的更多信息,见第9章“DpcForIsr和CustomDpc例程”。关于ISR和为ISR排队的DPC互动的更多信息,见第16章的“使用自旋锁”。
用DDK做的驱动中,中断为什么不能实现
我做的是PCI的驱动,用VC6 DDK来实现。板卡桥芯片用的是9052做的驱动中设置中断可是没有反应这是为什么呢?将9052的 LINTi1接了个开关,模拟实现中断的电平输入。相关程序如下:
//获取中断资源
case CmResourceTypeInterrupt:
irql = (KIRQL) resource->u.Interrupt.Level;
vector = resource->u.Interrupt.Vector;
affinity = resource->u.Interrupt.Affinity;
mode = (resource->Flags == CM_RESOURCE_INTERRUPT_LATCHED)? Latched : LevelSensitive;
irqshare = resource->ShareDisposition == CmResourceShareShared;
//连接中断
NTSTATUS status = IoConnectInterrupt(&pdx->InterruptObject, (PKSERVICE_ROUTINE) OnInterrupt,
(PVOID) pdx, NULL, vector, irql, irql, Latched, TRUE, affinity, FALSE);
//中断服务例程
BOOLEAN OnInterrupt(PKINTERRUPT InterruptObject, PDEVICE_EXTENSION pdx)
{ // OnInterrupt
//关中断
UCHAR HSR = READ_PORT_UCHAR(0x4c+pdx->portbase0);
KdPrint(("==============readHSRinterrupt!!!\n"));
HSR = HSR&0xFE;
WRITE_PORT_UCHAR(0x4c+pdx->portbase0,HSR);
KdPrint(("==============interrupt!!!\n"));
//恢复中断信号电平
//WRITE_REGISTER_UCHAR((PUCHAR)pdx->MemBar1+0x400000,0x10);
HSR = READ_PORT_UCHAR(0x4c+pdx->portbase0);
HSR = HSR|0x01;
WRITE_PORT_UCHAR(0x4c+pdx->portbase0,HSR);
IoRequestDpc(pdx->fdo, NULL, pdx);
return TRUE;
}
///WDMDeviceIOControl的事件响应
case IOCTL_ENABLE_INT:
{
//允许中断
UCHAR HSR = READ_PORT_UCHAR((PUCHAR)(0x4c+pdx->portbase0));
HSR = HSR | 0x01;
WRITE_PORT_UCHAR((PUCHAR)(0x4c+pdx->portbase0),HSR);
}
break;
//应用程序调用
DeviceIoControl(handle, IOCTL_ENABLE_INT, NULL, 0, NULL, 0, &dwReturn, NULL);
中断请求级(IRQL
)
为了将不同CPU体系中不同的处理硬件优先级方法统一起来,NT
使用了抽象的CPU
优先级方案。即中断请求级。IRQL
是一个数,定义了CPU
当前活动的重要性。HIGHEST_LEVEL
机器检查和总线错误(硬件)POWER_LEVEL
电源失效中断(硬件)IPI_LEVEL
多处理器系统处理器之间的门铃(硬件)CLOCK2_LEVEL
内部时钟2(硬件)CLOCK1_LEVEL
内部时钟1(硬件)PROFILE_LEVEL
轮廓文件定时器(硬件)DIRQLs IO
设备中断的平台相关的级数(硬件)DISPATCH_LEVEL
线程调度器和延迟过程调用执行(软件)DPC
例程都是在IRQL=DISPATCH_LEVEL
执行的,相当于ISR
(中断服务例程)的一个延续,
伴随着ISR
一起注册。
执行顺序:
执行I/O和中断------->ISR---------->DPC--------->I/O完成例程(IOCompleteRequest)--->APC(异步过程调用)
DPC
对象从最终用户角度有两种:DpcForIsr
和CustomDPC
。前者是与设备驱动对象(Device Object
)绑定的;后者则由驱动自行维护。但从实现上来说,只有一种DPC
对象存在,DpcForIsr
所涉及的维护函数,实际上都是对CustomDPC
的一个封装而已。DPC
是线程无关的,只有内核态的,这点不像APC
。APC_LEVEL
异步过程调用执行(软件)PASSIVE_LEVEL
一般的线程执行级(软件)Dispatch
例程的IRQL
是PASSIVE_LEVEL
级别
看看下面的代码有什么问题?***_Read
是一个Read
例程。
NTSTATUS ***_Read(IN PDEVICE_OBJECT pFdo,IN PIRP Irp)
{
....
KIRQL oldirql;
…..
// pdx->ReadBufferLock是在设备对象扩展里面的KSPIN_LOCK变量。
KeAcquireSpinLock(&pdx->ReadBufferLock,&oldirql);
ulBytesInReadBuffer = pdx->BytesInReadBuffer;
KeReleaseSpinLock(&pdx->ReadBufferLock,oldirql);
…..
}
通常Dispatch_read
是IRP_MJ_READ
的处理例程,它运行在PASSIVE_LEVEL
级别。但是当调用了KeAcquireSpinLock
了之后,它就运行在DISPATCH_LEVEL
级别了,这时是不能访问paged
内存的。所以这段代码有可能会导致Windows
蓝屏。
再看看下面这段代码会不会有问题?有什么问题?
NTSTATUS ***_DispatchClose(IN PDEVICE_OBJECT pFdo,IN PIRP Irp)
{
…..
KeAcquireSpinLock(&pdx->lkThread,&eOldIrqLevel);-
if (pdx->thread)
{
….
KeWaitForSingleObject(pdx->thread,Executive,KernelMode,FALSE,NULL);
….
}
KeReleaseSpinLock(&pdx->lkThread, eOldIrqLevel)
…..
}
通常DispatchClose
例程是运行IRQL
等于PASSIVE_LEVEL
,这是可以任意调用KeWaitForSingleObject
,但是当调用KeAcquireSpinLock
之后,IRQL
升级到DISPATCH_LEVEL
。这时再调用KeWaitForSingleObject
来无限期等待,会导致dead lock
,因为大家都是处在DISPATCH_LEVEL
例程,别的例程可能得不到机会来设置KeWaitForSingleObject
等待的事情,所以会导致dead lock
。
IoConnectInterrupt
IoConnectInterrupt
的目的是为设备驱动程序注册一个ISR
(中断服务例程),使得它可以再设备在指定的处理器上产生中断的时候被调用。
NTSTATUS
IoConnectInterrupt(
OUT PKINTERRUPT *InterruptObject,//指向驱动程序提供的中断对象存储地址,该参数随后要传递给KeSynchronizeExecution。
OUT PKSERVICE_ROUTINE ServiceRoutine,//中断服务例程的入口
IN PVOID ServiceContext,//指向驱动指定的即将传递给ISR的参数,ServiceContext必须在常驻内存中,可以是驱动程序创建的设备驱动的设备扩展,也可以是驱动创建的控制对象的控制拓展,还可以是设备驱动分配的非分页内存。
IN PKSPIN_LOCK SpinLock OPTIONAL,//指向已经初始化的自旋所,驱动程序负责自旋所的存储,并且该自旋所将用来同步被驱动程序其它例程共享的数据的访问,该参数在ISR处理多个中断向量或者驱动程序包含不止一个ISR时需要设置,否则,驱动程序不需要为中断自旋所分配存储空间,参数设置为NULL。
IN ULONG Vector,//输入获取的中断向量
IN KIRQL Irql,//输入获取的中断优先级DIRQL
IN KIRQL SynchronizeIrql,//指明ISR执行所在的DIRQL,当ISR需要处理多个中断向量或者驱动程序有多个ISR的时候,该值选择全部中断资源的u.Interrupt.Level中的最高值,否则和上面的Irql变量相等。
IN KINTERRUPT_MODE InterruptMode,//电平触发或者边沿触发
IN BOOLEAN ShareVector,//指明中断向量是否是可共享的。
IN KAFFINITY ProcessorEnableMask,//指定一个KAFFINITY值,用来说明设备中断可以在什么样的处理器平台上发生。
IN BOOLEAN FloatingSave //指明是否需要保存设备中断时的浮点堆栈,在X86平台下,该值必须是FALSE。
还没有评论,来说两句吧...