簡介
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.h
和 kernel/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);
};
這些巨集叫用會宣告新的單例模式類型:AGlobalLock
和
MyType::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
取得鎖定時,可用的以下其中一個選項:IrqSave
、NoIrqSave
、
和 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
是直接取得鎖定的唯一方法。有
有兩個重要的原因:
- 手動取得/發布作業較防護容易出錯,而且 手動發布。
- 啟用鎖定驗證功能後,防護機制就能提供 驗證工具會用來將主動保留的鎖定納入考量。這個方法 如何在堆疊上暫時儲存驗證工具狀態 鎖定,與守衛物件的使用模式相對應。 如果不使用這個方法,追蹤資料就必須儲存在 不斷增加記憶體用量 (即使未鎖定,或 儲存於堆積分配的記憶體中這兩種替代方式都不理想。
在極少數情況下,可以使用 lock()
存取基礎鎖定
檢測鎖定的存取子。進行此操作時應謹慎操控
因此基礎鎖定可能會導致 Pod 狀態
鎖定以及鎖定驗證工具的狀態否則可能缺少鎖頭
否則可能導致死結。你收到警告!
Clang 靜態分析和檢測鎖定
鎖定檢測旨在與 Clang 靜態鎖定互通 以便查詢及分析在一般情況下,可使用檢測鎖定作為「Mutex」 能力,並在任何靜態鎖定註解中指定。
有兩個特殊情況需要特別留意:
- 傳回功能的指標或參照。
- 解鎖透過參照方式通過的守衛。
功能的指標和參考資料
透過指標或參照傳回鎖定時,可能會方便或有必要
使用統一類型先前提過,檢測鎖定已包裝完成
會擷取包含的型別、基礎鎖定類型和
行號,區分屬於不同類型的鎖定
(::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):完整功能說明文件。
鎖定驗證錯誤
鎖定驗證工具可偵測並回報兩種常見的違規行為:
- 客戶開發時回報成配對違規事項。
- 由專屬迴圈偵測以非同步方式回報多鎖定週期 。
客戶開發時回報的違規行為
在鎖定擷取作業的當下偵測到違規情況時,驗證工具會採取以下做法 會在核心記錄中產生類似以下的訊息:
[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
- 觸發迴圈偵測功能並回報找到的任何迴圈 加入核心記錄檔