【问题标题】:spin_lock_irqsave vs spin_lock_irqspin_lock_irqsave 与 spin_lock_irq
【发布时间】:2011-02-03 07:32:58
【问题描述】:

在 SMP 机器上,我们必须在中断上下文中使用 spin_lock_irqsave 而不是 spin_lock_irq

我们为什么要保存标志(包含 IF)?

是否有另一个中断程序可以中断我们?

【问题讨论】:

    标签: linux-kernel spinlock


    【解决方案1】:

    spin_lock_irqsave基本上是用来保存在获取自旋锁之前的中断状态,这是因为自旋锁在中断上下文中获取锁时禁用中断,而在解锁时重新启用它。中断状态被保存,以便它应该再次恢复中断。

    例子:

    1. 假设在获取自旋锁之前禁用了中断 x
    2. spin_lock_irq 将禁用中断 x 并获取锁
    3. spin_unlock_irq 将启用中断 x。

    所以在释放锁后的第三步中,我们将启用中断 x,该中断在获得锁之前被禁用。

    因此,只有当您确定没有禁用中断时,您才应该使用spin_lock_irq,否则您应该始终使用spin_lock_irqsave

    【讨论】:

      【解决方案2】:

      如果在您的代码开始锁定之前中断已被禁用,当您调用spin_unlock_irq 时,您将以可能不需要的方式强制重新启用中断。相反,如果您还将当前中断启用状态保存在flagsspin_lock_irqsave 中,尝试在释放锁定后使用相同的flags 重新启用中断,该函数将只是恢复以前的状态(因此不一定启用中断)。

      spin_lock_irqsave 为例:

      spinlock_t mLock = SPIN_LOCK_UNLOCK;
      unsigned long flags;
      
      spin_lock_irqsave(&mLock, flags); // Save the state of interrupt enable in flags and then disable interrupts
      // Critical section
      spin_unlock_irqrestore(&mLock, flags); // Return to the previous state saved in flags
      

      spin_lock_irq( 不带 irqsave ) 的示例:

      spinlock_t mLock = SPIN_LOCK_UNLOCK;
      unsigned long flags;
      
      spin_lock_irq(&mLock); // Does not know if interrupts are already disabled
      // Critical section
      spin_unlock_irq(&mLock); // Could result in an unwanted interrupt re-enable...
      

      【讨论】:

      • 这个答案不正确。 spin_lock_irq 将无条件地禁用中断,而 irqsave 变体将保存中断状态,以防您不知道当前处于什么状态。
      • @NoahWatkins 这就是最后一部分的重点。它在评论中说可能导致错误解锁spin_lock_irq 是为了表明在不应该启用 IRQ 时启用了 IRQ。答案没有错;只是不太清楚。
      • @artlessnoise 当我们说 irq 被禁用时,系统中的所有 irq 都被禁用了吗?这对我来说不是很清楚。你能解释一下吗?
      • 像我这样的内核新手提示:spin_lock_irqsave 是一个宏,而不是一个函数,这就是它可以更改“passedflags 变量的原因。这让我感到困惑,直到我真正在内核代码中查找它。
      • @mk.. 只是在那个逻辑核心上中断。这个想法是线程在持有锁时不会被中断。
      【解决方案3】:

      除了spin_lock_irq 之外还需要spin_lock_irqsave 与除了local_irq_disable 之外还需要local_irq_save(flags) 的原因非常相似。下面是 Robert Love 的 Linux Kernel Development Second Edition 对这一要求的一个很好的解释。

      如果中断被中断,local_irq_disable() 例程是危险的 在调用之前已经禁用。对应的调用 local_irq_enable() 无条件启用中断,尽管 他们一开始就离开的事实。相反,需要一种机制 将中断恢复到以前的状态。这是一个普遍的担忧 因为内核中给定的代码路径可以通过 和 不启用中断,具体取决于调用链。例如, 想象一下前面的代码 sn-p 是一个更大函数的一部分。 想象一下,这个函数被另外两个函数调用,一个 禁用中断,一个不禁用。因为它正在成为 随着内核规模和复杂性的增长,了解所有代码变得更加困难 通向函数的路径,保存状态更安全 禁用它之前的中断系统。然后,当你准备好 重新启用中断,您只需将它们恢复到原始状态:

      unsigned long flags;
      
      local_irq_save(flags);    /* interrupts are now disabled */ /* ... */
      local_irq_restore(flags); /* interrupts are restored to their previous
      state */
      

      请注意,这些方法至少部分是作为宏实现的,所以 flags 参数(必须定义为无符号长整数)是 似乎是按价值传递的。该参数包含 包含中断状态的特定于体系结构的数据 系统。因为至少一种受支持的架构包含 将信息堆栈到值中(咳咳,SPARC),不能传递标志 到另一个函数(具体来说,它必须保持在同一个堆栈上 框架)。因此,调用 save 和调用 restore 中断必须发生在同一个函数中。

      前面的所有函数都可以从中断和 进程上下文。

      【讨论】:

        【解决方案4】:

        阅读 Why kernel code/thread executing in interrupt context cannot sleep? 链接到 Robert Loves article,我读到了这个:

        一些中断处理程序(已知 Linux 作为快速中断处理程序)运行 与本地的所有中断 处理器禁用。这是为了 确保中断处理程序运行 不间断,尽快 可能的。更何况,所有的中断 处理程序以其当前运行 中断线全部禁用 处理器。这确保了两个 相同的中断处理程序 中断线不运行 同时。它还可以防止设备 司机作家不必处理 递归中断,这很复杂 编程。

        【讨论】:

        • 这不回答问题。
        【解决方案5】:

        下面是linux kernel 4.15.18的部分代码,显示spiin_lock_irq()会调用__raw_spin_lock_irq()。但是,它不会保存任何标志,如您在下面的部分代码中看到的那样,而是禁用中断。

          static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
            {
                local_irq_disable();
                preempt_disable();
                spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
                LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
            }
        

        下面的代码显示了 spin_lock_irqsave() 保存当前阶段的标志,然后抢占禁用。

        static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
        {
            unsigned long flags;
        
            local_irq_save(flags);
            preempt_disable();
            spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
            /*
             * On lockdep we dont want the hand-coded irq-enable of
             * do_raw_spin_lock_flags() code, because lockdep assumes
             * that interrupts are not re-enabled during lock-acquire:
             */
        #ifdef CONFIG_LOCKDEP
            LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
        #else
            do_raw_spin_lock_flags(lock, &flags);
        #endif
            return flags;
        }
        

        【讨论】:

          【解决方案6】:

          这个问题从错误的断言开始:

          On an SMP machine we must use spin_lock_irqsave and not spin_lock_irq from interrupt context.
          

          这些都不应该从中断中使用 上下文,在 SMP 或 UP 上。也就是说,spin_lock_irqsave() 可以在中断上下文中使用,因为它更通用 (它可以在中断和正常上下文中使用),但是 你应该在中断上下文中使用spin_lock(), 和spin_lock_irq()spin_lock_irqsave() 来自正常上下文。 使用spin_lock_irq() 几乎总是错误的 在中断上下文中执行此 SMP 或 UP。它可能有效 因为大多数中断处理程序在本地启用 IRQ 的情况下运行, 但你不应该那样做。

          更新:由于有些人误解了这个答案,让我澄清一下 它只解释了什么是中断,什么不是中断 上下文锁定。这里没有声称spin_lock() 应该 在中断上下文中使用。它可以在一个过程中使用 上下文也是,例如如果不需要锁定中断 上下文。

          【讨论】:

          • 这是完全错误的答案。这里所说的几乎所有内容都是错误的。请对此投反对票或完全删除它。
          • 自旋锁方法与中断或进程上下文无关。答案说 spin_lock() 将在中断上下文中使用,而 spin_lock_irq()/spin_lock_irqsave() 将在进程上下文中使用。那是假的。这都是关于访问路径的。如果您确定可以从进程上下文和中断上下文访问您的数据,则使用 spin_lock_irq()/spin_lock_irqsave()。如果你确定你的代码只能从进程上下文中访问,那么使用 spin_lock(),你就不需要在这里为中断而烦恼了。
          • 现在关于 spin_lock_irq() 与 spin_lock_irqsave() .. 上面已在接受的答案中详细说明。是的 spin_lock_irqsave() 更安全。
          • 这些原语的不同之处仅在于它们对标志的处理,尤其是 IF 标志。原因是要确保中断处理程序永远不会尝试获取与进程上下文中已经持有的相同的锁,这在 UP 上会导致死锁。所以在进程上下文中,锁定函数应该清除 IF 标志,这在中断上下文中是不需要的。更重要的是,在中断上下文中,您不应该在解锁时设置 IF 标志(因为它最初可能已被清除),因此 _irq 版本不适合。但是通用 _irqsave 只有在不知道你所处的环境时才有用。
          • @jarvis1729 如果您说“答案说 spin_lock() 将在中断上下文中使用”,那么您误读了我。我正在解释根本需要锁定中断上下文的情况,因为这是最初的问题。如果您不需要在中断上下文中锁定,那么您当然可以在进程上下文中使用 spin_lock(),但我没有涵盖这种情况,因为问题明确涉及中断上下文。我的回答只是关于在中断上下文中使用什么和不使用什么
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-12-07
          • 1970-01-01
          • 2012-10-27
          • 1970-01-01
          • 2019-10-22
          • 1970-01-01
          相关资源
          最近更新 更多