歡迎您光臨本站 註冊首頁

Linux庫知識

←手機掃碼閱讀     火星人 @ 2014-03-26 , reply:0

引言:
在xmeeting中,關於usb手柄部分,採用動態庫調用方式,下面翻譯一篇David A. Wheeler寫的文章。文章就如何創建和使用靜態庫,共享庫以及動如何動態裝載庫進行了論述。內容綱要如下:
1.概述
2.靜態庫
3.共享庫
3.1 約定
3.2 使用
3.3 環境變數
3.4 創建共享庫
3.5 安裝與使用
3.6 兼容性
4.動態載入
4.1 dlopen()
4.2 dlerror()
4.3 dlsym()
4.4 dlclose()
4.5 示例
5.輔助知識
5.1 nm命令
5.2 庫的構建與析構函數
5.3 腳本
5.4 版本
5.5 GNU libtool
5.6 去除符號空間
5.7 外部執行體
5.8 C++ 與 C
5.9 加速C++初始化
5.10 Linux標準
1.概述
本文就如何在Linux系統中運用GNU工具創建和使用程序庫進行論述。所謂"程序庫",簡單說,就是包含了數據和執行碼的文件。其不能單獨執行,可以作為其它執行程序的一部分,來完成執行功能。庫的存在,可以使得程序模塊化,可以加快程序的再編譯,可以實現代碼重用,可以使得程序便於升級。程序庫可分三類:靜態庫,共享庫和動態載入庫。

靜態庫,是在執行程序運行前就已經加入到執行碼中,在物理上成為執行程序的一部分;共享庫,是在執行程序啟動時載入到執行程序中,可以被多個執行程序共享使用。動態載入庫,其實並不是一種真正的庫類型,應該是一種庫的使用技術,應用程序可以在運行過程中隨時載入和使用庫。
建議庫開發人員創建共享庫,比較明顯的優勢在於庫是獨立的,便於維護和更新;而靜態庫的更新比較麻煩,一般不做推薦。然而,它們又各有優點,後面會講到。在C++編程中,要使用動態載入技術,需要參考文章"C++ dlopen MINI-Howto"。
文章中講述的執行程序和庫都採用ELF(Executable and Linking Format)格式,儘管GNU GCC 工具可以處理其它格式,但不在本文的討論範圍。本文可以在http://www.dwheeler.com/program-library和http://www.linuxdoc.org找到。

2.靜態庫
靜態庫可以認為是一些目標代碼的集合。按照習慣,一般以".a"做為文件後綴名。使用ar(archiver)命令可以創建靜態庫。因為共享庫有著更大的優勢,靜態庫已經不被經常使用。但靜態庫使用簡單,仍有使用的餘地,並會一直存在。

靜態庫在應用程序生成時,可以不必再編譯,節省再編譯時間。但在編譯器越來越快的今天,這一點似乎已不重要。如果其他開發人員要使用你的代碼,而你又不想給其源碼,提供靜態庫是一種選擇。從理論上講,應用程序使用了靜態庫,要比使用動態載入庫速度快1-5%,但由於莫名的原因,實際上可能並非如此。由此看來,除了使用方便外,靜態庫可能並非一種好的選擇。

要創建一個靜態庫,或要將目標代碼加入到已經存在的靜態庫中,可以使用以下命令:
ar rcs my_libraty.a file1.o file2.o
以上表示要把目標碼file1.o和file2.o加入到靜態庫my_library.a中。若my_library.a不存在,會自動創建。

靜態庫創建成功后,需要連接到應用程序中來使用。如果你使用gcc(1)來產生執行程序,需要利用-l選項來指定靜態庫。更多信息,查看gcc使用手冊。

在使用gcc時,要注意其參數的順序。-l是連接器選項,一定要放在被編譯的文件名稱之後;若放在文件名稱之前,你會連接失敗,並會出現莫名其妙的錯誤。這一點切記。

你也可以直接使用連接器ld(1),使用其選項-l或-L。但最好使用gcc(1),因ld(1)的介面有可能會有變化。

3.共享庫
共享庫是在程序啟動時被裝載。當一個應用程序裝載了一個共享庫后,其它應用程序仍可以裝載同一個共享庫。基於linux的使用方法,共享庫還有其它靈活的而又精妙的特性:
更新庫並不影響應用程序使用舊的,非向後兼容的版本;
在執行特定程序時,可以覆蓋整個庫或更新庫中的特定函數;
以上操作不會影響已經運行的程序,他們仍會使用已經裝載的庫。

3.1約定
要想共享庫具有以上特性,一些約定需要遵守。你需要掌握共享庫名稱之間的區別,特別是搜名(soname)和實名(realname)之間的區別和關係;你還需要知道共享庫在文件系統的位置。
3.1.1名稱
每個共享庫都有一個特定的搜名(soname),其組成如下:
lib + 庫名 + .so + . + version
| | |_______________|
前綴 庫名 後綴
在文件系統中,搜名是一個指向實名的符號聯結。

每個共享庫也有一個實名,其真正包含有庫的代碼,組成如下:
搜名 + . + 子版本號 + . + 發布號
最後的句點和發布號是可選項。

另外,共享庫還有一個名稱,一般用於編譯連接,稱為連名(linker name),它可以被看作是沒有任何版本號的搜名。


看下面的例子:
lrwxrwxrwx 1 root root libpng.so -> libpng12.so
lrwxrwxrwx 1 root root libpng.so.2 -> libpng.so.2.1.0.12
-rw-r--r-- 1 root root libpng.so.2.1.0.12
在以上信息中, libpng.so.2.1.0.12是共享庫的實名(real name),libpng.so.2是共享庫搜名(soname),libpng.so 則是連接名(linker name),用於編譯連接。

3.2共享庫的裝載
在所有基於GNU glibc的系統(當然包括Linux)中,在啟動一個ELF二進位執行程序時,一個特殊的程序"程序裝載器"會被自動裝載並運行。在linux中,這個程序裝載器就是/lib/ld-linux.so.X(X是版本號)。它會查找並裝載應用程序所依賴的所有共享庫。
被搜索的目錄保存在/etc/ls.so.conf文件中,但一般/usr/local/lib並不在搜索之列,至少debian是這樣。這似乎是一個系統失誤,只好自己加上了。
當然,如果程序的每次啟動,都要去搜索一番,勢必效率不堪忍受。Linux系統已經考慮這一點,對共享庫採用了緩存管理。ldconfig就是實現這一功能的工具,其預設讀取/etc/ld.so.conf文件,對所有共享庫按照一定規範建立符號連接,然後將信息寫入/etc/ld.so.cache。 /etc/ld.so.cache的存在大大加快了程序的啟動速度。

3.3創建共享庫
共享庫的創建比較簡單,基本有兩步。首先使用-fPIC或-fpic創建目標文件,PIC或pic表示位置無關代碼,然後就可以使用以下格式創建共享庫了:
gcc -share _Wl,-soname,your_soname -o library_name file_list library_list
下面是使用a.c和b.c創建庫的示例:
gcc -fPIC -g -c -Wall a.c
gcc -fPIC -g -c -Wall b.c
gcc -share -Wl,-soname, libmyab.so.1 -o libmyab.so.1.0.1 a.o b.o -lc
-g表示帶有調試信息,-Wall表示產生警告信息。
幾個需要注意的地方:
(1)不推薦使用strip處理共享庫,最好不要使用-fomit--pointer編譯選項
(2)-fPIC和-fpic都可以產生目標獨立代碼,一般採用-fPIC,儘管其產生的目標文件可能會大些;-fpic產生的代碼小,執行速度快,但可能有平台依賴限制。
(3)一般情況下,-Wall,-soname,your_soname編譯選項是需要的。當然,-share選項更不能丟。

4 動態載入庫
DL技術可以允許應用程序在運行過程的任何時候去載入和使用指定的庫。這一技術在插件的實現上很實用。動態載入庫這一概念並不是著眼於庫的文件格式,而是指使用方式。存在著一組介面函數,使得應用程序可以採用DL技術。下面對這些介面函數逐一介紹,在最後給出應用示例。

4.1 dlopen
函數原型:void *dlopen(const char *libname,int flag);
功能描述:dlopen必須在dlerror,dlsym和dlclose之前調用,表示要將庫裝載到內存,準備使用。如果要裝載的庫依賴於其它庫,必須首先裝載依賴庫。如果dlopen操作失敗,返回NULL值;如果庫已經被裝載過,則dlopen會返回同樣的句柄。
參數中的libname一般是庫的全路徑,這樣dlopen會直接裝載該文件;如果只是指定了庫名稱,在dlopen會按照下面的機制去搜尋:
(1)根據環境變數LD_LIBRARY_PATH查找
(2)根據/etc/ld.so.cache查找
(3)查找依次在/lib和/usr/lib目錄查找。
flag參數表示處理未定義函數的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暫時不去處理未定義函數,先把庫裝載到內存,等用到沒定義的函數再說;RTLD_NOW表示馬上檢查是否存在未定義的函數,若存在,則dlopen以失敗告終。

4.2 dlerror
函數原型:char *dlerror(void);
功能描述:dlerror可以獲得最近一次dlopen,dlsym或dlclose操作的錯誤信息,返回NULL表示無錯誤。dlerror在返回錯誤信息的同時,也會清除錯誤信息。

4.3 dlsym
函數原型:void *dlsym(void *handle,const char *symbol);
功能描述:在dlopen之後,庫被裝載到內存。dlsym可以獲得指定函數(symbol)在內存中的位置(指針)。如果找不到指定函數,則dlsym會返回NULL值。但判斷函數是否存在最好的方法是使用dlerror函數,下面是示例:
dlerror();/*清除錯誤信息*/
= dlsym(handle,"_name");
if((error=dlerror()) != NULL)
{
/*錯誤處理*/
}
else
{
/*找到函數*/
}

4.4 dlclose
函數原型:int dlclose(void *);
功能描述:將已經裝載的庫句柄減一,如果句柄減至零,則該庫會被卸載。如果存在析構函數,則在dlclose之後,析構函數會被調用。

4.5動態載入庫示例
#i nclude
#i nclude
#i nclude

int main(int argc,char **argv)
{
void *handle;
double (*cosine)(double);
char *error;

handle = dlopen("/lib/libm.so.6",RTLD_LAZY);
if(!handle)
{
printf("%s\n",dlerror());
exit(1);
}

printf("opened /lib/libm.so.6\n");

cosine = dlsym(handle,"cos");
if((error = dlerror()) != NULL)
{
printf("%s\n",error);
dlclose(handle);
printf("after error,closed /lib/libm.so.6\n");
exit(1);
}

printf("%f\n",(*cosine)(2.0));

dlclose(handle);
printf("closed /lib/libm.so.6\n");

return 0;
}
編譯:gcc -o test test.c -ldl。在這個例子中,/lib/libm.so.6是動態載入庫,而/usr/lib/libdl.so則是共享庫。


5.相關知識
5.1 nm命令
nm(1)命令可以報告庫的符號列表,對於查看庫的相關信息是一個不錯的工具。具體使用查看幫助文檔。示例:
$nm -D libavcodec-0.4.7.so | grep 263
結果如下:
00116438 T ff_clean_h263_qscales
00122d14 T ff_h263_decode_end
001223d4 T ff_h263_decode_
00121340 T ff_h263_decode_init
0011048c T ff_h263_decode_mb
0011652c T ff_h263_get_gob_height
0010e664 T ff_h263_resync
00041744 T ff_h263_round_chroma
0010810c T ff_h263_update_motion_val
00115f64 T flv_h263_decode_picture_header
0010da98 T h263_decode_init_vlc
001127c8 T h263_decode_picture_header
001ab040 D h263_decoder
00106c44 T h263_encode_gob_header
0010b2b0 T h263_encode_init
00109d40 T h263_encode_mb
00105f94 T h263_encode_picture_header
001a85a0 D h263_encoder
001162d0 T h263_get_picture_format
0010a7b4 T h263_pred_motion
00106df8 T h263_send_video_packet
001ab180 D h263i_decoder
001a85e0 D h263p_encoder
00115c68 T intel_h263_decode_picture_header
其中,T表示正常代碼段,D表示初始化數據段

5.2庫的構建與析構函數
關於構建與析構函數,一般不需要自己去編程實現。如果你一定要自己做,下面是函數原型:
void __attribute__ ((constructor)) my_init(void);
void __attribute__ ((destructor)) my_fini(void);
在編譯共享庫時,不能使用"-nonstartfiles"或"-nostdlib"選項,否則,構建與析構函數將不能正常執行(除非你採取一定措施)。

5.3腳本共享庫
linux中,共享庫可以是腳本形式,當然需要專門的腳本語言。/usr/lib/libc.so是一個典型的例子,內容如下:
/* GNU ld
Use the shared library, but some s are only in
the static library, so try that secondarily. */
OUTPUT_FORMAT(elf32-i386)
GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a )

5.4 版本腳本(略)
5.5 GNU libtool(略)

5.6除去記號信息
共享庫中的記號信息多為調試之用,但佔用了磁碟空間。如果你的庫是為嵌入式系統所用,最好去掉記號信息。一種方法,利用strip(1)命令,使用方法查看其幫助文檔;另一種方法,使用GNU LD選項-s或-S,例如"-Wl -s"或"-Wl -S"。-S僅除去調試記號信息;-s除去所有記號信息。

5.7編譯優化
有一篇文章寫的不錯"Whirlwind tutorial On Creating really teensy ELF Executables For Linux"。這篇文章中可以說把程序的代碼優化到了極點。在我們實際的應用中,可能並需要那些技巧,但通過此文,我們可以更多的了解ELF。

5.8 C++與C
要使得你編寫的共享庫能同時被C和C++程序使用,庫的頭文件需要使用"extern C"預定義,下面是一個例子:
#ifndef LIB_HELLO_H
#define LIB_HELLO_H

#ifdef __cplusplus
extern "C"
{
#endif


.....頭文件代碼

#ifdef __cplusplus
}
#endif

#endif

5.9關於C++程序的啟動速度
C++應用程序的啟動速度是比較慢的。我一直使用firefox,感受頗深。有人認為這是因主函數啟動之前的代碼重定位所導致。有一篇文章"making C++ ready for the desktop"(by Waldo Bastian)對這問題作了分析。我讀了一下,理解不是很深刻。

5.10 Linux Standard Base(LSB)
LSB是一個項目,致力於制訂和推動一系列標準,儘力提高不同Linux發布版本之間的兼容性,從而為應用程序的開發提供一致性的介面。關於linux標準項目的詳細信息,可查閱網站www.linuxbase.org。




編寫so庫
Shared Object Library

在linux開發中,有很多程序需要在時間上做優化,像XFree86,mozilla等,所以需要有高精度的時間測量方法。下面給出解決方案。

1。基本函數gettimeofday
#i nclude
#i nclude

函數原型:int gettimeofday(struct timeval *tv, struct timezone *tz);
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
struct timezone {
int tz_minuteswest; /* minutes W of Greenwich */
int tz_dsttime; /* type of dst correction */
};

2。測量原理
(1)設置基準點
(2)在每個測試點,獲取時間信息,與基準點的時間差值作為優化依據

3。示常式序
#i nclude
#i nclude
void consume()
{
unsigned int i,j;
double y;
for(i=0;i<1000;i++)
for(j=0;j<1000;j++)
y=i*j;
}

main()
{
struct timeval tpstart,tpend;
float timeuse;

gettimeofday(&tpstart,NULL);
();
gettimeofday(&tpend,NULL);
timeuse=(tpend.tv_sec-tpstart.tv_sec)+
((float)tpend.tv_usec-(float)tpstart.tv_usec)/(1000000);
printf("Used Time:%f\n",timeuse);
}


4。編寫測量庫
頭文件anthony_timedebug.h:
#ifndef _ANTHONY_TIMEDEBUG_H
#define _ANTHONY_TIMEDEBUG_H

#ifdef __cplusplus
extern "C"
{
#endif

#i nclude
#i nclude
#i nclude

void timedebug_setstart();
void timedebug_getcurrent();


#ifdef __cplusplus
}
#endif

#endif
源文件:anthony_timedebug.c
#i nclude "anthony_timedebug.h"

struct timeval tpstart;

void timedebug_setstart()
{
gettimeofday(&tpstart,NULL);
}


void timedebug_getcurrent()
{
FILE *fp;
struct timeval tpend;
float used;

gettimeofday(&tpend,NULL);

used = (tpend.tv_sec-tpstart.tv_sec)+
((float)tpend.tv_usec-(float)tpstart.tv_usec)/(1000000);

fp = fopen("/tmp/timedebug.log","a");
if(fp)
{
fprintf(fp,"%f\n",used);
fclose(fp);
}
}

編譯目標文件:
gcc -fPIC -c anthony_timedebug.c
編譯庫:
gcc -shared -Wl,-soname,libanthony.so.1 -o libanthony.so.1.0.1 anthony_timedebug.o -lc
安裝:
cp anthony_timedebug.h /usr/include
cp libanthony.so.1.0.1 /usr/lib
cd /usr/lib
ln -s libanthony.so.1.0.1 libanthony.so
ldconfig

5.測試
測試程序test.c:
#i nclude

void consume()
{
int i,j;
for(i=0;i<10000;i++)
for(j=0;j<10000;j++);
}

main()
{
timedebug_setstart();
consume();
timedebug_getcurrent();
}
編譯:
gcc -o test test.c -lanthony
執行結果可以在/tmp/timedebug.log查看

[火星人 ] Linux庫知識已經有614次圍觀

http://coctec.com/docs/linux/show-post-187729.html