Linux 内核模块中未执行 CLI 指令

Vil*_*ray 4 c x86 assembly interrupt linux-kernel

我正在Intel Atom处理器(带有 2 个内核的x86_64)上编写Linux v3.2内核模块。我想禁用特定的 IRQ 编号,但在 Linux 上执行此操作时遇到问题。

我是双引导MS-DOS,通过直接与8259 PIC芯片通信,我可以轻松禁用 Intel 语法 x86 程序集中的中断:

CLI                ; disable all interrupts
MOV    DX, 0x21    ; set 8259 ioport address
IN     AL, DX      ; store current interrupt mask in AL
AND    AL, 0xDF    ; modify mask to disable IRQ 5
OUT    DX, AL      ; send new mask to 8259
STI                ; reenable interrupts
Run Code Online (Sandbox Code Playgroud)

这很有效,我成功地禁用了特定的 IRQ 号码。

Linux 中,我知道我必须使用disable_irq宏来禁用中断,但它似乎没有效果。

CLI                ; disable all interrupts
MOV    DX, 0x21    ; set 8259 ioport address
IN     AL, DX      ; store current interrupt mask in AL
AND    AL, 0xDF    ; modify mask to disable IRQ 5
OUT    DX, AL      ; send new mask to 8259
STI                ; reenable interrupts
Run Code Online (Sandbox Code Playgroud)

disable_irq行位于我的角色驱动程序open功能的开头。然而,虽然我的open函数中的其余代码在我打开设备节点时照常执行,但 IRQ 5 仍处于启用状态——它似乎disable_irq根本没有影响。

我不确定我是否disable_irq正确使用了宏,所以我决定尝试直接内联汇编来验证我的逻辑是否正确。我决定从简单开始,首先尝试禁用所有中断:

#include <linux/interrupt.h>
...
disable_irq(5);    // disable IRQ 5
Run Code Online (Sandbox Code Playgroud)

然而,即使是这条指令似乎也没有被执行,因为所有的中断仍然保持启用状态。

我现在很困惑,为什么直接汇编不禁用Linux上的中断?在Linux上禁用中断的正确方法是什么?


更新:我发现disable_irq只有在request_irq. 这是一个错误,还是预期的行为?

我发现一个线程似乎模糊地描述了我所看到的行为,但它已经过时,我不确定它是否仍然与我的 Linux 版本相关。


更新2:

这是我在运行Linux v3.2.0-4 的Debian上尝试的内核模块:

__asm__("cli");
Run Code Online (Sandbox Code Playgroud)

disable_irq/enable_irq工作正常。我对简单的组装说明并不太感兴趣(奇怪的是它们不起作用)。此外,我担心为什么local_irq_disable对任何内核都没有可观察到的影响——即 IRQ 仍然出现在所有内核上。

要检查中断,我在终端中运行以下命令:

$ watch -d -n 0.5 cat /proc/interrupt
Run Code Online (Sandbox Code Playgroud)

由于disable_irqenable_irq现在完美运行,我怀疑我只是忘了某种形式的初始化代码或也许local_irq_disable及相关功能已过时或不适用于x86处理器?

小智 5

我碰巧遇到了这个问题。已经一年了,但还不是一个很好的答案。我想提供一个关于 Intel 架构和 Linux 架构似乎缺少的高级观点。您可以深入了解优秀的英特尔® 64 位和 IA-32 架构软件开发人员手册,英特尔在其网站上以 PDF 格式分享了该手册。但这里是构建答案的信息,基本上,不要试图阻止所有 cpu 的特定向量 - 小心使用 spin_locks 和 local_irq_disable()。

每个处理器内核都有一个“中断标志”,它仅在该内核上启用或禁用中断机制。这就是 Linux 的 local_irq_* 例程的变化(它们使用 CLI/STI 指令或保存、修改和重新加载标志寄存器,更改 IF 标志)。

设备中断被路由到一个特定的处理器内核,到它的本地 APIC(高级可编程中断控制器),在那里为特定的中断向量设置一个 IRQ 位。阻止特定中断向量被注入的几种方法之一是在本地 APIC (LAPIC) 中为该内核设置屏蔽寄存器。它只能在实际运行在该特定核心上时才能完成,除非您知道您的代码正在该核心上运行,否则这很难做到。通常,如果用于确保不会发生嵌套中断,则此机制可能很有用。但是 LAPIC 可以更轻松地处理嵌套中断:使用 LAPIC 中提供的优先级屏蔽,因为您通常希望在处理程序中运行时阻止当前中断和所有较低优先级的中断。为此,请查看 LAPIC PPR 寄存器功能。

一般来说,我建议您以一种完全避免尝试全局“禁用”中断的想法的方式设计您的设备驱动程序代码。没有简单的方法可以防止“飞行中”中断或其他灾难的丢失。相反,使用优先级机制来处理设备中断处理程序中的中断,并小心使用 spin_lock 或 spin_lock_irqsave 构造其他代码,它们作为副作用阻止执行它的 cpu 上的中断。

如果联锁代码很短,您应该能够设计设备,以便在设备中断处理程序本身中进行 spin_lock_irqsave,这可以安全地防止其他内核在中断期间接触设备,反之亦然。

现在,如果您真的需要随机选择的内核阻止其他内核的中断,当它在中断处理程序之外运行时,您需要查看中断实际上是如何传递到内核的 LAPIC 的。我不知道您为什么要这样做 - 大多数设备驱动程序不会尝试这样做,但这可能与某些特定于设备的原因有关。

中断通过共享中断总线传送到 LAPIC,这允许在单个事务中将中断传输到一个或多个 LAPIC。(多播模式很少(如果有的话)用于设备,所以我不会在这里解释它们 - 请参阅英特尔的手册)。共享中断总线要么将中断发送到 IOAPIC 设备,要么通过前端总线直接发送到 LAPIC,通过其 APIC ID 寻址,并指定要使用的向量编号。(FSB 机制可以由更现代的设备直接使用 - PCI Express 和 HPET,但大多数传统设备使用 IOAPIC。)

要在所有处理器中全局阻止特定向量,您可以在概念上执行以下三件事之一:a) 重新编程设备用来设置“屏蔽”位的 IOAPIC 或 PCI Express 寄存器。但是,这有很多东西,并且可能需要停止所有其他可能出现中断或其他魔法的 CPU 内核。您可以在启动或停止设备时使用它一次。b) 对共享的 IDT 做一些事情来临时捕获中断,然后再将其转发给正确的处理程序。这也是复杂和危险的。c) 将设备状态更改为根本不产生中断。大多数设备都具有不产生中断的能力。

但是,必须记住,在尝试进行这种全局抑制时,可能会有一个或多个中断“在飞行中”。