1. 程式人生 > >linux下靜態庫基本概念

linux下靜態庫基本概念

一、基本概念

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會有如下錯誤:
[[email protected] lib]$ ls
libtest.a  libtest.so  t  t.cc  test.cc  test.h  test.o
[[email protected] lib]$ g++ -Wall -g t.cc -o t -L./ -Wl,-Bstatic -ltest -Wl,-Bdynamic -lm -lc
[[email protected] lib]$ g++ -Wall -g t.cc -o t -L./ -Wl,-Bstatic -ltest 
/usr/bin/ld: cannot find -lm
collect2: ld 返回 1
參考:
五、有關環境變數: 
    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的方法。
七、動態庫升級問題:
   在動態連結庫升級時,
   不能使用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,也不應與核心的檔案系統底層實現耦合。

以下是其他常用的linux作業系統目錄:/bin:存放最常用命令;/dev:裝置檔案;/etc:存放各種配置檔案;/home:使用者主目錄;/lib:系統最基本的動態連結共享庫;/mnt:一般是空的,用來臨時掛載別的檔案系統;/proc:虛擬目錄,是記憶體的對映;/sbin:系統管理員命令存放目錄;/usr:最大的目錄,存許應用程式和檔案;/usr/X11R6:X-Window目錄;/usr/src:Linux原始碼;/usr/include:系統標頭檔案;/usr/lib:存放常用動態連結共享庫、靜態檔案庫;/usr/bin、/usr/sbin:這是對/bin、/sbin的一個補充。

/boot:啟動Linux的核心檔案

相關推薦

linux靜態基本概念

一、基本概念1.1、什麼是庫 在 windows 平臺和 linux 平臺下都大量存在著庫。       本質上來說庫是一種可執行的二進位制程式碼(但不可以獨立執行),可以被作業系統載入記憶體執行。       由於 windows 和 linux 的平臺不同(主要是編譯器、

Linux靜態與動態

Linux 靜態庫 動態庫 靜態庫 先說說我們為什麽需要庫?當有些代碼我們大量會在程序中使用比如(scanf,printf等)這些函數我們需要在程序中頻繁使用,於是我們就把這些代碼編譯為庫文件,在需要使用時我們直接鏈接即可。 定義: ?程序在編譯時把靜態庫的代碼鏈接到可執行程序中,在代碼運行時不再

Linux靜態、動態的建立和使用

Linux下靜態庫、動態庫的建立和使用 Linux庫檔名由:字首lib、庫名和字尾3部分組成,靜態庫通常以.a作為字尾,動態庫以.so作為字尾, Linux下把動態庫叫做共享庫,so即shared object的縮寫。 靜態庫是程式編譯連結時使用,動態庫是程式執行時使用。

linux靜態動態的製作和使用

動態庫與靜態庫本質是二進位制的原始碼,只是人看不懂,對機器沒有影響。 靜態庫的製作和使用 命名規則:  名字一般分為三部分,開頭為“lib”,表示這是一個庫檔案,接下來是想取的名字,最後是字尾“.a”(windows下是lib)。例如:libhello.a 製作步驟:  1.原材料:

Linux靜態和動態的製作和使用

Linux作業系統支援的庫函式分為;   1.靜態庫:libxxx.a  在編譯時就將庫函式編譯進可執行程式中.      優點.  程式執行環境中不需要外部的函式庫.       缺點: 可執行程

Linux靜態與動態(.a、.so)

ref:http://niefei.blog.ccidnet.com/blog/ccid/do_showone/tid_42855.html 1. 介紹   使用GNU的工具我們如何在Linux下建立自己的程式函式庫?一個“程式函式庫”簡單的說就是一個檔案包含了一些編譯好的程式碼和資料,這些編譯好的程式碼和資

Linux靜態和動態(共享

Linux作業系統支援的函式庫分為靜態庫和動態庫,動態庫又稱共享庫。linux系統有幾個重要的目錄存放相應的函式庫,如/lib /usr/lib。 靜態函式庫:       這類庫的名字一般是libxxx.a;利用靜態函式庫編譯成的檔案比較大,因為整個函式庫的所有資料都會被整合進目的碼中,他的優點就顯而易見了

linux靜態.a和動態.so檔案的生成和使用

1.靜態庫是一些目標檔案(字尾名為.o)的集合體而已。 2.靜態庫的字尾名是.a,對應於windows作業系統的字尾名為.lib的靜態庫。 3.可以使用ar命令來建立一個靜態庫檔案。 來看一個例項,根據書中的程式碼簡化的,先看一看可以編譯成庫檔案的原始檔中的程式碼: /* test.c */ i

linux 靜態的編譯與使用

前言 最近在學習《linux/Unix系統程式設計手冊》,對下載原始碼後將原始碼編譯成庫並使用的過程進行記錄。 靜態庫的字尾是.a,它的產生分兩步 : 1、由原始檔編譯生成一堆.o,每個.o裡都包

關於linuxSVNhook的基本概念說明。

svn安裝完成後,建立一個svn倉庫。在倉庫目錄下,有一個hooks資料夾。裡面是一些tmpl字尾的檔案。我們一般情況下主要考慮的是pre-commit(提交前執行)和post-commit(提交後執行)兩個鉤子函式。當我們想要使用的時候,只需要複製(舉個栗子)post-c

linux靜態、動態總結

1.3、靜態庫,動態庫檔案在linux下是如何生成的: 以下面的程式碼為例,生成上面用到的hello庫: /* hello.c */ #include "hello.h" void sayhello() { printf("hello,world "); }首先用gcc編繹該檔案

linux靜態和動態使用的從無到有

首先我們先列出gcc編譯器的常用命令:我們在來看看gcc的編譯流程,因為我們在開發工具中玩了太久,一般都會忽略這些細節的:好,下面我們在來談一談靜態庫和動態庫關於靜態庫和動態庫的優點和結構實現我們就不BB了。ar是gnu歸檔工具,rcs表示(replace and creat

linux靜態和動態的通用生成模板

                                                                    Makefile檔案的編寫#########################################################

linux靜態和動態詳解

  原文:http://blog.chinaunix.net/uid-23069658-id-3142046.html 今天我們主要來說說Linux系統下基於動態庫(.so)和靜態(.a)的程式那些貓膩。在這之前,我們需要了解一下原始碼到可執行程式之間到底發生了什麼神奇而

Linux靜態和動態

文件拷貝 -shared info pri 歸檔文件 share 快捷 ima shared 函數庫分為靜態庫和動態庫 動態庫(格式為libname.so[.主版本號.次版本號.發行號])。在程序編譯時並不會被鏈接到目標代碼中,而是在程序運行時才被載入。 靜態庫是目標文件.

Linux動態(.so)和靜態(.a) 的區別 Linux動態(.so)和靜態(.a) 的區別 動態(.so)連結靜態(.a)的情況總結

Linux下動態庫(.so)和靜態庫(.a) 的區別   靜態庫在程式編譯時會被連線到目的碼中,程式執行時將不再需要該靜態庫。編譯之後程式檔案大,但載入快,隔離性也好。 動態庫在程式編譯時並不會被連線到目的碼中,而是在程式執行是才被載入,因此在程式執行時還需要動態庫存在。多個

Linux動態靜態操作

Linux命令之ar - 建立靜態庫.a檔案 用途說明 建立靜態庫.a檔案。用C/C++開發程式時經常用到,但我很少單獨在命令列中使用ar命令,一般寫在makefile中,有時也會在shell腳 本中用到。關於Linux下的庫檔案、靜態庫、動態庫以及怎樣建立和使用等相關知識,參見本文後面的相

Linux動態靜態的連結

一、檢視連結了哪些指令 ldd 程式名字 二、在應用程式需要連線外部庫的情況下,linux預設對庫的連線是使用動態庫,在找不到動態庫的情況下再選擇靜態庫。使用方式為: gcc test.cpp -L. -ltestlib 如果當前目錄有兩個庫libtestlib.

Linux環境靜態的生成和使用 (.a檔案)

        這一陣子的工作用到了linux,也用到了linux的靜態庫和動態庫。正好對這一塊兒一直不明白,趁此機會學習了一下。以下是筆記。先說一說linux下靜態庫的生成和使用方法。 &nb

LINUX動態呼叫靜態的方法

ppc_85xx-gcc -shared -fPIC liberr.c -o liberr.so-fPIC 作用於編譯階段,告訴編譯器產生與位置無關程式碼(Position-Independent Code),則產生的程式碼中,沒有絕對地址,全部使用相對地址,故而程式碼可以被載入器載入到記憶體的任意  位置