共享庫的概念

摘自:http://blog.csdn.net/zuokong/article/details/7006222

通常庫分爲:靜態庫,共享庫,動態加載庫,。下面分別介紹。
一、 靜態庫:
1.概念:
靜態庫就是一些目標文件的集合,以.a結尾。靜態庫在程序鏈接的時候使用,鏈接器會將程序中使用
到函數的代碼從庫文件中拷貝到應用程序中。一旦鏈接完成,在執行程序的時候就不需要靜態庫了。
由於每個使用靜態庫的應用程序都需要拷貝所用函數的代碼,所以靜態鏈接的文件會比較大。
2.創建與應用:
首先創建庫文件libhello.c
#include
void hello()
{
printf("hello, welcome to library world!\n");
}
創建頭文件libhello.h
void hello();
現在我們創建libhello靜態庫文件:
$ gcc -c libhello -o libhello.o
$ ar rcs libhello.a libhello.o
其中ar中的rcs的意思是: r表明將模塊加入到靜態庫中,c表示創建靜態庫,s表示生產索引。

我們寫一個測試程序:
$ cat test.c
#include
int main(void)
{
printf("use library hello.\n");
hello();
return 0;
}

編譯與鏈接:
$ gcc -c test.c -o test.o
$ gcc test.o -L. -lhello -o test
說明:-L.表示將當前目錄加入到庫搜索路徑。默認的庫搜索路徑在/usr/lib目錄下。
另外這裏說明一下易混淆的參數-I,它表示搜索頭文件的路徑。這樣gcc在查找頭文件的時候會首先到-I指定的目錄查找,然後纔是系統默認目錄。

-l參數: -lname表示庫搜索目錄下的libname.a 或者libname.so文件 ,這也是爲什麼庫文件都以lib開頭的原因之一。一個慣例嘛。當然了,如果你的庫文件不是libhello,而是hello. 那就不能用-l參數編譯了。 可以這樣:
gcc test.o -L. hello.a -o test

注意: $gcc -L.-lhello test.o -o test 會出錯!。
原因是: -l是鏈接器選項,必須要放到被編譯文件的後面。 所以上面的命令中-lhello一定要放到 test.o的後面。
運行:
$ ./test
use library hello.
hello, welcome to library world!


二、共享庫:
1、共享庫的概念:
共享庫以.so結尾. (so ==share object) 在程序的鏈接時候並不像靜態庫那樣在拷貝使用函數的代碼,而只是作些標記。然後在程序開始啓動運行的時候,動態地加載所需模塊。所以,應用程序在運行的時候仍然需要共享庫的支持。 共享庫鏈接出來的文件比靜態庫要小得多。
2、共享庫的命名
一般一個共享庫的有三個名字:soname,real-name, linker-name。下面先看實例:
$ ls -l /usr/lib/libncurses*
lrwxrwxrwx 1 root root 20 2008-05-25 13:54 libncurses.so ->/lib/libncurses.so.5
lrwxrwxrwx 1 root root 13 2008-05-26 15:18 libncurses.so.5 -> libtermcap.so
上面的libncurses.so.5就是soname, 其中ncurses是庫名,5分別是主版本號(major),
當然也可以有次版本號(minor)和發行號(release)。(類似於ibncurses.so.5.0.0)
.so當然表示共享庫了。通常soname只是real name的一個鏈接。
而libtermcap.so這是ncurse庫的real-name, 也就是包含真是代碼實現的文件.libncurses.so 則是linkername,用於應用程序鏈接的時候的一個搜索名。 它通常是soname的一個鏈接,形式爲libname.so
實際上,每一個庫都有一個soname,當連接器發現它正在查找的程序庫中有這樣一個名稱,連接器便會將soname嵌入連結中的二進制文件內,而不是它正在運行的實際文件名,在程序執行期間,程序會查找擁有soname名字的文件,而不是庫的文件名,換句話說,soname是庫的區分標誌。
這樣做的目的主要是允許系統中多個版本的庫文件共存,習慣上在命名庫文件的時候通常與soname相同
3、共享庫的裝載
(1) 在所有基於GNUglibc的系統(當然包括Linux)中,在啓動一個ELF二進制執行程序時,
一個特殊的程序"程序裝載器"會被自動裝載並運行。在linux中,這個程序裝載器就是
/lib/ld-linux.so.X(X是版本號)。它會查找並裝載應用程序所依賴的所有共享庫。
被搜索的目錄保存在/etc/ls.so.conf文件中,但一般/usr/local/lib並不在搜索之列,至少debian/ubuntu是這樣。這似乎是一個系統失誤,只好自己加上了。當然,如果程序的每次啓動,都要去搜索一番,勢必效率不堪忍受。Linux系統已經考慮這一點,對共享庫採用了緩存管理。ldconfig就是實現這一功能的工具,其缺省讀取/etc/ld.so.conf文件,對所有共享庫按照一定規範建立符號連接,然後將信息寫入/etc/ld.so.cache。
/etc/ld.so.cache的存在大大加快了程序的啓動速度。
(2) 當然你也可以通過設置環境變量LD_LIBRARY_PATH來設置ld的裝載路徑。這樣裝載器就會首先搜索該變量的目錄,然後纔是默認目錄。但是記住,LD_LIBRARY_PATH是用於開發和測試的,你可以將一些用於測試的替代共享庫的目錄放到該變量中,類似於/etc/ld.so.preload的作用。但是該變量不應該用於正常用戶的正常程序。
(3) 如果你不使用LD_LIBRARY_PATH環境變量,可以通過如下方式給裝載器傳入路徑:
$ /lib/ld-linux.so.2 --library-path PATH EXECUTABLE

 

(4) 創建共享庫和鏈接可執行文件類似:首先把源代碼編譯成目標文件,然後把目標文件鏈接起來.目標文件需要創建成位置無關碼(position-independent code)(PIC),概念上就是在可執行程序裝載它們的時候,它們可以放在可執行程序的內存裏的任何地方,(用於可執行文件的目標文件通常不是用這個方式編譯的.)鏈接動態庫的命令包含特殊標誌,與鏈接可執行文件的命令是有區別的.

 

在下面的例子裏,我們假設你的源程序代碼在 foo.c 文件裏並且將創建成名字叫 foo.so的共享庫.中介的對象文件將叫做 foo.o,除非我們另外註明.一個共享庫可以 包含多個對象文件,不過我們在這裏只用一個.

BSD/OS

創建 PIC 的編譯器標誌是 -fpic.創建共享庫的鏈接器標誌是 -shared.

gcc -fpic -c foo.c

ld -shared -o foo.so foo.o

上面方法適用於版本 4.0 的 BSD/OS.

FreeBSD

創建 PIC 的編譯器標誌是 -fpic.創建共享庫的鏈接器標誌是 -shared.

gcc -fpic -c foo.c

gcc -shared -o foo.so foo.o

上面方法適用於版本 3.0 的 FreeBSD.

HP-UX

創建 PIC 的系統編譯器標誌是 +z.如果使用 GCC 則是 -fpic. 創建共享庫的鏈接器標誌是 -b.因此

cc +z -c foo.c

gcc -fpic -c foo.c

然後

ld -b -o foo.sl foo.o

HP-UX 使用 .sl 做共享庫擴展,和其它大部分系統不同.

IRIX

PIC 是缺省,不需要使用特殊的編譯器選項.生成共享庫的鏈接器選項是 -shared.

cc -c foo.c

ld -shared -o foo.so foo.o

Linux

創建 PIC 的編譯器標誌是 -fpic.在一些平臺上的一些環境下, 如果 -fpic 不能用那麼必須使用-fPIC. 參考 GCC 的手冊獲取更多信息. 創建共享庫的編譯器標誌是 -shared.一個完整的例子看起來象:

cc -fpic -c foo.c

cc -shared -o foo.so foo.o

NetBSD

創建 PIC 的編譯器標誌是 -fpic.對於 ELF 系統, 帶 -shared 標誌的編譯命令用於鏈接共享庫.在老的非 ELF 系統裏,使用ld -Bshareable.

gcc -fpic -c foo.c

gcc -shared -o foo.so foo.o

OpenBSD

創建 PIC 的編譯器標誌是 -fpic. ld -Bshareable 用於鏈接共享庫.

gcc -fpic -c foo.c

ld -Bshareable -o foo.so foo.o

Solaris

創建 PIC 的編譯器命令是用 Sun 編譯器時爲 -KPIC 而用 GCC 時爲 -fpic.鏈接共享庫時兩個編譯器都可以用 -G 或者用 GCC 時還可以是 -shared.

cc -G -KPIC -o foo.so -c foo.c

gcc -fpic -c foo.c

gcc -G -o foo.so foo.o

Tru64 UNIX

PIC 是缺省,因此編譯命令就是平常的那個.帶特殊選項的 ld 用於鏈接:

cc -c foo.c

ld -shared -expect_unresolved '*' -o foo.sofoo.o

用 GCC 代替系統編譯器時的過程是一樣的;不需要特殊的選項.

UnixWare

SCO 編譯器創建 PIC 的標誌是-KPIC GCC 是 -fpic. 鏈接共享庫時 SCO 編譯器用 -G 而 GCC 用-shared.

cc -K PIC -c foo.c

cc -G -o foo.so foo.o

or

gcc -fpic -c foo.c

gcc -shared -o foo.so foo.o
4、共享庫的創建與應用
(1) 創建共享庫:
gcc -fpic/fPIC -c source.c -o source.o
gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list
說明: -fpic或者-fPIC表明創建positionindependent code,這通常是創建共享庫必須的。
-Wl 表明給鏈接器傳送參數,所以這裏-soname,library_name 爲給鏈接器的參數。
-shared 表明是使用共享庫
下面是使用a.c和b.c創建共享庫的示例:
gcc -fPIC -g -c -Wall a.c
gcc -fPIC -g -c -Wall b.c
gcc -shared -Wl,-soname, libmyab.so.1 -o libmyab.so.1.0.1 a.o b.o -lc
說明: lc == libc

幾個需要注意的地方:
a.不推薦使用strip處理共享庫,最好不要使用-fomit-frame-pointer編譯選項
b.-fPIC和-fpic都可以產生目標獨立代碼,具體應用取決於平臺,-fPIC是always work,
儘管其產生的目標文件可能會大些;-fpic產生的代碼小,執行速度快,但可能有平臺依賴限制。
c.一般情況下,-Wall,-soname,your_soname編譯選項是需要的。當然,-share選項更不能丟。
(2) 安裝使用共享庫
一旦你創建好共享庫後就需要安裝使用了,最簡單的辦法是將庫拷貝到默認目錄下(/usr/lib)。
然後創建一些符號鏈接,最簡單的方式還是使用ldconfig(8)來處理這裏符號鏈接。最後是重新
編譯鏈接你的程序,通過-L和-l參數指定庫路徑就可以了。
三、 動態加載庫
1. 概念
動態加載庫(dynamicallyloaded (DL) libraries)是指在程序運行過程中可以加載的函數庫。而不是像共享庫一樣在程序啓動的時候加載。DL對於實現插件和模塊非常有用,因爲他們可以讓程序在允許時等待插件的加載。在Linux中,動態庫的文件格式跟共享庫沒有區別,主要區別在於共享庫是運行時加載。
有專門的一組API用於完成打開動態庫,查找符號,處理出錯,關閉動態庫等功能。

下面對這些接口函數逐一介紹:
(1) dlopen
函數原型:void*dlopen(const char *libname,int flag);
功能描述:dlopen必須在dlerror,dlsym和dlclose之前調用,表示要將庫裝載到內存,準備使用。
如果要裝載的庫依賴於其它庫,必須首先裝載依賴庫。如果dlopen操作失敗,返回NULL值;如果庫已經被裝載過,則dlopen會返回同樣的句柄。
參數中的libname一般是庫的全路徑,這樣dlopen會直接裝載該文件;如果只是指定了庫名稱,在dlopen會按照下面的機制去搜尋:
a.根據環境變量LD_LIBRARY_PATH查找
b.根據/etc/ld.so.cache查找
c.查找依次在/lib和/usr/lib目錄查找。
flag參數表示處理未定義函數的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暫時不去處理未定義函數,先把庫裝載到內存,等用到沒定義的函數再說;RTLD_NOW表示馬上檢查是否存在未定義的函數,若存在,則dlopen以失敗告終。
(2) dlerror
函數原型:char*dlerror(void);
功能描述:dlerror可以獲得最近一次dlopen,dlsym或dlclose操作的錯誤信息,返回NULL表示無錯誤。dlerror在返回錯誤信息的同時,也會清除錯誤信息。
(3) dlsym
函數原型:void*dlsym(void *handle,const char *symbol);
功能描述:在dlopen之後,庫被裝載到內存。dlsym可以獲得指定函數(symbol)在內存中的位置(指針)。
如果找不到指定函數,則dlsym會返回NULL值。但判斷函數是否存在最好的方法是使用dlerror函數,
(4) dlclose
函數原型:intdlclose(void *);
功能描述:將已經裝載的庫句柄減一,如果句柄減至零,則該庫會被卸載。如果存在析構函數,則在dlclose之後,析構函數會被調用。

2. 使用實例
$cat dltest.c
#include
#include
#include
int main(int argc, char **argv)
{
void *handle;
double (*cosine)(double);
char *error;
handle = dlopen ("/lib/libm.so.6", RTLD_LAZY);
if (!handle) {
fputs (dlerror(), stderr);
exit(1);
}
cosine = dlsym(handle, "cos");
if ((error = dlerror()) != NULL) {
fputs(error, stderr);
exit(1);
}
printf ("%f\n", (*cosine)(2.0));
dlclose(handle);
return 0;
}

編譯: $ gcc -odltest dltest.c -ldl -Wall
運行: $ ./dltest
-0.416147
四、查看庫中的符號
(1) nm命令可以查可能一個庫中的符號
nm列出的符號有很多,常見的有三種,一種是在庫中被調用,但並沒有在庫中定義(表明需要其他庫支持),用U表示;一種是庫中定義的函數,用T表示,這是最常見的;另外一種是所謂的“弱態”符號,它們雖然在庫中被定義,但是可能被其他庫中的同名符號覆蓋,用W表示。例如,假設開發者希望知道上文提到的hello庫中是否定義了 printf():
$nm libhello.so |grep printf
U printf
U表示符號printf被引用,但是並沒有在函數內定義,由此可以推斷,要正常使用hello庫,必須有其它庫支持,再使用ldd命令查看hello依賴於哪些庫:
$ldd hello
libc.so.6=>/lib/libc.so.6(0x400la000)
/lib/ld-linux.so.2=>/lib/ld-linux.so.2 (0x40000000)
從上面的結果可以繼續查看printf最終在哪裏被定義.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章