0

最近我一直在阅读 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) 在 LKC_UPREQ 标志上休眠,由步骤 1 唤醒;这是睡眠后的代码(...之后,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 重置为自身——意思是,它获得了排他性!

4

1 回答 1

1

它的工作方式是,如果一个线程设置了 UPREQ 然后休眠,另一个线程将授予它排他锁并将其唤醒。授予线程清除 UPREQ 并增加独占计数,但不知道“谁”设置了 UPREQ,因此由设置 UPREQ 的线程来设置锁持有者字段。

由于 lockmgr 代码必须同时处理多独对单共享饥饿、多共享对单独占饥饿情况和死锁边缘情况,因此它相当复杂。在过去的一两年中发现了边缘案例,但这个特殊案例对我来说看起来不像是一个错误。只是有点令人困惑,因为第二个线程授予了排他锁(清除了 UPREQ 并增加了 excl 计数),但第一个线程仍然负责设置 lk_lockholder 字段。

于 2020-01-29T17:34:32.877 回答