(x | y) - y 为什么不能简单地写成 x,甚至是 `x | 0` 呢?

47

我在阅读内核代码时,在一个if语句中看到了一个表达式,就像这样

if (value == (SPINLOCK_SHARED | 1) - 1) {
         ............
}

SPINLOCK_SHARED = 0x80000000是一个预定义常量。

我想知道为什么我们需要(SPINLOCK_SHARED | 1) - 1进行类型转换?这个表达式的结果将是80000000,与0x80000000相同,不是吗?但是,为什么要进行按位或运算和减1操作呢?

感觉好像有些东西我没有理解到。。。


3
定义 SPINLOCK_SHARED 为 0x80000000(十六进制数)。 - RaGa__M
2
在这里,https://github.com/DragonFlyBSD/DragonFlyBSD/blob/master/sys/kern/kern_spinlock.c#L177 - RaGa__M
2
同一源代码文件还包含 if (atomic_cmpset_int(&spin->counta, SPINLOCK_SHARED|0, 1)) - Eric Postpischil
2
那么我认为我们需要询问作者为什么进行了更改。 - funnydman
1
常数在其他平台上可能已经设置了0位。不使用SPINLOCK_SHARED和-2的唯一猜测可能是某些处理器上进行有符号/无符号比较,spinlock | 1在某些地方被检查。 - Joop Eggen
显示剩余8条评论
6个回答

31

这段代码可以在_spin_lock_contested中找到,当其他人试图获取锁时,它会从_spin_lock_quick被调用:

count = atomic_fetchadd_int(&spin->counta, 1);
if (__predict_false(count != 0)) {
    _spin_lock_contested(spin, ident, count);
}
如果没有竞争,那么“count”(先前的值)应该为“0”,但它不是。这个“count”值被作为参数传递给“_spin_lock_contested”,作为“value”参数。然后,将检查此“value”是否与OP中的“if”匹配:
/*
 * WARNING! Caller has already incremented the lock.  We must
 *      increment the count value (from the inline's fetch-add)
 *      to match.
 *
 * Handle the degenerate case where the spinlock is flagged SHARED
 * with only our reference.  We can convert it to EXCLUSIVE.
 */
if (value == (SPINLOCK_SHARED | 1) - 1) {
    if (atomic_cmpset_int(&spin->counta, SPINLOCK_SHARED | 1, 1))
        return;
}

记住valuespin->counta的先前值,并且后者已经增加了1,因此我们期望spin->counta等于value + 1(除非在此期间发生了什么变化)。

因此,检查spin->counta == SPINLOCK_SHARED | 1atomic_cmpset_int的前提条件)相当于检查value + 1 == SPINLOCK_SHARED | 1,这可以重写为value == (SPINLOCK_SHARED | 1) - 1(同样地,如果在此期间没有发生任何变化)。

虽然value == (SPINLOCK_SHARED | 1) - 1可以重写为value == SPINLOCK_SHARED,但为了澄清比较的目的(即将递增的先前值与测试值进行比较),它保留不变。

换句话说,答案似乎是:为了澄清和代码一致性。


1
@RaGa__M: if 检查的意图是检查 value + 1(如果期间没有任何变化,应该与 spin->counta 相同)是否等于 SPINLOCK_SHARED | 1。如果将 if 检查写为 value == SPINLOCK_SHARED,则此意图不清楚,很难弄清检查的含义。在 if 检查中显式保留 SPINLOCK_SHARED | 1- 1 是有意的。 - Sander De Dycker
嗯...它可以简单地写成 value & SPINLOCK_SHARED,这样更易读。 - RaGa__M
@PabloH:看待同一件事情的不同方式:将值调整以适应当前状态与将测试值调整以适应先前状态。两种方法都是有效的,但最终你都在检查“value”,因此将其放在比较的左侧是有意义的。 - Sander De Dycker
@RaGa__M:你似乎没有抓住重点。目标不是使表达式更简单、更易读,而是使其更容易理解意图。这是两个不同的事情。通过简化表达式(如你所建议的),你会失去关于意图的信息,进而使代码的“为什么”更难理解。目前测试上方的注释很好地适配了代码并对其进行了澄清。但是,如果按照你的建议进行更改,那么情况就不再如此。必须添加更多的澄清来弥补代码中丢失的信息。 - Sander De Dycker
目标是,如果spinlock是共享的,则检查当前线程是否是唯一的执行者|1,如果是,则独占地获取它--设置1。@SanderDeDycker - RaGa__M
显示剩余6条评论

10

我认为目标可能是忽略最低有效位:

  • 如果SPINLOCK_SHARED用二进制表示为xxx0 -> 结果是xxx0
  • 如果SPINLOCK_SHARED = xxx1 -> 结果也是xxx0

也许使用一个位掩码表达式会更清晰吗?


8
这就是代码的作用,但问题是为什么你会对一个已定义常量执行这样的操作,特别是当该常量没有设置最低有效位时? - Sander De Dycker
4
因为 Linux 内核? - Lundin
9
Linux内核不会豁免可理解的编码实践,恰恰相反。 - Qix - MONICA WAS MISTREATED
1
@Lundin 我的意思是,我们不应该为此而努力。特别是因为Linux内核对于…嗯,全世界都如此重要。 - Qix - MONICA WAS MISTREATED
2
@Qix 如果你这么说的话。我曾经是 Linux 的忠实粉丝,直到我窥视了代码并阅读了内核编码风格文档。现在我离 Linux 计算机保持着 10 米的安全距离。 - Lundin
显示剩余8条评论

4

影响

(SPINLOCK_SHARED | 1) - 1

在与 value 进行比较之前,需要确保结果的低位比特已被清除。我认为这似乎是毫无意义的,但显然低位比特具有特定的用途或含义,在此代码中并不明显,我想我们必须假设开发人员对此有充分的理由。一个有趣的问题是 - 在您查看的代码库中是否使用了相同的模式(| 1)-1


2

这是一种写二进制掩码的混淆方式。可读版本是:value == (SPINLOCK_SHARED & ~1u)


5
是的,但是为什么。原帖提出了如果SPINLOCK_SHARED是一个已知常量,为什么会这样的问题。如果他们只是测试掩码中是否存在SPINLOCK_SHARED,为什么不使用if (value & SPINLOCK_SHARED)呢? - Qix - MONICA WAS MISTREATED
4
“value == (SPINLOCK_SHARED & ~1u)”不等同于“value == (SPINLOCK_SHARED | 1) - 1”,因为即使SPINLOCK_SHARED的类型比unsigned更宽,后者也可以正常工作。 - Eric Postpischil
4
说实话,我不确定 & ~1u 更清晰。在我的回答中,我想建议使用 & 0xFFFFFFFE,但我意识到这也不是很清晰。尽管如此,你的建议确实很简洁。 :-) - Bob Jarvis - Слава Україні
6
我们不确定它会是 0x80000000。OP已经说明它是用 #define SPINLOCK_SHARED 0x80000000 定义的,但这可能在 #if…#endif 内,并且在其他情况下使用了不同的定义,或者此代码的作者可能希望即使编辑了定义或使用其他定义它也能正常工作。无论如何,这两个代码片段本身并不等价。 - Eric Postpischil
2
@BobJarvis-ReinstateMonica 对于每天使用位运算符的人来说,这更清晰明了。将位运算与常规算术混合使用会令人困惑。 - Lundin
显示剩余7条评论

1
这只是为了更加清晰易懂而这样做的。这是因为atomic_fetchadd_int()(例如在sys/spinlock2.h中)返回加/减之前的值,并将该值传递给_spin_lock_contested()。
请注意,C编译器完全预计算所有常量表达式。事实上,当过程在这些参数中传递常量时,编译器甚至可以基于使用传递的过程参数的条件性地优化内联代码。这就是为什么sys/lock.h中的lockmgr()内联具有case语句的原因...因为整个case语句都将被优化并退化为对适当函数的直接调用。
此外,在所有这些锁定函数中,原子操作的开销比其他计算大两到三个数量级。
-Matt

这个答案来自作者。 - RaGa__M

0

这通常是为了处理一些额外的情况。例如,在这种情况下,我们说SPINLOCK_SHARED不能为1:

int SPINLOCK_SHARED = 0x01

int res = (SPINLOCK_SHARED | 1) - 1 // 0

2
这个说法有道理,但从问题中并不清楚,似乎 SPINLOCK_SHARED 是一个已定义的常量,而被测试的变量是 value。在这种情况下,谜团仍然存在。 - Roberto Caboni
谢谢您的回复,但我认为在原始情况下,SPINLOCK_SHARED不是0x01,您仍然保留了“| 1)-1”的部分。当SPINLOCK_SHARED持有“0x80000000”时,“| 1)-1”的影响会是什么? - RaGa__M
我能想到的唯一原因是他们想防止将来更改SPINLOCK_SHARED。但这一点并不明确。我会写信给内核开发人员,要求进行澄清或重新排列表达式以便自我记录。 - Qix - MONICA WAS MISTREATED

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接