天天看點

linux 共享庫那點事

        大多數大型軟體項目将包含幾個元件,您可能會在以後的某些其他項目中發現其中一些元件,或者隻是出于組織目的而将它們分開。當您具有一組可重用或邏輯上不同的函數時,從中建構一個so庫将很有幫助,這樣您就不必将源代碼複制到目前項目中并始終重新編譯它-這樣您就可以保留不同的子產品您的程式不相交,隻更改其中一個而不會影響其他程式。一旦編寫并測試了它,您就可以一次又一次地安全地重複使用它,進而節省了時間和每次将其建構到項目中的麻煩。

       建構靜态庫非常簡單,并且由于我們很少有關于它們的問題,是以我不會介紹它們。我們講解一下有關共享庫,這對于大多數人來說似乎更加令人困惑。 

      講解中要使用的代碼例子:

 foo.h 檔案:

#ifndef foo_h__
#define foo_h__

extern void foo(void);

#endif  // foo_h__
           

foo.c 檔案

#include <stdio.h>


void foo(void)
{
    puts("Hello, I am a shared library");
}
           

  main.c 檔案

#include <stdio.h>
#include "foo.h"

int main(void)
{
    puts("This is a shared library test...");
    foo();
    return 0;
}
           

foo.h 檔案定義了我們庫的一個單獨的函數接口 foo(),foo.c 檔案包含了接口函數的實作,main.c 是使用了我們庫檔案的程式代碼

步驟一 使用-pic 進行編譯

     我們需要将我們的庫源代碼編譯成與位置無關的代碼。

$ gcc -c -Wall -Werror -fpic foo.c
           

步驟二 從目标檔案建立共享庫

gcc -shared -o libfoo.so foo.o
           

步驟三 連結共享庫

如您所見,這實際上很容易。我們有一個共享庫。讓我們編譯main.c并将其與libfoo連結,我們醬最終編譯出來的程式命名為test,請注意,-lfoo選項不是在查找foo.o,而是在libfoo.so。 GCC假定所有庫均以lib開頭,并以.so或.a結尾(.so用于共享庫或共享庫,而.a用于靜态連結庫)。

$ gcc -Wall -o test main.c -lfoo
/usr/bin/ld: cannot find -lfoo
collect2: ld returned 1 exit status
           

 哦!連結器不知道在哪兒尋找ibfoo,GCC預設會去一系列的位置去查找,但我們的目錄不在該位置清單中。我們需要使用-L 選項告訴gcc 去那個地方尋找我們的libfoo 庫,在這個列子中我們使用目前路徑

$ gcc -L/home/username/foo -Wall -o test main.c -lfoo
           

 步驟四 讓庫在運作是可用

但我們運作編譯好的test 檔案時

$ ./test
./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
           

 加載器不能發現我們的共享庫(注釋3),我們沒有在标準的位置安裝過它,是以需要幫助加載器一下,我們有一對選項:我們可以使用環境變量LD_LIBRARY_PATH或者rpath。讓我們先嘗試使用LD_LIBRARY_PATH

使用LD_LIBRARY_PATH

讓我們将共享庫的路徑加到LD_LIBRARY_PATH 中

$ export LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH
$ ./test
This is a shared library test...
Hello, I am a shared library
           

test 可以執行了,LD_LIBRARY_PATH非常适合快速測試以及沒有管理者權限的系統,但是,不利的一面是導出LD_LIBRARY_PATH變量意味着它可能會導緻您運作的其他程式出現問題,這些程式也依賴LD_LIBRARY_PATH,如果您未将其恢複為以前的狀态。

使用rpath

現在讓我們試試rpath(首先我們要清空LD_LIBRARY_PATH)。Rpath或運作路徑是一種将共享庫的位置嵌入到可執行檔案中的方法,而不是依賴于預設位置或環境變量。我們在連結階段執行此操作,注意-Wl,-rpath=/home/username/foo 選項。-Wl部分将逗号分隔的選項發送到連結器,是以我們将-rpath 加上我們的工作目錄選項發送到連結器。

$ unset LD_LIBRARY_PATH
$ gcc -L/home/username/foo -Wl,-rpath=/home/username/foo -Wall -o test main.c -lfoo
$ ./test
This is a shared library test...
Hello, I am a shared library
           

不錯,這樣也工作了,rpath方法很棒,因為每個程式都可以獨立列出其共享庫的位置。

rpath vs. LD_LIBRARY_PATH

 但是,rpath有一些缺點。首先,它要求将共享庫安裝在固定的位置,以便程式的所有使用者都可以通路那些位置的那些庫。這意味着系統配置的靈活性降低。其次,如果該庫引用了NFS挂載或其他網絡驅動器,則在程式啟動時可能會遇到不希望的延遲

 使用ldconfig 指令 修改 ld.so

如果我們想安裝我們的庫,以便系統上的每個人都可以使用該庫怎麼辦?為此,您将需要管理者權限。您出于兩個原因需要這樣做:首先,将庫放置在标準位置,可能是/usr/lib或/usr/local/lib,普通使用者沒有寫通路權限。其次,您将需要修改ld.so配置檔案和緩存。以root使用者身份執行以下操作:

$ cp /home/username/foo/libfoo.so /usr/lib
$ chmod 0755 /usr/lib/libfoo.so
           

現在,檔案在标準位置并且具有正确的權限,每個人都可以讀取。我們需要告訴加載器它可以使用,是以讓我們更新緩存:

$ ldconfig
           

這将建立一個指向我們共享庫的連結并更新緩存,以便可以立即使用。讓我們仔細檢查:

$ ldconfig -p | grep foo
libfoo.so (libc6) => /usr/lib/libfoo.so
           

現在我們的庫已安裝。在測試之前,我們必須清理一些事情,再次清除LD_LIBRARY_PATH,以防萬一:

$ unset LD_LIBRARY_PATH
           

重新連結我們的可執行檔案。注意,我們不需要-L選項,因為我們的庫存儲在預設位置,并且我們沒有使用rpath選項:

$ gcc -Wall -o test main.c -lfoo
           

讓我們確定使用ldd使用庫的/usr/lib:

$ ldd test | grep foo
libfoo.so => /usr/lib/libfoo.so (0x00a42000)
           

不錯讓我們試着運作它

$ ./test
This is a shared library test...
Hello, I am a shared library
           

我們已經介紹了如何建構共享庫,如何與之連結以及如何解決共享庫中最常見的加載器問題-以及不同方法的正面和負面影響。

總結一下共享庫的加載次序:

1  在可執行檔案的DT_RPATH(-Wl,--rpath=)節中查找,除非這兒有DT_RUNPATH(-Wl,--rpath=.,--enable-new-dtags )節

2 在LD_LIBRARY_PATH中查找。如果出于安全原因将可執行檔案設定為setuid / setgid,則将跳過此步驟。

3 在可執行檔案的DT_RUNPATH中查找,除非設定了setuid / setgid位(出于安全原因)。

4 在緩存檔案/etc/ ld /so/cache中查找(可以使用-z nodeflib連結器選項禁用)。

5 在預設的/lib 和/usr/lib  中查找可以使用-z nodeflib連結器選項禁用)。

繼續閱讀