IRQ:Interrupt ReQuest,中断请求,当中断发生后,发生中断的设备通过它使用的中断请求信号线象中断控制器报告中断。CPU可以通过IRQ号来识别中断。 
         IRQL:Interrupt ReQuest Level,中断请求优先级,一个由windows虚拟出来的概念,划分在windows下中断的优先级,这里中断包括了硬中断和软中断,硬中断是由硬件产生,而软中断则是完全虚拟出来的。
假中断:Spurious Interrupt,当中断发生时中断控制器相关在服务位并未置位。windows也把IRQL低于当前IRQL的中断当作假中断来处理。

但是接触过硬件的人都知道硬件只有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
ddoffset FLAT:_KiUnexpectedInterrupt; irql 0
ddoffset FLAT:_HalpApcInterrupt ; irql 1
ddoffset FLAT:_HalpDispatchInterrupt2 ; irql 2
ddoffset FLAT:_KiUnexpectedInterrupt; irql 3
ddoffset FLAT:HalpHardwareInterrupt00 ; 8259 irq#0
ddoffset FLAT:HalpHardwareInterrupt01 ; 8259 irq#1
ddoffset FLAT:HalpHardwareInterrupt02 ; 8259 irq#2
ddoffset FLAT:HalpHardwareInterrupt03 ; 8259 irq#3
ddoffset FLAT:HalpHardwareInterrupt04 ; 8259 irq#4
ddoffset FLAT:HalpHardwareInterrupt05 ; 8259 irq#5
ddoffset FLAT:HalpHardwareInterrupt06 ; 8259 irq#6
ddoffset FLAT:HalpHardwareInterrupt07 ; 8259 irq#7
ddoffset FLAT:HalpHardwareInterrupt08 ; 8259 irq#8
ddoffset FLAT:HalpHardwareInterrupt09 ; 8259 irq#9
ddoffset FLAT:HalpHardwareInterrupt10 ; 8259 irq#10
ddoffset FLAT:HalpHardwareInterrupt11 ; 8259 irq#11
ddoffset FLAT:HalpHardwareInterrupt12 ; 8259 irq#12
ddoffset FLAT:HalpHardwareInterrupt13 ; 8259 irq#13
ddoffset FLAT:HalpHardwareInterrupt14 ; 8259 irq#14
ddoffset 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等函数也完全不一样。具体怎么不一样有时间我再分析分析。

http://blog.163.com/sun201201@126/blog/static/11864580200773111332622/

IRQL深入解析(3)--与IRQ比较相关推荐

  1. IRQL深入解析(1)--IRQL级别

    IRQL = Interrupt Request Level.即中断执行的优先级.一个由windows虚拟出来的概念,划分在windows下中断的优先级,这里中断包括了硬中断和软中断,硬中断是由硬件产 ...

  2. Linux设备驱动编程第三版-笔记

    第1章 设备驱动简介 1.1 驱动程序的角色 机制:提供什么能力. 策略:如何使用这些能力. 1.2. 划分内核 内核的角色可以划分:     一:进程管理 二:内存管理 三:文件系统 四:设备控制 ...

  3. Window Internal 读书笔记

    Chapter 1 Virtual Memory.  The size of the virtual address space varies for each hardware platform . ...

  4. linux和windows的异同

    网址:http://www.2cto.com/os/201203/121539.html 关于LINUX和WINDOWS的口水站已经很多了.本文企图从技术角度来比较下2个主流操作系统的异同.偏重于内核 ...

  5. (转载)从IRQ到IRQL(APIC版)

    从IRQ到IRQL(APIC版) 发布日期:2005-01-24 文摘内容: 文摘出处:https://www.xfocus.net/bbs/index.php?act=ST&f=2& ...

  6. 【原创】浅说windows下的中断请求级IRQL

    一 中断分类 根据中断源不同,可以将中断分为 硬件中断:硬件上产生的中断,可以来自处理器的内部和外部.处理器的外部中断可以来自各种PIN信号接口和Local APIC的LINT0和LINT1引脚,以及 ...

  7. linux kernel的中断子系统之(三):IRQ number和中断描述符【转】

    转自:http://www.wowotech.net/linux_kenrel/interrupt_descriptor.html 一.前言 本文主要围绕IRQ number和中断描述符(interr ...

  8. android启动---lk入口文件crt0.s解析

    android启动---lk入口文件crt0.s解析 // .section 伪操作, 用户可以通过.section 伪操作来自定义一个段,每一个段以段名为开始, //以下一个段名或者文件结尾为结束, ...

  9. Nordic系列芯片讲解九 (BLE事件回调机制解析)

    BLE事件回调机制解析 nRF5 SDK从版本14开始,对事件回调机制做了更新,引入了观察者模式,以解耦不同BLE Layer对BLE事件的回调函数. 实现这套机制用到了Flash的段(Section ...

最新文章

  1. 【深度学习】深入浅出transformer解决并行计算问题
  2. 《Xcode实战开发》——1.2节参与计划
  3. QT的QIcon类的使用
  4. 线段树求区间最大值RMQ(单点更新)
  5. 退出MFC应用程序的方法集
  6. [SHOI2014] 概率充电器
  7. redis-day1
  8. 浙江义乌计算机中专学校,浙江义乌有没有中专学校?
  9. 给 c# 程序员的十个重要提示
  10. Android 换行符号(\n)放到Android当中的TextView显示双斜杠(\\n)
  11. OkHttpClient源码分析(四)—— CacheInterceptor
  12. 回调函数—Java实现
  13. 压铸件孔隙率的检测与等级测定
  14. esxi6.5虚拟机迁移
  15. ppt如何将表格转化为饼图?
  16. 两行代码实现精简的网站访问量统计(不蒜子)
  17. 迷途emlog模板全站好看的变色模板源码
  18. IOS App的简单开发实例
  19. 国产办公计算机,国内第一台纯国产计算机在重庆下线,芯片、系统全是纯国产...
  20. 携程到底有没有大数据杀熟?!

热门文章

  1. 难上加难?女性在技术领域可以这样做……
  2. Go/Goland 开发笔记
  3. java写pdf中文不显示_java – iText pdf在使用NOTO字体或Source Hans时不显示中文字符...
  4. Python数学问题17:鸡兔同笼问题
  5. VisionPro基础篇(一): VisionPro界面介绍
  6. 鸿蒙OS原子化服务卡片原理和架构分析
  7. 中国100句绝美爱情诗
  8. Linux查看文件指令cat、more、less、head、tail用法
  9. item_search_img - 按图搜索淘宝商品(拍立淘)
  10. 少女长期与宠物睡觉 遭“宠物虫”噬骨导致瘫痪