Linux内核中断处理程序的互斥保护?

14

我需要保护我的中断处理程序避免被相同的中断多次调用吗?

给定以下代码,我不确定应该进行哪些系统调用。我当前的实现偶尔会出现随机死锁情况:

void interrupt_handler(void)
{
    down_interruptible(&sem);  // or use a lock here ?

    clear_intr(); // clear interrupt source on H/W

    wake_up_interruptible(...);

    up(&sem); // unlock?

    return IRQ_HANDLED;
}

void set/clear_intr()
{
    spin_lock_irq(&lock);
    RMW(x); // set/clear a bit by read/modify/write the H/W interrupt routing register
    spin_unlock_irq(&lock);
}

void read()
{
    set_intr();  // same as clear_intr, but sets a bit
    wait_event_interruptible(...);
}
  1. interrupt_handler中,应该使用spin_lock_irq/spin_lock_irqsave/local_irq_disable替代down_interruptible吗?
  2. set/clear_intr方法中,应该使用spin_lock_irqsave/local_irq_disable替代spin_lock_irq吗?
  3. 在硬件产生的中断被内核处理程序处理并且驱动程序在处理它之前不清除它时,它们能持续产生/获取中断吗?当处理程序正在处理中断时,interrupt_handler是否会反复调用?
  4. 如果当前实现的中断处理程序是可重入的,那么它会在down_interruptible上阻塞吗?

引用自LDD3:-

必须是可重入的——它必须能够同时在多个上下文中运行。


编辑1) 经过一些好帮助,我们得到以下建议 :-

  1. interrupt_handler中删除down_interruptible
  2. spin_lock_irq移到set/clear方法外部(你说不需要spin_lock_irqsave?!)我真的看不出这有什么好处?!

代码:

void interrupt_handler(void)
{
    read_reg(y); // eg of other stuff in the handler

    spin_lock_irq(&lock);

    clear_intr(); // clear interrupt source on H/W

    spin_unlock_irq(&lock);

    wake_up_interruptible(...);

    return IRQ_HANDLED;
}

void set/clear_intr()
{
    RMW(x);
}

void read()
{
    error_checks(); // eg of some other stuff in the read method

    spin_lock_irq(&lock);

    set_intr();  // same as clear_intr, but sets a bit

    spin_unlock_irq(&lock);

    wait_event_interruptible(...);

    // more code here...
}

编辑2)在阅读了一些其他的SO帖子之后:阅读为什么内核代码/线程在中断上下文中无法睡眠?,其中链接到罗伯特·洛夫(article)的文章,我发现了这个:

一些中断处理程序(在Linux中称为快速中断处理程序)会禁用本地处理器上的所有中断。这样做是为了确保中断处理程序尽可能快地运行而不被打断。此外,所有中断处理程序都在所有处理器上禁用其当前中断线。这确保了同一中断线的两个中断处理程序不会并发运行。它还防止设备驱动程序编写人员处理递归中断,这会使编程变得复杂。

我启用了快速中断(SA_INTERRUPT)!因此不需要互斥锁/锁/信号量/自旋/等待/睡眠等等!


你回答了自己的第一个问题(以及几个其他的重述)。总的来说,要避免在时间关键的事情上锁定(比如中断处理)。很抱歉我不能提供更多的答案,+1。 - sehe
@sehe 和它一起,方法不能说是可重入的吗!? - Ian Vaughan
3
当你确定在调用代码时没有禁用任何中断时,才应仅使用spin_lock_irq。我建议你至少更改中断处理程序中的调用,改用spin_lock_irqsave - Hasturkun
LDD3指出,“驱动程序代码”必须是可重入的,但它并没有说中断处理程序必须是可重入的。除了SMP问题之外,由于中断处理程序通常在禁用中断的情况下执行,因此不存在可重入性问题——除非您自己创建了这些问题。 - Dipstick
1
@Ian Vaughan - 在中断处理程序或原子上下文中不允许使用 down/up!!! - Dipstick
显示剩余3条评论
3个回答

13
不要在中断上下文中使用信号量,而是使用spin_lock_irqsave。引用自LDD3:
如果您有一个可以被(硬件或软件)中断上下文中的代码获取的自旋锁,您必须使用禁用中断的形式之一来获取自旋锁。否则,系统会在早晚某个时候发生死锁。如果您没有在硬件中断处理程序中访问锁,但是通过软件中断访问锁(例如,在任务队列中运行的代码,这是第7章中介绍的一个主题),那么您可以使用spin_lock_bh来安全地避免死锁,同时仍然允许硬件中断得到服务。
至于第2点,请使您的set_intrclear_intr要求调用者锁定自旋锁,否则您将发现您的代码死锁。同样来自LDD3:
为了使您的锁定正常工作,您必须编写一些函数,并假定它们的调用方已经获取了相关的锁。通常,只有您的内部静态函数可以以这种方式编写;从外部调用的函数必须显式处理锁定。当您编写具有关于锁定的假设的内部函数时,请为自己(以及与您的代码一起工作的任何其他人)一个好处,并明确记录这些假设。几个月后回来判断是否需要持有锁,以调用特定函数可能非常困难。

你的意思是在interrupt_handler函数内不要使用down_interruptible吗? - Ian Vaughan
1
@IanVaughan:是的,那样做很容易出问题。我已经扩展了我的答案。 - Hasturkun
你的意思是要么用spin_lock_irqsave替换down_interruptible(这肯定会使它不可重入)?还是确保我在我的set/clear中使用spin_lock_irqsave(而不是当前的spin_lock_irq)? - Ian Vaughan
重新编辑:我不明白将锁定/解锁移动到方法外面如何有所帮助,无论是调用者执行还是由我执行,都是一样的吗?除非我锁定更多的代码。 - Ian Vaughan
@IanVaughan:我主要建议将锁定/解锁移至方法外部以避免死锁,假设您的中断处理程序在锁定下执行的工作不仅是调用clear_intr。无论如何,您只需要在与中断处理程序共享资源时使用spin_lock_irqsave。使用spin_lock_irqsave并不会使您的代码不可重入(实际上相反,因为它通常用于确保状态不变得不一致),但您应该尽可能将锁定的区域保持小巧。 - Hasturkun

2

在中断上下文中使用自旋锁,因为如果您没有获取锁,则不希望在中断上下文中睡眠。


0

您发布的代码看起来不像是设备驱动程序中的irq处理程序。 内核驱动程序中的irq处理程序返回irqreturn_t并以int irq_no,void * data作为参数。

您还没有指定是否注册了线程处理程序或非线程处理程序。 非线程irq处理程序无论是否持有任何自旋锁都不能有任何睡眠调用。 wait_event,mutex,semaphore等都是睡眠调用,不能在非线程irq处理程序中使用。但是,您可以持有自旋锁以防止中断您的中断处理程序。这将确保可屏蔽的irq和调度程序不会在中间中断您的irq处理程序。

在线程irq处理程序中,可以使用诸如睡眠调用(等待队列,互斥量等)之类的东西,但仍不建议使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接