【问题标题】:Why is "sleeping" not allowed while holding a spinlock? [duplicate]为什么拿着自旋锁时不允许“睡觉”? [复制]
【发布时间】:2011-10-24 04:02:58
【问题描述】:

可能重复:
Why can't you sleep while holding spinlock?

据我所知,自旋锁应该在短时间内使用,并且只是代码中的选择,例如不允许休眠(抢占)的中断处理程序。

但是,我不知道为什么会有这样一个“规则”,即在持有自旋锁时根本不应该睡觉。我知道这不是推荐的做法(因为它不利于性能),但我认为没有理由不应该在自旋锁中允许休眠。

在获取信号量时不能持有自旋锁,因为在等待信号量时可能需要休眠,并且在持有自旋锁时不能休眠(来自 Robert Love 的“Linux Kernel Development”)。

我能看到的唯一原因是出于可移植性的原因,因为在单处理器中,自旋锁被实现为禁用中断,通过禁用中断,当然不允许休眠(但休眠不会破坏 SMP 系统中的代码)。

但我想知道我的推理是否正确,或者是否还有其他原因。

【问题讨论】:

  • 你说的这条规则是什么?
  • 我刚刚从我的问题出现的一本书中添加了一个块引用。
  • 这在我看来就像是特定于 Linux 内核开发的东西。如果您正在谈论 Linux 内核开发,请相应地标记问题。

标签: c synchronization linux-kernel sleep spinlock


【解决方案1】:

至少在 Linux 中,不允许在自旋锁中休眠有几个原因:

  1. 如果线程 A 在自旋锁中休眠,然后线程 B 尝试获取相同的自旋锁,则单处理器系统将死锁。线程 B 永远不会进入睡眠状态(因为自旋锁没有在 A 完成后唤醒 B 所需的等待名单),并且线程 A 永远不会有机会唤醒。
  2. 在信号量上使用自旋锁正是因为它们更有效 - 如果您不会长期竞争。允许休眠意味着您将有很长的争用期,抹去使用自旋锁的所有好处。在这种情况下,仅使用信号量,您的系统会更快。
  3. 自旋锁通常用于通过另外禁用中断来与中断处理程序同步。如果您睡眠,则此用例是不可能的(一旦您进入中断处理程序,您就无法切换回线程让它唤醒并完成其自旋锁临界区)。

为正确的工作使用正确的工具 - 如果您需要睡觉,信号量和互斥锁是您的朋友。

【讨论】:

  • 实际上信号量真的不应该在内核代码中使用,除非你有一个非常专业的用例(真的,如果你在 stackoverflow 上阅读这个问题,你不需要信号量 :) 只需使用互斥锁当你需要睡眠时锁定,当你需要在不可睡眠的上下文中锁定时使用自旋锁。
  • @bdonlan 我对第1点有疑问。你说线程A永远不会醒来?这是为什么?当线程 B 的时间片结束并且线程的睡眠时间结束时,线程 A 将使用其时间片执行,一旦完成将释放锁。不是这样吗?
  • @SumitTrehan:好点。如果自旋锁对于调度程序本身来说不是必需的,并且线程 B 不在中断上下文中并且没有禁用中断或抢占,那么最终线程 B 可以被抢占。但是,在 linux 中,当自旋锁被持有(或即将被持有)时,抢占总是被禁用。这是因为如果一个线程在不运行时持有自旋锁,其他线程会浪费大量时间在锁上自旋,并且中断处理程序可能会出现死锁。所以实际上线程 B 不会被抢占并且会死锁。
  • @bdonlan:你能解释一下这个案例吗:我们只有一个核心,A:持有自旋锁,B等待自旋锁(可能B正在睡觉)。中断正在触发。现在应该是睡觉了。中断处理程序完成后,A 还是 B 哪个会占用自旋锁?
  • @HoangPham 在持有自旋锁时不允许抢占,因此在中断完成后执行返回到 A。请注意,A not 进入睡眠状态(上下文切换确实不会发生)
【解决方案2】:
  • 实际上,您可以在禁用中断或其他某种排除活动的情况下进入睡眠状态。如果您不这样做,您正在睡觉的条件可能会由于中断而改变状态,然后您将永远不会醒来。睡眠代码通常不会在没有提升的优先级或包含决定睡眠和上下文切换之间的执行路径的其他关键部分的情况下进入。

  • 但是对于自旋锁来说,睡眠是一场灾难,因为锁一直处于设置状态。其他线程在碰到它时会旋转,并且在您从睡眠中醒来之前它们不会停止旋转。与自旋锁在最坏情况下预期的少数自旋相比,这可能是永恒的,因为自旋锁的存在只是为了同步对内存位置的访问,它们不应该与上下文切换机制交互。

    (就此而言,其他所有线程最终都可能遇到自旋锁,然后您将楔入整个系统每个核心的每个线程。)

【讨论】:

  • 能否详细说明第一点?
  • 第一点错了。你不能在禁用中断的情况下睡觉;要处理那里提到的比赛,只需按正确的顺序做事:将你的状态设置为 TASK_INTERRUPTIBLE,检查条件,然后 schedule() - 或者只使用 wait_event(),它会为你处理。
  • @Roland,我只是指内核进程代码的角度,所以你可以,在调用schedule()sleep()入口点的意义上.内核的睡眠机制将重新启用中断。现在所有常见的内核都是 SMP,因此禁用中断不再有任何作用,但可以肯定的是,如果不重新启用中断,它将不会执行任何硬件 wait-for-interrupt 指令。
【解决方案3】:

当您使用自旋锁时,您不能使用它。自旋锁用于真正需要保护关键区域和共享数据结构的地方。如果您在持有信号量的同时获得一个信号量,则锁定对您的锁定所附加到的任何关键区域(例如)的访问(它通常是特定较大数据结构的成员),同时允许该进程可能进入睡眠状态。例如,如果在此进程休眠时引发了 IRQ,并且 IRQ 处理程序需要访问仍被锁定的关键区域,则它会被阻塞,而 IRQ 永远不会发生这种情况。显然,您可以编造一些示例,说明您的自旋锁没有按应有的方式使用(例如,附加到 nop 循环的假设自旋锁);但这根本不是 Linux 内核中真正的自旋锁。

【讨论】:

    猜你喜欢
    • 2011-06-12
    • 2011-04-10
    • 1970-01-01
    • 2019-01-21
    • 2021-07-18
    • 1970-01-01
    • 1970-01-01
    • 2015-12-07
    相关资源
    最近更新 更多