名詞介紹
靜態函數庫
這類庫的名字一般是libxxx.a;利用靜態函數庫編譯成的文件比較大,因爲整個 函數庫的所有數據都會被整合進目標代碼中,他的優點就顯而易見了,即編譯後的執行程序不需要外部的函數庫支持,因爲所有使用的函數都已經被編譯進去了。當然這也會成爲他的缺點,因爲如果靜態函數庫改變了,那麼你的程序必須重新編譯。動態函數庫
這類庫的名字一般是libxxx.so;相對於靜態函數庫,動態函數庫在編譯的時候 並沒有被編譯進目標代碼中,你的程序執行到相關函數時才調用該函數庫裏的相應函數,因此動態函數庫所產生的可執行文件比較小。由於函數庫沒有被整合進你的程序,而是程序運行時動態的申請並調用,所以程序的運行環境中必須提供相應的庫。動態函數庫的改變並不影響你的程序,所以動態函數庫的升級比較方便。
linux系統有幾個重要的目錄存放相應的函數庫,如/lib /usr/lib
靜態庫在程序編譯時會被連接到目標代碼中,程序運行時將不再需要該靜態庫。
動態庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行是才被載入,因此在程序運行時還需要動態庫存在。
本文主要通過舉例來說明在Linux中如何創建靜態庫和動態庫,以及使用它們。
創建源文件
在創建函數庫前,我們先來準備舉例用的源程序,並將函數庫的源程序編譯成.o文件。
編輯以下幾個文件
hello.c
#include<stdio.h>
void hello(const char *name){
printf("Hello %s!\n",name);
}
hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif
main.c
#include<hello.h>
int main(){
hello("Kevin");
return 0;
}
編譯和使用靜態鏈接庫.a
生成.o目標文件
gcc -c hello.c生成.a靜態鏈接庫文件(打包成一個壓縮包,壓縮包裏面就是hello.o)
ar cr libmyhello.a hello.o
注:ar是用來創建,修改,和解壓歸檔;c是ar的參數,表示創建一個歸檔;r是ar的參數,表示插入文件到歸檔裏面。所以最終創建了libmyhello.o的歸檔,並往裏面插入hello.o使用靜態鏈接庫
gcc -o hello main.c -static -L. -lmyhello
注:-o 輸出文件名
-static 表示鏈接靜態鏈接庫。如果不使用該參數,而-L指定的目錄下同時擁有靜態鏈接庫和動態鏈接庫,gcc會默認使用動態鏈接庫。如果只有靜態鏈接庫,則會使用靜態鏈接庫。
-L 表示添加一個目錄到目錄列表,使得可以被-l用來搜索。
-l libary,當鏈接時,查找該library。
本質上,靜態鏈接庫就是.o文件的集合
編譯和使用動態鏈接庫.so
- 編譯動態鏈接庫.so
gcc -shared -fPIC -o libmyhello.so hello.c
注:-shared 表示生成共享庫(也就是動態鏈接庫)
-fPIC 作用於編譯階段,告訴編譯器產生與位置無關代碼(Position-Independent Code),則產生的代碼中,沒有絕對地址,全部使用相對地址,故而代碼可以被加載器加載到內存的任意位置,都可以正確的執行。這正是共享庫所要求的。共享庫被加載時,在內存的位置不是固定的。
PIC就是position independent code PIC使.so文件的代碼段變爲真正意義上的共享
如果不加-fPIC,則加載.so文件的代碼段時,代碼段引用的數據對象需要重定位,
重定位會修改代碼段的內容,這就造成每個使用這個.so文件代碼段的進程在內核裏都會生成這個.so文件代碼段的copy.每個copy都不一樣,取決於這個.so文件代碼段和數據段內存映射的位置.
不加fPIC編譯出來的so,是要再加載時根據加載到的位置再次重定位的.(因爲它裏面的代碼並不是位置無關代碼)
如果被多個應用程序共同使用,那麼它們必須每個程序維護一份so的代碼副本了.(因爲so被每個程序加載的位置都不同,顯然這些重定位後的代碼也不同,當然不能共享)
-fPIC 的使用,會生成 PIC 代碼,.so 要求爲 PIC,以達到動態鏈接的目的,否則,無法實現動態鏈接。 non-PIC 與 PIC 代碼的區別主要在於 access global data, jump label 的不同。 比如一條 access global data
的指令, non-PIC 的形勢是:ld r3, var1 PIC
的形式則是:ld r3, var1-offset@GOT
,意思是從
GOT 表的 index 爲 var1-offset 的地方處 指示的地址處裝載一個值,即var1-offset@GOT
處的4個 byte
其實就是 var1 的地址。這個地址只有在運行的時候才知道,是由 dynamic-loader(ld-linux.so) 填進去的。 再比如
jump label 指令 non-PIC 的形勢是:jump printf ,意思是調用 printf。 PIC 的形式則是:jump
printf-offset@GOT, 意思是跳到 GOT 表的 index 爲 printf-offset 的地方處指示的地址去執行,
這個地址處的代碼擺放在 .plt section,
每個外部函數對應一段這樣的代碼,其功能是呼叫dynamic-loader(ld-linux.so) 來查找函數的地址(本例中是 printf),然後將其地址寫到 GOT 表的 index 爲 printf-offset 的地方, 同時執行這個函數。這樣,第2次呼叫printf 的時候,就會直接跳到 printf 的地址,而不必再查找了。 GOT 是 data section, 是一個 table,除專用的幾個 entry,每個 entry 的內容可以再執行的時候修改; PLT 是 text section, 是一段一段的
code,執行中不需要修改。 每個 target 實現 PIC 的機制不同,但大同小異。比如 MIPS 沒有 .plt, 而是叫
.stub,功能和 .plt 一樣。 可見,動態鏈接執行很複雜,比靜態鏈接執行時間長;但是,極大的節省了 size,PIC和動態鏈接技術是計算機發展史上非常重要的一個里程碑。
- 使用動態鏈接庫
# gcc -o main main.c -L. -lmyhello
# ./main
# ./main: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory
出錯了。快看看錯誤提示,原來是找不到動態庫文件libmyhello.so。程序在運行時,會在/usr/lib和/lib等目錄中查找需要的動態庫文件。若找到,則載入動態庫,否則將提示類似上述錯誤而終止程序運行。我們將文件libmyhello.so複製到目錄/usr/lib中,再試試。(使用”-lmyhello”標記來告訴GCC驅動程序在連接階段引用共享函數庫libmyhello.so。”-L.”標記告訴GCC函數庫可能位於當前目錄。否則GNU連接器會查找標準系統函數目錄:它先後搜索1.elf文件的 DT_RPATH段—2.環境變量LD_LIBRARY_PATH—3./etc/ld.so.cache文件列表—4./lib/,/usr/lib目錄找到庫文件後將其載入內存,但是我們生成的共享庫在當前文件夾下,並沒有加到上述的4個路徑的任何一箇中,因此,執行後會出現錯誤)
# mv libmyhello.so /usr/lib
# ./main
# Hello Kevin!
沒有報錯,運行成功。
把/usr/lib/libmyhello.so刪除。
第二種方法:
既然連接器會搜尋LD_LIBRARY_PATH所指定的目錄,那麼我們可以將這個環境變量設置成當前目錄:
先執行:
export LD_LIBRARY_PATH=$(pwd)
再執行:
./main
成功!
第三種方法,就是使用ldconfig
注: 當用戶在某個目錄下面創建或拷貝了一個動態鏈接庫,若想使其被系統共享,可以執行一下”ldconfig 目錄名”這個命令.此命令的功能在於讓ldconfig將指定目錄下的動態鏈接庫被系統共享起來,意即:在緩存文件/etc/ld.so.cache中追加進指定目錄下的共享庫.本例讓系統共享了/home/kevin/clib目錄下的動態鏈接庫.該命令會重建/etc/ld.so.cache文件
# ldconfig /home/kevin/clib
# ./main
# Hello Kevin!
運行成功
可以查看程序執行時調用動態庫的過程:
# ldd main
linux-vdso.so.1 => (0x00007fff88dcf000)
libmyhello.so => /usr/lib/libmyhello.so (0x00007f9104ed6000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9104b11000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9105100000)
靜態庫鏈接時搜索路徑順序:
ld會去找GCC命令中的參數-L
再找gcc的環境變量LIBRARY_PATH
再找內定目錄 /lib /usr/lib /usr/local/lib 這是當初compile gcc時寫在程序內的
動態鏈接時、執行時搜索路徑順序:
編譯目標代碼時指定的動態庫搜索路徑;
環境變量LD_LIBRARY_PATH指定的動態庫搜索路徑;
配置文件/etc/ld.so.conf中指定的動態庫搜索路徑;
默認的動態庫搜索路徑/lib;
默認的動態庫搜索路徑/usr/lib。
有關環境變量:
LIBRARY_PATH環境變量:指定程序靜態鏈接庫文件搜索路徑
LD_LIBRARY_PATH環境變量:指定程序動態鏈接庫文件搜索路徑
參考:http://www.cnblogs.com/laojie4321/archive/2012/03/28/2421056.html