linux下靜態庫和動態庫一些東西


http://www.cnblogs.com/changefuture/archive/2011/12/22/2297460.html

Linux  動態鏈接庫和靜態庫示例

文件預覽

文件目錄樹如下,如你所見,非常簡單。

  • libtest/  
  • |-- lt.c  
  • |-- lt.h  
  • `-- test.c  

 

代碼

#lt.c

  • /* lt.c 
  •  * 
  •  */  
  •   
  • #include <stdio.h>  
  •   
  • void myprint(void)  
  • {  
  •   printf("Linux library test!\n");  
  • }  


# lt.h

  • /* lt.h 
  •  *  
  •  */  
  •   
  • void myprint(void);  


#test.c

  • /* test.c 
  •  * 
  •  */  
  •   
  • #include "lt.h"  
  •   
  • int main(void)  
  • {  
  •   myprint();  
  •   return 0;  
  • }  

先看靜態庫

首先做成靜態庫 liblt.a 。

  • $ gcc -c lt.c -o lt.o  
  • $ ar cqs liblt.a lt.o  


再者,鏈接,這里指定了靜態庫的位置,注意文件順序不可亂序。

  • $ gcc test.c liblt.a -o test  


這個時候再來看他的引用庫情況。

  • $ ldd test                                            //ldd  是用來查看鏈接那些動態鏈接庫
  •         linux-gate.so.1 =>  (0xffffe000)  
  •         libc.so.6 => /lib/libc.so.6 (0xb7e29000)  
  •         /lib/ld-linux.so.2 (0xb7f6e000)  

動態庫

做成動態庫 liblt.so 。

  • $ gcc -c lt.c -o lt.o  
  • $ gcc -shared -Wall -fPIC lt.o -o liblt.so  


鏈接方法I,拷貝到系統庫里再鏈接,讓gcc自己查找

  • $ sudo cp liblt.so /usr/lib  
  • $ gcc -o test test.o -llt  

這里我們可以看到了 -llt 選項,-l[lib_name] 指定庫名,他會主動搜索 lib[lib_name].so 。這個搜索的路徑可以通過 gcc --print-search-dirs來查找。
鏈接方法II,手動指定庫路徑

  • $ cc -o test test.o -llt -B /path/to/lib

這里的-B 選項就添加 /path/to/lib 到gcc搜索的路徑之中。這樣鏈接沒有問題但是方法II 中手動鏈接好的程序在執行時候仍舊需要指定庫路徑(鏈接和執行是分開的)。需要添加系 統變量 LD_LIBRARY_PATH :

  • $ export LD_LIBRARY_PATH=/path/to/lib  


這個時候再來檢測一下test程序的庫鏈接狀況(方法I情況)

  • $ ldd test  
  •         linux-gate.so.1 =>  (0xffffe000)  
  •         liblt.so => /usr/lib/liblt.so (0xb7f58000)  
  •         libc.so.6 => /lib/libc.so.6 (0xb7e28000)  
  •         /lib/ld-linux.so.2 (0xb7f6f000)  

恩,是不是比靜態鏈接的程序多了一個 liblt.so ?恩,這就是靜態與動態的最大區別,靜態情況下,他把庫直接加載到程序里,而在動態鏈接的時候,他只是保留接口,將動態庫與程序代碼獨立。這樣就可以提高代碼的可復用度,和降低程序的耦合度。

 

=================================================

http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520101023104745738/

一、基本概念

1.1、什么是庫

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

       本質上來說庫是一種可執行的二進制代碼(但不可以獨立執行),可以被操作系統載入內存執行

       由於 windows 和 linux 的平台不同(主要是編譯器、匯編器和連接器 的不同),因此二者庫的二進制是不兼容的。

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

 

1.2、 庫的種類

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

   二者的不同點在於代碼被載入的時刻不同:

   靜態庫的代碼在編譯過程中已經被載入可執行程序,因此生成的可執行程序體積較大。靜態用.a為后綴, 例如: libhello.a

   共享庫(動態庫)的代碼是在可執行程序運行時才載入內存的,在編譯過程中僅簡單的引用,因此生成的可執行程序代碼體積較小。

   動態通常用.so為后綴, 例如:libhello.so

      共享庫(動態庫)的好處是:: 不同的應用程序如果調用相同的庫,那么在內存里只需要有一份該共享庫的實例。

      為了在同一系統中使用不同版本的庫,可以在庫文件名后加上版本號為后綴,例如: libhello.so.1.0,由於程序連接默認以.so為文件后綴名。所以為了使用這些庫,通常使用建立符號連接的方式。

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

      ln -s libhello.so.1 libhello.so

1.3、靜態庫,動態庫文件在linux下是如何生成的:
以下面的代碼為例,生成上面用到的hello庫:
/* hello.c */  
#include "hello.h"  
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 -o libhello.so.1.0 hello.o
 
1.4、庫文件是如何命名的,有沒有什么規范: 
在 linux 下,庫文件一般放在/usr/lib和/lib下, 
靜態庫的名字一般為libxxxx.a,其中 xxxx 是該lib的名稱;
動態庫的名字一般為libxxxx.so.major.minor,xxxx 是該lib的名稱,major是主版本號,minor是副版本號
 
1.5、可執行程序在執行的時候如何定位共享庫(動態庫)文件 :
    當系統加載可執行代碼(即庫文件)的時候,能夠知道其所依賴的庫的名字,但是還需要知道絕對路徑,此時就需要系統動態載入器 (dynamic linker/loader) 
    對於 elf 格式的可執行程序,是由 ld-linux.so* 來完成的,它先后搜索 elf 文件的 DT_RPATH 段-->環境變量LD_LIBRARY_PATH—->/etc/ld.so.cache 文件列表--> /lib/,/usr/lib 目錄找到庫文件后將其載入內存
    如: export LD_LIBRARY_PATH=’pwd’      將當前文件目錄添加為共享目錄。
 
1.6、使用ldd工具,查看可執行程序依賴那些動態庫或着動態庫依賴於那些動態庫
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 庫 
 
1.7、使用nm工具,查看靜態庫動態庫有那些函數名;
T類表示函數是當前庫中定義的U類表示函數是被調用的,在其它庫中定義的W類當前庫中定義,被其它庫中的函數覆蓋)。
    有時候可能需要查看一個庫中到底有哪些函數,nm工具可以打印出庫中的涉及到的所有符號,這里的庫既可以是靜態的也可以是動態的。

nm列出的符號有很多, 常見的有三種::

T類:庫中定義的函數,用T表示,這是最常見的

U類:在庫中被調用,但並沒有在庫中定義(表明需要其他庫支持),用U表示

W類:是所謂的“弱態”符號,它們雖然在庫中被定義,但是可能被其他庫中的同名符號覆蓋,用W表示

例如,假設開發者希望知道上文提到的hello庫中是否引用了 printf():

   nm libhello.so | grep printf 

發現printf是U類符號,說明printf被引用,但是並沒有在庫中定義。

由此可以推斷,要正常使用hello庫,必須有其它庫支持,使用ldd工具查看hello依賴於哪些庫:

ldd libhello.so

libc.so.6=>/lib/libc.so.6(0x400la000)

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

從上面的結果可以繼續查看printf最終在哪里被定義,有興趣可以go on

 

1.8、使用ar工具,可以生成靜態庫,同時可以查看靜態庫中包含那些.o文件,即有那些源文件構成

可以使用 ar -t libname.a 來查看一個靜態庫由那些.o文件構成。

可以使用 ar q libname.a xxx1.o xxx2.o xxx3.o ... xxxn.o 生成靜態庫

 

1.9、如何查看動態庫和靜態庫是32位,還是64位下的庫:

如果是動態庫,可以使用file *.so;

如果是靜態哭,可以使用objdump -x *.a

 

Linux下進行程序設計時,關於庫的使用:
一、gcc/g++命令中關於庫的參數:     -shared: 該選項指定生成動態連接庫;     -fPIC:表示編譯為位置獨立(地址無關)的代碼,不用此選項的話,編譯后的代碼是位置相關的,所以動態載入時,是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。     -L:指定鏈接庫的路徑,-L. 表示要連接的庫在當前目錄中     -ltest:指定鏈接庫的名稱為test,編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,后面加上.so來確定庫的名稱
    -Wl,-rpath: 記錄以來so文件的路徑信息。     LD_LIBRARY_PATH:這個環境變量指示動態連接器可以裝載動態庫的路徑。      當然如果有root權限的話,可以修改/etc/ld.so.conf文件,然后調用 /sbin/ldconfig來達到同樣的目的,
     不過如果沒有root權限,那么只能采用修改LD_LIBRARY_PATH環境變量的方法了。 
調用動態庫的時候,有幾個問題會經常碰到:
    1、有時,明明已經將庫的頭文件所在目錄 通過 “-I” include進來了,庫所在文件通過 “-L”參數引導,並指定了“-l”的庫名,但通過ldd命令察看時,就是死活找不到你指定鏈接的so文件,這時你要作的就是通過修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件來指定動態庫的目錄。通常這樣做就可以解決庫無法鏈接的問題了。
 
二、靜態庫鏈接時搜索路徑的順序:     1. ld會去找gcc/g++命令中的參數-L;    2. 再找gcc的環境變量LIBRARY_PATH,它指定程序靜態鏈接庫文件搜索路徑;
      export LIBRARY_PATH=$LIBRARY_PATH:data/home/billchen/lib     3. 再找默認庫目錄 /lib  /usr/lib  /usr/local/lib,這是當初compile gcc時寫在程序內的。 
 
三、動態鏈接時、執行時搜索路徑順序:      1. 編譯目標代碼時指定的動態庫搜索路徑;
    2. 環境變量LD_LIBRARY_PATH指定動態庫搜索路徑,它指定程序動態鏈接庫文件搜索路徑;
      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:data/home/billchen/lib      3. 配置文件/etc/ld.so.conf中指定的動態庫搜索路徑;     4. 默認的動態庫搜索路徑/lib;     5. 默認的動態庫搜索路徑/usr/lib。 
 
四、靜態庫和動態鏈接庫同時存在時,gcc/g++默認鏈接的是動態庫:
    當一個庫同時存在靜態庫和動態庫時,比如libmysqlclient.a和libmysqlclient.so同時存在時:
    在Linux下,動態庫和靜態庫同事存在時,gcc/g++的鏈接程序,默認鏈接的動態庫
    可以使用下面的方法,給連接器ld傳遞參數,看是否鏈接動態庫還是靜態庫。
-Wl,-Bstatic -llibname //指定讓gcc/g++鏈接靜態庫 使用: gcc/g++ test.c -o test -Wl,-Bstatic -llibname -Wl,-Bdynamic -lm -lc
-Wl,-Bdynamic -llibname //指定讓gcc/g++鏈接動態庫 使用: gcc/g++ test.c -o test -Wl,-Bdynamic -llibname
   如果要完全靜態加在,使用-static參數,即將所有的庫以靜態的方式鏈入可執行程序,這樣生成的可執行程序,不再依賴任何庫,同事出現的問題是,這樣編譯出來的程序非常大,占用空間。
如果不適用-Wl,-Bdynamic -lm -c會有如下錯誤:
[chenbaihu@build17 lib]$ ls libtest.a  libtest.so  t  t.cc  test.cc  test.h  test.o [chenbaihu@build17 lib]$ g++ -Wall -g t.cc -o t -L./ -Wl,-Bstatic -ltest -Wl,-Bdynamic -lm -lc [chenbaihu@build17 lib]$ g++ -Wall -g t.cc -o t -L./ -Wl,-Bstatic -ltest  /usr/bin/ld: cannot find -lm collect2: ld 返回 1
參考:
http://lists.gnu.org/archive/html/help-gnu-utils/2004-03/msg00009.html
 
         
五、有關環境變量:     LIBRARY_PATH環境變量:指定程序靜態鏈接庫文件搜索路徑     LD_LIBRARY_PATH環境變量:指定程序動態鏈接庫文件搜索路徑 
 
六、庫的依賴問題:
   比如我們有一個基礎庫libbase.a,還有一個依賴libbase.a編譯的庫,叫做libchild.a;在我們編譯程序時,一定要先-lchild再-lbase。 如果使用 -lbase -lchild,在編譯時將出現一些函數undefined,而這些函數實際上已經在base中已經定義;
   為什么會有庫的依賴問題?    一、靜態庫解析符號引用:       鏈接器ld是如何使用靜態庫來解析引用的。在符號解析階段,鏈接器從左至右,依次掃描可重定位目標文件(*.o)靜態庫(*.a)在這個過程中,鏈接器將維持三個集合:    集合E:可重定位目標文件(*.o文件)的集合。    集合U:未解析(未定義)的符號集,即符號表中UNDEF的符號。    集合D: 已定義的符號集。    初始情況下,E、U、D均為空。    1、對於每個輸入文件f,如果是目標文件(.o),則將f加入E,並用f中的符號表修改U、D(在文件f中定義實現的符號是D,在f中引用的符號是U),然后繼續下個文件。    2、如果f是一個靜態庫(.a),那么鏈接器將嘗試匹配U中未解析符號與靜態庫成員(靜態庫的成員就是.o文件)定義的符號。如果靜態庫中某個成員m(某個.o文件)定義了一個符號來解析U中引用,那么將m加入E中,    同時使用m的符號表,來更新U、D。對靜態庫中所有成員目標文件反復進行該過程,直至U和D不再發生變化。此時,靜態庫f中任何不包含在E中的成員目標文件都將丟棄,鏈接器將繼續下一個文件。    3、當所有輸入文件完成后,如果U非空,鏈接器則會報錯,否則合並和重定位E中目標文件,構建出可執行文件。  到這里,為什么會有庫的依賴問題已經得到解答:  因為libchild.a依賴於libbase.a,但是libbase.a在libchild.a的左邊,導致libbase.a中的目標文件(*.o)根本就沒有被加載到E中,所以解決方法就是交換兩者的順序。當然也可以使用-lbase -lchild -lbase的方法。
參考文章:http://pananq.com/index.php/page/3/
 
七、動態庫升級問題:
   在動態鏈接庫升級時,    不能使用cp newlib.so oldlib.so,這樣有可能會使程序core掉;    而應該使用:    rm oldlib.so 然后 cp newlib.so oldlib.so    或者     mv oldlib.so oldlib.so_bak 然后 cp newlib.so oldlib.so

為什么不能用cp newlib.so oldlib.so ?

在替換so文件時,如果在不停程序的情況下,直接用 cp new.so old.so 的方式替換程序使用的動態庫文件會導致正在運行中的程序崩潰。

 

解決方法:

解決的辦法是采用“rm+cp” 或“mv+cp” 來替代直接“cp” 的操作方法。

linux系統的動態庫有兩種使用方法:運行時動態鏈接庫,動態加載庫並在程序控制之下使用。

 

1、為什么在不停程序的情況下,直接用 cp 命令替換程序使用的 so 文件,會使程序崩潰? 很多同學在工作中遇到過這樣一個問題,在替換 so 文件時,如果在不停程序的情況下,直接用cp new.so old.so的方式替換程序使用的動態庫文件會導致正在運行中的程序崩潰,退出。

這與 cp 命令的實現有關,cp 並不改變目標文件的 inode,cp 的目標文件會繼承被覆蓋文件的屬性而非源文件。實際上它是這樣實現的: strace cp libnew.so libold.so 2>&1 |grep open.*lib.*.so open("libnew.so", O_RDONLY|O_LARGEFILE) = 3 open("libold.so", O_WRONLY|O_TRUNC|O_LARGEFILE) = 4 在 cp 使用“O_WRONLY|O_TRUNC” 打開目標文件時,原 so 文件的鏡像被意外的破壞了。這樣動態鏈接器 ld.so 不能訪問到 so 文件中的函數入口。從而導致 Segmentation fault,程序崩潰。ld.so 加載 so 文件及“再定位”的機制比較復雜。

 

2、怎樣在不停止程序的情況下替換so文件,並且保證程序不會崩潰? 答案是采用“rm+cp” 或“mv+cp” 來替代直接“cp” 的操作方法。

在用新的so文件 libnew.so 替換舊的so文件 libold.so 時,如果采用如下方法: rm libold.so //如果內核正在使用libold.so,那么inode節點不會立刻別刪除掉。 cp libnew.so libold.so 采用這種方法,目標文件 libold.so 的 inode 其實已經改變了,原來的 libold.so 文件雖然不能用"ls"查看到,但其inode並沒有被真正刪除,直到內核釋放對它的引用。

(即: rm libold.so,此時,如果ld.so正在加在libold.so,內核就在引用libold.so的inode節點,rm libold.so的inode並沒有被真正刪除,當ld.so對libold.so的引用結束,inode才會真正刪除。這樣程序就不會崩潰,因為它還在使用舊的libold.so,當下次再使用libold.so時,已經被替換,就會使用新的libold.so)

同理,mv只是改變了文件名,其 inode 不變,新文件使用了新的 inode。這樣動態鏈接器 ld.so 仍然使用原來文件的 inode 訪問舊的 so 文件。因而程序依然能正常運行。

(即: mv libold.so ***后,如果程序使用動態庫,還是使用舊的inode節點,當下次再使用libold.so時,就會使用新的libold.so)

到這里,為什么直接使用“cp new_exec_file old_exec_file”這樣的命令時,系統會禁止這樣的操作,並且給出這樣的提示“cp: cannot create regular file `old': Text file busy”。

這時,我們采用的辦法仍然是用“rm+cp”或者“mv+cp”來替代直接“cp”,這跟以上提到的so文件的替換有同樣的道理

但是,為什么系統會阻止cp覆蓋可執行程序,而不阻止覆蓋so文件

這是因為 Linux 有個 Demand Paging 機制,所謂“Demand Paging”,簡單的說,就是系統為了節約物理內存開銷,並不會程序運行時就將所有頁(page)都加載到內存中,而只有在系統有訪問需求時才將其加載。“Demand Paging”要求正在運行中的程序鏡像注意,並非文件本身不被意外修改因此內核在啟動程序后會鎖定這個程序鏡像的 inode

對於 so 文件,它是靠 ld.so 加載的,而ld.so畢竟也是用戶態程序,沒有權利去鎖定inode,也不應與內核的文件系統底層實現耦合。

========================

--本文系轉載,原貼地址已給出。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM