Zircon 中的執行階段鎖定驗證

簡介

Zircon 整合執行階段鎖定驗證工具,可診斷鎖定不一致的問題 排序可能會導致死結。本文件將探討 驗證工具已整合 如何在建構期間啟用及調整驗證工具 驗證工具輸出的內容

如要瞭解驗證工具本身的運作理論,請前往 設計文件

啟用鎖定驗證工具

鎖定驗證功能預設為停用。停用鎖定檢測時 是透明的,適用於基礎鎖定的零負載包裝函式 基本問題

藉由設定 GN 建構引數,即可在編譯時間啟用驗證工具 enable_lock_dep 設為 true。在此變數中,此變數的編寫邏輯是 由 zircon/kernel/BUILD.gn 處理。

您可以在 GN 叫用中設定此變數,如下所示:

fx set <your build options> --args 'enable_lock_dep = true'

啟用鎖定驗證工具後,您就能使用一組通用的免鎖定資料 產生結構,以追蹤檢測設備之間的關係 鎖定功能。藉由擴增鎖定的取得/發布作業,進一步更新 這些資料結構

鎖定檢測

必須手動操作執行階段鎖定驗證工具目前的卸載 透過包裝函式類型來檢測核心中的每個鎖定。包裝函式類型提供了 驗證工具需要的內容來正確識別鎖定,並產生 具備相同結構定義或角色鎖定的全域追蹤結構。

核心會在 kernel/spinlock.h 中定義用於此目的的公用程式巨集, kernel/mutex.h

會員鎖定

使用鎖定成員的類型,如下所示:

#include <kernel/mutex.h>

class MyType {
public:
    // ...
private:
    mutable fbl::Mutex lock_;
    // ...
};

可以像以下這樣:

#include <kernel/mutex.h>

class MyType {
public:
    // ...
private:
    mutable DECLARE_MUTEX(MyType) lock_;
    // ...
};

請注意,包含的類型會傳遞到巨集 DECLARE_MUTEX(containing_type)。這個類型可提供驗證工具 需要區分「MyType」成員的鎖定與以下鎖定的鎖定: 其他型別的成員。

巨集 DECLARE_SPINLOCK(containing_type) 為 正在檢測 SpinLock 名成員。

有好奇的使用者來說,上述範例中的巨集會展開為這個類型。 運算式:::lockdep::LockDep<containing_type, fbl::Mutex, __LINE__>。這個 運算式會產生 lockdep::LockDep<> 類型的唯一例項化。 和包含型別的 有多個 Mutex。

全域鎖定

全域鎖定是以單例模式模式進行檢測。核心會定義 並用於 kernel/mutex.hkernel/spinlock.h 的公用程式巨集。

在 Zircon 全域鎖定通常是以全域/命名空間範圍定義,或 做為靜態成員

example.h:

#include <kernel/mutex.h>

extern fbl::Mutex a_global_lock;

class MyType {
public:
    // ...
private:
    static fbl::Mutex all_objects_lock_;
};

example.cpp:

#include "example.h"

fbl::Mutex a_global_lock;

fbl::Mutex MyType::all_objects_lock_;

這項檢測作業可宣告會宣告的單例模式,簡化宣告鎖定 皆可用於不同範圍,並自動處理 ODR 用途

example.h:

#include <kernel/mutex.h>

DECLARE_SINGLETON_MUTEX(AGlobalLock);

class MyType {
public:
    // ...
private:
    DECLARE_SINGLETON_MUTEX(AllObjectsLock);
};

這些巨集叫用會宣告新的單例模式類型:AGlobalLockMyType::AllObjectsLock。這些類型具有靜態的 Get() 方法 該物件會傳回基礎全域鎖定和所有必要的 這些指令。請注意,您不需要在 鎖定時,支援的範本類型會自動處理此事件。

巨集 DECLARE_SINGLETON_SPINLOCK(name) 為 宣告全域 SpinLock

後鎖

透過限定範圍的能力類型取得和釋出檢測鎖定 《Guard》和《GuardMultiple》。在核心中,這些類型是由 kernel/lockdep.h

簡易互斥鎖的 Guard 作業與 AutoLock 類似:

#include <kernel/mutex.h>

class MyType {
public:
    // ...

    int GetData() const {
        Guard<fbl::Mutex> guard{&lock_};
        return data_;
    }

    int DoSomething() {
        Guard<fbl::Mutex> guard{&lock_};
        int data_copy = data_;
        guard.Release();

        return DoWorkUnlocked(data_copy);
    }

private:
    mutable DECLARE_MUTEX(MyType) lock_;
    int data_{0} TA_GUARDED(lock_);
};

SpinLock 類型需要額外的範本引數才能選取 Guard 取得鎖定時,可用的以下其中一個選項:IrqSaveNoIrqSave、 和 TryLockNoIrqSave。省略其中一個類型標記後, 編譯時間錯誤。

#include <kernel/spinlock.h>

class MyType {
public:
    // ...

    int GetData() const {
        Guard<SpinLock, IrqSave> guard{&lock_};
        return data_;
    }

    void DoSomethingInIrqContext() {
        Guard<SpinLock, NoIrqSave> guard{&lock_};
        // ...
    }

    bool TryToDoSomethingInIrqContext() {
        if (Guard<SpinLock, TryLockNoIrqSave> guard{&lock_}) {
            // ...
            return true;
        }
        return false;
    }

private:
    mutable DECLARE_SPINLOCK(MyType) lock_;
    int data_{0} TA_GUARDED(lock_);
};

檢測全域鎖定的方式類似:

#include <kernel/mutex.h>
#include <fbl/intrusive_double_list.h>

class MyType : public fbl::DoublyLinkedListable<MyType> {
public:
    // ...

    void AddToList(MyType* object) {
        Guard<fbl::Mutex> guard{AllObjectsLock::Get()};
        all_objects_list_.push_back(*object);
    }

private:
    DECLARE_SINGLETON_MUTEX(AllObjectsLock);
    fbl::DoublyLinkedList<MyType> all_objects_list_ TA_GUARDED(AllObjectsLock::Get());
};

請注意,檢測鎖定不會沒有手動 Acquire()Release() 方法;使用 Guard 是直接取得鎖定的唯一方法。有 有兩個重要的原因:

  1. 手動取得/發布作業較防護容易出錯,而且 手動發布。
  2. 啟用鎖定驗證功能後,防護機制就能提供 驗證工具會用來將主動保留的鎖定納入考量。這個方法 如何在堆疊上暫時儲存驗證工具狀態 鎖定,與守衛物件的使用模式相對應。 如果不使用這個方法,追蹤資料就必須儲存在 不斷增加記憶體用量 (即使未鎖定,或 儲存於堆積分配的記憶體中這兩種替代方式都不理想。

在極少數情況下,可以使用 lock() 存取基礎鎖定 檢測鎖定的存取子。進行此操作時應謹慎操控 因此基礎鎖定可能會導致 Pod 狀態 鎖定以及鎖定驗證工具的狀態否則可能缺少鎖頭 否則可能導致死結。你收到警告!

Clang 靜態分析和檢測鎖定

鎖定檢測旨在與 Clang 靜態鎖定互通 以便查詢及分析在一般情況下,可使用檢測鎖定作為「Mutex」 能力,並在任何靜態鎖定註解中指定。

有兩個特殊情況需要特別留意:

  1. 傳回功能的指標或參照。
  2. 解鎖透過參照方式通過的守衛。

功能的指標和參考資料

透過指標或參照傳回鎖定時,可能會方便或有必要 使用統一類型先前提過,檢測鎖定已包裝完成 會擷取包含的型別、基礎鎖定類型和 行號,區分屬於不同類型的鎖定 (::lockdep::LockDep<Class, Locktype, Index>).因此,如果貴機構將 Pod 從統一 (虛擬) 介面 (例如核心) 傳回鎖定 Dispatcher::get_lock())。

幸運的是,有一個簡單的解決方式:每個檢測的鎖定作業也不例外 ::lockdep::Lock<LockType> 的子類別 (或簡稱為 Lock<LockType> 核心)。這個類型僅依附基礎 LockType,而非 因此您能夠順利使用 指標或參照類型,以更通用的方式參照檢測鎖定。 這種類型也可用在型別註解中。

以下說明模式,與 核心 Dispatcher 類型。

#include <kernel/mutex.h>


struct LockableInterface {
    virtual ~LockableInterface() {}
    virtual Lock<fbl::Mutex>* get_lock() = 0;
    virtual void DoSomethingLocked() TA_REQ(get_lock()) = 0;
};

class A : public LockableInterface {
public:
    Lock<fbl::Mutex>* get_lock() override { return &lock_; }
    void DoSomethingLocked() override {
        data_++;
    }
    void DoSomething() {
        Guard<fbl::Mutex> guard{get_lock()};
        DoSomethingLocked();
        // ...
    }
private:
    mutable DECLARE_MUTEX(A) lock_;
    int data_ TA_GUARDED(get_lock());
};

class B : public LockableInterface {
public:
    Lock<fbl::Mutex>* get_lock() override { return &lock_; }
    void DoSomethingLocked() override {
        // ...
    }
    void DoSomething() {
        Guard<fbl::Mutex> guard{get_lock()};
        DoSomethingLocked();
        // ...
    }
private:
    mutable DECLARE_MUTEX(B) lock_;
    char data_[32] TA_GUARDED(get_lock());
};

請注意,A::lock_ 的類型是 ::lockdep::LockDep<A, fbl::Mutex, __LINE__>B::lock_ 類型為 ::lockdep::LockDep<B, fbl::Mutex, __LINE__>。不過,這兩種類型 Lock<fbl::Mutex> 的子類別,因此可以在 中將其視為此型別 指標和參照運算式。

雖然這個方法相當方便,但 Clang 靜態分析的限制卻妨礙這項作業 我們理解 LockableInterface::get_lock() 等於 A::lock_B::lock_,甚至是其當地環境。所以 而在所有鎖定註解中使用 get_lock()

透過參考資源解鎖防護門

在極少數情況下,釋出儲存在 Guard 的 來自函式呼叫者的函式

TODO(eieio):完整功能說明文件。

鎖定驗證錯誤

鎖定驗證工具可偵測並回報兩種常見的違規行為:

  1. 客戶開發時回報成配對違規事項。
  2. 由專屬迴圈偵測以非同步方式回報多鎖定週期 。

客戶開發時回報的違規行為

在鎖定擷取作業的當下偵測到違規情況時,驗證工具會採取以下做法 會在核心記錄中產生類似以下的訊息:

[00002.668] 01032:01039> ZIRCON KERNEL OOPS
[00002.668] 01032:01039> Lock validation failed for thread 0xffff000001e53598 pid 1032 tid 1039 (userboot:userboot):
[00002.668] 01032:01039> Reason: Out Of Order
[00002.668] 01032:01039> Bad lock: name=lockdep::LockClass<SoloDispatcher<ThreadDispatcher, 316111>, Mutex, 282, (lockdep::LockFlags)0> order=0
[00002.668] 01032:01039> Conflict: name=lockdep::LockClass<SoloDispatcher<ProcessDispatcher, 447439>, Mutex, 282, (lockdep::LockFlags)0> order=0
[00002.668] 01032:01039> {{{module:0:kernel:elf:0bf16acb54de1ceef7ffb6ee4449c6aafc0ab392}}}
[00002.668] 01032:01039> {{{mmap:0xffffffff10000000:0x1ae1f0:load:0:rx:0xffffffff00000000}}}
[00002.668] 01032:01039> {{{mmap:0xffffffff101af000:0x49000:load:0:r:0xffffffff001af000}}}
[00002.668] 01032:01039> {{{mmap:0xffffffff101f8000:0x1dc8:load:0:rw:0xffffffff001f8000}}}
[00002.668] 01032:01039> {{{mmap:0xffffffff10200000:0x76000:load:0:rw:0xffffffff00200000}}}
[00002.668] 01032:01039> {{{bt:0:0xffffffff10088574}}}
[00002.668] 01032:01039> {{{bt:1:0xffffffff1008f324}}}
[00002.668] 01032:01039> {{{bt:2:0xffffffff10162860}}}
[00002.668] 01032:01039> {{{bt:3:0xffffffff101711e0}}}
[00002.668] 01032:01039> {{{bt:4:0xffffffff100edae0}}}

此為資訊和非嚴重錯誤。第一行可用來識別 以及發生核心鎖定違規的流程下一行會指出 違規類型接下來的兩行會識別找到的鎖定類型 與之前的觀察不一致:「鎖定」鎖定是 「衝突」是 Pod 中已受到的 這是與即將接收的鎖定不一致的 。以上所有行都是堆疊追蹤前方的堆疊追蹤 防止惡意鎖定

多鎖週期

透過專屬專屬專屬 ID,偵測三個或多個鎖定之間的圓形依附元件 迴圈偵測執行緒。因為這項偵測是在與 系統不會提供導致循環追蹤的鎖定作業。

迴圈偵測執行緒的報表如下所示:

[00002.000] 00000.00000> ZIRCON KERNEL OOPS
[00002.000] 00000.00000> Circular lock dependency detected:
[00002.000] 00000.00000>   lockdep::LockClass<VmObject, fbl::Mutex, 249, (lockdep::LockFlags)0>
[00002.000] 00000.00000>   lockdep::LockClass<VmAspace, fbl::Mutex, 198, (lockdep::LockFlags)0>
[00002.000] 00000.00000>   lockdep::LockClass<SoloDispatcher<VmObjectDispatcher>, fbl::Mutex, 362, (lockdep::LockFlags)0>
[00002.000] 00000.00000>   lockdep::LockClass<SoloDispatcher<PortDispatcher>, fbl::Mutex, 362, (lockdep::LockFlags)0>

在這個週期中涉及的每個鎖定,都會報告為一個群組。經常 兩個循環相依鎖定的任一執行緒,會由單一執行緒取得 導致手動偵測難以或無法執行。不過, 三個以上執行緒之間有死結也可能是真實的, 長期系統穩定性

核心指令

啟用鎖定驗證工具後,可以使用下列核心指令:

  • k lockdep dump - 傾印以下應用程式的依附元件圖表和連結集 (迴圈) 所有檢測的鎖定功能
  • k lockdep loop - 觸發迴圈偵測功能並回報找到的任何迴圈 加入核心記錄檔