中断处理

每个cpu有一张中断表,简称IDT。

IDT的整体布局:【异常->空白->5系->硬】(推荐采用7字口诀的方式重点记忆)

异常:前20个表项存放着各个异常的描述符(IDT表不仅可以放中断描述符,还放置了所有异常的异常处理描述符,0x00-0x13)

保留:0x14-0x1F,忽略这块号段

空白:接下来存放一组空闲的保留项(0x20-0x29),供系统和程序员自己分配注册使用

5系:然后是系统自己注册的5个预定义的软中断向量(软中断指手动的INT指令)

(0x2A-0x2E  5个系统预注册的中断向量,0x2A:KiGetTickCount, 0x2B:KiCallbaclReturn

0x2C:KiRaiseAssertion,  0x2D:KiDebugService,  0x2E:KiSystemService)

硬:  最后的表项供驱动程序注册硬件中断使用和自定义注册其他软中断使用(0x30-0xFF)

下面是中断号的具体的分配情况:

0x00-0x13固定分配给异常:

0x14-0x1f:Intel保留给他公司将来自己使用(OS和用户都不要试图去使用这个号段,不安全)

----------------------以下的号段可用于自由分配给OS、硬件、用户使用----------------------- 

linux等其他系统是怎么划分这块号段的,不管,我们只看Windows的情况

0x20-0x29:Windows没占用,因此这块号段我们也可以自由使用

0x2A-0x2E:Windows自己本身使用的5个中断号

0x30-0xFF:Windows决定把这块剩余的号段让给硬件和用户使用

参见《寒江独钓》一书P93页注册键盘中断时,搜索空闲未用表项是从0x20开始,到0x29结束的,就知道为什么寒江独钓是在这段范围内搜索空白表项了(其实我们也完全可以从0x14开始搜索)

Windows系统中,0x30-0xFF这块号段让给了硬件和用户自己使用。事实上,这块号段的开头部分默认都是让给硬件IRQ使用的,也即是分配给硬件IRQ的。IRQ N默认映射到中断号0x30+N,如IRQ0用于系统时钟,系统时钟中断号默认对应就是0x30。当然程序员也可以修改APIC(可编程中断控制器)将IRQ映射到自定义的中断号。

IRQ对外部设备分配,但IRQ0,IRQ2,IRQ13必须如下分配: 

IRQ0 ---->间隔定时设备 

IRQ2 ---->8259A芯片

IRQ13 ---->外部数学协处理器 

其余的IRQ可以任意分配给外部设备。

虽然一个IRQ只对应一个中断号,但是由于IRQ数量有限,而设备种类成千上万,因此多个设备可以使用同一个IRQ,进而,多个设备可以分配同一个中断号。因此,一个中断号可以共享给多个设备同时使用。

Pnp设备在插入系统后,相应的总线驱动会自动为其创建一个用作栈底基石的pdo,然后给这个pdo发出一个IRP_MN_QUERY_RESOURCE_REQUIREMENTS,查询得到初步的资源需求。然后,pnp管理器会找到相应的硬件端口驱动,调用其AddDevice函数,当这个函数返回后,该硬件设备的设备栈已经建立起立了,pnp管理器就给栈顶设备发出一个IRP_MN_FILTER_RESOURCE_REQUIREMENTS再次询问该硬件需要的资源(功能驱动此时可以拦截处理这个irp,修改资源需求),当确定好最终的资源需求后,系统就协调分配端口号、中断号、DIRQL等硬件资源给它。分配完后,就发出一个IRP_MN_START_DEVICE给栈顶设备请求启动该硬件设备。当该irp下发来到端口驱动(指真正的硬件驱动)时, 端口驱动这时就需要在分配的中断号上注册一个中断服务例程,以处理硬件中断,与设备进行交互。下面的函数就是用来注册中断服务例程的(准确的说法叫‘挂接中断’)

NTSTATUS

IoConnectInterrupt(OUT PKINTERRUPT *InterruptObject,//返回创建的中断对象(一般是一个数组)

IN PKSERVICE_ROUTINE ServiceRoutine,//我们的isr(our isr)

IN PVOID ServiceContext,//isr的参数

IN PKSPIN_LOCK SpinLock,//我们isr的自旋锁,用于多cpu互斥(一般传NULL即可)

IN ULONG Vector,//分配到的中断号

IN KIRQL Irql,//isr对应的irql

IN KIRQL SynchronizeIrql,//必须>=Irql,一般=Irql即可(isr实际运行在这个irql)

IN KINTERRUPT_MODE InterruptMode,//表示是否允许执行本中断的下一个中断

IN BOOLEAN ShareVector,//表示本中断对象是否想要共享中断号以及是否允许共享

IN KAFFINITY ProcessorEnableMask,//本isr的cpu亲缘性,一般全部cpu都亲缘。

IN BOOLEAN FloatingSave)//一般为FALSE

{

PKINTERRUPT Interrupt;

PKINTERRUPT InterruptUsed;//当前的中断对象

PIO_INTERRUPT IoInterrupt;//中断对象数组的头部

PKSPIN_LOCK SpinLockUsed;//实际使用的自旋锁

BOOLEAN FirstRun;

CCHAR Count = 0;//cpu号

KAFFINITY Affinity;//cpu亲缘性掩码

PAGED_CODE();

*InterruptObject = NULL;

Affinity = ProcessorEnableMask & KeActiveProcessors;//本isr的cpu亲缘性与实有cpu的交集

while (Affinity)

{

if (Affinity & 1)  Count++;

Affinity >>= 1;

}

//上面的循环根据本isr可以在哪些cpu上运行,得出可运行的cpu个数

if (!Count) return STATUS_INVALID_PARAMETER;

//分配一个中断对象数组

IoInterrupt = ExAllocatePoolWithTag(NonPagedPool, (Count - 1) * sizeof(KINTERRUPT) +

sizeof(IO_INTERRUPT),TAG_KINTERRUPT);

if (!IoInterrupt) return STATUS_INSUFFICIENT_RESOURCES;

*InterruptObject = &IoInterrupt->FirstInterrupt;

//if 用户没提供自旋锁,就使用内置的自旋锁。一般用户不需自己提供自旋锁

SpinLockUsed = SpinLock ? SpinLock : &IoInterrupt->SpinLock;

Interrupt = (PKINTERRUPT)(IoInterrupt + 1);//后面的中断对象数组地址

FirstRun = TRUE;

RtlZeroMemory(IoInterrupt, sizeof(IO_INTERRUPT));

Affinity = ProcessorEnableMask & KeActiveProcessors;

for (Count = 0; Affinity; Count++, Affinity >>= 1) //Count其实表示cpu号

{

if (Affinity & 1)

{

//第一次使用头部中的那个内置中断对象

InterruptUsed = FirstRun ? &IoInterrupt->FirstInterrupt : Interrupt;

//构造一个中断对象

KeInitializeInterrupt(InterruptUsed,ServiceRoutine,ServiceContext,

SpinLockUsed,Vector,Irql,SynchronizeIrql,

InterruptMode,ShareVector,Count,FloatingSave);

if (!KeConnectInterrupt(InterruptUsed))//关键,挂接中断对象到目标cpu的指定中断号

{

//if 挂接失败

if (FirstRun)

ExFreePool(IoInterrupt);

else

IoDisconnectInterrupt(&IoInterrupt->FirstInterrupt);

return STATUS_INVALID_PARAMETER;

}

if (FirstRun)

FirstRun = FALSE;

Else //记录各cpu的那个中断号上挂接的中断对象地址

IoInterrupt->Interrupt[(UCHAR)Count] = Interrupt++;

}

}

return STATUS_SUCCESS;

}

如上,这个函数用来将指定isr挂接到各个cpu的指定中断号上。因为在多cpu系统中,一个设备可以向每个cpu都发出中断,因此,必须在每个cpu的IDT中都要挂接登记那个中断的isr。具体是怎么挂接的呢?这个函数会创建一个中断对象数组,然后将各个中断对象对应挂接到各cpu的同一中断号上。由于老式机器是单cpu的,因此,早期的中断对象结构IO_INTERRUPT就包含一个中断对象任意,后来的机器对其进行了扩展,在这个结构后面是一个中断对象数组,用来挂接到其他cpu上。

另外,由于多个设备可以共用同一中断号,所以每个中断号需要一个自己的链表来记录所有挂接在此中断号上的所有中断对象。

typedef struct _IO_INTERRUPT

{

KINTERRUPT FirstInterrupt;//内置的中断对象

PKINTERRUPT Interrupt[MAXIMUM_PROCESSORS];//记录各cpu上挂接的中断对象地址

KSPIN_LOCK SpinLock;//内置的isr自旋锁,如果用户没提供,就默认使用这个公共的自旋锁。

} IO_INTERRUPT, *PIO_INTERRUPT;

该结构体后面紧跟一个INTERRUPT结构体数组

typedef struct _KINTERRUPT  //中断对象

{

CSHORT Type;

CSHORT Size;

LIST_ENTRY InterruptListEntry;//用来挂入中断对象链表

PKSERVICE_ROUTINE ServiceRoutine;//我们的isr(用户的isr)

PVOID ServiceContext;//isr 参数

KSPIN_LOCK SpinLock; //一般无用

ULONG TickCount;//没用

PKSPIN_LOCK ActualLock;//我们isr实际使用的自旋锁

PKINTERRUPT_ROUTINE DispatchAddress;//中间的Dispatch isr函数地址

CCHAR Number;//要挂往的目标cpu

ULONG Vector;//要挂往的目标中断号

KIRQL Irql;//isr对应的isr

KIRQL SynchronizeIrql;//isr实际运行在的irql

BOOLEAN FloatingSave;//一般为FALSE

BOOLEAN Connected;//表示本中断对象是否挂上去了

BOOLEAN ShareVector;//是否想要共享中断号,以及是否允许后来的中断对象共享

KINTERRUPT_MODE Mode;//是否允许继续执行本中断对象后面的中断对象的isr

ULONG ServiceCount;//没用

ULONG DispatchCount;//没用

ULONG DispatchCode[DISPATCH_LENGTH];//这不是数组,而是一段代码,表示本中断对象的模板isr

} KINTERRUPT;

下面的函数用来构造、初始化一个中断对象

VOID

KeInitializeInterrupt(IN PKINTERRUPT Interrupt,

IN PKSERVICE_ROUTINE ServiceRoutine,IN PVOID ServiceContext,

IN PKSPIN_LOCK SpinLock,IN ULONG Vector,IN KIRQL Irql,

IN KIRQL SynchronizeIrql,IN KINTERRUPT_MODE InterruptMode,

IN BOOLEAN ShareVector,IN CHAR ProcessorNumber,IN BOOLEAN FloatingSave)

{

ULONG i;

PULONG DispatchCode = &Interrupt->DispatchCode[0],Patch = DispatchCode;//patch表示补丁处

Interrupt->Type = InterruptObject;

Interrupt->Size = sizeof(KINTERRUPT);

if (SpinLock)//由于这个函数未导出,由系统内部调用,传的SpinLock参数很少为NULL

Interrupt->ActualLock = SpinLock;//使用头部中公共的自旋锁或者我们提供的自旋锁

else

{

KeInitializeSpinLock(&Interrupt->SpinLock);

Interrupt->ActualLock = &Interrupt->SpinLock;

}

Interrupt->ServiceRoutine = ServiceRoutine;

Interrupt->ServiceContext = ServiceContext;

Interrupt->Vector = Vector;

Interrupt->Irql = Irql;

Interrupt->SynchronizeIrql = SynchronizeIrql;

Interrupt->Mode = InterruptMode;

Interrupt->ShareVector = ShareVector;

Interrupt->Number = ProcessorNumber;

Interrupt->FloatingSave = FloatingSave;

Interrupt->TickCount = MAXULONG;

Interrupt->DispatchCount = MAXULONG;

//先拷贝模板isr的字节码到中断对象内部

for (i = 0; i < DISPATCH_LENGTH; i++)

*DispatchCode++ = ((PULONG)KiInterruptTemplate)[i];

//patch 指向模板isr中的mov edx,0指令的操作数部分

Patch = (PULONG)((ULONG)Patch +

((ULONG)&KiInterruptTemplateObject -4 - (ULONG)KiInterruptTemplate));

*Patch = PtrToUlong(Interrupt);//也即将原mov edx,0 改为 mov edx,本中断对象的地址

Interrupt->Connected = FALSE;//尚未挂入

}

下面是系统的模板isr:

_KiInterruptTemplate:

KiEnterTrap KI_PUSH_FAKE_ERROR_CODE

_KiInterruptTemplate2ndDispatch:

mov edx, 0  //这条指令的操作数0将被动态修改成具体中断对象的地址

_KiInterruptTemplateObject:

mov eax, offset @KiInterruptTemplateHandler@8  //KiInterruptTemplateHandler函数

jmp eax

_KiInterruptTemplateDispatch:

上面就是系统的模板isr,每个中断对象的模板isr就是从系统的模板isr复制过来的,然后稍作修改。

当构造好中断对象后,就需要把它挂接到目标cpu的目标中断号上。下面的函数就这个用途

BOOLEAN  //返回值表示是否挂接成功

KeConnectInterrupt(IN PKINTERRUPT Interrupt)

{

BOOLEAN Connected, Error, Status;

KIRQL Irql, OldIrql;

UCHAR Number;

ULONG Vector;

DISPATCH_INFO Dispatch;

Number = Interrupt->Number;//目标cpu

Vector = Interrupt->Vector;//目标中断号

Irql = Interrupt->Irql;

// SynchronizeIrql 必须 >= Irql

if ((Irql > HIGH_LEVEL) || (Number >= KeNumberProcessors) ||

(Interrupt->SynchronizeIrql < Irql) ||  (Interrupt->FloatingSave))

{

return FALSE;

}

Connected = FALSE;

Error = FALSE;

KeSetSystemAffinityThread(1 << Number);//改变当前线程的cpu亲缘性先,挪到目标cpu上去运行

------------------------------------华丽的分割线--------------------------------------

//下面的这些代码已经处在目标cpu上运行了

OldIrql = KiAcquireDispatcherLock();

if (!Interrupt->Connected)//if尚未挂接

{

//查询当前cpu这个中断号上的最近一次(也即上一次)的挂接情况

KiGetVectorDispatch(Vector, &Dispatch);

if (Dispatch.Type == NoConnect)//如果这个中断号尚未挂接有任何中断对象

{

Interrupt->Connected = Connected = TRUE;

InitializeListHead(&Interrupt->InterruptListEntry);//独立

//NormalConnect表示以普通方式挂上去(非链接方式),相当于覆盖方式

KiConnectVectorToInterrupt(Interrupt, NormalConnect);

Status = HalEnableSystemInterrupt(Vector, Irql, Interrupt->Mode);//APIC相关

if (!Status) Error = TRUE;

}

else if ((Dispatch.Type != UnknownConnect) && //已挂接有中断对象

(Interrupt->ShareVector) &&   //本中断对象想要共享这个中断号

(Dispatch.Interrupt->ShareVector) &&  //并且上次挂接的那个中断对象允许共享

(Dispatch.Interrupt->Mode == Interrupt->Mode))

{

Interrupt->Connected = Connected = TRUE;

//if 上一个中断对象不是以链接方式挂上去的,就改为链接方式挂上去

if (Dispatch.Type != ChainConnect)

{

ASSERT(Dispatch.Interrupt->Mode != Latched);

KiConnectVectorToInterrupt(Dispatch.Interrupt, ChainConnect);

}

//关键。挂在上一个中断对象的后面

InsertTailList(&Dispatch.Interrupt->InterruptListEntry,

&Interrupt->InterruptListEntry);

}

}

KiReleaseDispatcherLock(OldIrql);

KeRevertToUserAffinityThread();

if ((Connected) && (Error))

{

KeDisconnectInterrupt(Interrupt);

Connected = FALSE;

}

return Connected;

}

下面的函数用于查询当前cpu指定中断号上的最近一次挂接情况(查询最近一次挂上去的中断对象,以及它当时是怎么挂上去的)

VOID  KiGetVectorDispatch(IN ULONG Vector,IN PDISPATCH_INFO Dispatch)

{

PKINTERRUPT_ROUTINE Handler;

PVOID Current;

UCHAR Type;

UCHAR Entry;

Entry = HalVectorToIDTEntry(Vector);//这个宏将中断向量号转换为IDT表项索引(一般相同)

//固定为KiUnexpectedInterruptN函数的地址,N表示IRQ

Dispatch->NoDispatch = (PVOID)(((ULONG_PTR)&KiStartUnexpectedRange) +

(Entry – 0x30) *KiUnexpectedEntrySize);

Dispatch->InterruptDispatch = (PVOID)KiInterruptDispatch;//这个字段固定

Dispatch->FloatingDispatch = NULL; //尚不支持

Dispatch->ChainedDispatch = (PVOID)KiChainedDispatch;//这个字段固定

Dispatch->FlatDispatch = NULL;

Current = KeQueryInterruptHandler(Vector);//获得这个中断向量处当前存放的isr函数地址

if ((PKINTERRUPT_ROUTINE)Current == Dispatch->NoDispatch)

{

Dispatch->Interrupt = NULL;//表示尚未挂接有任何中断对象

Dispatch->Type = NoConnect; //表示尚未挂接有任何中断对象

}

else

{

//关键,可有isr(其实是个模板isr)反推出当前的中断对象(即最近一次挂上去的对象)

Dispatch->Interrupt = CONTAINING_RECORD(Current,KINTERRUPT,DispatchCode);

Handler = Dispatch->Interrupt->DispatchAddress;

if (Handler == Dispatch->ChainedDispatch)

Dispatch->Type = ChainConnect;//上次的中断对象是以链接方式挂上去的

else if ((Handler == Dispatch->InterruptDispatch) ||

(Handler == Dispatch->FloatingDispatch))

{

Dispatch->Type = NormalConnect;//上次的中断对象是以普通方式挂上去的

}

else

Dispatch->Type = UnknownConnect;//不确定上次的中断对象是怎么挂上去的

}

}

下面这个函数返回当前cpu上指定中断向量处的isr。注意:任一时刻,每个isr可能是个模板isr,可能是个用户自定义的isr,也可能没有isr(即以KiUnexpectedInterruptN函数占位)。

PVOID

KeQueryInterruptHandler(IN ULONG Vector)

{

PKIPCR Pcr = (PKIPCR)KeGetPcr();

UCHAR Entry;

Entry = HalVectorToIDTEntry(Vector);

return (PVOID)(((Pcr->IDT[Entry].ExtendedOffset << 16) & 0xFFFF0000) |

(Pcr->IDT[Entry].Offset & 0xFFFF));

}

真正的挂接操作是调用下面的函数完成的

VOID KiConnectVectorToInterrupt(IN PKINTERRUPT Interrupt,IN CONNECT_TYPE Type)//挂接类型

{

DISPATCH_INFO Dispatch;

PKINTERRUPT_ROUTINE Handler;//将要填到IDT表项中的最直接isr

KiGetVectorDispatch(Interrupt->Vector, &Dispatch);

if (Type == NoConnect)//if用户要撤销挂接

Handler = Dispatch.NoDispatch;

else

{

//填好本中断对象的dispatch isr

Interrupt->DispatchAddress = (Type == NormalConnect) ? Dispatch.InterruptDispatch:

Dispatch.ChainedDispatch;

Handler = (PVOID)&Interrupt->DispatchCode;//本中断对象的模板isr

}

//将本中断对象的模板isr或者KiUnexpectedInterruptN填写到IDT的对应表项处。

//可以看出,通过IoConnectInterrupt函数,IDT中的每个isr都是最后一次挂接的中断对象的模板isr

KeRegisterInterruptHandler(Interrupt->Vector, Handler);

}

VOID KeRegisterInterruptHandler(IN ULONG Vector,IN PVOID Handler)

{

UCHAR Entry;

ULONG_PTR Address;

PKIPCR Pcr = (PKIPCR)KeGetPcr();

Entry = HalVectorToIDTEntry(Vector);

Address = PtrToUlong(Handler);

//将isr填写到相应的表项中

Pcr->IDT[Entry].ExtendedOffset = (USHORT)(Address >> 16);

Pcr->IDT[Entry].Offset = (USHORT)Address;

}

通过IoConnectInterrupt函数挂接的中断对象,都是将其模板isr填写到IDT表项中,这样,谁最后挂接,谁的模板isr就会最后覆写到那个表项处。如果使用了同一中断号的各个中断对象都是以链接方式挂接上去的,那么这些中断对象将组成一个链表。这样,当cpu收到对应的中断号时,会找到IDT中对应表项的isr给予执行。而那个isr就是最后挂接的中断对象的模板isr,这个模板isr的代码前面已看过,它将跳转进入下面的函数

VOID FASTCALL

KiInterruptTemplateHandler(IN PKTRAP_FRAME TrapFrame, //ecx

IN PKINTERRUPT Interrupt)  //edx

{

KiEnterInterruptTrap(TrapFrame);

((PKI_INTERRUPT_DISPATCH)Interrupt->DispatchAddress)(TrapFrame, Interrupt);//关键

}

看到没,每个中断对象的模板isr,会调用它的dispatch isr。以链接方式挂上去的中断对象的dispatchisr都是KiChainedDispatch,反之则是KiInterruptDispatch。我们看:

VOID FASTCALL

KiChainedDispatch(IN PKTRAP_FRAME TrapFrame,IN PKINTERRUPT Interrupt)

{

KIRQL OldIrql;

BOOLEAN Handled;

PLIST_ENTRY NextEntry, ListHead;

KeGetCurrentPrcb()->InterruptCount++;//递增中断计数

//HalBeginSystemInterrupt会提升irql至指定irql,以准备执行我们的isr

if (HalBeginSystemInterrupt(Interrupt->Irql,Interrupt->Vector,&OldIrql))//APIC相关

{

ListHead = &Interrupt->InterruptListEntry;

NextEntry = ListHead;

while (TRUE) //遍历中断对象链表,直至找到一个能处理这个中断的中断对象为止

{

if (Interrupt->SynchronizeIrql > Interrupt->Irql)//再次提升irql

OldIrql = KfRaiseIrql(Interrupt->SynchronizeIrql);

KxAcquireSpinLock(Interrupt->ActualLock);//加锁,保障多cpu互斥

//执行我们的isr(即用户自己提供的isr),注意返回值

Handled = Interrupt->ServiceRoutine(Interrupt,Interrupt->ServiceContext);

KxReleaseSpinLock(Interrupt->ActualLock);

if (Interrupt->SynchronizeIrql > Interrupt->Irql)

KfLowerIrql(OldIrql);

//if本中断对象认领了,且不许执行下一个中断对象就退出查找循环。

//(LevelSensitive即FALSE,一般的中断对象都这样)

if ((Handled) && (Interrupt->Mode == LevelSensitive)) break;

NextEntry = NextEntry->Flink;

if (NextEntry == ListHead)//if链表中的最后一个中断对象

{

if (Interrupt->Mode == LevelSensitive) break;

if (!Handled) break;//if没能处理这个中断,退出循环

}

Interrupt = CONTAINING_RECORD(NextEntry, KINTERRUPT, InterruptListEntry);

}

KiExitInterrupt(TrapFrame, OldIrql, FALSE);

}

Else  //清理中断Trap帧,恢复中断现场,回到原断点处继续执行

KiExitInterrupt(TrapFrame, OldIrql, TRUE);

}

用户自己的isr的原型是:

BOOLEAN  InterruptService(__in struct _KINTERRUPT  *Interrupt,__in PVOID  ServiceContex );

我们的这个isr应该根据 ServiceContex判断这个中断是不是我们驱动中的设备发出的,若是,才能处理,返回TRUE。否则应返回FALSE,让系统继续寻找中断对象链表中的下一个中断对象去认领。

而对于以普通覆写方式挂上去的中断对象,它的dispatch isr是KiInterruptDispatch,我们看:

VOID FASTCALL

KiInterruptDispatch(IN PKTRAP_FRAME TrapFrame,IN PKINTERRUPT Interrupt)

{

KIRQL OldIrql;

KeGetCurrentPrcb()->InterruptCount++;//递增中断计数

if (HalBeginSystemInterrupt(Interrupt->SynchronizeIrql,Interrupt->Vector,&OldIrql))

{

KxAcquireSpinLock(Interrupt->ActualLock);

//调用其自己的isr

Interrupt->ServiceRoutine(Interrupt, Interrupt->ServiceContext);

KxReleaseSpinLock(Interrupt->ActualLock);

KiExitInterrupt(TrapFrame, OldIrql, FALSE);

}

else

KiExitInterrupt(TrapFrame, OldIrql, TRUE);

}

看到没,普通方式挂上去的中断对象,它独占中断号,当发生相应中断时,系统简单执行一下它自己的isr后,就返回了,不会有在链表中查找的过程。

底层驱动在卸载时,往往要撤销挂接的那些中断,我们看下中断对象如如何撤销挂接的。

VOID IoDisconnectInterrupt(PKINTERRUPT InterruptObject)

{

LONG i;

PIO_INTERRUPT IoInterrupt;

PAGED_CODE();

IoInterrupt = CONTAINING_RECORD(InterruptObject,IO_INTERRUPT,FirstInterrupt);

KeDisconnectInterrupt(&IoInterrupt->FirstInterrupt);//撤销第一个中断对象的挂接

for (i = 0; i < KeNumberProcessors; i++)//撤销其它中断对象的挂接

{

if (IoInterrupt->Interrupt[i])

KeDisconnectInterrupt(&InterruptObject[i]);

}

ExFreePool(IoInterrupt);//释放整个中断对象数组加头部占用的那块内存

}

BOOLEAN KeDisconnectInterrupt(IN PKINTERRUPT Interrupt)

{

KIRQL OldIrql, Irql;

ULONG Vector;

DISPATCH_INFO Dispatch;

PKINTERRUPT NextInterrupt;

BOOLEAN State;

KeSetSystemAffinityThread(1 << Interrupt->Number);

OldIrql = KiAcquireDispatcherLock();

State = Interrupt->Connected;

if (State)

{

Irql = Interrupt->Irql;

Vector = Interrupt->Vector;

KiGetVectorDispatch(Vector, &Dispatch);//获取上次的挂接信息

if (Dispatch.Type == ChainConnect)

{

ASSERT(Irql <= SYNCH_LEVEL);

if (Interrupt == Dispatch.Interrupt)//if 要撤销挂接的中断对象就是最近挂接的那个

{

Dispatch.Interrupt = CONTAINING_RECORD(Dispatch.Interrupt->

InterruptListEntry.Flink,KINTERRUPT,InterruptListEntry);

KiConnectVectorToInterrupt(Dispatch.Interrupt, ChainConnect);

}

//关键。脱出链表

RemoveEntryList(&Interrupt->InterruptListEntry);

NextInterrupt = CONTAINING_RECORD(Dispatch.Interrupt->InterruptListEntry.Flink,

KINTERRUPT,InterruptListEntry);

if (Dispatch.Interrupt == NextInterrupt)//也即if 链表中就剩下一个中断对象了

{

KiConnectVectorToInterrupt(Dispatch.Interrupt, NormalConnect);//改为普通方式

}

}

Else //if原来本身就只挂着一个中断对象

{

HalDisableSystemInterrupt(Interrupt->Vector, Irql);

KiConnectVectorToInterrupt(Interrupt, NoConnect);//改为KiUnexpectedInterruptN

}

Interrupt->Connected = FALSE;

}

KiReleaseDispatcherLock(OldIrql);

KeRevertToUserAffinityThread();

return State;

}

通过IoConnectInterrupt函数挂接注册中断,确实为程序员减轻了大量负担。通过这种方式注册的isr,分三层。第一层是中断对象的模板isr,第二层是中断对象的dispatch isr,第三层才是用户自己提供的isr。每当发生中断时,系统逐层调用这三层isr。因此,也可以说,我们提供的那个isr被系统托管了,IDT表项中的isr是系统的托管isr。

当然,程序员,也可以直接修改IDT中的表项,改成自己的isr,这就是所谓的isr hook(注意要进行isr hook的话,必须每个cpu都要hook)

最后我们看一下典型的系统时钟中断是怎么处理的。系统每隔10ms产生一次时钟中断,时钟中断的IRQ固定是0,中断号默认映射到0x30,时钟中断的isr最终进入下面的函数。

VOID FASTCALL

HalpClockInterruptHandler(IN PKTRAP_FRAME TrapFrame)

{

KIRQL Irql;

KiEnterInterruptTrap(TrapFrame);

//提升irql至CLOCK2_LEVEL

if (HalBeginSystemInterrupt(CLOCK2_LEVEL, 0x30, &Irql))//0x30就是时钟中断的中断号

{

/* Update the performance counter */

HalpPerfCounter.QuadPart += HalpCurrentRollOver;

HalpPerfCounterCutoff = KiEnableTimerWatchdog;

KeUpdateSystemTime(TrapFrame, HalpCurrentTimeIncrement, Irql);//关键函数

}

KiEoiHelper(TrapFrame);

}

VOID FASTCALL

KeUpdateSystemTime(IN PKTRAP_FRAME TrapFrame,IN ULONG Increment,IN KIRQL Irql)

{

PKPRCB Prcb = KeGetCurrentPrcb();

ULARGE_INTEGER CurrentTime, InterruptTime;

ULONG Hand, OldTickCount;

//更新启动以来的运行时间计数

InterruptTime.HighPart = SharedUserData->InterruptTime.High1Time;

InterruptTime.LowPart = SharedUserData->InterruptTime.LowPart;

InterruptTime.QuadPart += Increment;//Increment一般为10ms

SharedUserData->InterruptTime.High1Time = InterruptTime.HighPart;

SharedUserData->InterruptTime.LowPart = InterruptTime.LowPart;

SharedUserData->InterruptTime.High2Time = InterruptTime.HighPart;

OldTickCount = KeTickCount.LowPart;

InterlockedExchangeAdd(&KiTickOffset, -(LONG)Increment);//递减

/* Check for incomplete tick */

if (KiTickOffset <= 0)

{

//更新系统时间

CurrentTime.HighPart = SharedUserData->SystemTime.High1Time;

CurrentTime.LowPart = SharedUserData->SystemTime.LowPart;

CurrentTime.QuadPart += KeTimeAdjustment;

SharedUserData->SystemTime.High2Time = CurrentTime.HighPart;

SharedUserData->SystemTime.LowPart = CurrentTime.LowPart;

SharedUserData->SystemTime.High1Time = CurrentTime.HighPart;

//更新Tick计数

CurrentTime.HighPart = KeTickCount.High1Time;

CurrentTime.LowPart = OldTickCount;

CurrentTime.QuadPart += 1;//递增Tick计数

KeTickCount.High2Time = CurrentTime.HighPart;

KeTickCount.LowPart = CurrentTime.LowPart;

KeTickCount.High1Time = CurrentTime.HighPart;

//更新Tick计数(供应用程序GetTickCount访问)

SharedUserData->TickCount.High2Time = CurrentTime.HighPart;

SharedUserData->TickCount.LowPart = CurrentTime.LowPart;

SharedUserData->TickCount.High1Time = CurrentTime.HighPart;

Hand = OldTickCount & (TIMER_TABLE_SIZE - 1);

if (KiTimerTableListHead[Hand].Time.QuadPart <= InterruptTime.QuadPart)

{

if (!Prcb->TimerRequest)

{

Prcb->TimerRequest = (ULONG_PTR)TrapFrame;

Prcb->TimerHand = Hand;

HalRequestSoftwareInterrupt(DISPATCH_LEVEL);

}

}

OldTickCount++;

}

//下面是关键。检查是否有到点的定时器

Hand = OldTickCount & (TIMER_TABLE_SIZE - 1);

//if有到点的定时器

if (KiTimerTableListHead[Hand].Time.QuadPart <= InterruptTime.QuadPart)

{

if (!Prcb->TimerRequest)

{

Prcb->TimerRequest = (ULONG_PTR)TrapFrame;//关键。插入一个定时器dpc

Prcb->TimerHand = Hand;

HalRequestSoftwareInterrupt(DISPATCH_LEVEL);//发出一个DPC中断请求

}

}

if (KiTickOffset <= 0)

{

KiTickOffset += KeMaximumIncrement;

KeUpdateRunTime(TrapFrame, Irql);

}

else

{

Prcb->InterruptCount++;

}

KiEndInterrupt(Irql, TrapFrame);

}

上面的函数在发现有定时器到点后,就会发出一个DPC,然后在系统定时器中断退出后,就会扫描执行DPC队列,调用KiTimerExpiration函数扫描所有到点的定时器,然后唤醒在那些定时器上等待的线程。

KiTimerExpiration函数的代码,有兴趣的读者自己看。

[10]Windows内核情景分析---中断处理的更多相关文章

  1. [1]windows 内核情景分析---说明

    本文说明:这一系列文章(笔记)是在看雪里面下载word文档,现转帖出来,希望更多的人能看到并分享,感谢原作者的分享精神. 说明 本文结合<Windows内核情景分析>(毛德操著).< ...

  2. windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数

    windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数 1.KeRaiseIrql函数 这个 KeRaiseIrql() 只是简单地调用 hal 模块的 KfRa ...

  3. 几个常用内核函数(《Windows内核情景分析》)

    参考:<Windows内核情景分析> 0x01  ObReferenceObjectByHandle 这个函数从句柄得到对应的内核对象,并递增其引用计数. NTSTATUS ObRefer ...

  4. [14]Windows内核情景分析 --- 文件系统

    文件系统 一台机器上可以安装很多物理介质来存放资料(如磁盘.光盘.软盘.U盘等).各种物理介质千差万别,都配备有各自的驱动程序,为了统一地访问这些物理介质,windows设计了文件系统机制.应用程序要 ...

  5. [7] Windows内核情景分析---线程同步

    基于同步对象的等待.唤醒机制: 一个线程可以等待一个对象或多个对象而进入等待状态(也叫睡眠状态),另一个线程可以触发那个等待对象,唤醒在那个对象上等待的所有线程. 一个线程可以等待一个对象或多个对象, ...

  6. [4]Windows内核情景分析---内核对象

    写过Windows应用程序的朋友都常常听说"内核对象"."句柄"等术语却无从得知他们的内核实现到底是怎样的, 本篇文章就揭开这些技术的神秘面纱. 常见的内核对象 ...

  7. [11]Windows内核情景分析---设备驱动

    设备驱动 设备栈:从上层到下层的顺序依次是:过滤设备.类设备.过滤设备.小端口设备[过.类.过滤.小端口] 驱动栈:因设备堆栈原因而建立起来的一种堆栈 老式驱动:指不提供AddDevice的驱动,又叫 ...

  8. windows内核情景分析之中断处理(毛德操)[转]

    中断处理 每个cpu有一张中断表,简称IDT. IDT的整体布局:[异常->空白->5系->硬](推荐采用7字口诀的方式重点记忆) 异常:前20个表项存放着各个异常的描述符(IDT表 ...

  9. [13]Windows 内核情景分析 --- 网络通信

    典型的基于tcpip协议套接字方式的网络通信模块层次: 应用程序 socket api WS2_32.dll socket irp Afd.sys tdi irp Tcpip.sys 回调函数接口 各 ...

随机推荐

  1. influxdb服务器 relay

    https://www.influxdata.com/time-series-platform/influxdb/ https://www.xusheng.org/blog/2016/07/30/pe ...

  2. day0321 生成器

    一.生成器 1.迭代器: 1.1.调用方法直接返回 1.2.可迭代对象通过执行iter方法得到 迭代器的优势:节省内存. 2.生成器:有些情况我们也需要也需要节省空间,只能是自己写来实现迭代器的功能就 ...

  3. xcode工程编译错误:error: Couldn’t materialize

    错误信息: error: Couldn't materialize: couldn't get the value of variable amount: variable not available ...

  4. js中的事件轮询(event loop)机制

    异步任务指的是,不进入主线程.而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行. ...

  5. html 标签笔记

    <一.HTML 基础结构> <html>________________________________<head><title>无标题文档</t ...

  6. word2vec参数理解

    之前写了对word2vec的一些简单理解,实践过程中需要对其参数有较深的了解: class gensim.models.word2vec.Word2Vec(sentences=None,size=10 ...

  7. ERROR 1153 (08S01): Got a packet bigger than 'max_allowed_packet' bytes怎么处理

    今天ytkah进行了应急数据库恢复,用Navicat for Mysql导入sql文件出现ERROR 1153 (08S01): Got a packet bigger than 'max_allow ...

  8. ITouch在xcode下提示‘No such file or directory, at ‘/SourceCache/DVTi...'

    版权声明:本文为博主原创文章,转载或又一次发表请先与我联系. https://blog.csdn.net/jonahzheng/article/details/37692733       用一个台老 ...

  9. oracle-安装-init.sh

    !#/bin/bashgroupadd -g 1001 oinstallgroupadd -g 1002 dbagroupadd -g 1003 opergroupadd -g 1004 asmadm ...

  10. 永久有效的 webstorm license server 20180808

    下载地址  https://download.jetbrains.com/webstorm/WebStorm-2018.3.2.exe 2018年10月26日,最近老是过期,搞了一个1年有效的代码,是 ...