bmyyyud
驱动老牛
驱动老牛
  • 注册日期2002-02-22
  • 最后登录2010-01-21
  • 粉丝0
  • 关注0
  • 积分1000分
  • 威望130点
  • 贡献值0点
  • 好评度106点
  • 原创分0分
  • 专家分0分
阅读:2057回复:5

[bmyyyud转载]IRQ to IRQL最近大家再讨论IRQ与IRQL关系

楼主#
更多 发布于:2005-05-23 11:22
我看到安焦的文章相当不错,推荐给大家
发帖者:SoBeIt 讨论区:技术研究推荐区
标 题:从IRQ到IRQL(PIC版)
发信站:安全焦点(2004年12月18日18时45分17秒)

从IRQ到IRQL(PIC版)              
                                                                                     SoBeIt

    这个题目让我想起了小时候学的课文《从百草园到三味书屋》,然后就想起了以前无忧无虑的快乐时光,这是上了大学以后所不再有的,有时常常叹息过去的美好日子不会再有了。sigh~扯远了。

    本文所有的东西都不涉及APIC。先来介绍一下名词,免得有些哥们看晕了:)

PIC:Programmed Interrupt Controller,可编程中断控制器,是一块芯片,里面包含了中断请求寄存器、中断在服务寄存器、中断屏蔽寄存器等很多寄存器,用来控制中断。一般我们的电脑里都是用的8259A中断控制器芯片,共有两块,一主一从,每块负责8个中断请求信号线,主的负责IRQ0-IRQ7,从的负责IRQ8- IRQ15。
APIC:Advance Programmed Interrupt Controller,高级可编程中断控制器,用与多处理器,因为它支持100多个以上的中断向量,所以不是用固定映射的方法,而是通过一定算法映射。
IRQ:Interrupt ReQuest,中断请求,当中断发生后,发生中断的设备通过它使用的中断请求信号线象中断控制器报告中断。CPU可以通过IRQ号来识别中断。
IRQL:Interrupt ReQuest Level,中断请求优先级,一个由windows虚拟出来的概念,划分在windows下中断的优先级,这里中断包括了硬中断和软中断,硬中断是由硬件产生,而软中断则是完全虚拟出来的。
假中断:Spurious Interrupt,当中断发生时中断控制器相关在服务位并未置位。windows也把IRQL低于当前IRQL的中断当作假中断来处理。

    写驱动的人一开始就会接触到IRQL这个概念,它实现了WINDOWS里的中断优先级制度,高优先级的中断总是可以优先被处理,而低优先级的中断则不得不等待高优先级中断被处理完后才得到处理。这就象一个特权社会的不同特权阶层,社会底层的人被迫服从于社会高层的人的安排。就象当IRQL= 0X15时,所有IRQL低于0x15的中断发生时都不得不等待知道这个中断被处理完,IRQL降下来,然后下一个被处理的是IRQL低于0x15而高于其它所有等待的中断IRQL的中断。这种安排使中断处理有序化,重要的中断先于次重要的中断被处理。一个常规的IRQL如下:
31:高
30:掉点
29:处理器间中断
28:时钟
27:配置文件
26



3:设备中断(其实只用了16个)
2:DPC/调度
1:APC
0:无源

    但是接触过硬件的人都知道硬件只有IRQ这个概念,而完全没有IRQL这个东东,但我们写驱动时可以不必去理会IRQ,取而代之的是与IRQL打交道。那么IRQ这个东西哪去了呢?我们知道发生中断时,CPU会用中断向量做索引在IDT(中断描述符表)中找到对应的中断服务例程。那么中断向量又从哪来呢?实际上,它保存在8259A中断控制器里的中断向量寄存器中,每个系统有两个8259A中断控制器,关系是一主一从,主中断控制器掌管IRQ0- IRQ7的中断,对应0x20、0x21端口;从中断控制器掌管IRQ8-IRQ15的中断,对应0xa0、0xa1端口。主中断控制器的中断向量寄存器保存IRQ0的中断向量,从中断控制器的中断向量寄存器保存IRQ8的中断向量。中断发生时,CPU从中断向量寄存器中取出IRQ0的中断向量与当前 IRQ相加,既可得当前中断向量。中断向量寄存器并不是一开始就是这个值,在实模式下,IRQ分别对应了BIOS中的中断处理程序。但到了保护模式下时原 BIOS中断处理程序的中断号都对应了异常处理程序(0x0-0x11),所以进入保护模式后就得进行中断的重映射,向8259A中断控制器编程,使它按操作系统的意图进行中断映射(正因为如此,PIC才叫“可编程”)。听起来好象很难,其实很简单,分别向0x20和0xa0发送4个ICW(初始化命令字)就可以完成对它的编程。ICW1包括是否工作在级联环境、中断请求的触发模式等;ICW2就是IRQ0的中断向量(向0xa1是发IRQ8的中断向量),要求是8位对齐;ICW3是主、从中断控制器的级联状态,指示由IRQx(一般是IRQ2)作为主、从中断控制器连接的中断,向0x20、0xa0 端口发送的命令是不一样的;ICW4指示是否工作于x86模式下及是否自动清楚EOI等。windows在启动阶段初始化时对中断控制器编程,ICW2对于0x21端口是0x30,对于0xa1是0x38。至此,中断映射完毕,中断发生后可以直接从IDT中索引中断处理程序。

    当中断发生并索引到对应中断处理程序后转入中断处理程序执行,每个中断处理程序开始的代码都是一样的,是一段预处理代码,它是怎么产生的呢?当IoConnectInterrupt注册中断处理程序时,会产生一个KINTERRUPT结构,结构如下:

typedef struct _KINTERRUPT {
    CSHORT Type;
    CSHORT Size;
    LIST_ENTRY InterruptListEntry;
    PKSERVICE_ROUTINE ServiceRoutine;
    PVOID ServiceContext;
    KSPIN_LOCK SpinLock;
    ULONG TickCount;
    PKSPIN_LOCK ActualLock;
    PVOID DispatchAddress;
    ULONG Vector;
    KIRQL Irql;
    KIRQL SynchronizeIrql;
    BOOLEAN FloatingSave;
    BOOLEAN Connected;
    CHAR Number;
    UCHAR ShareVector;
    KINTERRUPT_MODE Mode;
    ULONG ServiceCount;
    ULONG DispatchCount;
    ULONG DispatchCode[106];
} KINTERRUPT, *PKINTERRUPT;

中断描述符表中保存的中断服务例程的入口地址就是这个KINTERRUPT结构的DispatchCode的地址。这段代码的功能很明白,就是调用 HalBeginSystemInterrupt完成从IRQ到IRQL的映射(同样负责映射的函数还有KfRaiseIrql、 KfLowerIrql、HalEndSystemInterrupt等函数)。只有完成了映射后才会转到实际的中断处理程序,也就是用户注册的中断处理程序的执行。

    IRQL是一个完全虚拟出来的概念,M$为了实现这一个虚拟的机制,完全虚拟了一个中断控制器,它在KPCR中:

+024 byte Irql            //IRQL
+028 uint32 IRR            //虚拟中断请求寄存器
+02c uint32 IrrActive        //虚拟中断在服务寄存器
+030 uint32 IDR            //虚拟中断屏蔽寄存器

和一个实际中断控制器几乎一模一样,除去少部分实现IRQL机制的代码外,整个系统其实都是在和这个虚拟出来的东西交流,而上层系统对此是一无所知,对着一个假的东西整天RaiseIrql来LowerIrql去的还玩得不亦乐乎^^。其实IRQL可以理解为是windows硬件抽象层模拟了实际的IRQ 的实现方式,使上层和硬件抽象层打交道就象以前直接和硬件打交道一样,并将IRQ的16个中断扩展了为了32个,除去映射了IRQ的16个,剩下的全归系统实现各种功能使用。它的初始化是在前面提到的向保护模式过渡时编程PIC后,会向实际两个8259A中断控制器发中断屏蔽码,屏蔽掉所有中断,这也就是为什么启动时你按什么键系统都不会有反应的原因,全都给屏蔽了。然后第一次调用KfRaiseIrql,提升的IRQL是当前IRQL(KPCR刚刚初始化完,当前IRQL当然是0)。IRQL从0到32,对应32个优先级,相应的寄存器当然必须是32位的,所以IRR、IDR等都是一个DWORD,每个位对应了一个优先级。

    扯了那么多,还没扯到关键的IRQ是怎么映射为IRQL上来和IRQL是怎么实现的:)IRQL和IRQ有个很简单的线性关系,就是IRQL= 0x27-IRQ。前面提到了每个中断在处理前都会调用HalBeginSystemInterrupt,因为整个系统是由中断驱动的,所以 HalBeginSystemInterrupt才是整个IRQL映射机制的心脏,它会在系统第一次被中断时启动这个机制并在系统每一次中断时维持这个机制,而其它象HalEndSystemInterrupt、KfLowerIrql等都是在这个机制被启动后完善这个机制的组件。

BOOLEAN
HalBeginSystemInterrupt(
    IN KIRQL Irql
    IN CCHAR Vector,
    OUT PKIRQL OldIrql)

它的输入参数Irql和Vector从哪来?当然是从前面注册的KINTERRUPT结构中取出了。这个函数首先通过把Vector-0x30获取当前中断的IRQ,然后跳转到一个指针数组,里面包含了对应IRQ的中断的一个简单处理例程,除了少部分IRQ7(并口1中断)、IRQ13(协处理器中断)、 IRQ15不一样外,其它的都是指向同一个函数(其实前面那几个不一样的也只是做点小处理,主要是判断是否是假中断,若不是则也跳到那个函数)。真正的工作在这个函数里开始了,从虚拟中断控制器中(KPCR+0x24)取出当前IRQL,并与当前中断的IRQL判断,若当前IRQL小于当前中断IRQL,则修改虚拟中断控制器的IRQL为当前中断IRQL,然后向中断控制器发对应中断EOI表示中断已处理完,可以响应下一个来自这个IRQ的中断(如果中断是IRQ8-IRQ15,属于从中断控制器管理,则还要向主中断控制发一个对应IRQ2的EOI)。若当前中断IRQL小于当前IRQL,则说明有个更高优先级的中断在被处理,则设置虚拟中断控制器中的中断请求寄存器IRR中的相关位,表示该IRQL发生请求,但未被处理。同时从 KiI8259MaskTable中取出当前IRQL的掩码(这个掩码是32位,每个IRQL对应一个掩码,一般都是掩码对应IRQL以上的位为1,以下的位为0,表示只接受大于当前IRQL的请求,如11111111111111111111110000000000B是IRQL17的掩码)与当前虚拟中断控制器中的中断屏蔽寄存器IDR相或之后设置虚拟的IDR,表示拒绝来自这些IRQL的请求。并把该掩码发实际的中断控制器,设置中断屏蔽寄存器,防止该未被处理的中断再发生一次。注意,系统并没有向中断控制器发出该未被处理的中断的EOI,表示该中断并没有处理完。最后 HalBeginSystemInterrupt返回FALSE(注意,是返回,前面只是跳转到那个函数里,返回地址并没有变),表示这是一个假中断,系统象什么事也没有一样继续干该干的事。

    调用完HalBeginSystemInterrupt后开始调用实际由用户注册的中断处理程序,处理完后会调用 HalEndSystemInterrupt,调用这个函数时必须是关中断的。这个函数和所有HalEndSoftwareInterrupt、 KfLowerIrql、KfLowerIrqlToXXX函数功能差不多,就是降低当前IRQL,从另一个掩码表FindHigherIrqlMask 中取出要降低到的IRQL的掩码放到edx中(要说这个表和刚才那个表有啥不同,就是这个表差不多是对上一个每个掩码取反,注意,是差不多,不是完全),与上虚拟中断请求寄存器来判断是否有更高级的IRQL的请求在等待,当然,并没有改变虚拟中断请求寄存器。同时把虚拟中断控制器的IRQL设置为要降低到的IRQL。若没有更高级的IRQL请求在等待,则HalEndSystemInterrupt返回,否则要处理等待的IRQL请求,此时会判断虚拟在服务寄存器是否为空,不为空则表示还有中断在处理,直接返回,这种情况是某些延时了的硬件中断处理。为空的话可以处理等待的中断了,从edx中(edx里是什么内容,往上找吧)找出左边第一个不是0的位,也就是在等待的中断中IRQL最高的一个中断。(当然,这里也会比较一下该中断对应的IRQL是不是已经小于DISPATCH_LEVEL,小于的话已经是软中断了,就会跳到其它地方处理)。然后用虚拟中断屏蔽寄存的值设置实际的两个中断控制器里的屏蔽寄存器的值,接着如果虚拟中断在服务寄存器IrrActive对应要处理中断IRQL的位没有置位的话则置位,表示当前处于在服务状态,并清除原先设置的虚拟中断请求寄存器IRR中相关位。现在到了关键的一步,以当前IRQL为索引,跳转到一个函数指针表中索引对应的函数。这个表叫做 SWInterruptHandlerTable,其作用就象实模式下那个中断向量表一样,索引对应的中断处理程序。我们来看看表里有啥内容:

SWInterruptHandlerTable label dword
        dd      offset FLAT:_KiUnexpectedInterrupt      ; irql 0
        dd      offset FLAT:_HalpApcInterrupt           ; irql 1
        dd      offset FLAT:_HalpDispatchInterrupt2     ; irql 2
        dd      offset FLAT:_KiUnexpectedInterrupt      ; irql 3
        dd      offset FLAT:HalpHardwareInterrupt00     ; 8259 irq#0
        dd      offset FLAT:HalpHardwareInterrupt01     ; 8259 irq#1
        dd      offset FLAT:HalpHardwareInterrupt02     ; 8259 irq#2
        dd      offset FLAT:HalpHardwareInterrupt03     ; 8259 irq#3
        dd      offset FLAT:HalpHardwareInterrupt04     ; 8259 irq#4
        dd      offset FLAT:HalpHardwareInterrupt05     ; 8259 irq#5
        dd      offset FLAT:HalpHardwareInterrupt06     ; 8259 irq#6
        dd      offset FLAT:HalpHardwareInterrupt07     ; 8259 irq#7
        dd      offset FLAT:HalpHardwareInterrupt08     ; 8259 irq#8
        dd      offset FLAT:HalpHardwareInterrupt09     ; 8259 irq#9
        dd      offset FLAT:HalpHardwareInterrupt10     ; 8259 irq#10
        dd      offset FLAT:HalpHardwareInterrupt11     ; 8259 irq#11
        dd      offset FLAT:HalpHardwareInterrupt12     ; 8259 irq#12
        dd      offset FLAT:HalpHardwareInterrupt13     ; 8259 irq#13
        dd      offset FLAT:HalpHardwareInterrupt14     ; 8259 irq#14
        dd      offset FLAT:HalpHardwareInterrupt15     ; 8259 irq#15

可以看到IRQL2是处理APC的例程,IRQL3的例程会处理DPC和环境切换(我在《SYMANTEC防火墙内核堆栈溢出漏洞利用方法总结》一文中提到过),那么这些HalpHardwareInterruptXX之类的是什么?很简单,就是int xx,然后返回。因为中断被延迟错过了由系统机制索引IDT表然后处理的机会,操作系统只好自己模仿一个中断来索引IDT表找到中断处理程序。前面提到的如果是一个软中断在等待,则会略过前面那些对硬件的操作直接索引IDT表找处理程序,不是IRQL2的就是IRQL3的,所以我前面说过软中断其实所谓 “中断”都是虚拟出来的,连int指令都没执行过。处理完并返回到HalEndSystemInterrput后的处理就简单了,将虚拟中断在服务寄存器中相关位清0,并再判断是否还有高于当前IRQL的中断在等待,有,则继续刚才的处理过程;没有,工作完成,可以返回了。 HalEndSystemInterrupt返回后,中断处理程序就执行完毕,iret返回被中断的地方。

    其它的象KfLowerIrql、HalEndSoftwareInterrput和HalEndSystemInterrupt基本原理是一样的。至于KfRaiseIrql,不要以为它有多复杂,它仅仅是修改了虚拟中断控制器的IRQL而已。

    现在回头再来看这套机制,它并不是为了提高效率,如果单为提高效率完全可以通过开/关中断来完成,而它处理每个中断都白白多了那么多代码,还虚拟了一堆东西出来,反而拖了系统速度。这个机制这么实现除了实现系统的一些功能外,几乎可以说是为了移植性,想想那时M$正在编写windows时因为 David Culter的事不得不和Digital公司签订的必须支持Alpha处理器的“不平等”条约,使得windows必须把可移植性放在首位。就如前所说,整个系统大部分只需要和一个虚拟出来的中断控制器打交道就行,而不必管实际的中断处理器怎样,在驱动看来,它还是在和硬件打交道。这也就是硬件抽象的含义,把一个具体的东西抽象成一个虚拟的东西。至于用这套机制实现的一些系统功能确实有一定的优越之处,象linux从2.4内核起也实现了softirq 这种类似于windows下软中断的概念,而用tasklet这种softirq来处理硬件中断的下半部分(Bottom half),则类似于windows里DPC的作用。

    有些朋友可能会说,按这么来说键盘的IRQ是1,中断向量就是0x31,那么也应该处于IDT表里的0x31处,为什么在虚拟机里看怎么不是这个位置?这个问题也困扰了我很久,直到我不久前才明白,就是虚拟机默认使用了APIC,不再是那么简单的固定映射。包括 HalBeginSystemInterrupt等函数也完全不一样。具体怎么不一样有时间我再分析分析。

    写了这么多也不知道说明白了没有。上了大学以后就再没写过作文,语言表达能力明显下降了,明明知道是这么一回事,可是写出来就不一样了。今天考混凝土又被郁闷了,于是一口气完成了这篇文章。难免会有错漏,欢迎与我探讨:)

最新喜欢:

threebagsthreeb... ShentuShentu
滚滚长江东逝水 浪花淘尽英雄 是非成败转头空 青山依旧在 几度夕阳红 白发渔樵江渚上 惯看秋月春风 一壶浊酒喜相逢 古今多少事 尽付笑谈中
sharpor
驱动小牛
驱动小牛
  • 注册日期2005-04-04
  • 最后登录2007-05-10
  • 粉丝0
  • 关注0
  • 积分127分
  • 威望17点
  • 贡献值0点
  • 好评度16点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2005-05-23 15:22
感觉讲得有点老了,不过还是让我知道很多以前8259是怎么工作的。。。thanks
Shentu
驱动小牛
驱动小牛
  • 注册日期2004-04-05
  • 最后登录2011-01-24
  • 粉丝0
  • 关注0
  • 积分234分
  • 威望24点
  • 贡献值0点
  • 好评度20点
  • 原创分0分
  • 专家分0分
板凳#
发布于:2005-05-24 10:08
值得收藏的好文!
baiyuanfan
驱动小牛
驱动小牛
  • 注册日期2004-11-23
  • 最后登录2007-06-21
  • 粉丝0
  • 关注0
  • 积分320分
  • 威望38点
  • 贡献值0点
  • 好评度20点
  • 原创分0分
  • 专家分0分
地板#
发布于:2005-05-25 11:15
这个是APIC的:

从IRQ到IRQL(APIC版)

                                                    SoBeIt
    事实上,老久的PIC在很早以前就被淘汰了,取而代之的是APIC。由于APIC可以兼容PIC,所以在很多单处理器系统上我们看到的PIC实际是APIC的兼容PIC模式。APIC主要应用于多处理器操作系统,是为了解决IRQ太少和处理器间中断而产生的,当然,单处理器操作系统也可以使用APIC(不是模拟PIC)。APIC的HAL和PIC的HAL有很大的不同,很突出的一个特点就是APIC的HAL不用再象PIC的HAL那样虚拟一个中断控制器,IRQL的概念已经可以通过中断向量的形式被APIC支持。事实上,因为被APIC所支持,所以在APIC HAL里IRQL的实现比PIC HAL那样虚拟一个中断控制器要简单得多了。

    现在来简单介绍一下APIC的结构(关于APIC详细的描述请参考《IA-32 Inel Architecture Software Developer\'s Manual Volume 3 Chapter 8》)。整个APIC系统由本地APIC、IO APIC和APIC串行总线组成(在Pentium 4和Xeon以后,APIC总线放到了系统总线中)组成。每个处理器中集成了一个本地APIC,而IO APIC是系统芯片组中一部分,APIC总线负责连接IO APIC和各个本地APIC。本地APIC接收该处理器产生的本地中断比如时钟中断,以及由该处理器产生的处理器间中断,并从APIC串行总线接收来自IO APIC的消息;IO APIC负责接收所有外部的硬件中断,并翻译成消息选择发给接收中断的处理器,以及从本地APIC接收处理器间中断消息。

    和PIC一样,控制本地APIC和IO APIC的方法是通过读写该单元中的相关寄存器。不过和PIC不一样的是,Intel把本地APIC和IO APIC的寄存器都映射到了物理地址空间,本地APIC默认映射到物理地址0xffe00000,IO APIC默认映射到物理地址0xfec00000。windows HAL再进一步把本地APIC映射到虚拟地址0xfffe0000,把IO APIC映射到虚拟地址0xffd06000,也就是说对该地址的读写实际就是对寄存器的读写,本地APIC里几个重要的寄存有EOI寄存器,任务优先级寄存器(TPR),处理器优先级寄存器(PPR),中断命令寄存器(ICR,64位),中断请求寄存器(IRR,256位,对应每个向量一位),中断在服务寄存器(ISR,256位)等。IO APIC里几个重要的寄存器有版本寄存器,I/O寄存器选择寄存器、I/O窗口寄存器(用要访问的I/O APIC寄存器的索引设置地址I/O寄存器选择寄存器,此时访问I/O窗口寄存器就是访问被选定的寄存器)还有很重要的是一个IO重定向表,每一个表项是一个64位寄存器,包括向量和目标模式、传输模式等相关位,每一个表项连接一条IRQ线,表项的数目随处理器的版本而不一样,在Pentium 4上为24个表项。表项的数目保存在IO APIC版本寄存器的[16:23]位。APIC系统支持255个中断向量,但Intel保留了0-15向量,可用的向量是16-255。并引进一个概念叫做任务优先级=中断向量/16,因为保留了16个向量,所以可用的优先级是2-15。当用一个指定的优先级设置本地APIC中的任务优先级寄存器TPR后,所有优先级低于TPR中优先级的中断都被屏蔽,是不是很象IRQL的机制?事实上,APIC HAL里的IRQL机制也就是靠着这个任务优先级寄存器得以实现。同一个任务优先级包括了16个中断向量,可以进一步细粒度地区分中断的优先级。

    在HAL里虽然HalBeginSystemInterrupt仍然是IRQL机制的发动引擎,但是因为有APIC的支持,它和其它共同实现IRQL的函数要比PIC HAL里对应的函数功能简单得多。HalBeginSystemInterrupt通过用IRQL做索引在HalpIRQLtoTPR数组中获取该IRQL对应的任务优先级,用该优先级设置任务优先级寄存器TPR,并把TPR中原先的任务优先级/16做为索引在HalpVectorToIRQL数组中获取对应的原先的IRQL然后返回。若IRQL是从低于DISPATCH_LEVEL提升到高于DISPATCH_LEVEL,还需要设置KPCR+0x95(0xffdff095)为DISPATCH_LEVEL(0x2),表示是从DISPATCH_LEVEL以下的级别提升IRQL。HalEndSystemInterrupt向本地APIC的EOI寄存发送0,表示中断结束,可以接收新中断。并还要判断要降到的IRQL是否小于DISPATCH_LEVEL,若小于则进一步判断KPCR+0x96(0xffdff096)是否置位,若置位则表示有DPC中断在等待(在IRQL高于DISPATCH_LEVEL被引发,然后等待直到IRQL降到低于DISPATCH_LEVEL),则将KPCR+0x95和KPCR+0x96清0后调用KiDispatchInterrupt响应DPC软中断。否则做的工作就是和HalBeginSystemInterrupt一样的过程:把要降到的IRQL转换成任务优先级设置TRP,并把久的任务优先级转成IRQL返回。KfRaiseIrql、KfLowerIrql之类的函数也是这么一回事,把当前IRQL转成任务优先级修改TPR,并把原先TPR的值转成原先的IRQL并返回。而现在软中断的产生也有了APIC支持,APIC通过产生一个发向自己的处理器间中断,就可以产生一个软中断,因为可以指定该中断的向量,所以软中断就可以区分优先级别,如APC_LEVEL、DISPATCH_LEVEL。产生软中断的函数一样还是HalRequestSoftwareInterrupt,该函数会先判断KPCR+0x95是否和要产生的软中断IRQL一样,若是的话则置位KPCR+0x96并返回,表示现在IRQL大于DISPATCH_LEVEL所以不处理DPC中断。否则以要产生的软中断的IRQL为索引从HalpIRQLtoTPRHAL取出对应任务优先级,并或上0x4000,表示是发向自身的固定处理间中断,并用该值设置中断命令寄存器ICW的低32位,然后读取中断命令寄存器ICW的低32位是否为0x1000,确定中断消息已经发送后就返回,这时候软中断已经产生。值得注意的是APIC HAL里没有HalEndSoftwareInterrupt这个函数。HAL为软中断的IRQL提供了一个固定的中断向量:

#define ZERO_VECTOR             0x00    // IRQL 00
#define APC_VECTOR              0x3D    // IRQL 01
#define DPC_VECTOR              0x41    // IRQL 02
#define APIC_GENERIC_VECTOR     0xC1    // IRQL 27
#define APIC_CLOCK_VECTOR       0xD1    // IRQL 28
#define APIC_SYNCH_VECTOR       0xD1    // IRQL 28
#define APIC_IPI_VECTOR         0xE1    // IRQL 29
#define POWERFAIL_VECTOR        0xEF    // IRQL 30
#define APIC_PROFILE_VECTOR     0xFD    // IRQL 31


现在看一下一些重要的数据:

这是我写的代码输出的IO APIC重定向表内容:

Redirect Table Index:    0x17
Redirect Table[ 0]:      ff
Redirect Table[ 1]:      b3
Redirect Table[ 2]:      ff
Redirect Table[ 3]:      51
Redirect Table[ 4]:      ff
Redirect Table[ 5]:      ff
Redirect Table[ 6]:      62
Redirect Table[ 7]:      ff
Redirect Table[ 8]:      d1
Redirect Table[ 9]:      b1
Redirect Table[ a]:      ff
Redirect Table[ b]:      ff
Redirect Table[ c]:      52
Redirect Table[ d]:      ff
Redirect Table[ e]:      ff
Redirect Table[ f]:      92
Redirect Table[10]:      ff
Redirect Table[11]:      a3
Redirect Table[12]:      83
Redirect Table[13]:      93
Redirect Table[14]:      ff
Redirect Table[15]:      ff
Redirect Table[16]:      ff
Redirect Table[17]:      ff

这是IDT表中被注册的向量:

1f: 80064908 (hal!HalpApicSpuriousService)
37: 800640b8 (hal!PicSpuriousService37)
3d: 80065254 (hal!HalpApcInterrupt)
41: 800650c8 (hal!HalpDispatchInterrupt)
50: 80064190 (hal!HalpApicRebootService)
51: 817f59e4
(Vector:51,Irql:4,SyncIrql:4,Connected:TRUE,No:0,ShareVector:FALSE,Mode:Latched,ISR:serial!SerialCIsrSw(f3c607c7))
52: 817f5044
(Vector:52,Irql:4,SyncIrql:a,Connected:TRUE,No:0,ShareVector:FALSE,Mode:Latched,ISR:i8042prt!I8042MouseInterruptService(f3c57a2c))
83: 817d2d44
(Vector:83,Irql:7,SyncIrql:7,Connected:TRUE,No:0,ShareVector:TRUE,Mode:LevelSensitive,ISR:NDIS!ndisMIsr(bff1b794))
92: 81821384
(Vector:92,Irql:8,SyncIrql:8,Connected:TRUE,No:0,ShareVector:FALSE,Mode:Latched,ISR:atapi!ScsiPortInterrupt(bff892be))
93: 8185ed64
(Vector:93,Irql:8,SyncIrql:8,Connected:TRUE,No:0,ShareVector:TRUE,Mode:LevelSensitive,ISR:uhcd!UHCD_InterruptService(f3f0253e))
a3: 8186cdc4
(Vector:a3,Irql:9,SyncIrql:9,Connected:TRUE,No:0,ShareVector:TRUE,Mode:LevelSensitive,ISR:SCSIPORT!ScsiPortInterrupt(bff719f0))
b1: 818902e4
(Vector:b1,Irql:a,SyncIrql:a,Connected:TRUE,No:0,ShareVector:TRUE,Mode:LevelSensitive,ISR:ACPI!ACPIInterruptServiceRoutine(bffe14b4))
b3: 81881664
(Vector:b3,Irql:a,SyncIrql:a,Connected:TRUE,No:0,ShareVector:FALSE,Mode:Latched,ISR:i8042prt!I8042KeyboardInterruptService(f3c51918))
c1: 800642fc (hal!HalpBroadcastCallService)
d1: 80063964 (hal!HalpClockInterrupt)
e1: 80064858 (hal!HalpIpiHandler)
e3: 800645d4 (hal!HalpLocalApicErrorService)
fd: 80064d64 (hal!HalpProfileInterrupt)
fe: 80064eec (hal!HalpPerfInterrupt)

象a3、b1这类输出内容很多的是被硬件注册的中断向量,而象d1、e3这种输出内容少的是注册为了的HAL内部使用的中断向量和本地APIC中断向量

这是几个重要的数组:

HalVectorToIrql(这个数组是以向量除于16做索引):
8006a304  00 ff ff 01 02 04 05 06-07 08 09 0a 1b 1c 1d 1e

HalpIRQLtoTPR:
8006a1e4  00 3d 41 41 51 61 71 81-91 a1 b1 b1 b1 b1 b1 b1
8006a1f4  b1 b1 b1 b1 b1 b1 b1 b1-b1 b1 b1 c1 d1 e1 ef ff

HalpINTItoVector:
8006ada0  00 b3 61 51 a2 b2 62 91-a1 b1 71 81 52 82 72 92
8006adb0  00 a3 83 93 00 00 00 00-00 00 00 00 00 00 00 00

HalVectorToINTI:
8006a204  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a214  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a224  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a234  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a244  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a254  ff 03 0c ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a264  ff 02 06 ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a274  ff 0a 0e ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a284  ff 0b 0d 12 ff ff ff ff-ff ff ff ff ff ff ff ff
8006a294  ff 07 0f 13 ff ff ff ff-ff ff ff ff ff ff ff ff
8006a2a4  ff 08 04 11 ff ff ff ff-ff ff ff ff ff ff ff ff
8006a2b4  ff 09 05 01 ff ff ff ff-ff ff ff ff ff ff ff ff
8006a2c4  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a2d4  ff 08 ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a2e4  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a2f4  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff  


vBucket:
8006ae30  02 02 02 03 03 03 03

    举个例子来说明一下,在我虚拟机里SCSI Controller的IRQ是17(注意,已经大于16了),到重定向表中查找第17项,得到中断向量为0xa3,再看IDT,0xa3对应处理例程是SCSIPORT!ScsiPortInterrupt。

    vBucket数组干啥用的?它就是用来分配新的向量。分配算法很简单,当要分配一个新的向量时,就在vBucket数组从右到左搜索最小的一个数i,该数对应在vBucket中索引为Index,新向量为(0x50+Index*16+i+1),新向量对应的IRQL为(4+i+1),同时会把vBucket中这个i加1,i不等大于16。象给出的这个vBucket,下一次计算时i=2, index=2。不过这些用于硬件的向量在IO系统初始化时调用HalpGetSystemInterruptVector分配好了,然后通过IoConnectInterrupt把IDT中注册的向量位置的例程注册为中断处理程序。这里并不是每个注册的向量都会对应中断处理程序,象上面给出的例子中,0xa1、0xa2、0xb1等向量就没有对应。

    IRQL机制为内核同步提供了很大的便利,既对驱动开发者隐藏了底层中断机制,也方便了驱动开发者的内核同步。LINUX从2.5内核开始引进的软中断和任务队列等机制,很大程度上也来自windows这套机制的借鉴。

    终于考完试,解放了,呵呵。这个东西其实还有很多可写的,只是没空再深入去分析了。在未来的64位系统里,APIC这种基于中断引脚的机制很快也要被SAPIC这种基于消息的更强大的机制所取代。
各类后门,木马,Exp,0day
bmyyyud
驱动老牛
驱动老牛
  • 注册日期2002-02-22
  • 最后登录2010-01-21
  • 粉丝0
  • 关注0
  • 积分1000分
  • 威望130点
  • 贡献值0点
  • 好评度106点
  • 原创分0分
  • 专家分0分
地下室#
发布于:2005-05-25 11:33
嘻嘻,真好,补全了 :D
滚滚长江东逝水 浪花淘尽英雄 是非成败转头空 青山依旧在 几度夕阳红 白发渔樵江渚上 惯看秋月春风 一壶浊酒喜相逢 古今多少事 尽付笑谈中
qfzcx
驱动牛犊
驱动牛犊
  • 注册日期2004-10-13
  • 最后登录2010-01-11
  • 粉丝0
  • 关注0
  • 积分55分
  • 威望87点
  • 贡献值1点
  • 好评度1点
  • 原创分0分
  • 专家分0分
5楼#
发布于:2005-05-26 17:16
不错 透彻
游客

返回顶部