瞭解動態連結(三)—— 共用模組的全域變數問題,動態全域變數
假設 module.c 中引用了一個共用模組中定義的全域變數 global:
1 extern int global;2 3 int foo() {4 global = 1;5 }
編譯器無法確定變數 global 的定義是在模組內部還是外部。假設 module.c 是可執行檔的一個源檔案,可執行程式不是 PIC 的,不會進行重定位。連結器會在 .bss 段建立一個 global 變數的副本,這樣造成同一個變數同時存在於多個位置。問題的解決辦法是讓所有對變數 global 的訪問都指向可執行檔中的那個副本。
ELF 共用庫在編譯時間,預設把所有全域變數都當作是定義在其他模組中,通過 GOT 表實現外部存取。當共用模組被裝載時,如果某個全域變數在可執行檔中擁有副本,動態連結器就把 GOT 表中的相應地址指向該副本。如果該變數在可執行檔中沒有副本,那麼 GOT 表中的相應地址就指向模組內部的該變數副本。
假設 libx.so 中定義了一個全域變數 G,進程A和B都使用 libx.so。那麼當 libx.so 被兩個進程載入時,它的資料區段在每個進程中都有獨立的副本,所以進程 A 和 B 訪問的都是自己進程中的那個全域變數 G 的副本,相互之間沒有影響。但如果是同一個進程的線程 A 和 B,則他們訪問的是同一個副本。
而有時希望同一個進程中的不同線程,也訪問全域變數的不同副本,這樣可以避免線程之間對全域變數的幹擾,或者避免做線程同步。這可以通過線程私人儲存(Thread Local Storage, TLS) 來實現。在 Android 系統中,TLS是藉助副處理器來實現的,在 Linker 和 getpid 等函數的實現中都能看到有關 TLS 的代碼。
有時也會希望多個進程共用同一個全域變數的副本,藉此實現處理序間通訊。記得以前寫 Windows DLL 時,有“共用資料區段”的概念就是實現這個的。
學習資料: 《程式員的自我修養——連結、裝載和庫》