繁体   English   中英

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

[英]CLI instruction not executed in Linux kernel module

我正在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_irqenable_irq现在完美运行,我怀疑我只是忘记了某种初始化代码,或者local_irq_disable和相关功能已被弃用或不适用于 x86 处理器?

我碰巧遇到了这个问题。 已经一年了,但还不是一个很好的答案。 我想提供一个关于 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 寄存器功能。 通常,您不需要编写任何代码即可从中受益,因为 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) 将设备状态更改为根本不产生中断。 大多数设备都具有不产生中断的能力。

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

disable_irq()/enable_irq() 和 request_irq() 之间没有关系。 Linus 在您发布链接的线程中也回应了这一点。 disable_irq() 可以在不注册该特定 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");
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM