一、公約
1. 庫的命名習慣
一個linux DLL 有三個不同名字的檔案組成
soname 檔案 lib + 連結庫名字 + .so + .版本號碼
每當連結庫介面改變時都遞增版本號碼。soname 檔案其實只是一個符號連結而已,指向他的real name 檔案。
real name 檔案 lib + 連結庫名字 + .so + .版本號碼.次版本號碼.發行號
發行號是可選的。該檔案包含實際代碼。
linker name 檔案 lib + 連結庫名字 + .so
編譯器以這個名字來請求指定的連結庫。
當程式在內部列出所需要的連結庫時,僅僅使用 soname。當你建立一個連結庫時,使用 real name。安裝一個新的連結庫時,把它複製到一個DLL檔案夾裡,然後運行程式 ldconfig(8)。ldconfig 檢查存在的 real name 檔案,並且建立指向它的符號連結 soname 檔案。ldconfig 還做一件事情就是建立 cache 檔案 /etc/ld.so.cache
ldconfig 不會建立 linker name 檔案,但是一般性 linker name 檔案在安裝連結庫的時候建立。linker name 檔案也只是一個符號連結,指向最新的 soname 檔案或 real name 檔案。建議指向 soname 檔案,因為當你更新庫以後,在編譯器連結的時候,一般總是想使用新的庫。
2. 庫的放置
DLL 必須放置在檔案系統的指定位置。多數開源軟體遵守GNU 標準:當分發原始碼的時候,庫預設安裝在 /usr/local/lib,命令安裝在 /usr/local/bin。該標準還定義了如何重寫這些預設標準以及如何調用安裝程式。
Filesystem Hierarchy Standard(FHS) 規定:多數庫應安裝在 /usr/lib,啟動時需要的庫安裝在 /lib,非系統庫應安裝在 /usr/local/lib
GNU 標準是針對開發人員的,FHS 是針對發行者的。
二、 庫是如何被使用的
在基於 GNU glibc 的系統上,包括所有 linux 系統,ELF 可執行二進位檔案的運行自動導致程式載入器被載入並且運行。在 linux 下,載入器是 /lib/ld-linux.so.X(X是版本號碼)。然後載入器搜尋、載入程式所要使用的動態連結程式庫。
被搜尋的資料夾清單儲存在檔案 /etc/ld.so.conf 裡。
在程式啟動的時候搜尋這些檔案夾是很沒有效率的,所以實際上使用緩衝。ldconfig(8) 預設讀取 /etc/ld.so.conf 檔案,在 DLL 檔案夾裡建立合適的符號連結,在 /etc/ld.so.cache 裡寫入一個緩衝。緩衝大大加速了庫的讀取。所以,當一個 DLL 被添加、刪除時,或DLL檔案夾被改變時都需要運行 ldconfig 程式,當安裝了一個新的 DLL 時,由軟體包管理器自動運行 ldconfig。當程式啟動時,裝載器實際使用的是緩衝。
環境變數
LD_LIBRARY_PATH
該變數裡所指定的檔案夾將會首先被搜尋,然後才會搜尋預設的 DLL 檔案夾。該變數對開發與測試比較有用,但不應該為了給普通使用者使用而設定。如果你不想設定該變數,在 linux 下你可以直接調用程式載入器,比如,你可以傳遞 PATH 參數給載入器代替該變數來運行程式: /lib/ld-linux.so.2 --library-path PATH EXECUTABLE
不帶參數執行載入器,可以得到更多協助。但是,不要這樣執行程式,僅供調試時使用。
LD_DEBUG
看名字就知道,是供調試使用的。該變數是dl*函數的開關,用來顯示正在做的事情的詳細資料。可以取值為:
files |
顯示so檔案的載入順序 |
bindings |
顯示關於符號幫定的資訊 |
libs |
顯示庫搜尋路徑的資訊 |
versions |
顯示版本依賴的資訊 |
help |
使用該值運行程式將會顯示可用的選項 |
三、建立動態連結程式庫
首先用 -fpic 或 -fPIC 選項建立要放入 DLL 中的目標檔案。使用該選項產生的程式碼是位置無關的代碼(DLL的必要條件)。使用 gcc 的 -Wl 選項傳遞 soname 參數給連結器。-Wl 選項裡不能有未轉義的 whitespace。使用如下命令建立 DLL: gcc -shared -Wl,-soname,your_soname \
-o library_name file_list library_list
舉個例子: gcc -fPIC -g -c -Wall a.c
gcc -fPIC -g -c -Wall b.c
gcc -shared -Wl,-soname,libmystuff.so.1 \
-o libmystuff.so.1.0.1 a.o b.o -lc
該例子首先建立了兩個與位置無關的目標檔案 a.o、b.o,然後產生了一個包含兩者的 DLL。注意:-g 選項使程式碼封裝含調試資訊,-Wall 選項用來產生警告,兩者並不是建立 DLL 必須的,但是建議加上。
不要 strip 所產生的 DLL,或使用編譯器參數 -fomit-frame-pointer,這樣做將會無法使用調試器。
-fPIC 選項總是可以使用,但產生的程式碼比使用 -fpic 的要大。-fpic 選項產生的程式碼比較小、快,但是有平台相關的限制,當建立 DLL 時,連結器將會告訴你是否符合限制。
連結器還有一個有用的選項 -rpath,可以用來指定程式在運行時搜尋DLL時的路徑,使用 gcc 時可以這樣傳遞參數給連結器: -Wl,-rpath,$(DEFAULT_LIB_INSTALL_PATH)
如果你使用了這個選項,就不用考慮 LD_LIBRARY_PATH 這個環境變數了。
四、安裝、使用動態連結程式庫
1.安裝在標準位置
最簡單的安裝方式是複製 DLL 到一個標準的 DLL 檔案夾(/usr/lib等)並且運行 ldconfig(8),然後手動建立 linker name 符號連結。
2.安裝在非標準位置
下面的命令將會在指定的檔案夾裡建立適當的 soname 符號連結。 ldconfig -n directory_with_shared_libraries
然後手動建立 linker name 檔案指向 soname 檔案。
編譯器的時候使用 -l、-L 參數指定需要連結的庫和庫所在的位置。
除非使用 -rpath 參數指定過執行階段程式庫搜尋路徑,否則在運行時也必須指定。
比如可以使用如下命令添加當前工作目錄到 LD_LIBRARY_PATH 來運行程式: LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH my_program
ldd 命令可以用來查看程式的依賴,例如: ldd /bin/ls
輸出的是 soname 列表