DragonflyBSD:锁管理器(kern_lock.c)代码中可能存在竞争条件?
DragonflyBSD: possible race-condition in lock manager (kern_lock.c) code?
最近我一直在阅读 lock_manager (kern_lock.c) 代码,并遇到了一些我认为会产生竞争条件的场景。
第 1 步:
@undo_shreq(...):如果有待处理的升级请求,代码将重置 "LKC_UPREQ" 标志并执行 wakeup() 调用;这件事只有在
时才会发生
(count & (LKC_EXREQ | LKC_UPREQ | LKC_CANCEL)) &&
(count & (LKC_SMASK | LKC_XMASK)) == 0)
第 2 步:
现在,并行地,另一个线程 T2 正在尝试获取独占锁并且它达到了简单的条件(即)@lockmgr_exclusive(...)
if ((count & (LKC_UPREQ | LKC_EXREQ |
LKC_XMASK)) == 0 &&
((count & LKC_SHARED) == 0 ||
(count & LKC_SMASK) == 0))
因此,T2 将计数加 1 并将自己设置为所有者线程——这意味着它获得了独占性。
第 3 步:
线程 (T1) 在步骤 1 唤醒的 LKC_UPREQ 标志上休眠;这是睡眠后的代码(....在 LK_SLEEPFAIL 和睡眠错误健全性检查之后),@lockmgr_upgrade(...)
if ((count & LKC_UPREQ) == 0) { // reset by step 1
KKASSERT((count & LKC_XMASK) == 1); // true, by step 2
lkp->lk_lockholder = td;
break;
}
我明白了(如有错误请指正),在第 3 步,线程 T1 将 lk_lockholder 重置为其自身——意思是,它获得了独占性!
它的工作方式是,如果一个线程设置UPREQ然后休眠,另一个线程将授予它独占锁并将其唤醒。授予线程清除 UPREQ 并增加独占计数,但不知道 'who' 设置 UPREQ,因此由设置 UPREQ 的线程设置锁持有者字段。
由于 lockmgr 代码必须同时处理多独占到单共享饥饿、多共享到单独占饥饿情况以及死锁边缘情况,因此它相当复杂。在过去的一两年里发现了边缘案例,但这个特殊案例对我来说看起来不像是错误。只是有点混乱,因为排他锁被第二个线程授予(UPREQ 清除并且 excl 计数递增)但第一个线程仍然负责设置 lk_lockholder 字段。
最近我一直在阅读 lock_manager (kern_lock.c) 代码,并遇到了一些我认为会产生竞争条件的场景。
第 1 步:
@undo_shreq(...):如果有待处理的升级请求,代码将重置 "LKC_UPREQ" 标志并执行 wakeup() 调用;这件事只有在
时才会发生(count & (LKC_EXREQ | LKC_UPREQ | LKC_CANCEL)) &&
(count & (LKC_SMASK | LKC_XMASK)) == 0)
第 2 步:
现在,并行地,另一个线程 T2 正在尝试获取独占锁并且它达到了简单的条件(即)@lockmgr_exclusive(...)
if ((count & (LKC_UPREQ | LKC_EXREQ |
LKC_XMASK)) == 0 &&
((count & LKC_SHARED) == 0 ||
(count & LKC_SMASK) == 0))
因此,T2 将计数加 1 并将自己设置为所有者线程——这意味着它获得了独占性。
第 3 步:
线程 (T1) 在步骤 1 唤醒的 LKC_UPREQ 标志上休眠;这是睡眠后的代码(....在 LK_SLEEPFAIL 和睡眠错误健全性检查之后),@lockmgr_upgrade(...)
if ((count & LKC_UPREQ) == 0) { // reset by step 1
KKASSERT((count & LKC_XMASK) == 1); // true, by step 2
lkp->lk_lockholder = td;
break;
}
我明白了(如有错误请指正),在第 3 步,线程 T1 将 lk_lockholder 重置为其自身——意思是,它获得了独占性!
它的工作方式是,如果一个线程设置UPREQ然后休眠,另一个线程将授予它独占锁并将其唤醒。授予线程清除 UPREQ 并增加独占计数,但不知道 'who' 设置 UPREQ,因此由设置 UPREQ 的线程设置锁持有者字段。
由于 lockmgr 代码必须同时处理多独占到单共享饥饿、多共享到单独占饥饿情况以及死锁边缘情况,因此它相当复杂。在过去的一两年里发现了边缘案例,但这个特殊案例对我来说看起来不像是错误。只是有点混乱,因为排他锁被第二个线程授予(UPREQ 清除并且 excl 计数递增)但第一个线程仍然负责设置 lk_lockholder 字段。