posts - 134,comments - 22,trackbacks - 0

          1.庫文件簡(jiǎn)介

          庫文件是一個(gè)包含了編譯后代碼、數(shù)據(jù)的文件,用于與程序其他代碼連編,它可以使得程序模塊化、編譯速度更快,并且易于更新。庫文件分為三種(實(shí)質(zhì)為兩種,在隨后兩句話有解釋):靜態(tài)庫(在程序之前就已經(jīng)裝載進(jìn)其中了)、共享庫(在程序啟動(dòng)之時(shí)加載進(jìn)去,在程序直接共享)、動(dòng)態(tài)加載庫(dynamically loaded,DL)(在程序運(yùn)行中任何時(shí)候都可以被加載進(jìn)程序中使用,事實(shí)上DL并非是一個(gè)完全不同的庫類型,共享庫可以用作DL而被動(dòng)態(tài)加載(靜態(tài)庫在Linux貌似無法用dlopen加載)。注意有些人使用dynamically linked libraries (DLLs)來指代共享庫,有些人使用DLL這個(gè)詞來形容任何可以被用作DL的庫文件,這個(gè)請(qǐng)區(qū)分對(duì)待。

          在具體使用中,我們應(yīng)該多使用共享庫,這使得用戶可以獨(dú)立于使用該庫文件的程序而更新庫。DL的確非常有用,但有時(shí)候我們可能并不需要那些靈活性,而對(duì)于靜態(tài)庫,由于更新起來實(shí)在費(fèi)勁,我們一般不使用。

          2.靜態(tài)庫的建立

          靜態(tài)庫就是一堆普通的目標(biāo)文件(object file),習(xí)慣上靜態(tài)庫以.a為后綴,這是使用ar命令生成的。靜態(tài)庫允許用戶不用重新編譯代碼就可以鏈接程序,以節(jié)省重新編譯的時(shí)間,其實(shí)這個(gè)時(shí)間已經(jīng)在強(qiáng)大的機(jī)器配置和快速的編譯器中顯得微不足道了,這個(gè)常常用來提供程序而不是源代碼。速度上,靜態(tài)ELF(Executable and Linking Format)庫文件比共享庫或者動(dòng)態(tài)加載庫快1%-5%,但實(shí)際上常常因?yàn)槠渌蛩囟⒉灰欢臁?/font>

          我們寫主文件prog.c:

            1: #include
            2: void ctest1(int *);
            3: void ctest2(int *);
            4:
            5: int main()
            6: {
            7:    int x;
            8:    ctest1(&x);
            9:    printf("Valx=%d\n",x);
           10:
           11:    return 0;
           12: }
           13: 然后寫這兩個(gè)函數(shù)的實(shí)現(xiàn):

          ctest1.c

            1: void ctest1(int *i)
            2: {
            3:    *i=5;
            4: }       ctest2.c

            1: void ctest2(int *i)
            2: {
            3:    *i=100;
            4: }我們首先編譯這兩個(gè)函數(shù)實(shí)現(xiàn)的源文件:

          gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ gcc -Wall -c ctest1.c ctest2.c
          gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ ls
          ctest1.c  ctest1.o  ctest2.c  ctest2.o  prog.c

          然后創(chuàng)建靜態(tài)庫libctest.a:

          gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ ar -cvq libctest.a ctest1.o ctest2.o

          a - ctest1.o
          a - ctest2.o

          我們查看一下這個(gè)庫中的文件:

          gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ ar -t libctest.a
          ctest1.o
          ctest2.o

          此時(shí)我們可以編譯我們的程序了,注意-l選項(xiàng),后邊的參數(shù)是去掉lib和.a的部分,并且需要放在要編譯的文件名之后,否則會(huì)報(bào)錯(cuò)。:

          gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ gcc -o test prog.c -L./ –lctest
          gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ ls
          ctest1.c  ctest1.o  ctest2.c  ctest2.o  libctest.a  prog.c  test
          gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ ./test
          Valx=5

          3.共享庫的建立

          共享庫是在程序啟動(dòng)時(shí)加載的庫文件。當(dāng)共享庫加載完畢后所有啟動(dòng)的起來的程序都將使用新的共享庫。在創(chuàng)建共享庫之前,還需要了解一些知識(shí):

          命名規(guī)則:
          每一個(gè)共享庫都有一個(gè)soname,一般都形如libname.so.versionNumber,其中versionNumber每當(dāng)接口發(fā)生改變時(shí)都要增加,一個(gè)完全的soname的前綴應(yīng)該是它所在目錄,在一個(gè)實(shí)際系統(tǒng)中,一個(gè)完整的soname只是共享庫文件的real name的符號(hào)鏈接。程序運(yùn)行時(shí)在內(nèi)部列出所需的共享庫時(shí)使用的就是soname。
          每一個(gè)共享庫也有一個(gè)real name,這是包含實(shí)際代碼的文件名,real name使用soname為前綴,并且在后邊添加一些信息,一般都形如soname.MinorNumber.ReleaseNumber。 最后的releaseNumber可有可無。這個(gè)是生成共享庫時(shí)實(shí)際文件的名稱。
          同時(shí),在編譯器要求使用一個(gè)共享庫時(shí)使用的名字稱為linker name,一般都是去掉版本號(hào)的soname,用于gcc中-lname這樣的選項(xiàng)的編譯。
          這幾個(gè)名字的關(guān)系:你在創(chuàng)建實(shí)際庫文件中指定libreadline.so.3.0為real name ,并且使用符號(hào)鏈接創(chuàng)建soname ->libreadline.so.3和linker name-> /usr/lib/libreadline.so。
          放置位置:
          GNU標(biāo)準(zhǔn)推薦將所有默認(rèn)的庫安裝在/usr/local/lib,這指的是開發(fā)者源代碼默認(rèn)的位置。
          FHS指出大多數(shù)的庫文件應(yīng)該放在/usr/lib,而啟動(dòng)所需的庫則應(yīng)該放在/lib中,而非系統(tǒng)庫應(yīng)該放在/usr/local/lib。這指的是發(fā)行版默認(rèn)的位置,這兩個(gè)標(biāo)準(zhǔn)并沒有矛盾。
          共享庫的主要有三個(gè)步驟:

          創(chuàng)建目標(biāo)代碼。
          創(chuàng)建庫。
          使用符號(hào)鏈接創(chuàng)建默認(rèn)版本的共享庫(可選)。
          現(xiàn)在我們舉個(gè)例子來說明,首先我們編譯源代碼,使用-fPIC選項(xiàng)生成共享庫所需的位置獨(dú)立代碼(position-independent code (PIC)):

          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ gcc -Wall -fPIC -c *.c
          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ls
          ctest1.c  ctest1.o  ctest2.c  ctest2.o  prog.c  prog.o

          然后我們創(chuàng)建庫文件:

          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ gcc -shared -Wl,-soname,libctest.so.1 -o libctest.so.1.0   *.o
          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ls
          ctest1.c  ctest1.o  ctest2.c  ctest2.o  libctest.so.1.0  prog.c  prog.o

          -shared選項(xiàng)指明生成共享目標(biāo)文件,-W1(注意是小寫L而不是一)指明傳入鏈接器的參數(shù),在此我們?cè)O(shè)定了該庫的soname為libctest.so.1,-o則指明了生成的目標(biāo)庫文件為libctest.so.1.0(這個(gè)就是real name)。

          最后創(chuàng)建所需的符號(hào)鏈接:

          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ sudo mv libctest.so.1.0 /usr/local/lib/libctest.so.1.0
          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ sudo ln -sf /usr/local/lib/libctest.so.1.0 /usr/local/lib/libctest.so.1
          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ sudo ln -sf /usr/local/lib/libctest.so.1.0 /usr/local/lib/libctest.so

          創(chuàng)建的libctest.so就是上面所謂linker name,用于編譯時(shí)-lctest選項(xiàng)。

          創(chuàng)建的libctest.so.1就是soname,我們?cè)谏线呎f過程序在運(yùn)行時(shí)需要這個(gè)名字的符號(hào)鏈接。

          此時(shí)我們的共享庫就建好了,接著我們編譯程序:

          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ gcc -Wall -L/usr/local/lib prog.c -lctest -o prog
          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ls
          ctest1.c  ctest1.o  ctest2.c  ctest2.o  prog  prog.c  prog.o

          我們編譯完畢,該庫并不會(huì)包含在可執(zhí)行文件中,只有在執(zhí)行時(shí)來會(huì)動(dòng)態(tài)加載進(jìn)來。我們可以通過ldd列出一個(gè)可執(zhí)行程序所有的依賴,在我的系統(tǒng)中還找不到/usr/local/bin的路徑:

          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ldd prog
              linux-gate.so.1 =>  (0x00a5c000)
              libctest.so.1 => not found
              libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00a6f000)
              /lib/ld-linux.so.2 (0x00451000)

          此時(shí),運(yùn)行會(huì)報(bào)找不到庫的錯(cuò)誤:

          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ./prog
          ./prog: error while loading shared libraries: libctest.so.1: cannot open shared object file: No such file or directory

          我們可以將所需庫的路徑加入到系統(tǒng)路徑中,有三種方法可以完成:

          A.在/etc/ld.so.conf中加入所在路徑,然后執(zhí)行l(wèi)dconfig配置鏈接器運(yùn)行時(shí)綁定配置。你也可以創(chuàng)建一個(gè)文件,將路徑寫入,然后使用ldconfig –f filename將配置寫入。
          B.修改LD_LIBRARY_PATH環(huán)境變量(Linux下,AIX下為L(zhǎng)IBPATH),在其中添加路徑。若你直接在.bashrc文件中配置則重啟后不失效,否則在shell中設(shè)置重啟后失效。
          我們使用A方法中的-f選項(xiàng):

          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ vi libctest.conf
          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ sudo ldconfig -f libctest.conf
          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ./prog
          Valx=5
          gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ldd prog
              linux-gate.so.1 =>  (0x00f6f000)
              libctest.so.1 => /usr/local/lib/libctest.so.1 (0x005d9000)
              libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00718000)
              /lib/ld-linux.so.2 (0x001e6000)

          其中l(wèi)ibctest.conf中寫入路徑:/usr/local/lib。程序運(yùn)行正常。

          4.動(dòng)態(tài)加載庫的使用

          動(dòng)態(tài)加載庫是在非程序啟動(dòng)時(shí)動(dòng)態(tài)加載進(jìn)入程序的庫,這對(duì)于實(shí)現(xiàn)插件或動(dòng)態(tài)模塊有很大的幫助。在Linux中,動(dòng)態(tài)加載庫的形式并不特殊,它使用上述兩種程序庫,使用提供的API在程序運(yùn)行時(shí)動(dòng)態(tài)加載。注意,在不同平臺(tái)上動(dòng)態(tài)加載庫的API并不相同,所以可能會(huì)有移植問題出現(xiàn)。

          我們可以通過nm命令先查看一下我們創(chuàng)建的庫里面有哪些symbol(可以理解為函數(shù)方法)供我們使用:

          gnuhpc@gnuhpc-desktop:~/MyCode/lib$ nm /usr/local/lib/libctest.so
          00001f18 a _DYNAMIC
          00001ff4 a _GLOBAL_OFFSET_TABLE_
                   w _Jv_RegisterClasses
          00001f08 d __CTOR_END__
          00001f04 d __CTOR_LIST__
          00001f10 d __DTOR_END__
          00001f0c d __DTOR_LIST__
          000005a0 r __FRAME_END__
          00001f14 d __JCR_END__
          00001f14 d __JCR_LIST__
          00002014 A __bss_start
                   w __cxa_finalize@@GLIBC_2.1.3
          00000540 t __do_global_ctors_aux
          00000420 t __do_global_dtors_aux
          00002010 d __dso_handle
                   w __gmon_start__
          000004d7 t __i686.get_pc_thunk.bx
          00002014 A _edata
          0000201c A _end
          00000578 T _fini
          000003a0 T _init
          00002014 b completed.7021
          000004dc T ctest1
          000004ec T ctest2
          00002018 b dtor_idx.7023
          000004a0 t frame_dummy
          000004fc T main
                   U printf@@GLIBC_2.0

          這個(gè)命令對(duì)靜態(tài)庫和共享庫都支持,第二列為symbol類型,小寫字母表示符號(hào)是本地的,大寫字母表示符號(hào)是全局(外部)的,幾個(gè)常見的字母含義如下:T為代碼段普通定義,D為已初始化數(shù)據(jù)段,B為未初始化數(shù)據(jù)段,U為未定義(用到該符號(hào)但是沒有在該庫中定義)。

          我們創(chuàng)建ctest.h:

            1: #ifndef CTEST_H
            2: #define CTEST_H
            3:
            4: #ifdef __cplusplus
            5: extern "C" {
            6: #endif
            7:
            8: void ctest1(int *);
            9: void ctest2(int *);
           10:
           11: #ifdef __cplusplus
           12: }
           13: #endif
           14:
           15: #endif這里使用extern C是為了使得該庫既可以用于C語言又可以用于C++。

          我們動(dòng)態(tài)加載庫進(jìn)來:progdl.c

            1: #include
            2: #include
            3: #include "ctest.h"
            4:
            5: int main(int argc, char **argv)
            6: {
            7:    void *lib_handle;
            8:    double (*fn)(int *);
            9:    int x;
           10:    char *error;
           11:
           12:    lib_handle = dlopen("/usr/local/lib/libctest.so", RTLD_LAZY);
           13:    if (!lib_handle)
           14:    {
           15:       fprintf(stderr, "%s\n", dlerror());
           16:       exit(1);
           17:    }
           18:
           19:    fn = dlsym(lib_handle, "ctest1");
           20:    if ((error = dlerror()) != NULL) 
           21:    {
           22:       fprintf(stderr, "%s\n", error);
           23:       exit(1);
           24:    }
           25:
           26:    (*fn)(&x);
           27:    printf("Valx=%d\n",x);
           28:
           29:    dlclose(lib_handle);
           30:    return 0;
           31: }里面的方法解釋如下:

          void * dlopen(const char *filename, int flag); 
          若filename為絕對(duì)路徑,那么dlopen就會(huì)試圖打開它而不搜索相關(guān)路徑,否則就現(xiàn)在環(huán)境變量LD_LIBRARY_PATH處搜索,然后在/etc/ld.so.cache以及/lib和/usr/lib搜索。flag我們只解釋兩個(gè)常用的選項(xiàng):若為RTLD_LAZY則表示在動(dòng)態(tài)庫執(zhí)行時(shí)解決未定義符號(hào)問題,而RTLD_NOW則表示在dlopen返回前解決未定義符號(hào)問題。當(dāng)你調(diào)試時(shí)你應(yīng)該用RTLD_NOW,這個(gè)時(shí)候若存在未解決的引用程序還可以繼續(xù)進(jìn)行。另外,RTLD_NOW選項(xiàng)可能會(huì)使打開庫的這個(gè)操作稍微慢一點(diǎn),但是以后尋找函數(shù)時(shí)就會(huì)快一點(diǎn)。注意,若程序庫相互依賴則應(yīng)該按依賴順序依次載入,比如X依賴Y,那么要先載入Y然后再載入X。返回的是一個(gè)句柄,若失敗則返回null.

          char *dlerror(void);
          報(bào)告任何上一次對(duì)加載庫操作的錯(cuò)誤。兩次調(diào)用期間若有操作錯(cuò)誤則第二次會(huì)報(bào)告, 否則第二次則返回null——它報(bào)告完錯(cuò)誤就等待下一個(gè)錯(cuò)誤的發(fā)生,上一次錯(cuò)誤的情況一旦報(bào)告就不再提及。

          void *dlsym(void *handle, const char *symbol);
          尋找對(duì)應(yīng)symbol的函數(shù)方法,handle就是dlopen返回的句柄。一般如下使用:
            1:  dlerror(); /* clear error code */
            2:  s = (actual_type) dlsym(handle, symbol_being_searched_for);
            3:  if ((err = dlerror()) != NULL) {
            4:   /* handle error, the symbol wasn't found */
            5:  } else {
            6:   /* symbol found, its value is in s */
            7:  }int dlclose(void *handle);
          關(guān)閉一個(gè)動(dòng)態(tài)加載庫。當(dāng)一個(gè)動(dòng)態(tài)庫被加載多次時(shí),你需要用同樣次數(shù)dlclose該動(dòng)態(tài)庫才可以deallocated.

          我們編譯該代碼gcc -g -rdynamic -o progdl progdl.c -ldl,即可得到可執(zhí)行文件(其中-g選項(xiàng)是為了gdb調(diào)試所用),其中的庫為動(dòng)態(tài)加載后又關(guān)閉的。我們使用gdb看一下代碼:

          (gdb) b main
          Breakpoint 1 at 0x804878d: file progdl.c, line 12.
          (gdb) r
          Starting program: /home/gnuhpc/MyCode/lib/dynamic/progdl

          Breakpoint 1, main (argc=1, argv=0xbffff4a4) at progdl.c:12
          12       lib_handle = dlopen("/usr/local/lib/libctest.so", RTLD_LAZY);
          (gdb) f
          #0  main (argc=1, argv=0xbffff4a4) at progdl.c:12
          12       lib_handle = dlopen("/usr/local/lib/libctest.so", RTLD_LAZY);
          (gdb) s
          13       if (!lib_handle)
          (gdb) n
          19       fn = dlsym(lib_handle, "ctest1");
          (gdb)
          20       if ((error = dlerror()) != NULL) 
          (gdb)
          26       (*fn)(&x);
          (gdb)
          27       printf("Valx=%d\n",x);
          (gdb) p x
          $1 = 5
          (gdb) p fn
          $2 = (double (*)(int *)) 0x28c4dc

          可以看到fn獲得了ctest1的地址。

           

          參考文獻(xiàn):

          http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html

          http://www.linuxjournal.com/article/3687

          http://www.dwheeler.com/program-library/Program-Library-HOWTO/

           

          本文來自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/gnuhpc/archive/2010/12/20/6086143.aspx

          posted on 2011-01-20 00:21 何克勤 閱讀(409) 評(píng)論(0)  編輯  收藏

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 山阳县| 泰州市| 松阳县| 文安县| 甘德县| 清丰县| 辽源市| 澳门| 美姑县| 连云港市| 通州区| 新民市| 庐江县| 临沭县| 泌阳县| 会宁县| 郯城县| 昌吉市| 呼伦贝尔市| 阳新县| 老河口市| 达州市| 深圳市| 舟山市| 昌图县| 怀来县| 阿克苏市| 韩城市| 漳平市| 蛟河市| 苍溪县| 浑源县| 岳阳市| 银川市| 肇州县| 南陵县| 阳朔县| 贵港市| 中宁县| 阿克苏市| 鱼台县|