我正在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上的中断的正确方法是什么?
UPDATE:我发现disable_irq
仅在request_irq
之后执行时才起作用。这是错误,还是预期的行为?
我找到了a thread that seems to vaguely describe the behavior I'm seeing,但是它已经过时,并且我不确定它是否与我的Linux版本相关。
UPDATE2:
这是我在运行[[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处理器?
#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"); }
每个处理器内核都有一个“中断标志”,仅在该内核上启用或禁用该中断机制。这就是Linux的local_irq_ *例程更改的内容(它们使用CLI / STI指令或保存,修改和重新加载标志寄存器,从而更改IF标志)。
设备中断被路由到特定的处理器内核,并路由到其本地APIC(高级可编程中断控制器),在其中将特定中断向量的IRQ位置1。阻止注入特定中断向量的几种方法之一是在该内核的本地APIC(LAPIC)中设置屏蔽寄存器。它只能在实际在该特定内核上运行时才能完成,除非您知道您的代码在该内核上运行,否则这将很难进行。通常,如果用于防止嵌套中断不会发生,则此机制可能很有用。但是LAPIC可以更轻松地处理嵌套的中断:使用LAPIC中提供的优先级屏蔽,因为您通常希望在处理程序中运行时阻止当前中断和所有较低优先级的中断。查看为此的LAPIC PPR寄存器功能。通常,您无需编写任何代码即可从中受益,因为Linux的中断处理是围绕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)更改设备状态以完全不产生中断。大多数设备具有不产生中断的能力。
但是,必须记住,在尝试进行这种全局抑制时可能会有一个或多个中断在运行中。