• 当前位置:首页>>编程开发A>>安全防御>>The NT Insider:Stop Interrupting Me -- Of PICs and APICs
  • The NT Insider:Stop Interrupting Me -- Of PICs and APICs
  • 创建时间:2005-03-16
    文章属性:翻译
    文章提交:tombkeeper (t0mbkeeper_at_hotmail.com)

    The NT Insider:Stop Interrupting Me -- Of PICs and APICs

    董岩 译
    greatdong_2001@163.com

    尽管 Windows 的设计是使其能够运行在多种平台上,而实际上我们大多数都用的是32位的 x86 系统。正如我们在最近一期 The NT Insider (“Don't Call Us – Calling Conventions for the x86”, V10N1)所见到的,在尝试分析 crash dump 时,对 x86 构建调用帧的透彻理解所带来的益处是无与伦比的——特别是在没有符号的时候。在编写驱动程序时,程序员就成了操作系统的一部分,深刻理解操作系统所运行的平台有助于开发和调试。

    记住,永远要使用 HAL 来编写平台相关的代码。但这并不意味着就可以对所运行的平台体系一无所知。我们编写了一系列的文章来探索平台的一些内幕,这些内幕都是我们所感兴趣的也是我们认为每一个驱动程序开发人员都应该知道的。下面这篇文章就是这个系列文章中的第一篇。我们假设读者了解设备的基本知识,还知道一些 Windows 驱动程序的东西:中断服务程序、IRQLs 和其它相关的东西。


    The Interrupt

    要是没有输入输出,CPU 可就没什么事好做了(duh! 没有输入输出的计算机也肯定没什么意思)。当设备状态发生变化时,要么是因为它要传送数据,要么是因为一些其它的外部情况需要人注意,设备设置设备相关的寄存器的某个设备相关的位。为了检测到设备状态的变化,驱动程序要反复测试此位。但是这样效率太低了。另外一种解决办法就是当设备状态变化时它可以产生一个中断将变化异步地通知给系统。通过使用中断,在我们不使用设备时可以忽略设备的存在,因此不用浪费时钟周期进行查询并提高了系统整体的性能。

    因为中断是设备和设备驱动开发的不可分割的一部分,所以我们将探索中断的内部运行。同时,我们还希望解释清楚从设备产生中断到设备驱动得到通知这之间的过程。我们的解释是站在驱动开发者的角度的,这也就意味着我们不会无限度地追究硬件的细节。因此,如果读者是搞硬件的,熟悉中断控制器的连接,请不要抱怨我们没有讲到 8259 与 8259A-2 之间的区别、何时及如何对 OCW4 编程或是 CPU 第二次将 INTA 置为有效时作了哪些数据交换。可不是我们不知道哦(当然也不绝对),只是从本文的出发点来看,我们不用管这些东西。


    Interrupt Descriptor Table

    要理解中断,先理解“中断描述符表(IDT)”可是很重要的。简单讲,IDT 就是一个函数指针的数组。每个数组成员中的函数要么指向一个中断处理程序(在驱动开发里叫 Interrupt Service Routine)要么指向一个异常处理程序。这里我们只关心中断,异常就忽略了。IDT 的索引就是“中断向量”,中断向量是一个 UCHAR 值。注意中断和异常处理程序的数目限制在256个。每个 CPU 都有自己的 IDT,可以通过 WinDBG 的 kdex2x86.idt 命令察看。下面的代码节选自在我的测试系统上使用此命令所得到的输出。

    1: kd> !kdex2x86.idt

    IDT for processor #0

    ...

        dd: 80ac2ac2 (nt!_KiUnexpectedInterrupt173)

        de: 80ac2acc (nt!_KiUnexpectedInterrupt174)

        df: 80ac2ad6 (nt!_KiUnexpectedInterrupt175)

        e0: 80ac2ae0 (nt!_KiUnexpectedInterrupt176)

        e1: 804e0084 (HAL!HalpIpiHandler)

        e2: 80ac2af4 (nt!_KiUnexpectedInterrupt178)

        e3: 804dfdd8 (HAL!HalpLocalApicErrorService)

        e4: 80ac2b08 (nt!_KiUnexpectedInterrupt180)

    ...

    当设备产生中断时,中断向量又是如何成为 CPU 的 IDT 的索引,进而调用了相应的终端服务程序的呢?唔,这时就要提到硬件中断控制器了。下面就来看一下两种 Intel “可编程中断控制器”(PIC)的实现,一种是传统的 8259 PIC,另一种则是更高级的“Advanced PIC”(APIC)。我们还要挖掘一下 Windows 的中断处理机制,使得我们可以明白 PIC 和 APIC 是如何在现实世界中使用的。


    The 8259

    起初 IBM PC 使用 Intel 的 8259 PIC。8259 只支持单处理器的系统且只提供标号为 IRQ0-IRQ7 的8条“中断请求线”使设备与中断相关联,因此最多只能处理8个中断。之后,系统中加入了第二个 8259,这个 8259 通过三条级联线(CAS0-CAS2)与主 8259 级联起来。第二个 8259 的 INT 线还要连到主 8259 的 IRQ2 上,这样算上新添加的8个 IRQs 中再减去用于连接的一个 IRQ,就有了总共15个 IRQs。Figure 1 就是简化的示意图。


    Figure 1

    所有现代的主板都要么使用两个物理 8259 芯片,要么使用其它芯片来模拟这两个芯片。好了,历史课到此结束,我们再深入一点儿细节。

    每个需要中断的设备都需要得到一个唯一的 IRQ 然后连接到 8259 上。为了产生一个指定 IRQ 的中断,设备要使总线上相应的 IRQ 线有效。

    8259 为每个 IRQ 都分配了一个优先级,IRQ0 最高,号越大优先级越低。因此 IRQ0 是最高,IRQ15 最低,对吗?哦,不,也不全对。还记着第二个 8259 连到了 IRQ2 上吗?实际的 IRQ 优先级为:


    IRQ0, IRQ1, (now off to the second 8259) IRQ8-IRQ15, (now back to the first 8259) IRQ3-IRQ7

    乍一看这种安排也不是那么显然,但如果歪过脑袋来眯缝着眼看似乎还是合理的。

    每一个 IRQ 都是“可屏蔽的”,意思就是可以通过编程 8259 的“Interrupt Mask Register”(IMR)可以将其禁用。如果 IRQ 被屏蔽掉,则连到这个 IRQ 的设备中断请求就都被忽略了。再有,高优先级的 IRQ 比低优先级的 IRQ 先得到服务,而且高优先级的 IRQ 还可以打断低优先级的 IRQ。因此若服务 IRQ1 时 IRQ0 又来了,则 IRQ1 中断的处理停止,IRQ0 中断被送往 CPU。

    很重要的一点是,尽管存在着硬件优先级,但 Windows 系统并没有实际使用它。Windows 系统通过直接操纵 IMR (见本文 conclustion 处的 sidebar)为 8259 加上了自己的优先级机制。

    现在我们知道了设备时如何连接到 8259 以及 8259 是如何连接到一起的,那 8259 又是如何与 CPU 连接的呢?

    x86 体系的 CPU 有两条中断线,LINT0 和 LINT1。在 8259 的配置中,LINT1 连到了“Non-Maskable Interrupt”(NMI),当检测到严重的、潜在的、不可恢复的错误时就会产生 NMI。之所以叫做“Non-Maskable Interrupt”是因为没有办法阻断它——除了处理器,没有谁能屏蔽它。引起 NMI 的一个典型的例子就是内存校验错。

    LINT0 被用作“Interrupt Input Line”(INTR),它连接在主 8259 的 INT 脚上。8259 通过使 INT 有效来将中断通知给系统。当 CPU 确认后,8259 通过总线向 CPU 发送一个8位的值(之前被 O/S 编程进了 PIC)。这个8位的值就是相应 IRQ 的中断向量。这个中断向量被用作 IDT 的索引来确定中断服务程序(ISR)的地址。然后,CPU 跳转到 ISR,进行服务此中断所需的处理。

    唔,看起来还不算很难。但是,要是需要中断的设备多于15个该怎么办呢?那就要共享中断了,这时链接起来的 ISRs 就粉墨登场了。在这种情形下,OS 调用第一个注册到所给中断向量上的 ISR,将中断通知给它。假设此中断是一个 level-triggered 的中断(比如 PCI 总线上的 line-based 的中断),OS 会调用与所给中断向量号相关联的所有的 ISRs 直到找到返回 TRUE 的那个,返回 TRUE 就说明这个中断就是用于此设备的。共享中断存在一些问题,因为写得不好的 ISR 会使系统挂掉。实际上,甚至写得好的 ISR 都有可能挂掉系统。要得到对此问题的解释,可以参阅 http://www.microsoft.com/hwdev/platform/proc/apic.asp 处的文章。再有,8259 不能用在多处理器系统中,这就太过时了,因为如今我们使用的桌面计算机大多都有两颗 CPU。还是用 APIC 吧。


    The APIC

    通常所说的 APIC 实际上由两个部分:“Local APIC”(LAPIC)和“I/O APIC”(IOAPIC)。系统中每个(逻辑上的)CPU 一般都有一个片上的 LAPIC。因此,若系统有四个 CPU 则有四个 LAPICs(注意,因为是每个逻辑处理器有一个 LPIC,所以如果有两个超线程的 CPU 也会有四个 LPI
  • 上一篇:A Crash Course on the Depths of Win32 Structured Exception Handling
    下一篇:SYMANTEC防火墙内核堆栈溢出漏洞利用方法总结