Linux下建立函數庫(一)

來源:互聯網
上載者:User
  1. 介紹

  使用GNU的工具我們如何在Linux下建立自己的程式函數庫? 一個“程式函數庫”簡單的說就是一個檔案包含了一些編譯好的代碼和資料,這些編譯好的代碼和資料可以在事後供其他的程式使用。程式函數庫可以使整個程式更加模組化,更容易重新編譯,而且更方便升級。 程式函數庫可分為3種類型:靜態函數庫(static libraries)、共用函數庫(shared libraries)和動態載入函數庫(dynamically loaded
libraries)。
  靜態函數庫是在程式執行前就加入到目標程式中去了;而共用函數庫則是在程式啟動的時候載入到程式中,它可以被不同的程式共用;動態載入函數庫則可以在程式啟動並執行任何時候動態載入。實際上,動態函數庫並非另外一種庫函數格式,區別是動態載入函數庫是如何被程式員使用的。後面我們將舉例說明。  本文檔主要參考Program Library HOWTO,作者是luster(hwang@ustc.edu),任何非商業目的的再次發行本文檔都是允許的,但是請保留作者資訊和本著作權聲明。本文檔首先在www.linuxaid.com.cn發布。   2. 靜態函數庫   靜態函數庫實際上就是簡單的一個普通的目標檔案的集合,一般來說習慣用“.a”作為檔案的尾碼。可以用ar這個程式來產生靜態函數庫檔案。Ar
是archiver的縮寫。靜態函數庫現在已經不在像以前用得那麼多了,主要是共用函數庫與之相比較有很多的優勢的原因。慢慢地,大家都喜歡使用共用函數庫了。不過,在一些場所靜態函數庫仍然在使用,一來是保持一些與以前某些程式的相容,二來它描述起來也比較簡單。  靜態庫函數允許程式員把程式link起來而不用重新編譯代碼,節省了重新編譯代碼的時間。不過,在今天這麼快速的電腦面前,一般的程式的重新編譯也花費不了多少時間,所以這個優勢已經不是像它以前那麼明顯了。靜態函數庫對開發人員來說還是很有用的,例如你想把自己提供的函數給別人使用,但是又想對函數的原始碼進行保密,你就可以給別人提供一個靜態函數庫檔案。理論上說,使用ELF格式的靜態庫函數產生的程式碼可以比使用共用函數庫(或者動態函數
庫)的程式運行速度上快一些,大概1-5%。  建立一個靜態函數庫檔案,或者往一個已經存在地靜態函數庫檔案添加新的目標代碼,可以用下面的命令:
ar rcs my_library.a file1.o file2.o
  這個例子中是把目標代碼file1.o和
file2.o加入到my_library.a這個函數庫檔案中,如果my_library.a不存在則建立一個新的檔案。在用ar命令建立靜態庫函數的時候,還有其他一些可以選擇的參數,可以參加ar的使用協助。這裡不再贅述。  一旦你建立了一個靜態函數庫,你可以使用它了。你可以把它作為你編譯和串連過程中的一部分用來產生你的可執行代碼。 如果你用gcc來編譯產生可執行代碼的話,你可以用“-l”參數來指定這個庫函數。你也可以用ld來做,使用它的“-l”和“-L”參數選項。具體用法,可以參考info:gcc。     3. 共用函數庫  共用函數庫中的函數是在當一個可執行程式在啟動的時候被載入。如果一個共用函數庫正常安裝,所有的程式在重新啟動並執行時候都可以自動載入最新的函數庫中的函數。對於Linux系統還有更多的可以實現的功能:o 升級了函數庫但是仍然允許程式使用老版本的函數庫。 o 當執行某個特定程式的時候可以覆蓋某個特定的庫或者庫中指定的函數。 o 可以在庫函數被使用的過程中修改這些函數庫。   3.1. 一 些約定  如果你要編寫的共用函數庫支援所有有用的特性,你在編寫的過程中必須遵循一系列約定。你必須理解庫的不同的名字間的區別,例如它的 “soname”和“real
name”之間的區別和它們是如何相互作用的。你同樣還要知道你應該把這些庫函數放在你檔案系統的什麼位置等等。下面我們具體看看這些問題。   3.1.1 . 共用庫的命名   每個共用函數庫都有個特殊的名字,稱作“soname”。Soname名字命名必須以“lib”作為首碼,然後是函數庫的名字,然後是“.so”,最後是版本號碼資訊。不過有個特例,就是非常底層的C庫函數都不是以lib開頭這樣命名的。  每個共用函數庫都有一個真正的名字(“real name”),它是包含真正庫函數代碼的檔案。真名有一個主要版本號,和一個發行版本號碼。最後一個發行版本號碼是可選的,可以沒有。主要版本號和發行版本號碼使你可以知道你到底是安裝了什麼版本的庫函數。另外,還有一個名字是編譯器編譯的時候需要的函數庫的名字,這個名字就是簡單的soname名字,而不包含任何版本號碼資訊。  管理共用函數庫的關鍵是區分好這些名字。  ★ 當可執行程式需要在自己的程式中列出這些他們需要的共用庫函數的時候,它只要用soname就可以了;   ★ 反過來,當你要建立一個新的共用函數庫的時候,你要指定一個特定的檔案名稱,其中包含很細節的版本資訊。  ★ 當你安裝一個新版本的函數庫的時候,你只要先將這些函數庫檔案拷貝到一些特定的目錄中,運行ldconfig這個實用就可以。Ldconfig檢查已經存在的庫檔案,然後建立soname的符號連結到真正的函數庫,同時設定/etc/ld.so.cache這個緩衝檔案。這個我們稍後再討論。  Ldconfig並不設定連結的名字,通常的做法是在安裝過程中完成這個連結名字的建立,一般來說這個符號連結就簡單的指向最新的soname
或者最新版本的函數庫檔案。最好把這個符號連結指向soname,因為通常當你升級你的庫函數的後,你就可以自動使用新版本的函數庫勒。   我們來舉例看看:  
/usr/lib/libreadline.so.3
是一個完全的完整的soname,ldconfig可以設定一個符號連結到其他某個真正的函數庫檔案,例如是
/usr/lib/libreadline.so.3.0。同時還必須有一個連結名字,例如/usr/lib/libreadline.so
就是一個符號連結指向/usr/lib/libreadline.so.3。   3.1.2 . 檔案系統中函數庫檔案的位置  共用函數庫檔案必須放在一些特定的目錄裡,這樣通過系統的環境變數設定,應用程式才能正確的使用這些函數庫。大部分的源碼開發的程式都遵循
GNU的一些標準,我們可以看info協助檔案獲得相信的說明,info資訊的位置是:info:
standards#Directory_Variables。 GNU標準建議所有的函數庫檔案都放在/usr/local/lib目錄下,而且建議命令可執行程式都放在/usr/local/bin目錄下。這都是一些習慣問題,是可以改變的。  檔案系統層次化標準FHS(Filesystem Hierarchy
Standard)(http://www.pathname.com/fhs)規定了在一個發行包中大部分的函數庫檔案應該安裝到/usr/lib目錄下,但是如果某些庫是在系統啟動的時候要載入的,則放到/lib目錄下,而那些不是系統本身一部分的庫則放到/usr/local/lib下面。  上面兩個路徑的不同並沒有本質的衝突。GNU提出的標準主要對於開發人員開發源碼的,而FHS的建議則是針對發行版本的路徑的。具體的位置資訊可以看/etc/ld.so.conf裡面的配置資訊。   3.2. 這些函數庫如何使用  在基於GNU glibc的系統裡,包括所有的linux系統,啟動一個ELF格式的二進位可執行檔會自動啟動和運行一個program

loader。對於Linux系統,這個loader的名字是/lib/ld-linux.so.X(X是版本號碼)。這個loader啟動後,反過來就會
load所有的其他本程式要使用的共用函數庫。  到底在哪些目錄裡尋找共用函數庫呢?這些定義預設的是放在/etc/ld.so.conf檔案裡面,我們可以修改這個檔案,加入我們自己的一些特殊的路徑要求。大多數RedHat系列的發行包的/etc/ld.so.conf檔案裡面不包括/usr/local/lib這個目錄,如果沒有這個目錄的話,我們可以修改/etc/ld.so.conf,自己手動加上這個條目。  如果你想覆蓋某個庫中的一些函數,用自己的函數替換它們,同時保留該庫中其他的函數的話,你可以在/etc/ld.so.preload中加入你想要替換的庫(.o結尾的檔案),這些preloading的庫函數將有優先載入的權利。  當程式啟動的時候搜尋所有的目錄顯然會效率很低,於是Linux系統實際上用的是一個高速緩衝的做法。Ldconfig預設情況下讀出
/etc/ld.so.conf相關資訊,然後設定適當地符號連結,然後寫一個cache到/etc/ld.so.cache這個檔案中,而這個
/etc/ld.so.cache則可以被其他程式有效使用了。這樣的做法可以大大提高訪問函數庫的速度。這就要求每次新增加一個動態載入的函數庫的時候,就要運行ldconfig來更新這個cache,如果要刪除某個函數庫,或者某個函數庫的路徑修改了,都要重新運行ldconfig來更新這個
cache。通常的一些包管理器在安裝一個新的函數庫的時候就要運行ldconfig。  另外,FreeBSD使用cache的檔案不一樣。FreeBSD的ELF cache是/var/run/ld-elf.so.hints,而a.out的cache責是/var/run/ld.so.hints。它們同樣是通過ldconfig來更新。  3.3. 環境變數  各種各樣的環境變數控制著一些關鍵的過程。例如你可以臨時為你特定的程式的一次執行指定一個不同的函數庫。Linux系統中,通常變數
LD_LIBRARY_PATH就是可以用來指定函數庫尋找路徑的,而且這個路徑通常是在尋找標準的路徑之前尋找。
這個是很有用的,特別是在調試一個新的函數庫的時候,或者在特殊的場合使用一個非標準的函數庫的時候。環境變數LD_PRELOAD列出了所有共用函數庫中需要優先載入的庫檔案,功能和
/etc/ld.so.preload類似。這些都是有/lib/ld-linux.so這個loader來實現的。值得一提的是,
LD_LIBRARY_PATH可以在大部分的UNIX-linke系統下正常起作用,但是並非所有的系統下都可以使用,例如HP-UX系統下,就是用
SHLIB_PATH這個變數,而在AIX下則使用LIBPATH這個變數。  LD_LIBRARY_PATH在開發和調試過程中經常大量使用,但是不應該被一個普通使用者在安裝過程中被安裝程式修改,大家可以去參考

http://www.visi.com/~barr/ldpath.html,這裡有一個文檔專門介紹為什麼不使用LD_LIBRARY_PATH這個

變數。  事實上還有更多的環境變數影響著程式的調入過程,它們的名字通常就是以LD_或者RTLD_打頭。大部分這些環境變數的使用的文檔都是不全,通常搞得人頭昏眼花的,如果要真正弄清楚它們的用法,最好去讀loader的源碼(也就是gcc的一部分)。  允許使用者控制動態連結函數庫將涉及到setuid/setgid這個函數,如果特殊的功能需要的話。因此,GNU
loader通常限制或者忽略使用者對setuid和setgid這些變數使用。如果loader通過判斷程式的相關環境變數判斷程式的是否使用了
setuid或者setgid,如果uid和euid不同,或者gid和egid不一樣,那麼loader就假定程式已經使用了setuid或者
setgid,然後就大大的限制控制這個老連結的許可權。如果閱讀GNU
glibc的庫函數源碼,就可以清楚地看到這一點,特別的我們可以看elf/rtld.c和sysdeps/generic/dl-sysdep.c這兩個檔案。這就意味著如果你使得uid和gid與euid和egid分別相等,然後調用一個程式,那麼這些變數就可以完全起效。  3.4. 建立一個共用函數庫  現在我們開始學習如何建立一個共用函數庫。其實建立一個共用函數庫非常容易。首先建立object檔案,這個檔案通過gcc –fPIC
參數命令加入到共用函數庫裡面。
PIC的意思是“位置無關代碼”(Position Independent Code)。下面是一個標準的格式:

gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list
  下面再給一個例子,它建立兩個object檔案(a.o和b.o),然後建立一個包含a.o和b.o的共用函數庫。例子中”-g”和“-Wall”參數不是必須的。
gcc -fPIC -g -c -Wall a.cgcc -fPIC -g -c -Wall b.cgcc -shared -Wl,-soname,liblusterstuff.so.1 -o liblusterstuff.so.1.0.1 a.o b.o -lc
  下面是一些需要注意的地方:   不用使用-fomit-frame-pointer這個編譯參數,除非你不得不這樣。雖然使用了這個參數,獲得的函數庫仍然可以使用,但是這使得偵錯工具幾乎沒有用,無法跟蹤調試。 ·   使用-fPIC來產生代碼,而不是-fpic。 ·   某些情況下,使用gcc
來產生object檔案,需要使用“-Wl,-export-dynamic”這個選項參數。通常,動態函數庫的符號表裡麵包含了這些動態對象的符號。
這個選項在建立ELF格式的檔案時候,會將所有的符號加入到動態符號表中。可以參考ld的協助獲得更詳細的說明。   3.5. 安裝和使用共用函數庫  一旦你了一個共用函數庫,你還需要安裝它。其實簡單的方法就是 拷貝你的庫檔案到指定的標準的目錄(例如/usr/lib),然後運行ldconfig。  如果你沒有許可權去做這件事情,例如你不能修改
/usr/lib目錄,那麼你就只好通過修改你的環境變數來實現這些函數庫的使用了。首先,你需要建立這些共用函數庫;然後,設定一些必須得符號連結,特別是從soname到真正的函數庫檔案的符號連結,簡單的方法就是運行ldconfig:
ldconfig -n directory_with_shared_libraries
  然後你就可以設定你的LD_LIBRARY_PATH這個環境變數,它是一個以逗號分隔的路徑的集合,這個可以用來指明共用函數庫的搜尋路徑。例如,使用bash,就可以這樣來啟動一個程式my_program:
LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH my_program
  如果你需要的是重載部分函數,則你就需要建立一個包含需要重載的函數的object檔案,然後設定LD_PRELOAD環境變數。通常你可以很
方便的升級你的函數庫,如果某個API改變了,建立庫的程式會改變soname。然而,如果一個函數升級了某個函數庫而保持了原來的soname,你可以強行將老版本的函數庫拷貝到某個位置,然後重新命名這個檔案(例如使用原來的名字,然後後面加.orig尾碼),然後建立一個小的“wrapper”指令碼來設定這個庫函數和相關的東西。例如下面的例子:
#!/bin/sh export LD_LIBRARY_PATH=/usr/local/my_lib:$LD_LIBRARY_PATH exec /usr/bin/my_program.orig $*
  我們可以通過運行ldd來看某個程式使用的共用函數庫。例如你可以看ls這個工具 + 生產力使用的函數庫:
ldd /bin/ls       libtermcap.so.2 => /lib/libtermcap.so.2 (0x4001c000)       libc.so.6 => /lib/libc.so.6 (0x40020000)      /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
  通常我麼可以看到一個soname的列表,包括路徑。在所有的情況下,你都至少可以看到兩個庫:
· /lib/ld-linux.so.N(N是1或者更大,一般至少2)。這是這個用力載入其他所有的共用庫的庫。· libc.so.N(N應該大於或者等於6)。這是C語言函數庫。
  值得一提的是,不要在對你不信任的程式運行
ldd命令。在ldd的manual裡面寫得很清楚,ldd是通過設定某些特殊的環境變數(例如,對
於ELF對象,設定LD_TRACE_LOADED_OBJECTS),然後運行這個程式。這樣就有可能使得某地程式可能使得ldd來執行某些意想不到的代碼,而產生不安全的隱患。   3.6. 不相容的函數庫  如果一個新版的函數庫要和老版本的二進位的庫不相容,則soname需要改變。對於C語言,一共有4個基本的理由使得它們在二進位代碼上很難相容:  o. 一個函數的行文改變了,這樣它就可能與最開始的定義不相符合。  o. 輸出的資料項目改變了。  o. 某些輸出的函數刪除了。  o. 某些輸出函數的介面改變了。  如果你能避免這些地方,你就可以保持你的函數庫在二進位代碼上的相容,或者說,你可以使得你的程式的應用二進位介面(ABI:Application Binary Interface)上相容。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.