调用 spin_lock_irqsave,而不是 local_irq_disable 后跟 spin_lock,对于每个处理器结构是否相同?
Is calling spin_lock_irqsave, instead of local_irq_disable followed by spin_lock, the same for a per-prorcessor struct?
考虑以下内核代码
local_irq_disable();
__update_rq_clock(rq);
spin_lock(&rq->lock);
rq
是指向每个处理器 struct
的指针(即不受 SMP 并发影响)。因为 rq
在调用 local_irq_disable
之后将永远不会在另一个地方被访问(因为 rq
只被一个处理器使用并且禁用本地中断意味着没有中断处理程序将 运行 on那CPU),那么在前面的函数之间嵌入__update_rq_clock
有什么意义呢?换句话说,考虑到 rq
在 __update_rq_clock
内的两种情况下无论锁定与否都是安全的,它与以下内容有什么区别,它禁用中断并在单个调用中获取锁定?
spin_lock_irqsave(&rq->lock, flags);
__update_rq_clock(rq);
首先也是最重要的:您展示的两个示例具有不同的语义:local_irq_disable
不保存 IRQ 的旧状态。换句话说,当调用相应的 local_irq_enable
函数时,它将 强制 重新启用 IRQ(无论它们是否已被禁用)。另一方面,spin_lock_irqsave
确实保存了旧的 IRQ 状态,因此以后可以通过 spin_unlock_irqrestore
恢复它。因此,您展示的两段代码非常不同,比较它们的意义不大。
现在,进入真正的问题:
Since rq
will never be accessed in an another place after calling local_irq_disable
(because rq
is used by only a single processor and disabling local interrupts means no interrupt handlers will run on that CPU)
这并不总是正确的。没有阻止 CPU 访问另一个 CPU 的每个 CPU 数据的“魔法屏障”。这仍然是可能的,在这种情况下,必须通过适当的锁定机制来格外小心。
虽然每个 CPU 变量通常旨在为单个 CPU 提供对对象的快速访问,因此 可以 具有以下优势不需要锁定,除了约定之外没有什么可以阻止处理器挖掘其他处理器的每个CPU数据(quote)。
运行队列就是一个很好的例子:由于调度程序经常需要将任务从一个运行队列迁移到另一个运行队列,因此它肯定会在某个时刻同时访问两个运行队列。事实上,这可能是 struct rq
有一个 .lock
字段的原因之一。
事实上,在不保持 rq->lock
的情况下进行 rq 时钟更新似乎被认为是最近内核代码中的一个错误,正如您从 update_rq_clock()
中的 this lockdep assertion 中看到的那样:
void update_rq_clock(struct rq *rq)
{
s64 delta;
lockdep_assert_held(&rq->lock);
// ...
感觉你在第一个代码片段中显示的语句应该重新排序为先锁定然后更新,但是代码很旧(v2.6.25),并且调用 __update_rq_clock()
似乎是在获取锁之前故意制作的。很难说出为什么,但也许旧的运行队列语义不需要锁定来更新 .lock
/.prev_clock_raw
,因此之后进行锁定只是为了最小化临界区的大小。
考虑以下内核代码
local_irq_disable();
__update_rq_clock(rq);
spin_lock(&rq->lock);
rq
是指向每个处理器 struct
的指针(即不受 SMP 并发影响)。因为 rq
在调用 local_irq_disable
之后将永远不会在另一个地方被访问(因为 rq
只被一个处理器使用并且禁用本地中断意味着没有中断处理程序将 运行 on那CPU),那么在前面的函数之间嵌入__update_rq_clock
有什么意义呢?换句话说,考虑到 rq
在 __update_rq_clock
内的两种情况下无论锁定与否都是安全的,它与以下内容有什么区别,它禁用中断并在单个调用中获取锁定?
spin_lock_irqsave(&rq->lock, flags);
__update_rq_clock(rq);
首先也是最重要的:您展示的两个示例具有不同的语义:local_irq_disable
不保存 IRQ 的旧状态。换句话说,当调用相应的 local_irq_enable
函数时,它将 强制 重新启用 IRQ(无论它们是否已被禁用)。另一方面,spin_lock_irqsave
确实保存了旧的 IRQ 状态,因此以后可以通过 spin_unlock_irqrestore
恢复它。因此,您展示的两段代码非常不同,比较它们的意义不大。
现在,进入真正的问题:
Since
rq
will never be accessed in an another place after callinglocal_irq_disable
(becauserq
is used by only a single processor and disabling local interrupts means no interrupt handlers will run on that CPU)
这并不总是正确的。没有阻止 CPU 访问另一个 CPU 的每个 CPU 数据的“魔法屏障”。这仍然是可能的,在这种情况下,必须通过适当的锁定机制来格外小心。
虽然每个 CPU 变量通常旨在为单个 CPU 提供对对象的快速访问,因此 可以 具有以下优势不需要锁定,除了约定之外没有什么可以阻止处理器挖掘其他处理器的每个CPU数据(quote)。
运行队列就是一个很好的例子:由于调度程序经常需要将任务从一个运行队列迁移到另一个运行队列,因此它肯定会在某个时刻同时访问两个运行队列。事实上,这可能是 struct rq
有一个 .lock
字段的原因之一。
事实上,在不保持 rq->lock
的情况下进行 rq 时钟更新似乎被认为是最近内核代码中的一个错误,正如您从 update_rq_clock()
中的 this lockdep assertion 中看到的那样:
void update_rq_clock(struct rq *rq)
{
s64 delta;
lockdep_assert_held(&rq->lock);
// ...
感觉你在第一个代码片段中显示的语句应该重新排序为先锁定然后更新,但是代码很旧(v2.6.25),并且调用 __update_rq_clock()
似乎是在获取锁之前故意制作的。很难说出为什么,但也许旧的运行队列语义不需要锁定来更新 .lock
/.prev_clock_raw
,因此之后进行锁定只是为了最小化临界区的大小。