4

我正在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

这工作得很好,我成功地禁用了特定的 IRQ 号。

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

#include <linux/interrupt.h>
...
disable_irq(5);    // disable IRQ 5

disable_irq行位于我的角色驱动程序open函数的开头。然而,当我打开我的设备节点时,我函数中的其余代码open照常执行,IRQ 5 保持启用 - 似乎disable_irq根本没有任何效果。

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

__asm__("cli");

然而,甚至这条指令似乎都没有被执行,因为所有的中断仍然处于启用状态。

我现在完全糊涂了,为什么直接汇编不禁用Linux上的中断?在Linux上禁用中断的正确方法是什么?


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

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


更新2:

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

#include <linux/module.h>
#include <linux/kernel.h> /* Needed for KERN_INFO */
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irqflags.h> /* Needed for local_irq_disable et al. */

MODULE_LICENSE("GPL");

static unsigned long flags = 0;

static int __init initialization_routine(void)
{
        local_irq_save(flags);
        local_irq_disable();

        /* __asm__("cli"); */
        /* disable_irq(15); */
        return 0;
}

static void __exit cleanup_routine(void) {
        local_irq_restore(flags);

        /* __asm__("sti"); */
        /* enable_irq(15); */
        return;
}   

module_init(initialization_routine);
module_exit(cleanup_routine);

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

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

$ watch -d -n 0.5 cat /proc/interrupt

由于disable_irq现在enable_irq运行完美,我怀疑我只是忘记了某种初始化代码,或者local_irq_disable相关功能可能已被弃用或不适用于 x86 处理器?

4

2 回答 2

5

我碰巧想到了这个问题。已经一年了,但还不是一个很好的答案。我想提供一个关于英特尔架构和 Linux 架构似乎缺少的高级观点。您可以深入了解出色的英特尔® 64 和 IA-32 架构软件开发人员手册,英特尔在其网站上以 PDF 形式分享了该手册。但这里是构建答案的信息,基本上,不要试图阻止所有 cpus 的特定向量 - 小心使用 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) 将设备状态更改为根本不产生中断。大多数设备都具有不产生中断的能力。

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

于 2015-06-05T21:59:40.497 回答
3

disable_irq()/enable_irq() 与 request_irq() 之间没有关系。Linus 在您发布其链接的线程中也回应了同样的问题。disable_irq() 可以在不注册该特定中断号的情况下调用。我测试了这个模块以验证行为并且它有效。下面是我使用的模块代码

#include <linux/module.h>
#include <linux/kernel.h> /* Needed for KERN_INFO */
#include <linux/init.h>
#include <linux/interrupt.h>

MODULE_LICENSE("GPL");

static int __init initialization_routine(void)
{
        disable_irq(15);
        return 0;
}

static void __exit cleanup_routine(void) {

        enable_irq(15);
        return;
}   

module_init(initialization_routine);
module_exit(cleanup_routine);

加载我的模块后... /proc/interrupt 的输出

15:      68321          0          0          0   IO-APIC-edge      ata_piix

删除我的模块后... /proc/interrupt 的输出

15:      68325          0          0          0   IO-APIC-edge      ata_piix

我还尝试使用 cli 和 sti 汇编指令而不是 disable_irq() 和 enable_irq()。但是加载模块会导致 dmesg 中的以下输出..

[root@localhost 5]# dmesg
------------[ cut here ]------------
WARNING: CPU: 1 PID: 5989 at init/main.c:699 do_one_initcall+0x13e/0x1a0()
initcall initialization_routine+0x0/0x9 [test_module] returned with disabled interrupts 
Modules linked in: test_module(OF+) rfcomm lp bridge bnep 8021q garp stp llc ipt_REJECT nf_conntrack_ipv4 nf_defrag_ipv4 iptable_filter ip_tables ip6t_REJECT nf_conntrack_ipv6 nf_defrag_ipv6 xt_state nf_conntrack ip6table_filter ip6_tables ipv6 fuse dm_mirror dm_region_hash dm_log dm_mod uinput ppdev parport_pc parport btusb bluetooth rfkill snd_ens1371 snd_rawmidi snd_ac97_codec ac97_bus snd_seq snd_seq_device snd_pcm snd_timer snd soundcore snd_page_alloc e1000 microcode sg pcspkr shpchp i2c_piix4 i2c_core ext4(F) jbd2(F) mbcache(F) floppy(F) sd_mod(F) crc_t10dif(F) sr_mod(F) cdrom(F) mptspi(F) mptscsih(F) mptbase(F) scsi_transport_spi(F) pata_acpi(F) ata_generic(F) ata_piix(F) [last unloaded: test_module]
CPU: 1 PID: 5989 Comm: insmod Tainted: GF       W  O 3.11.0-rc2 #5
Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/02/2012
 00000000000002bb ffff8800271b3d38 ffffffff8154516d 00000000000002bb
 ffff8800271b3d88 ffff8800271b3d78 ffffffff8104bf1c ffff8800271b3d68
 0000000000000000 ffffffffa0540000 0000000000000000 0000000000000000
Call Trace:
 [<ffffffff8154516d>] dump_stack+0x49/0x5c
 [<ffffffff8104bf1c>] warn_slowpath_common+0x8c/0xc0
 [<ffffffffa0540000>] ? 0xffffffffa053ffff
 [<ffffffff8104c006>] warn_slowpath_fmt+0x46/0x50
 [<ffffffff8126c329>] ? strlcat+0x69/0x80
 [<ffffffffa0540000>] ? 0xffffffffa053ffff
 [<ffffffff8100030e>] do_one_initcall+0x13e/0x1a0
 [<ffffffff81077995>] ? __blocking_notifier_call_chain+0x65/0x80
 [<ffffffff810b43b4>] do_init_module+0x44/0x1b0
 [<ffffffff810b61d2>] load_module+0x5b2/0x6f0
 [<ffffffff810b3b00>] ? __unlink_module+0x30/0x30
 [<ffffffff810b3280>] ? module_sect_show+0x30/0x30
 [<ffffffff810b64d2>] SyS_init_module+0xd2/0x120
 [<ffffffff81551d42>] system_call_fastpath+0x16/0x1b
---[ end trace 456a5393bc94bdcf ]---

这可能是因为我在虚拟机上运行 Linux。但是,sti 和 cli 指令永远不应该直接在内核模块中使用。您应该始终使用提供的内核 API 来禁用单个内核上的中断,而不是在整个系统范围内禁用它们。

编辑1:

我假设您正在 x86 机器上运行。local_irq_disable() 最终调用执行汇编指令cli的以下函数。正如您已经提到 cli/sti 无法在您的系统上运行,local_irq_disable()/local_irq_enable() 也不会。

static inline void native_irq_disable(void)
{
        asm volatile("cli": : :"memory");
}
于 2013-09-21T18:15:04.250 回答