編譯動態鏈接庫.so和靜態鏈接庫.a

名詞介紹


  1. 靜態函數庫
    這類庫的名字一般是libxxx.a;利用靜態函數庫編譯成的文件比較大,因爲整個 函數庫的所有數據都會被整合進目標代碼中,他的優點就顯而易見了,即編譯後的執行程序不需要外部的函數庫支持,因爲所有使用的函數都已經被編譯進去了。當然這也會成爲他的缺點,因爲如果靜態函數庫改變了,那麼你的程序必須重新編譯。

  2. 動態函數庫
    這類庫的名字一般是libxxx.so;相對於靜態函數庫,動態函數庫在編譯的時候 並沒有被編譯進目標代碼中,你的程序執行到相關函數時才調用該函數庫裏的相應函數,因此動態函數庫所產生的可執行文件比較小。由於函數庫沒有被整合進你的程序,而是程序運行時動態的申請並調用,所以程序的運行環境中必須提供相應的庫。動態函數庫的改變並不影響你的程序,所以動態函數庫的升級比較方便。
    linux系統有幾個重要的目錄存放相應的函數庫,如/lib /usr/lib

靜態庫在程序編譯時會被連接到目標代碼中,程序運行時將不再需要該靜態庫。

動態庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行是才被載入,因此在程序運行時還需要動態庫存在。

本文主要通過舉例來說明在Linux中如何創建靜態庫和動態庫,以及使用它們。

創建源文件


在創建函數庫前,我們先來準備舉例用的源程序,並將函數庫的源程序編譯成.o文件。

編輯以下幾個文件

hello.c

#include<stdio.h>
void hello(const char *name){
    printf("Hello %s!\n",name);
}

hello.h

#ifndef HELLO_H
#define HELLO_H

void hello(const char *name);

#endif

main.c

#include<hello.h>
int main(){
    hello("Kevin");
    return 0;
}

編譯和使用靜態鏈接庫.a


  1. 生成.o目標文件
    gcc -c hello.c

  2. 生成.a靜態鏈接庫文件(打包成一個壓縮包,壓縮包裏面就是hello.o)
    ar cr libmyhello.a hello.o
    注:ar是用來創建,修改,和解壓歸檔;c是ar的參數,表示創建一個歸檔;r是ar的參數,表示插入文件到歸檔裏面。所以最終創建了libmyhello.o的歸檔,並往裏面插入hello.o

  3. 使用靜態鏈接庫
    gcc -o hello main.c -static -L. -lmyhello
    注:-o 輸出文件名
    -static 表示鏈接靜態鏈接庫。如果不使用該參數,而-L指定的目錄下同時擁有靜態鏈接庫和動態鏈接庫,gcc會默認使用動態鏈接庫。如果只有靜態鏈接庫,則會使用靜態鏈接庫。
    -L 表示添加一個目錄到目錄列表,使得可以被-l用來搜索。
    -l libary,當鏈接時,查找該library。

本質上,靜態鏈接庫就是.o文件的集合

編譯和使用動態鏈接庫.so


  1. 編譯動態鏈接庫.so
    gcc -shared -fPIC -o libmyhello.so hello.c

注:-shared 表示生成共享庫(也就是動態鏈接庫)
-fPIC 作用於編譯階段,告訴編譯器產生與位置無關代碼(Position-Independent Code),則產生的代碼中,沒有絕對地址,全部使用相對地址,故而代碼可以被加載器加載到內存的任意位置,都可以正確的執行。這正是共享庫所要求的。共享庫被加載時,在內存的位置不是固定的。
PIC就是position independent code PIC使.so文件的代碼段變爲真正意義上的共享
如果不加-fPIC,則加載.so文件的代碼段時,代碼段引用的數據對象需要重定位,
重定位會修改代碼段的內容,這就造成每個使用這個.so文件代碼段的進程在內核裏都會生成這個.so文件代碼段的copy.每個copy都不一樣,取決於這個.so文件代碼段和數據段內存映射的位置.
不加fPIC編譯出來的so,是要再加載時根據加載到的位置再次重定位的.(因爲它裏面的代碼並不是位置無關代碼)
如果被多個應用程序共同使用,那麼它們必須每個程序維護一份so的代碼副本了.(因爲so被每個程序加載的位置都不同,顯然這些重定位後的代碼也不同,當然不能共享)
-fPIC 的使用,會生成 PIC 代碼,.so 要求爲 PIC,以達到動態鏈接的目的,否則,無法實現動態鏈接。 non-PIC 與 PIC 代碼的區別主要在於 access global data, jump label 的不同。 比如一條 access global data
的指令, non-PIC 的形勢是:ld r3, var1 PIC的形式則是:ld r3, var1-offset@GOT,意思是從
GOT 表的 index 爲 var1-offset 的地方處 指示的地址處裝載一個值,即var1-offset@GOT處的4個 byte
其實就是 var1 的地址。這個地址只有在運行的時候才知道,是由 dynamic-loader(ld-linux.so) 填進去的。 再比如
jump label 指令 non-PIC 的形勢是:jump printf ,意思是調用 printf。 PIC 的形式則是:jump
printf-offset@GOT, 意思是跳到 GOT 表的 index 爲 printf-offset 的地方處指示的地址去執行,
這個地址處的代碼擺放在 .plt section,
每個外部函數對應一段這樣的代碼,其功能是呼叫dynamic-loader(ld-linux.so) 來查找函數的地址(本例中是 printf),然後將其地址寫到 GOT 表的 index 爲 printf-offset 的地方, 同時執行這個函數。這樣,第2次呼叫printf 的時候,就會直接跳到 printf 的地址,而不必再查找了。 GOT 是 data section, 是一個 table,除專用的幾個 entry,每個 entry 的內容可以再執行的時候修改; PLT 是 text section, 是一段一段的
code,執行中不需要修改。 每個 target 實現 PIC 的機制不同,但大同小異。比如 MIPS 沒有 .plt, 而是叫
.stub,功能和 .plt 一樣。 可見,動態鏈接執行很複雜,比靜態鏈接執行時間長;但是,極大的節省了 size,PIC和動態鏈接技術是計算機發展史上非常重要的一個里程碑。

  1. 使用動態鏈接庫
# gcc -o main main.c -L. -lmyhello
# ./main
# ./main: 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中,再試試。(使用”-lmyhello”標記來告訴GCC驅動程序在連接階段引用共享函數庫libmyhello.so。”-L.”標記告訴GCC函數庫可能位於當前目錄。否則GNU連接器會查找標準系統函數目錄:它先後搜索1.elf文件的 DT_RPATH段—2.環境變量LD_LIBRARY_PATH—3./etc/ld.so.cache文件列表—4./lib/,/usr/lib目錄找到庫文件後將其載入內存,但是我們生成的共享庫在當前文件夾下,並沒有加到上述的4個路徑的任何一箇中,因此,執行後會出現錯誤)

# mv libmyhello.so /usr/lib
# ./main
# Hello Kevin!

沒有報錯,運行成功。

把/usr/lib/libmyhello.so刪除。

第二種方法:
既然連接器會搜尋LD_LIBRARY_PATH所指定的目錄,那麼我們可以將這個環境變量設置成當前目錄:

先執行:

export LD_LIBRARY_PATH=$(pwd)

再執行:

./main

成功!

第三種方法,就是使用ldconfig
注: 當用戶在某個目錄下面創建或拷貝了一個動態鏈接庫,若想使其被系統共享,可以執行一下”ldconfig 目錄名”這個命令.此命令的功能在於讓ldconfig將指定目錄下的動態鏈接庫被系統共享起來,意即:在緩存文件/etc/ld.so.cache中追加進指定目錄下的共享庫.本例讓系統共享了/home/kevin/clib目錄下的動態鏈接庫.該命令會重建/etc/ld.so.cache文件

# ldconfig /home/kevin/clib
# ./main
# Hello Kevin!

運行成功

可以查看程序執行時調用動態庫的過程:

# ldd main
linux-vdso.so.1 =>  (0x00007fff88dcf000)
libmyhello.so => /usr/lib/libmyhello.so (0x00007f9104ed6000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9104b11000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9105100000)

靜態庫鏈接時搜索路徑順序:

  1. ld會去找GCC命令中的參數-L

  2. 再找gcc的環境變量LIBRARY_PATH

  3. 再找內定目錄 /lib /usr/lib /usr/local/lib 這是當初compile gcc時寫在程序內的

動態鏈接時、執行時搜索路徑順序:

  1. 編譯目標代碼時指定的動態庫搜索路徑;

  2. 環境變量LD_LIBRARY_PATH指定的動態庫搜索路徑;

  3. 配置文件/etc/ld.so.conf中指定的動態庫搜索路徑;

  4. 默認的動態庫搜索路徑/lib;

  5. 默認的動態庫搜索路徑/usr/lib。

有關環境變量:

LIBRARY_PATH環境變量:指定程序靜態鏈接庫文件搜索路徑

LD_LIBRARY_PATH環境變量:指定程序動態鏈接庫文件搜索路徑

參考:http://www.cnblogs.com/laojie4321/archive/2012/03/28/2421056.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章