Linux/Unix系統串連庫使用 (zz)

來源:互聯網
上載者:User

linux庫的知識(概)

引言:在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-frame-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();/*清除錯誤資訊*/  function = dlsym(handle,"function_name");  if((error=dlerror()) != NULL)  {    /*錯誤處理*/  }  else  {    /*找到函數*/  }

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

4.5動態載入庫樣本#include <stdlib.h>#include <stdio.h>#include <dlfcn.h>

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_qscales00122d14 T ff_h263_decode_end001223d4 T ff_h263_decode_frame00121340 T ff_h263_decode_init0011048c T ff_h263_decode_mb0011652c T ff_h263_get_gob_height0010e664 T ff_h263_resync00041744 T ff_h263_round_chroma0010810c T ff_h263_update_motion_val00115f64 T flv_h263_decode_picture_header0010da98 T h263_decode_init_vlc001127c8 T h263_decode_picture_header001ab040 D h263_decoder00106c44 T h263_encode_gob_header0010b2b0 T h263_encode_init00109d40 T h263_encode_mb00105f94 T h263_encode_picture_header001a85a0 D h263_encoder001162d0 T h263_get_picture_format0010a7b4 T h263_pred_motion00106df8 T h263_send_video_packet001ab180 D h263i_decoder001a85e0 D h263p_encoder00115c68 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 script     Use the shared library, but some functions 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。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.