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鏈接器選項禁用)。

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