————————————————————————————————————————————————————————————————————————————————————算法
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()。
——————————————————————————————————————————————————————