天天看點

關于Linux靜态庫和動态庫的分析

原文:http://linux.chinaunix.net/techdoc/net/2009/02/04/1060670.shtml

1.什麼是庫

在windows平台和linux平台下都大量存在着庫。

本質上來說庫是一種可執行代碼的二進制形式,可以被作業系統載入記憶體執行。

由于windows和linux的本質不同,是以二者庫的二進制是不相容的。

本文僅限于介紹linux下的庫。

2.庫的種類

linux下的庫有兩種:靜态庫和共享庫(動态庫)。

二者的不同點在于代碼被載入的時刻不同。

靜态庫的代碼在編譯過程中已經被載入可執行程式,是以體積較大。

共享庫的代碼是在可執行程式運作時才載入記憶體的,在編譯過程中僅簡單的引用,是以代碼體積較小。

3.庫存在的意義

庫是别人寫好的現有的,成熟的,可以複用的代碼,你可以使用但要記得遵守許可協定。

現實中每個程式都要依賴很多基礎的底層庫,不可能每個人的代碼都從零開始,是以庫的存在意義非同尋常。

共享庫的好處是,不同的應用程式如果調用相同的庫,那麼在記憶體裡隻需要有一份該共享庫的執行個體。

4.庫檔案是如何産生的在linux下

靜态庫的字尾是.a,它的産生分兩步

step 1.由源檔案編譯生成一堆.o,每個.o裡都包含這個編譯單元的符号表

step 2.ar指令将很多.o轉換成.a,成文靜态庫

動态庫的字尾是.so,它由gcc加特定參數編譯産生。

例如:

$ gcc -fpic -c *.c $ gcc -shared -wl,-soname, libfoo.so.1 -o libfoo.so.1.0 *.

5.庫檔案是如何命名的,有沒有什麼規範

在linux下,庫檔案一般放在/usr/lib /lib下,

靜态庫的名字一般為libxxxx.a,其中xxxx是該lib的名稱

動态庫的名字一般為libxxxx.so.major.minor,xxxx是該lib的名稱,major是主版本号, minor是副版本号

6.如何知道一個可執行程式依賴哪些庫

ldd指令可以檢視一個可執行程式依賴的共享庫,

例如# ldd /bin/lnlibc.so.6

=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2

=> /lib/ld- linux.so.2 (0×40000000)

可以看到ln指令依賴于libc庫和ld-linux庫

7.可執行程式在執行的時候如何定位共享庫檔案

當系統加載可執行代碼時候,能夠知道其所依賴的庫的名字,但是還需要知道絕對路徑

此時就需要系統動态載入器(dynamic linker/loader)

對于elf格式的可執行程式,是由ld-linux.so*來完成的,它先後搜尋elf檔案的 dt_rpath段—環境變量ld_library_path—/etc/ld.so.cache檔案清單—/lib/,/usr/lib目錄找到庫檔案後将其載入記憶體

8.在新安裝一個庫之後如何讓系統能夠找到他

如果安裝在/lib或者/usr/lib下,那麼ld預設能夠找到,無需其他操作。

如果安裝在其他目錄,需要将其添加到/etc/ld.so.cache檔案中,步驟如下

1.編輯/etc/ld.so.conf檔案,加入庫檔案所在目錄的路徑

2.運作ldconfig,該指令會重建/etc/ld.so.cache檔案

我們通常把一些公用函數制作成函數庫,供其它程式使用。函數庫分為靜态庫和動态庫兩種。靜态庫在程式編譯時會被連接配接到目标代碼中,程式運作時将不再需要該靜态庫。動态庫在程式編譯時并不會被連接配接到目标代碼中,而是在程式運作是才被載入,是以在程式運作時還需要動态庫存在。本文主要通過舉例來說明在linux中如何建立靜态庫和動态庫,以及使用它們。在建立函數庫前,我們先來準備舉例用的源程式,并将函數庫的源程式編譯成.o檔案。 

第1步:編輯得到舉例的程式--hello.h、hello.c和main.c; 

hello.h(見程式1)為該函數庫的頭檔案。

hello.c(見程式2)是函數庫的源程式,其中包含公用函數hello,該函數将在螢幕上輸出"hello xxx!"。

main.c(見程式3)為測試庫檔案的主程式,在主程式中調用了公用函數hello。 

程式1: hello.h

#ifndef hello_h

#define hello_h

void hello(const char *name);

#endif //hello_h

程式2: hello.c

#include 

void hello(const char *name)

{

  printf("hello %s!\n", name);

}

  程式3: main.c

#include "hello.h"

int main()

  hello("everyone");

  return 0;

第2步:将hello.c編譯成.o檔案; 

無論靜态庫,還是動态庫,都是由.o檔案建立的。是以,我們必須将源程式hello.c通過gcc先編譯成.o檔案。 

在系統提示符下鍵入以下指令得到hello.o檔案。 

# gcc -c hello.c 

(注1:本文不介紹各指令使用和其參數功能,若希望詳細了解它們,請參考其他文檔。) 

(注2:首字元"#"是系統提示符,不需要鍵入,下文相同。) 

我們運作ls指令看看是否生存了hello.o檔案。 

# ls 

hello.c hello.h hello.o main.c 

(注3:首字元不是"#"為系統運作結果,下文相同。) 

在ls指令結果中,我們看到了hello.o檔案,本步操作完成。 

下面我們先來看看如何建立靜态庫,以及使用它。 

第3步:由.o檔案建立靜态庫; 

靜态庫檔案名的命名規範是以lib為字首,緊接着跟靜态庫名,擴充名為.a。例如:我們将建立的靜态庫名為myhello,則靜态庫檔案名就是libmyhello.a。在建立和使用靜态庫時,需要注意這點。建立靜态庫用ar指令。 

在系統提示符下鍵入以下指令将建立靜态庫檔案libmyhello.a。 

# ar cr libmyhello.a hello.o 

我們同樣運作ls指令檢視結果: 

hello.c hello.h hello.o libmyhello.a main.c 

ls指令結果中有libmyhello.a。 

第4步:在程式中使用靜态庫; 

靜态庫制作完了,如何使用它内部的函數呢?隻需要在使用到這些公用函數的源程式中包含這些公用函數的原型聲明,然後在用gcc指令生成目标檔案時指明靜态庫名,gcc将會從靜态庫中将公用函數連接配接到目标檔案中。注意,gcc會在靜态庫名前加上字首lib,然後追加擴充名.a得到的靜态庫檔案名來查找靜态庫檔案。 

在程式3:main.c中,我們包含了靜态庫的頭檔案hello.h,然後在主程式main中直接調用公用函數hello。下面先生成目标程式hello,然後運作hello程式看看結果如何。 

# gcc -o hello main.c -l. -lmyhello 

# ./hello 

hello everyone! 

我們删除靜态庫檔案試試公用函數hello是否真的連接配接到目标檔案 hello中了。 

# rm libmyhello.a 

rm: remove regular file `libmyhello.a'? y 

程式照常運作,靜态庫中的公用函數已經連接配接到目标檔案中了。 

我們繼續看看如何在linux中建立動态庫。我們還是從.o檔案開始。 

第5步:由.o檔案建立動态庫檔案; 

動态庫檔案名命名規範和靜态庫檔案名命名規範類似,也是在動态庫名增加字首lib,但其檔案擴充名為.so。例如:我們将建立的動态庫名為myhello,則動态庫檔案名就是libmyhello.so。用gcc來建立動态庫。 

在系統提示符下鍵入以下指令得到動态庫檔案libmyhello.so。 

# gcc -shared -fpci -o libmyhello.so hello.o 

我們照樣使用ls指令看看動态庫檔案是否生成。 

hello.c hello.h hello.o libmyhello.so main.c 

第6步:在程式中使用動态庫; 

在程式中使用動态庫和使用靜态庫完全一樣,也是在使用到這些公用函數的源程式中包含這些公用函數的原型聲明,然後在用gcc指令生成目标檔案時指明動态庫名進行編譯。我們先運作gcc指令生成目标檔案,再運作它看看結果。 

./hello: 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中,再試試。 

# mv libmyhello.so /usr/lib 

./hello: error while loading shared libraries: /usr/lib/libhello.so: cannot restore segment prot after reloc: permission denied

由于selinux引起,

# chcon -t texrel_shlib_t /usr/lib/libhello.so

# ./hello

成功了。這也進一步說明了動态庫在程式運作時是需要的。 

我們回過頭看看,發現使用靜态庫和使用動态庫編譯成目标程式使用的gcc指令完全一樣,那當靜态庫和動态庫同名時,gcc指令會使用哪個庫檔案呢?抱着對問題必究到底的心情,來試試看。 

先删除 除.c和.h外的 所有檔案,恢複成我們剛剛編輯完舉例程式狀态。 

# rm -f hello hello.o /usr/lib/libmyhello.so 

hello.c hello.h main.c 

在來建立靜态庫檔案libmyhello.a和動态庫檔案libmyhello.so。 

hello.c hello.h hello.o libmyhello.a libmyhello.so main.c 

通過上述最後一條ls指令,可以發現靜态庫檔案libmyhello.a和動态庫檔案libmyhello.so都已經生成,并都在目前目錄中。然後,我們運作gcc指令來使用函數庫myhello生成目标檔案hello,并運作程式 hello。 

# gcc -o hello main.c -l. -lmyhello 

從程式hello運作的結果中很容易知道,當靜态庫和動态庫同名時, gcc指令将優先使用動态庫。 

基本概念 

庫有動态與靜态兩種,動态通常用.so為字尾,靜态用.a為字尾。 

例如:libhello.so libhello.a 為了在同一系統中使用不同版本的庫,可以在庫檔案名後加上版本号為字尾,例如: libhello.so.1.0,由于程式連接配接預設以.so為檔案字尾名。是以為了使用這些庫,通常使用建立符号連接配接的方式。 

ln -s libhello.so.1.0 libhello.so.1 

ln -s libhello.so.1 libhello.so 

1、使用庫 

當要使用靜态的程式庫時,連接配接器會找出程式所需的函數,然後将它們拷貝到執行檔案,由于這種拷貝是完整的,是以一旦連接配接成功,靜态程式庫也就不再需要了。然 而,對動态庫而言,就不是這樣。動态庫會在執行程式内留下一個标記指明當程式執行時,首先必須載入這個庫。由于動态庫節省空間,linux下進行連接配接的預設操作是首先連接配接動态庫,也就是說,如果同時存在靜态和動态庫,不特别指定的話,将與動态庫相連接配接。 現在假設有一個叫hello的程式開發包,它提供一個靜态庫libhello.a 一個動态庫libhello.so,一個頭檔案hello.h,頭檔案中提供sayhello()這個函數

/* hello.h */ void sayhello(); 另外還有一些說明文檔。 

這一個典型的程式開發包結構 與動态庫連接配接 linux預設的就是與動态庫連接配接,下面這段程式testlib.c使用hello庫中的sayhello()函數 

/*testlib.c*/ 

#include  

int main() 

   sayhello(); 

   return 0; 

使用如下指令進行編譯 $gcc -c testlib.c -o testlib.o 

用如下指令連接配接: $gcc testlib.o -lhello -o testlib 

連接配接時要注意,假設libhello.o 和libhello.a都在預設的庫搜尋路徑下/usr/lib下,如果在其它位置要加上-l參數 與與靜态庫連接配接麻煩一些,主要是參數問題。還是上面的例子: 

$gcc testlib.o -o testlib -wi,-bstatic -lhello 

注:這個特别的"-wi,-bstatic"參數,實際上是傳給了連接配接器ld。訓示它與靜态庫連接配接,如果系統中隻有靜态庫當然就不需要這個參數了。 如果要和多個庫相連接配接,而每個庫的連接配接方式不一樣,比如上面的程式既要和libhello進行靜态連接配接,又要和libbye進行動态連接配接,其指令應為: 

$gcc testlib.o -o testlib -wi,-bstatic -lhello -wi,-bdynamic -lbye 

2、動态庫的路徑問題 為了讓執行程式順利找到動态庫,有三種方法: 

(1)把庫拷貝到/usr/lib和/lib目錄下。 

(2)在ld_library_path環境變量中加上庫所在路徑。 

例如動态庫libhello.so在/home/ting/lib目錄下,以bash為例,使用指令: 

$export ld_library_path=$ld_library_path:/home/ting/lib 

(3) 修改/etc/ld.so.conf檔案,把庫所在的路徑加到檔案末尾,并執行ldconfig重新整理。這樣,加入的目錄下的所有庫檔案都可見。 

3、檢視庫中的符号 

有時候可能需要檢視一個庫中到底有哪些函數,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最終在哪裡被定義,有興趣可以go on 

4、生成庫 

第一步要把源代碼編繹成目标代碼。 

以下面的代碼為例,生成上面用到的hello庫: 

/* hello.c */ 

#include   

void sayhello() 

  printf("hello,world "); 

用gcc編繹該檔案,在編繹時可以使用任何全法的編繹參數,例如-g加入調試代碼等: gcc -c hello.c -o hello.o 

(1)連接配接成靜态庫 連接配接成靜态庫使用ar指令,其實ar是archive的意思 

$ar cqs libhello.a hello.o 

(2)連接配接成動态庫 生成動态庫用gcc來完成,由于可能存在多個版本,是以通常指定版本号: 

$gcc -shared -wl,-soname,libhello.so.1 -o libhello.so.1.0 hello.o 

另外再建立兩個符号連接配接: 

$ln -s libhello.so.1.0 libhello.so.1 

$ln -s libhello.so.1 libhello.so 

這樣一個libhello的動态連接配接庫就生成了。最重要的是傳gcc -shared 參數使其生成是動态庫而不是普通執行程式。 -wl 表示後面的參數也就是-soname,libhello.so.1直接傳給連接配接器ld進行處理。實際上,每一個庫都有一個soname,當連接配接器發現它正在查找的程式庫中有這樣一個名稱,連接配接器便會将soname嵌入連結中的二進制檔案内,而不是它正在運作的實際檔案名,在程式執行期間,程式會查找擁有

soname名字的檔案,而不是庫的檔案名,換句話說,soname是庫的區分标志。 這樣做的目的主要是允許系統中多個版本的庫檔案共存,習慣上在命名庫檔案的時候通常與soname相同 libxxxx.so.major.minor 其中,xxxx是庫的名字,major是主版本号,minor 是次版本号 

本文來自chinaunix部落格,如果檢視原文請點:http://blog.chinaunix.net/u1/53989/showart_1812426.html

繼續閱讀