——————————————————————————————————————————————————————————程序員
第二部分僅考察下圖所示的代碼片斷——configure_backtrace_handler() 後面的五條函數調用序列;在這些看似簡潔的編程
邏輯背後其實蘊涵樂許多「類 UNIX」系統相關的概念,所以或許要用到整個篇幅來說解。首先,從這些自注釋的函數名稱來看,緩存
無非就是更新系統時間的估計值、tor 的線程和壓縮功能初始化、日誌系統初始化,以及初始化單調定時器子系統(monotime_init):數據結構
調用 UNIX 庫函數 time(),把返回的當前時間(一個 time_t 結構實例,通常定義在類 UNIX 文件系統的 /user/include/time.h 頭文件中)傳入 多線程
update_approx_time()(\tor-0.3.1.8\src\common\util.c),後者用它來初始化全局(靜態)的 cached_approx_time 變量,表明緩存當前時間的估計值,相關代碼片斷以下:架構
值得一提,tor 源碼樹中的「common」路徑下包含一些各組件都會利用到的公共設施,好比對類 UNIX 系統機制——pthread——的app
封裝例程、對 OpenSSL 庫的封裝例程、對開源事件通知庫 libevent 的封裝例程。。。。等等。函數
因爲 update_approx_time() 每秒被調用一次,cached_approx_time 也就每秒更新一次,此後就能夠隨時利用 approx_time() 查詢最近一次 update_approx_time() 調用所學習
更新的 cached_approx_time。ui
tor 用上述邏輯實現了本身的計時體系,避免在關鍵路徑上直接調用庫函數 time(),這是一種編程訣竅(hack)!
除了前述的 Win32 平臺相關代碼和註冊崩潰回調外,update_approx_time() 在任何 tor 組件初始化前就被調用,體現出時間參照的重要性。
——————————————————————————————————————————————————————————————————————————————————————————————
tor_threads_init()(\tor-0.3.1.8\src\common\compat_pthreads.c)設立線程使用的公共結構,而後調用
set_main_thread() -> tor_get_thread_id() -> pthread_self() 把當前線程標記爲主線程(存儲在全局變量 main_thread_id 內)。
事實上,compat_pthreads.c 中全部的多線程支持函數都依賴於 UNIX/Linux 平臺上的 pthread 機制。
而因爲本系列博文的目的是考察 tor 的原理和架構,因此我不想試圖陷入系統庫函數底層代碼中分析,相關的代碼片斷以下:
上圖中的 threads_initialized 是一個全局變量,它扮演着一種狀態標記的角色,綜觀 tor 源碼隨處可見此類編程技巧——
這些狀態起初被設置爲 0,而後一些相關的例程(名稱中帶有 init )會經過檢測這些標記來判斷是否執行過初始化任務,好比
上圖中,若 threads_initialized = 0 則調用 pthread 系列的函數來初始化線程用到的一些公共設施(attr_recursive、attr_detached),它們都是一些全局的結構實例,
相關的代碼片段以下:
源碼裏的註釋已經寫得很清楚了:結構體 pthread_attr_t 表示一個使線程開始分離的 pthread 屬性;
結構體 pthread_mutexattr_t 表明一個互斥鎖屬性,它被指定爲「遞歸」性質的——在已經持有該鎖的狀況下還能夠從新上鎖。
如前所述,碰到平臺相關的庫函數時,如非特別重要,我通常會忽略分析,把精力集中在 tor 程序的邏輯上。
談到 tor_threads_init() 結尾處的 set_main_thread() -> tor_get_thread_id() -> pthread_self() 調用邏輯,相關的代碼片斷以下:
你能夠在上圖看見 tor_get_thread_id() 在內部定義了一個聯合(union),其中具有一個「id」字段,它最終被返回並賦給全局變量 main_thread_id 。
值得關注的另外一個焦點是 tor_assert 宏(\tor-0.3.1.8\src\common\util_bug.h),它根據表達式的求值結果採起相應的措施,相關的代碼片斷以下:
註釋中提到,tor_assert 在斷言失敗的狀況下會發送錯誤消息到日誌、標準錯誤(stderr),而後調用系統服務 abort(),終止程序,
所以用到 tor_assert 的地方想必都是一些攸關 tor 可否正常運行的檢查邏輯!
——————————————————————————————————————————————————————————————————
\tor-0.3.1.8\src\common\compress.h 中定義了一些數據壓縮辦法、壓縮級別(程度)。
其中僅有 gzip 與 zlib 明確被 tor_compress()(壓縮)和 tor_uncompress()(解壓)支持,相關的代碼片斷以下:
tor_compress_init() 函數體位於相同路徑下的 compress.c 文件內:它首先調用 atomic_counter_init() 初始化一個全局變量
total_compress_allocation(一個 atomic_counter_t 結構實例),描述爲壓縮狀態分配的總字節開銷;
atomic_counter_init() 執行 memset() 將整個結構內容初始化爲 0,接着調用 tor_mutex_init_nonrecursive() 處理該結構的 mutex 字段。
tor_compress_init() 還初始化了全部的壓縮模塊,包括 zlib,以及不常見的 lzma、zstd 等壓縮辦法。而實際的初始化邏輯僅僅是構建並歸零爲各模塊狀態分配的字節計數器
(都是些 atomic_counter_t 對象),相關的代碼片斷以下:
在分析 atomic_counter_init() 內部邏輯以前,有必要先來討論一下 tor 獨創的 atomic_counter_t 結構
(\tor-0.3.1.8\src\common\compat_threads.h)——後文簡稱「原子計數器」! 相關的代碼片斷以下:
由此可知,atomic_counter_t 是一個複合結構,其內包含了 tor_mutex_t 結構,然後者在 Win32 平臺下,是對臨界區(CRITICAL SECTION)的封裝;
在支持 pthread 的類 UNIX 平臺上,則是對 pthread_mutex_t 結構的封裝。示意圖以下,這種兼容各平臺的設計思想確實值得學習:
而對於 Windows 特有的 CRITICAL SECTION 原生支持則經過下面的條件編譯塊實現:
能夠從上圖看到,在 Windows 平臺上,tor_mutex_* 系列的函數都封裝了 Windows 特有的臨界區相關 API——
tor_mutex_init_nonrecursive() -> InitializeCriticalSection()
tor_mutex_uninit() -> DeleteCriticalSection()
tor_mutex_acquire() -> EnterCriticalSection()
tor_mutex_release() -> LeaveCriticalSection()
原子計數器結構內的「mutex」成員封裝了系統底層的互斥鎖,用來保護對母結構(亦即 atomic_counter_t)的同步/互斥訪問;
而真正要保護的對象則是其內的「val」成員,它實現了計數器的功能,像 tor_compress_init() 就用該字段來記錄爲壓縮狀態分配的總字節開銷;
實際上,tor 提供了專門的例程來操縱原子計數器,而且源碼註釋也建議程序員經過此類例程來訪問它,而不要直接修改此數據結構,避免預料以外的錯誤操做!
這體現了面向對象編程中的「對象方法」思惟,相關的代碼片斷以下:
如您所見,訪問「val」字段前(不管是增長仍是刪減)都須要先獲取互斥鎖,修改完後再釋放互斥鎖。注意,此類支持例程都接收一枚原子計數器指針,
因此調用它們時,須要傳入一個原子計數器結構的地址,就像在 tor_compress_init() 內那樣:
atomic_counter_init(&total_compress_allocation);
同理,像 &counter->mutex 此類運算,是先經過形參 counter(原子計數器指針)引用到 mutex 成員,而後取它的地址,這樣才
與它們的參數類型匹配(經過指針選擇結構成員 -> 的優先級,高於取地址操做符 & 的優先級),相關的代碼片斷以下:
從上圖可知,tor_mutex_init_nonrecursive() 的形參類型爲一枚 tor_mutex_t 指針,因此 atomic_counter_init() 在調用它
時,傳入了 &counter->mutex ,再次體現指針與地址的等價性。
另外咱們從上圖瞭解到:歸根究底,仍是要經過 pthread_mutex_init() 來初始化原子計數器內的互斥鎖。。。。。
tor_compress_init() 內部的流程能夠粗略總結以下圖:
——————————————————————————————————————————————————————
限於篇幅,第三部分將分析剩餘的兩條函數調用—— init_logging() 與 monotime_init()。