1. 程式人生 > >連結裝載與庫 第8章 linux共享庫的組織

連結裝載與庫 第8章 linux共享庫的組織

由於動態連結的優點,大量的程式使用動態連結機制,導致系統裡面存在數量極為龐大的共享物件。必須得有很好的機制來管理這些共享庫,否則這些共享物件散落在各個目錄下,長期的維護,升級,都有會很大的問題。

8.1 共享庫版本

8.1.1 共享庫的相容性

共享庫的版本會不斷的更新,以修正原有的bug,增加新的功能,或改進效能。
共享庫的更新可以分為相容更新和不相容更新。
版本之間的相容性是共享庫面臨的一個大問題。共享庫的升級可能導致原有的程式不能正常執行,或發生非常微妙的錯誤,難以定位。

8.1.2 共享庫版本命名

linux規定共享庫的檔案命名規則如下:
libname.so.x.y.z
x 主版本號
y 次版本號
z 釋出版本號

主版本號表示庫的重大升級,不同版本號的庫之間是不相容的,依賴於舊的主版本號的程式必須要做出相應的改動並重新編譯才能使用新版本的共享庫

次版本號表示庫的增量升級。即增加一些新的介面符號,且保持原來的符號不變。在主版本號相同的情況下,高的次版本號的庫向後相容低的次版本號的庫。

釋出版本號表示庫的一些錯誤的修正,效能的改進,不新增介面。

(但是也有很多軟體不完全遵守這一規則,比如Glibc)

8.1.3 SO-NAME

SO-NAME是一種命名機制,用來記錄共享庫的依賴關係。
每個共享庫都有一個對應的SO-NAME,是共享庫的檔名去掉次版本號和釋出版本號,只保留主版本號。
在linux系統中,系統會為每個共享庫在它所在的目錄建立一個跟SO-NAME相同的並且指向它的軟體連結。
如果系統有多個主版本號相同,次版本號不同的共享庫,SO-NAME這個軟體連結會指定次版本號和釋出版本號最新的共享庫。
編譯輸出ELF檔案時,SO-NAME會被儲存到“.dynamic”段中。

這樣如果共享庫只是增量升級的時候,我們就可以用新版本的共享庫替換掉舊版本的共享庫,並將名為SO-NAME的軟體連結指向新版本的共享庫。

ldconfig這個工具可以遍歷所有的預設共享目錄,並更新所有的SO-NAME軟連結,使之指向最新版本的共享庫。

8.2 符號版本

SO-NAME由於只儲存了主版本號,那麼便存在一個問題,如果程式依賴的是高次版本號的共享庫,而環境上是低次版本號的共享庫,SO-NAME無法判斷出程式是否能正常工作。

基於符號的版本機制是對SO-NAME的一個補充。

8.2.1 基於符號的版本機制

共享庫的次版本號升級,都給在新的次版本號中新增的全域性符號打上相應的標記。
從而每個全域性符號都擁有對應的標籤。

在編譯連結應用程式的時候,連結器可以在程式的最終輸出檔案中記錄它所用到的最小的版本符號集合。

程式執行時,動態連結器通過程式內記錄的它所依賴的所有共享庫的符號集合的版本資訊來判定當前系統共享庫中的符號集合版本是否滿足要求。

8.2.2 solaris 中的符號版本機制

8.2.3 Linux中的符號版本

linux系統下的共享庫符號版本機制並沒有被廣泛應用。主要使用的是Glibc的軟體包中的20多個共享庫
GCC在Solaris符號版本機制上實現了2個擴充套件:

  1. 除了在符號版本指令碼中指定符號的版本,還可以使用“.symver”這個彙編指令指定
  2. 在連結層面提供符號過載機制。

8.3 共享庫系統路徑

linux在內的大多數開源作業系統都遵守FHS(file hierarchy standard)標準。規定了系統檔案應該如何存放,包括結構組織和作用。
FHS規定,一個系統主要有三個存放共享庫的位置:
/lib 存放系統最關鍵和基礎的共享庫。比如動態連結器,C語言執行庫,數學庫
/usr/lib 非系統執行時所需要的關鍵性的共享庫。主要是一些開發是用到的共享庫
/usr/local/lib 跟作業系統本身不十分相關的庫,主要是一些三方應用程式的庫。

另:
/bin VS /usr/bin VS /usr/local/bin
https://unix.stackexchange.com/questions/8656/usr-bin-vs-usr-local-bin-on-linux

8.4 共享庫的查詢過程

一個動態連結的模組所依賴的模組的路徑儲存在".dynamic"段中,由DT_NEED型別的項表示。如果DT_NEED表示的是絕對路徑,則動態連結器去對應的絕對路徑下查詢。如果是相對路徑,那麼動態連結器會在/lib, /usr/lib 和/etc/ld.so.conf檔案指定的目錄中查詢庫。
一般會在/etc/ld.so.cache中指定/usr/local/lib

linux系統中都有一個ldconfig程式,作用是為各個目錄下的共享庫建立,刪除和更新對應的SO-NAME。還會將SO-NAME收集起來,集中存放到/etc/ld.so.cache檔案裡面。先從/etc/ld.so.cache中查詢,查詢不到時才會去系統目錄下查詢。所以,如果在系統指定的共享庫目錄下新增更新或刪除庫後,都應該執行ldconfig命令,以便調整SO-NAME和/etc/ld.so.cache

8.5 環境變數

LD_LIBRARY_PATH
臨時改變應用程式的共享庫查詢路徑而不影響其他程式。
如果為某個程序設定了LD_LIBRARY_PATH,動態連結器會首先查詢LD_LIBRARY_PATH指定的目錄。

linux還在一種方法可以實現與LD_LIBRARY_PATH類似的功能。那就是直接執行動態連結器來啟動程式:
/lib/ld-linux.so.2 -library-path /path /bin/xxx

LD_LIBRARY_PATH對應共享庫的開發和測試十分方便,但它不應該被濫用。
LD_LIBRARY_PATH也會影響GCC編譯時查詢庫的路徑。相當於使用GCC的-L引數指定目錄。

LD_PRELOAD
指定預先裝載的一些庫甚至是目標檔案。在LD_PRELOAD中指定的檔案會在動態連結器按照固定的規則搜尋共享庫之前裝載。它比LD_LIBRARY_PATH裡面所指定的目錄還要優先,無論程式是否依賴它們,LD_PRELOAD指定的共享庫或目標檔案都會被裝載。

由於全域性符號介入機制的存在,LD_PRELOAD指定的檔案中的全域性符號就會覆蓋後面載入的同名全域性符號,這使得可以方便的改寫標準C庫中的某個或某幾個函式而不影響其他函式,對於程式的呼叫十分有用

LD_DEUBG
可以開啟動態連結器的除錯功能,動態連結器會列印有用的資訊,對於開發呼叫共享庫有很大的幫助。
可以設定以下選項:

  • files 列印整個裝載過程,顯示依賴哪些共享庫並且按照使用步驟裝載和初始化,共享庫裝載時的地址等。
  • bindings 顯示動態連結符號的繫結過程
  • libs 顯示共享庫的查詢過程
  • versions 顯示符號的版本依賴關係
  • reloc 顯示重定位過程
  • symbols 顯示符號的查詢過程
  • statistics 顯示動態連結過程中的各種統計資訊
  • all 顯示以上所有資訊
  • help 顯示上面的各種可選值的幫助項

8.6 共享庫的建立和安裝

8.6.1 共享庫的建立

gcc使用"-Wl"引數來將指定的引數傳遞給動態連結器。
如使用如下命令來生成一個共享庫:
gcc -shared -Wl,-soname,libhahaha.so.1 -o libhahaha.so.1.0.0 source_files -lxxx -lyyy
如果不使用-soname來指定共享庫的SO-NAME,那麼共享庫就沒有對應的SO-NAME。

個人理解-l選項指定的庫最終會儲存到“.dynamic”段的DT_NEED項中。

8.6.2 清除符號資訊

正常編譯出來的共享庫或可執行檔案中帶有符號資訊和除錯資訊,這些資訊在呼叫時非常有用。但對於最終釋出的版本來說,用處不大,可以通過strip工作清除掉。
strip xxx.so
也可以使用ld的“-s”或“-S”引數,使得連結器產生的輸出檔案不產生符號資訊。
-S 消除除錯符號資訊
-s 消除所有符號資訊
當然,這兩個引數也可以通過gcc傳遞給動態連結器

8.6.3 共享庫的安裝

  1. 最簡單的方法是複製到系統的共享庫目錄,然後執行ldconfig
  2. 拷貝到任意目錄,執行ldconfig -n shared_library_path

8.6.4 共享庫構造和析造函式

在函式宣告時加上
void attribute((constructor)) init_function(void);
擁有這種屬性的函式會在共享庫被載入時執行,即在程式的main函式執行之前

與建構函式相對應的是解構函式,在main函式執行完畢之後執行。
void attribute((destructor)) init_function(void);

如果有多個建構函式和解構函式,可以指定優先順序。
void attribute((constructor(5))) init_function(void);
void attribute((constructor(10))) init_function(void);

建構函式優先順序數字小的先執行,解構函式優先順序數字大的先執行。
一般是建構函式和對應的解構函式優先順序一樣。