Linux 的庫文件
[日期:2012-04-10] 來源:Linux社區 作者:yangzhongxuan
1.什麼是庫
在windows平臺和linux平臺下都存在着大量的庫。
本質上來說庫是一種可執行代碼的二進制形式,可以被操作系統載入內存執行。
由於windows和linux的本質不同,因此二者庫的二進制是不兼容的。
本文僅限於介紹linux下的庫。
2.庫的種類及區別
•linux庫有分類
靜態庫和共享庫(動態庫)
•區別:
後綴不同
通常共享庫以.so(SharedObject的縮寫)結尾,靜態鏈接庫通常以.a結尾(Archive的縮寫)。在終端缺省情況下,共享庫通常爲綠色,而靜態庫爲黑色。
代碼被載入的時刻不同
靜態庫——編譯時加載
動態庫——語句調運時加載
靜態庫的代碼在編譯過程中已經被載入可執行程序,因此體積較大。
動態庫,程序中只保留庫文件的名字和函數名,在運行時去查找庫文件和函數體並載入內存,程序的體積基本變化不大。
靜態庫的原則是“以空間換時間”,增加程序體積,減少運行時間;
動態庫則是“以時間換空間”,增加運行時間,減少了程序本身的體積。
不同的應用程序如果調用相同的動態庫,那麼在內存裏只需要有一份該動態庫的實例。
3.庫存在的意義
•便於管理。
你調用函數的時候,只是一個#include就搞定,操作系統就自己幫你去找你要用的東西,(前提是你的環境變量要正確,或者你的庫在系統默認的地方),而且庫函數是成熟穩定的。
•縮短開發週期。
你不用去編寫所用的代碼,你可以用牛人們已經用了千百遍的代碼——標準庫
這也是團隊合作的必要。你有時候只需要瞭解函數的功能、入口、出口。
•有利於升級
如病毒庫的升級,具體的我還得查查資料。哈哈見笑。
4.生成庫文件
•概述
編寫函數代碼
編譯生成各目標文件
用ar文件對目標文件歸檔,生成靜態庫文件。注意歸檔文件名必須以lib打頭(可查看庫文件命名規則)。
使用要點:
在gcc的-I參數後加上靜態庫頭文件的路徑。
在gcc的-L參數後加上庫文件所在目錄
在gcc的-l參數後加上庫文件名,但是要去掉lib和.a擴展名。
比如庫文件名是libtest.a那麼參數就是-ltest
•編寫最簡單的源文件
編寫如下兩個文件,注意放在同一目錄中
myalib.h //靜態庫頭文件
myalib.c //靜態庫實現文件
1.//myalib.h
2.
3.voidtest();
4.
5.
6.//myalib.c
7.#inlcude<stdio.h> 8.
9.voidtest()
10.
11.{
12.
13.printf("test\n");
14.
15.}
•製作靜態庫文件
生成目標文件
gcc-c myalib.c
執行完後會生成一個myalib.o文件
用ar命令歸檔,格式爲ar-rc <生成的檔案文件名><.o文件名列表>
再次提醒,歸檔文件名一定要以lib打頭,.a結尾。
ar-rc libtest.a myalib.o
執行完後會生成一個libtest.a文件
•使用靜態庫文件
編寫一個測試程序main.c,內容爲
1.//main.c
2.
3.
4.#include"myalib.h"
5.
6.intmain(int argc,char* argv[])
7.
8.{
9.
10.test();
11.
12.return0;
13.
14.}
編譯目標文件,注意要把靜態庫頭文件的路徑加到-I參數裏面
gcc-I ./ -o main.o -c main.c
現在生成了一個main.o文件
生成可執行文件,注意要把靜態庫文件的路徑加到-L參數裏面,
把庫文件名(去掉打頭的lib和結尾的.a)加到-l參數後面。如下面所示
gcc-o main -L ./ main.o -ltest
此時就會生成一個名爲main的可執行文件
另外,注意-l參數好象應該加到輸入文件名的後面,否則會報錯。
比如gcc-o main -L./ -ltest main.o就會提示
main.o(.text+0x11):In function `main':
:undefined reference to `test'
collect2:ld returned 1 exit status
執行可執行文件查看效果
執行./main,輸出
test
說明執行成功。
•生成動態庫文件
gcc-fPIC --shared test.c -o libtest.so
生成libtest.so
•使用動態庫文件
編譯可執行文件
gccmain.c -o main -I./ -L./ -ltest
指定加載庫的路徑,後面會詳細介紹。
exportLD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
執行./main,輸出
test
說明執行成功。
5.庫文件命名
GNU庫的使用必須遵守LibraryGNU PublicLicense(LGPL許可協議)。該協議與GNU許可協議略有不同,開發人員可以免費使用GNU庫進行軟件開發,但必須保證向用戶提供所用的庫函數的源代碼。
系統中可用的庫都存放在/usr/lib和/lib目錄中。庫文件名由前綴lib和庫名以及後綴組成。根據庫的類型不同,後綴名也不一樣。共享庫的後綴名由.so和版本號組成,靜態庫的後綴名爲.a。
靜態庫的名字一般爲libxxxx.a,其中xxxx是該lib的名稱
動態庫的名字一般爲libxxxx.so.major.minor,xxxx是該lib的名稱,major是主版本號,minor是副版本號
6.查看可執行文件的依賴庫
ldd命令可以查看一個可執行程序依賴的共享庫,
# ldd./main
linux-gate.so.1=> (0x00ec1000)
libtest.so=> ./libtest.so (0x00967000)
libc.so.6=> /lib/tls/i686/cmov/libc.so.6 (0x0025b000)
/lib/ld-linux.so.2(0x00471000)
7.可執行程序定位共享庫
當系統加載可執行代碼時候,能夠知道其所依賴的庫的名字,但是還需要知道絕對路徑
此時就需要系統動態載入器(dynamiclinker/loader)對於elf格式的可執行程序,是由ld-linux.so*來完成的,它先後搜索
elf文件的DT_RPATH段,
LD_LIBRARY_PATH指定的路徑,
/etc/ld.so.cache文件列表,
/lib/,/usr/lib,目錄
找到庫文件後將其載入內存。
8.指定庫路徑
如果安裝在/lib或者/usr/lib下,那麼ld默認能夠找到。
方法1
如果安裝在其他目錄,需要將其添加到/etc/ld.so.cache文件中,步驟如下
編輯/etc/ld.so.conf文件,加入庫文件所在目錄的路徑
運行ldconfig,該命令會重建/etc/ld.so.cache文件
方法2:(臨時指定)
#export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/libpath
9.庫的題外話
庫文件在連接(靜態庫和共享庫)和運行(僅限於使用共享庫的程序)時被使用,其搜索路徑是在系統中進行設置的。一般Linux系統把 /lib和 /usr/lib兩個目錄作爲默認的庫搜索路徑,所以使用這兩個目錄中的庫時不需要進行設置搜索路徑即可直接使用。對於處於默認庫搜索路徑之外的庫,需要將庫的位置添加到庫的搜索路徑之中。設置庫文件的搜索路徑有下列兩種方式:
在環境變量 LD_LIBRARY_PATH中指明庫的搜索路徑。
在 /etc/ld.so.conf文件中添加庫的搜索路徑。
將自己可能存放庫文件的路徑都加入到/etc/ld.so.conf
添加方法也極其簡單,將庫文件的絕對路徑直接寫進去就OK,一行一個。例如:
/usr/local/lib
/opt/lib
我的機是Ubuntu10.4系統/etc/ld.so.conf的內容如下:
#cat/etc/ld.so.conf
include/etc/ld.so.conf.d/*.conf
爲了加快程序執行時對共享庫的定位速度,避免使用搜索路徑查找共享庫的低效率,linux是直接讀取庫列表文件/etc/ld.so.cache從中進行搜索的。
/etc/ld.so.cache是一個非文本的數據文件,不能直接編輯,它是根據/etc/ld.so.conf中設置的搜索路徑由/sbin/ldconfig命令將這些搜索路徑下的共享庫文件集中在一起而生成的。
因此,爲了保證程序執行時對庫的定位,在/etc/ld.so.conf中進行了庫搜索路徑的設置之後,還必須要運行/sbin/ldconfig命令更新/etc/ld.so.cache 文件之後纔可以。
ldconfig,簡單的說,它的作用就是將/etc/ld.so.conf列出的路徑下的庫文件緩存到/etc/ld.so.cache以供使用。因此當安裝完一些庫文件,(例如剛安裝好glib),或者修改ld.so.conf增加新的庫路徑後,需要運行一下/sbin/ldconfig使所有的庫文件都被緩存到ld.so.cache中,如果沒做,即使庫文件明明就在/usr/lib下的,也是不會被使用的,結果編譯過程中抱錯,缺少xxx庫。
在程序連接時(不是運行),對於庫文件(靜態庫和共享庫)的搜索路徑,除了上面的設置方式之外,還可以通過-L參數顯式指定。因爲用-L設置的路徑將被優先搜索,所以在連接的時候通常都會以這種方式直接指定要連接的庫的路徑。
10.動態庫的顯式調用
庫函數dlopen()將打開一個新庫,並把它裝入內存。該函數主要用來加載庫中的符號,這些符號在編譯的時候是不知道的。比如Apache Web服務器利用這個函數在運行過程中加載模塊,這爲它提供了額外的能力。一個配置文件控制了加載模塊的過程。這種機制使得在系統中添加或者刪除一個模塊時,都不需要重新編譯了。
dlopen()在dlfcn.h中定義,並在dl庫中實現。它需要兩個參數:一個文件名和一個標誌。文件名可以是我們學習過的庫名字。標誌指明是否立刻計算庫的依賴性。如果設置爲RTLD_NOW的話,則立刻計算;如果設置的是RTLD_LAZY,則在需要的時候才計算。另外,可以指定RTLD_GLOBAL,它使得那些在以後才加載的庫可以獲得其中的符號。
當庫被裝入後,可以把 dlopen()返回的句柄作爲給 dlsym()的第一個參數,以獲得符號在庫中的地址。使用這個地址,就可以獲得庫中特定函數的指針,並且調用裝載庫中的相應函數。
下面詳細說明一下這些函數。
•dlerror
原型爲:const char *dlerror(void);
當動態鏈接庫操作函數執行失敗時,dlerror可以返回出錯信息,返回值爲NULL時表示操作函數執行成功。
•dlopen
原型爲:void *dlopen (const char *filename, int flag);
dlopen用於打開指定名字(filename)的動態鏈接庫,並返回操作句柄。
filename:如果名字不以/開頭,則非絕對路徑名,將按下列先後順序查找該文件。
(1)用戶環境變量中的LD_LIBRARY值;
(2)動態鏈接緩衝文件/etc/ld.so.cache
(3)目錄/lib,/usr/lib
flag表示在什麼時候解決未定義的符號(調用)。取值有兩個:
1)RTLD_LAZY :表明在動態鏈接庫的函數代碼執行時解決。
2)RTLD_NOW :表明在dlopen返回前就解決所有未定義的符號,一旦未解決,dlopen將返回錯誤。
dlopen調用失敗時,將返回NULL值,否則返回的是操作句柄。
•dlsym
取函數執行地址
原型爲:void *dlsym(void *handle, char *symbol);
dlsym根據動態鏈接庫操作句柄(handle)與符號(symbol),返回符號對應的函數的執行代碼地址。由此地址,可以帶參數執行相應的函數。
如程序代碼:void (*add)(int x,int y); /*說明一下要調用的動態函數add*/
add=dlsym("xxx.so","add");/*打開xxx.so共享庫,取add函數地址*/
add(89,369);/*帶兩個參數89和369調用add函數*/
•dlclose:關閉動態鏈接庫
原型爲:int dlclose (void *handle);
dlclose用於關閉指定句柄的動態鏈接庫,只有當此動態鏈接庫的使用計數爲0時,纔會真正被系統卸載。
編寫測試文件
1.#include<stdio.h>
2.
3.#include<dlfcn.h> //用於動態庫管理的系統頭文件 4.
5.#include"test.h" //要把函數的頭文件包含進來,否則編譯時會報錯
6.
7.intmain(int argc,char* argv[])
8.
9.{
10.
11.//聲明對應的函數的函數指針
12.
13.void(*pTest)();
14.
15.//加載動態庫
16.
17.void*pdlHandle = dlopen("libtest.so", RTLD_LAZY); 18.
19.//錯誤處理
20.
21.if(pdlHandle== NULL ) { 22.
23.printf("Failedload library\n");
24.
25.return-1;
26.
27.}
28.
29.char*pszErr = dlerror(); 30.
31.if(pszErr!= NULL)
32.
33.{
34.
35.printf("%s\n",pszErr);
36.
37.return-1;
38.
39.}
40.
41.//獲取函數的地址
42.
43.pTest= dlsym(pdlHandle, "test"); 44.
45.pszErr= dlerror(); 46.
47.if(pszErr!= NULL)
48.
49.{
50.
51.printf("%s\n",pszErr);
52.
53.dlclose(pdlHandle);
54.
55.return-1;
56.
57.}
58.
59.//實現函數調用
60.
61.(*pTest)();
62.
63.
64.//程序結束時關閉動態庫
65.
66.dlclose(pdlHandle);
67.
68.return0;
69.
70.}
2、編譯測試文件使用-ldl選項指明生成的對象模塊需要使用共享庫
gcc -omain -ldl main.c
執行完後就生成了一個main文件
3、執行測試程序
執行 ./main
本篇文章來源於 Linux公社網站(www.linuxidc.com) 原文鏈接:http://www.linuxidc.com/Linux/2012-04/58352p2.htm