2

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

根据 Robert Love 的《Linux 内核开发》一书:“那么什么时候重新调度是安全的?只要内核不持有锁,内核就能够抢占内核中运行的任务。”

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

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

谢谢!

4

2 回答 2

2

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

首先标题是问:

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

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

此外,索赔:

如果另一个任务被调度并试图获取锁,它将阻塞(或旋转直到它的时间片结束),

并非总是如此。

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

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

于 2020-09-12T06:02:44.467 回答
1

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

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

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

这个答案得到了另一个引用FreeBSD 文档的stackoverflow 线程的证实:

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

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

于 2017-11-28T11:51:16.617 回答