【问题标题】:Why kernel preemption is safe only when preempt_count == 0?为什么只有当 preempt_count == 0 时内核抢占才是安全的?
【发布时间】:2017-11-27 18:27:35
【问题描述】:

Linux 内核 2.6 引入了一个新的每线程字段——preempt_count——每当获取/释放锁时,该字段就会递增/递减。该字段用于允许内核抢占:“如果设置了need_resched 并且preempt_count 为零,则更重要的任务是可运行的,并且抢占是安全的。”

根据罗伯特·洛夫的"Linux Kernel Development" book: “那么什么时候重新调度是安全的?只要内核不持有锁,内核就能够抢占内核中运行的任务。”

我的问题是:为什么在内核中运行的任务持有锁时抢占它不安全?

如果另一个任务被调度并试图获取锁,它将阻塞(或自旋直到它的时间片结束),所以我们不会同时让两个线程进入同一个临界区。如果我们确实抢占了在内核模式下持有锁的任务,任何人都可以概述一个有问题的场景吗?

谢谢!

【问题讨论】:

  • 起初我认为另一个stackoverflow问题([stackoverflow.com/questions/5215958/…)提供了一个可能的解决方案:在持有锁的同时切换任务可能会导致死锁,例如,如果两个任务以不同的顺序获取锁。但我不认为这是答案,因为内核无论如何都应该强制执行相同的锁定顺序,以防止多处理器系统上的死锁。并且 Robert Love 还说:“因为内核是 SMP 安全的,如果没有持有锁,当前代码是可重入的并且能够被抢占。”
  • 但是对所提到的问题的确切答案就是对您问题的回答。你说的是锁定顺序,但是问题中single lock时没有顺序。再说一遍:在持有锁的同时禁用抢占是内核防止死锁的方法,即使在单锁上也是如此。
  • @Tsyvarev:但是如何我们才能陷入僵局?让我们假设线程#1 持有一个锁并放弃了 CPU。然后安排线程#2,尝试获取锁,然后阻塞。最终,线程#2 完成了它的时间片,所以线程#1 再次被调度,释放锁,一切都很好。我错过了什么吗?
  • Eventually, thread #2 finishes its time slice - 1. 线程#2 的整个时间片将是浪费时间。锁成本太高了。 2. 线程的调度属性(如优先级)可能会延迟切换回线程#1 甚至更多。无限延迟意味着死锁。
  • @Tsyvarev:Robert Love 正在谈论“安全”,IMO 更多的是关于正确性而不是性能,所以我不确定“浪费时间”是否是答案。

标签: multithreading linux-kernel preemption


【解决方案1】:

虽然这是一个老问题,但接受的答案并不正确。

首先标题是问:

为什么内核抢占只有在 preempt_count > 0 时才是安全的?

这是不对的,恰恰相反。内核抢占在 preempt_count > 0 时禁用,在 preempt_count == 0 时启用。

此外,声明:

如果安排了另一个任务并尝试获取锁,它将阻塞(或旋转直到其时间片结束),

并非总是如此。

假设您获得了一个自旋锁。启用抢占。发生进程切换,并且在新进程的上下文中运行一些 softirq。运行 softirqs 时禁用抢占。如果其中一个软中断尝试获取您的锁,它将永远不会停止旋转,因为抢占被禁用。因此,您遇到了死锁。

您无法控制抢占您的进程是否会运行软中断。禁用软中断的 preempt_count 字段是特定于进程的。 Softirq 必须在禁用抢占的情况下运行,以保留 softirq 的 per-cpu 序列化。

【讨论】:

  • 感谢您的回答!它似乎比我的答案更正确,所以我会接受它。 (我也解决了这个问题,感谢您发现我的错误。)
  • 一些参考资料:UTLK3 第 173--174 页确认软中断禁用内核抢占。
【解决方案2】:

在@Tsyvarev 的帮助下,我想我现在可以回答我自己的问题并描述一个有问题的场景,在这个场景中我们确实抢占了一个在内核模式下持有锁的任务。

  1. 线程 #1 持有自旋锁并被抢占。
  2. 然后安排线程 #2,并旋转以获取自旋锁。

现在,如果线程#2 是一个常规进程,它最终会完成它的时间片。在这种情况下,线程#1 将再次被调度,释放锁,我们都很好。 但是,如果线程 #2 是更高优先级的实时进程,线程 #1 将永远无法再次运行,我们就会出现死锁。

这个答案得到了another stackoverflow thread的证实,它引用了FreeBSD documentation

虽然锁可以在抢占的情况下保护大多数数据,但并非所有 内核是抢占安全的。例如,如果一个线程持有一个 自旋互斥锁被抢占,新线程尝试获取相同的自旋 互斥体,新线程可能会像被中断的线程一样永远旋转 永远没有机会执行。

虽然上面的引用没有明确解释为什么“被中断的线程可能永远没有机会再次执行”。

【讨论】:

    猜你喜欢
    • 2010-10-23
    • 2017-03-05
    • 2018-06-10
    • 2019-02-23
    • 2018-06-05
    • 2015-03-05
    • 2018-09-16
    • 1970-01-01
    • 2011-07-10
    相关资源
    最近更新 更多