在 Linux 開發時,我們經常會看到一些形如 xxx.so 的名稱出現,其中 so 是 Shared Object 的縮寫,即可以共享的目標檔案,也就是我們所稱為的動態連結庫,和在 Windows 下大家玩遊戲時遇到的 xxx.dll 錯誤中的檔案是一個型別的。
面試中經常會問到以下問題:
怎麼建立一個動態庫?動態庫檔案的字尾名是什麼?怎麼使用一個動態庫?動態庫的命名規範?系統預設的動態庫的查詢路徑?動態庫顯示連線所使用的系統庫是什麼?一、什麼是庫庫是寫好的現有的,成熟的,可以複用的程式碼。現實中每個程式都要依賴很多基礎的底層庫,不可能每個人的程式碼都從零開始,因此庫的存在意義非同尋常。本質上來說庫是一種可執行程式碼的二進位制形式,可以被作業系統載入記憶體執行。
庫有兩種:
靜態庫(.a、.lib)動態庫(.so、.dll)在一個程式的編譯過程中,分為以下幾個步驟:預處理,編譯,彙編,連結。本文中討論的連結庫就是針對最後一個步驟「連結」而言的。
動態庫和靜態庫的區別
左圖為靜態連結庫,右圖為動態連結庫
對於靜態連結庫而言在連結階段,會將彙編生成的「目標檔案.o」與引用到的庫一起連結打包到可執行檔案中。因此對應的連結方式稱為靜態連結:
靜態連結庫對函式庫的連結是放在編譯時期完成的。程式在執行時與函式庫就沒有了任何的聯絡。它比較浪費空間和資源,因為所有相關的目標檔案與牽涉到的函式庫被連結合成一個可執行檔案。靜態庫對程式的更新和釋出也會帶來麻煩。如果靜態庫更新了,所有使用它的應用程式都需要重新編譯、部署、釋出給使用者。靜態連結可以理解為最後生成了一個「單檔案免安裝綠色版」的程式,優點在於移植的時候只需要移動這一個檔案,缺點在於檔案體積非常大,為了解決這樣的問題,就有了動態連結庫。動態連結庫在程式編譯時並不會被連線到目的碼中,而是在程式執行時才被載入。
不同的應用程式如果呼叫相同的庫,那麼在記憶體裡只需要有一份該共享庫的例項,可以實現程序之間的資源共享。(因此動態庫也稱為共享庫)規避了空間浪費問題。動態庫在程式執行時才被載入,也解決了靜態庫對程式的更新、部署和釋出帶來的麻煩。使用者只需要更新動態庫即可將一些程序升級變得簡單,增量更新。動態庫連線到系統空間,如果多個程式連線了同一個庫,那麼只需要一份,優點在於編譯程式的時候不會將對應的庫檔案全部打包在生成的程式中,而是保留了到對應庫的連結,缺點就是移植的時候如果只移動了對應的程式沒有安裝相關的庫的話,就會看到類似以下喜聞樂見的結果了。
在 Linux 下一個動態庫有y三個不同名字的檔案組成:
soname 檔案lib + 連結庫名字 + .so + .版本號real name 檔案lib + 連結庫名字 + .so + .版本號.次版本號.發行號linker name 檔案lib + 連結庫名字 + .so當程式在內部列出所需要的連結庫時,僅僅使用 soname。當你建立一個連結庫時,使用 real name。安裝一個新的連結庫時,把它複製到一個DLL資料夾裡,然後執行程式 ldconfig。ldconfig 檢查存在的 real name 檔案,並且建立指向它符號連結 soname 檔案。可能大家比較常見到的有 libsodium 等。
二、建立一個動態庫有了上面關於庫的一些基礎知識之後,我們可以開始嘗試建立一個動態庫來供程式使用了。
比如我們有一個求最大值的函式 max(int a,int b,int c) ,放在檔案 max.c 中檔案內容如下:
可以通過:
將其編譯為共享庫,-fPIC是編譯選項,PIC是 Position Independent Code 的縮寫,表示要生成位置無關的程式碼,這是動態庫需要的特性; -shared是連結選項,告訴 gcc 生成動態庫而不是可執行檔案。為了讓使用者知道我們的動態庫中有哪些介面可用,我們需要編寫對應的標頭檔案,比如可以寫一個 max.h :
設定一個驅動函式來測試我們編寫的動態庫:
通過 gcc test.c -L. -lmax來生成 a.out,其中-lmax表示要連結 libmax.so,-L.表示搜尋要連結的庫檔案時包含當前路徑。
同一目錄下同時存在同名的動態庫和靜態庫,比如 libmax.so和 libmax.a都在當前路徑下,則 gcc 會優先連結動態庫。
但是這樣直接執行的話,會出現一個錯誤:
由於 Linux 是通過/etc/ld.so.cache檔案搜尋要連結的動態庫的,而 /etc/ld.so.cache 是 ldconfig 程式讀取 /etc/ld.so.conf 檔案生成的,本次使用的動態庫 libmax.so 並不在對應的目錄下,就會導致程式無法找到對應的動態連結庫,這樣我們的解決方法有二:
如果僅僅是本地使用,可以在編譯後指定一個環境變數:LD_LIBRARY_PATH=. ./a.out ,這樣程式會在本地尋找如果需要在系統層面共享這個庫,可以把 libmax.so 所在的路徑新增到 /etc/ld.so.conf 中,再以 root 許可權執行 ldconfig 程式,更新 /etc/ld.so.cache具體採用的方法因使用場景而異,如果僅僅是測試用途的話,可以直接使用新增環境變數的方式解決。小結
動態連結庫是各個系統中的一個重要的組成部分且在 Linux 開發相關領域中尤為重要,也是一個面試的高頻考點,除了動態連結庫以外,還有以下相關知識也是高頻考點,在面試前一定要準備好:
Linux 下 make 與 makefile。用什麼引數指定 makefile 檔案?什麼是預設的makefile 檔案?Linux 靜態庫的使用,怎麼建立一個靜態庫?怎麼使用一個靜態庫?靜態庫檔案的字尾名是什麼?靜態庫的命名規範?本文作者:Nova Kwok