Zircon 執行緒安全性註解

Zircon 程式碼利用 clang 的執行緒安全分析功能,記錄並機器學習驗證某些不變性資料。為 clang 建構時,系統會檢查這些註解 (請參閱入門指南,瞭解如何使用 Clang 進行建構)。

使用方法

Clang 說明文件

在 Zircon 中,我們提供一組會包裝註解的巨集,並為其同步基本功能加上註解。編寫涉及同步處理或註解現有程式碼的新程式碼時,在大多數情況下,建議您使用 <lib/zircon-internal/thread_annotations.h 提供的執行緒註解巨集。這些巨集全都會以前置字串 "TA_" 開頭,用於執行緒分析。最常用的項目如下:

  • TA_GUARDED(x):鎖定的變數受到能力 (例如鎖定) 保護x
  • TA_ACQ(x...) 函式會取得集合 x 中的所有互斥鎖,並在傳回後保留這些互斥鎖
  • TA_REL(x...) 函式會釋放組合 x 中的所有互斥鎖
  • TA_REQ(x...) 函式會要求呼叫端保留集合 x 中的所有互斥鎖。
  • TA_EXCL(x...) 函式會要求呼叫端不得擁有集合 x 中的任何互斥鎖

舉例來說,如果類別包含受互斥鎖保護的成員變數 'int foo_',就會加上以下註解:

// example.h

class Example {
public:
    // Public function has no locking requirements and thus needs no annotation.
    int IncreaseFoo(int by);

private:
    // This is an internal helper routine that can only be called with |lock_|
    // held. Calling this without holding |lock_| is a compile-time error.
    // Annotations like TA_REQ, TA_ACQ, TA_REL, etc are part of the function's
    // interface and must be on the function declaration, usually in the header,
    // not the definition.
    int IncreaseFooLocked(int by) TA_REQ(lock_);

    // This internal routine requires that both |lock_| and |foo_lock_| be held by the
    // caller.
    int IncreaseFooAndBarLocked(int foo_by, int bar_by) TA_REQ(lock_) TA_REQ(bar_lock_);

    // The TA_GUARDED(lock_) annotation on |foo_| means that |lock_| must be
    // held to read or write from |foo_|.
    int foo_ TA_GUARDED(lock_);

    // |lock_| can be declared after annotations referencing it,
    // if desired.
    Mutex lock_;

    Mutex bar_lock_;
};

// example.cpp

int Example::IncreaseFoo(int by) {
    int new_value;
    {
        AutoLock lock(&lock_);  // fbl::AutoLock is annotated
        new_value = IncreaseFooLocked(by);
    }
    return new_value;
}

請注意,在註解中,可允許多個互斥物件組合,一個註解可以多次套用註解,或為註解提供以半形逗號分隔的清單。也就是說,以下兩個宣告是相等的。

    int IncreaseFooAndBarLocked(int foo_by, int bar_by) TA_REQ(lock_) TA_REQ(bar_lock_);
    int IncreaseFooAndBarLocked(int foo_by, int bar_by) TA_REQ(lock_, bar_lock_);

透過 sysroot 公開的程式庫程式碼必須使用 system/public/zircon/compiler.h 提供的較不明確命名巨集,以免與 sysroot 的消費者發生衝突。

最佳做法

註解應與註解和 ID 相輔相成,讓程式碼更容易理解。註解不會取代註解或清除名稱。編寫與鎖定相關的程式碼時,請嘗試遵循下列最佳做法:

  • 受鎖定功能保護的群組成員變數。如果空間足夠,除了註解外,也請記錄受註解保護的內容。舉例來說,如果多個成員變數都受一個鎖定保護,而數個變數都受不同的鎖定保護,則註解更容易閱讀,比逐一檢視註解一樣容易閱讀。

  • 命名需要鎖定的函式後,加上「Locked()」後置字串。如果可能有多個鎖定可以呼叫函式,請考慮在函式名稱中清楚說明選擇。請記住,呼叫程式碼的讀者無法查看註解。

限制

執行緒安全分析是純粹在編譯期間完成的靜態檢查,無法理解有條件保留的鎖定或鎖定的模式,這些模式會以無法透過靜態註解表示的方式表達。在許多情況下,這項分析仍然實用,但有時分析會無法理解。停用分析的主要逸出方法是將註解 TA_NO_THREAD_SAFETY_ANALYSIS 新增至函式定義中,其中包含會混淆分析的程式碼。您也可以使用其他逸出機制,詳情請參閱 Clang 說明文件。需要停用分析的情境可能對人類和機器而言可能很複雜,因此應附上表示使用中不變數的註解。

執行緒安全性分析可透過多種方式取消,例如使用指標時。舉例來說,當處理受保護資料成員的地址時,Clang 會失去防護措施,例如,如果呼叫 memset(&foo_, 0, sizeof(foo_)) 未按住鎖頭_,就不會視為違規。