------- Tor 源碼分析第三部分—— 日誌設施與智能鏈表 --------

————————————————————————————————————————————————————————————————————————————————————算法

init_logging()(\tor-0.3.1.8\src\common\log.c)內部邏輯以下圖所示:編程

 


它的任務是初始化 tor 使用的全局日誌設施;它首先檢測並初始化用於保護日誌信息和日誌文件的互斥鎖(log_mutex),它是一個 tor_mutex_t 對象數組

—— 請複習前一篇的相關討論——具體方式爲:安全

 判斷相同源文件內的全局變量 log_mutex_initialized 的值—— 0 表明日誌互斥鎖還沒有初始化,那麼它就調用 tor_mutex_init()數據結構

;顯然,實際負責初始化 log_mutex 的是 pthread 庫例程 pthread_mutex_init() 、而且把狀態標記 log_mutex_initialized 置 1 。函數

涉及到的全局變量以下圖:佈局

 

 

接下來,它檢查一枚全局的 smartlist_t 指針—— pending_cb_messages(基於 CallBack 的日誌消息隊列)——若該指針爲 NULL 則性能

調用 smartlist_new()(\tor-0.3.1.8\src\common\container.c)來初始化它。編碼

而後檢查傳入的實參——若爲 1,代表禁用啓動消息隊列,它就把全局變量 queue_startup_messages 修改成 0,意味着在等待配置日誌的過程當中不保spa

存消息;事實上,tor_main() 中爲該實參傳入 0 指示消息應該在早期就記錄下來,以下圖

 

這種狀況下,它再次調用 smartlist_new()來分配構建一個 smartlist_t 結構指針並賦給全局變量 pending_startup_messages,用於

 

處理啓動時刻日誌消息隊列。pending_startup_messages 指向的各種啓動消息(待輸出)會在日誌系統初始化完畢後從新播放出來。

涉及到的全局變量以下圖:

 

若是你已經頭暈了,那麼咱們就梳理一下目前爲止討論到全局變量和用途吧:

log_mutex—— 保護日誌文件和消息的互斥鎖;

log_mutex_initialized—— log_mutex 是否初始化;

pending_cb_messages—— 一枚 smartlist_t 指針(smartlist_t*),存儲基於 CallBack 的日誌消息隊列,可用 smartlist_new() 初始化它;

pending_startup_messages—— 一枚 smartlist_t 指針,存儲啓動時刻日誌消息隊列,可用 smartlist_new() 初始化它;

queue_startup_messages—— 啓動時刻是否記錄日誌消息;

disable_startup_queue—— init_logging() 的形參,調用者藉助它來開啓或關閉啓動時刻消息隊列功能

init_logging() 內部邏輯以下圖所示:

 

tor 實現了一種容量可調整、可存儲任意數據類型的智能鏈表——smartlist,它由結構體 smartlist_t 

 

(\tor-0.3.1.8\src\common\container.h)來表示。「container」亦即容器,該模塊抽象了許多數據結構來當成容器,

smartlist 只是其中之一。

smartlist_t 內有一個指針數組(list),每一個元素(void*)能夠指向任意類型的數據,且元素數量可調整,這就是它叫智能鏈表的緣由

,以下圖:

 

註釋裏寫得很清楚:list 數組的大小可調整,調整後須要更新 capacity 字段(當前容量上限);

num_used 字段記錄當前元素數,這些元素(void*)指向有效的數據。你須要意識到一點—— 在 num_used 小於等於 capacity 時,

list 的大小不變;num_used 大於 capacity 時,list 就會動態擴展,同時更新 num_used 和 capacity。

對 smartlist_t 的操做由一些精心編寫的例程實施,它們都以 smartlist_ 爲前綴,其它模塊函數無需知曉智能鏈表的內部細節,

只需調用這些例程來保證安全、正確地使用智能鏈表便可,這再次體現出數據抽象和封裝、以及接口暴露。。。等高級 C 編程技巧!

這些 smartlist 例程原型以下圖所示,分析它們的內部邏輯有助於深刻理解智能鏈表的設計思想:


smartlist_new() 在堆中分配一塊內存用於 smartlist_t 結構,而後返回一枚指向該內存塊的指針,以下圖所示:

 

 

能夠看到,實際的分配函數是 tor_malloc(),後者封裝了系統庫函數 malloc(),並實現 tor 本身的安全分配算法;分配的內存大小

亦即結構體 smartlist_t 的大小(應該是 12 字節),該值在編譯階段計算出來。在返回一枚 smartlist_t 指針前,它還會將 num_used 字段設置爲零,

表示還沒有加入元素;將 capacity 字段設置爲 SMARTLIST_DEFAULT_CAPACITY 宏定義的常量值(16)表示初始的容量上限爲 16 枚

void 指針(總大小爲 16 * 4 = 64 字節);接下來它就會調用 tor_calloc() 實際分配另外一塊 64 字節大小的堆內存,用於

存儲那些 void 指針,並讓 list 字段持有該內存塊的地址。smartlist_new() 返回後的堆內存佈局以下圖所示:


我在後面分析其它 smartlist_*() 輔助例程時,會將此圖擴展以解釋它們的做用。

smartlist_free() 銷燬一個智能鏈表,但它並未實際釋放堆中分配的 smartlist_t 結構與 void* 數組佔據的內存,以下圖:


smartlist_free() 經過調用 tor_free() -> raw_free() -> free() 把傳入的 smartlist_t 指針置 NULL 後返回,所以它不會回收

相關的堆內存,以下圖所示:

 

 

smartlist_add() 往智能鏈表(void* 數組)內加入新元素。

它首先調用 smartlist_ensure_capacity(),檢查當前的容量上限是否容許加入,不然會先動態調大這個 void* 數組的容量後再把新元素

追加到尾部。以下圖所示:

 

注意,它在調用 smartlist_ensure_capacity() 的同時就會經過一枚 smartlist_t 指針遞增 num_used 字段值(寓意追加後的

元素數),而後在 smartlist_ensure_capacity() 內部會檢查追加後的元素數是否超出當前容量上限,並採起相應措施!

smartlist_ensure_capacity() 處理完畢後,就能夠確保做爲數組下標的表達式 sl->list[sl->num_used++] 訪問到的目標元素在

許可範圍內,它被初始化爲一枚新的 void 指針,語法解析以下圖所示:

 

假設當前元素數已達初始上限(num_used = capacity = 16),相關的堆內存智能鏈表佈局以下圖:


執行 smartlist_add() 調用後的堆內存智能鏈表佈局以下圖:


因而可知,smartlist_ensure_capacity() 會按照 2 次冪來擴展 void* 數組的當前容量上限,而 smartlist_add() 在擴展部分

的起始地址處追加元素,擴展部分的其他元素爲 NULL,留待後續使用。


如今讓咱們深刻 smartlist_ensure_capacity() 內部研究它的堆內存分配算法,這種每次以 2 的冪擴展的機制在某種程度上相似於

OS 的內核內存分配算法簡化版!以下圖所示:


smartlist_ensure_capacity() 邏輯要點:

① 它是一個內聯(inline)例程,這意味着在編譯階段,編譯器會直接將其插入到調用者函數的內部,換言之,當咱們反彙編

smartlist_add() 時,不會看到相似「call smartlist_ensure_capacity」這種指令,由於後者的邏輯已經被硬編碼至前者內部,

這可以減小函數調用、返回時的棧幀建立、銷燬等性能開銷!

 

② 它的第二個參數類型爲 size_t(亦即 unsigned int),這是一種安全表示長度、大小的類型(無負值),如前所述,

smartlist_add() 會爲該參數傳入遞增 1 後的值,而 smartlist_ensure_capacity() 會判斷這個更新的值是否超出了 void* 數組

的當前上限;

 

③ 開頭部分的一些條件編譯塊計算 OS/硬件平臺支持的 void* 數組最大上限—— SIZE_MAX 定義在平臺相關的 limits.h 頭文件內,

以下圖所示,該頭文件位於 Visual Studio 安裝目錄的 include 子路徑下,它的值取決於編譯器,好比這裏的 0xFFFFFFFF,

就是十進制的 4,294,967,295 ;同理,INT_MAX 的值計算爲 2,147,483,647 ;

SIZEOF_VOID_P 的值在 32 位平臺上爲 4。

 

通過一系列的預計算,最終得出 MAX_CAPACITY(表示 void* 數組的最大上限,以「元素數」爲單位)的值爲

1,073,741,823 個元素(SIZE_MAX / (sizeof(void*)),也就是說,void* 數組最大到 4 GB

 

④ 用到了 tor_assert() 檢查追加後的元素數,當超過最大容量上限時,調用庫函數 abort() 終止程序運行;

tor_assert() 在前一篇講過了。

 

⑤ 若是追加後的元素數在當前容量上限許可內,smartlist_ensure_capacity() 直接返回,不作任何事,它的調用者

能夠安心執行後續操做;若是追加後的元素數超出當前上限,則每次按照 2 的冪爲倍數增大當前上限,直到可以容納

追加後的元素數,而且將擴展部分的內存初始化爲零,以供後續使用、還要更新 capacity 字段爲新的上限。


在分析源碼的算法時,每每言詞描述顯得蒼白無力,仍是看看下面這張我繪製的 smartlist_ensure_capacity() 內部邏輯吧,

是否是有點 OS 內核內存分配器的影子?

 

咱們最後再分析一下 smartlist_ensure_capacity() 內部實際負責擴展內存的 tor_reallocarray(),

以及負責清零內存的 memset() 函數調用,做爲對本篇的收場,以下圖所示:

 

由此咱們推測出 init_logging() 以及相關的組件使用 smartlist 來集中管理日誌消息。

下一篇將分析第二個條件編譯塊前的函數調用——monotime_init()。

——————————————————————————————————————————————————————

相關文章
相關標籤/搜索