linux下的程序如許多命令(ls 、echo、cd)要實現他們的功能,需要許多函數,在這些程序的源碼中,這些函數的來源可以有兩種,一種是自己編寫,另一種是通過調用別人已經寫好的函數,自己在編寫程序的時候如果有別人已經寫好的函數,直接調用這些函數會提高自己的工作效率,何樂而不為呢。
既然是我們要調用別人已經寫好的函數,我們先要了解一下這些函數在哪里,如何調用它們了。linux中人們把預先編譯好的函數集合起來,這些函數都是按照可重用的原則編寫的,用來執行某項常見任務的,比如屏幕處理函數集合(curses集合),說專業點,人們把這樣的函數集合叫函數庫。標準的系統庫文件一般都存儲在/lib和/usr/lib目錄中,你可以通過ls 命令查看到這些目錄下有很多后綴為.so 或則.a的文件,這些文件就是為我們提供函數的。
linux 中的函數庫分有兩種:靜態庫和共享庫。可以從庫文件的后綴區分它們,后綴為.a的代表靜態庫,以.so為后綴的代表共享庫。為了了解關于庫,我們下面創建一個靜態庫,然后在程序中使用它。
一、靜態庫
1.建立一個fred.c的文件在里面我們寫入如下代碼:
[root@M2_test_192.168.8.93_61618_A code]# vim fred.c
#include <stdio.h>
void fred(int arg){
printf("fred:you passed %d \n",arg);
}
2.再建立一個bill.c的文件,在里面也寫入一些代碼
[root@M2_test_192.168.8.93_61618_A code]# vim bill.c
#include <stdio.h>
void bill(char *arg){
printf("bill:you passed %s \n",arg);
}
然后分別編譯這兩個函數,產生要包含在庫文件中的目標文件
[root@M2_test_192.168.8.93_61618_A code]# gcc -c bill.c fred.c
這里gcc通過-c選項來阻止編譯器創建一個完整的程序,如果試圖創建一個完整的程序將不會成功,因為我們還為定義main函數
下面我們開始編寫一個調用bill函數的程序。首先,為我們的庫文件創建一個頭文件,這個頭文件將聲明我們的庫文件中的函數,它應該被所希望使用我們庫文件的應用程序所包含。
3.創建lib.h頭文件:
[root@M2_test_192.168.8.93_61618_A code]# vim lib.h
void bill(char *);
void fred(int);
4.編寫一個名為program.c的程序,他包含庫的頭文件并且調用庫中的一個函數
[root@M2_test_192.168.8.93_61618_A code]# vim program.c
#include "lib.h"
int main()
{
bill("Hello World");
return 0;
}
5.我們開始編譯這個程序,我們暫時為編譯器顯示指定目標文件,然后要求編譯器編譯我們的文件并將其與預先編譯好的目標模塊bill.o鏈接
[root@M2_test_192.168.8.93_61618_A code]# gcc -c program.c
[root@M2_test_192.168.8.93_61618_A code]# gcc -o program program.o bill.o
[root@M2_test_192.168.8.93_61618_A code]# ./program
bill:you passed Hello World
6.現在我們創建并使用一個庫文件,靜態庫文件也稱作歸檔文件,都是以.a結尾的,可以通過file命令查看:
[root@M2_test_192.168.8.93_61618_A code]# file /lib/libdevmapper.a
/lib/libdevmapper.a: current ar archive
創建這樣的靜態庫文件需要用ar命令,將我們創建好的函數文件添加進到歸檔文件(靜態庫文件)libfoo.a中
[root@M2_test_192.168.8.93_61618_A code]# ar crv libfoo.a bill.o fred.o
a - bill.o
a - fred.o
庫文件創建好了,某些系統可能需要為函數庫生成一個內容表,我們通過ranlib命令來完成這一任務,linux中,如果我們使用GNU的軟件開發工具,這一步是不必需的,但是做了也無妨
[root@M2_test_192.168.8.93_61618_A code]# ranlib libfoo.a
現在我們的靜態函數庫可以使用了
[root@M2_test_192.168.8.93_61618_A code]# gcc -o program program.c libfoo.a
[root@M2_test_192.168.8.93_61618_A code]# ./program
bill:you passed Hello World
也可以使用-l選項來訪問我們的函數庫,但是因為其未保存到標準位置,所以我們必須要用-L選項來指示編譯器在何處可以找到函數庫
[root@M2_test_192.168.8.93_61618_A code]# gcc -o program program.c -L. -lfoo
[root@M2_test_192.168.8.93_61618_A code]# ./program
bill:you passed Hello World
二、共享庫
靜態庫有一個缺點,當我們同時運行許多程序,并且他們都是用來自同一個函數庫的函數時,就會在內存中有同一個函數的多份拷貝,在程序文件自身也有多份同樣的拷貝,將消耗大量寶貴的內存和磁盤空間。
linux支持共享庫,它克服了上述不足。共享庫和靜態庫保存位置是一樣的,文件后綴不一樣,共享庫是以.so結尾的。與靜態庫不同,程序使用靜態庫的時候,程序本身不會包含函數的代碼,而是引用運行時可以訪問的共享代碼。當編譯好的程序被裝載到內存中執行時,函數引用被解析并產生對共享庫的調用,如果有必要共享庫才被加載到內存中。通過這種方法,系統可只保留一份拷貝并供許多程序同時引用,并且在磁盤上也僅保留一份。
我們可以這么說,引用靜態可庫的程序本身在編譯的時候連接器會把引用的函數復制到程序中,即程序包含執行所需的所有函數 , 換句話說,它們是“完整的”。因為這一原因,這樣的程序一旦編譯以后,就不依賴任何外部庫就可以運行了。但是共享庫不同,編譯完程序以后,引用過共享庫的程序要正確執行,引用的外部庫一定要存在,否則執行程序必然失敗。
ldd 命令來確定某一特定可執行程序是否引用共享庫。如下
[root@M2_test_192.168.8.93_61618_A code]# ldd program
libc.so.6 => /lib64/libc.so.6 (0x0000003485600000)
/lib64/ld-linux-x86-64.so.2 (0x0000003485200000)
因為我們的靜態函數庫本身又引用了stdio.h,這個頭文件使用的printf這個函數是從共享庫 /lib64/libc.so.6的引用的,至于還有一個叫/lib64/ld-linux-x86-64.so.2的共享庫,這個后面我們會具體了解下它。
我們再來看看,同樣功能的兩個程序,一個可能是引用靜態庫,另外一個引用了共享庫
[root@M2_test_192.168.8.93_61618_A code]# ldd /sbin/sln
not a dynamic executable
[root@M2_test_192.168.8.93_61618_A code]# ldd /bin/ln
libc.so.6 => /lib64/libc.so.6 (0x0000003485600000)
/lib64/ld-linux-x86-64.so.2 (0x0000003485200000)
個是一個完整的,不依賴外部庫的程序,通過ldd以后提示不是一個動態鏈接的可執行程序,后面一個引用了兩個共享庫,我們再來看這兩個程序的大小
[root@M2_test_192.168.8.93_61618_A code]# ll -h /sbin/sln
-rwxr-xr-x 1 root root 605K Jun 8 2010 /sbin/sln
[root@M2_test_192.168.8.93_61618_A code]# ll -h /bin/ln
-rwxr-xr-x 1 root root 32K Mar 1 2010 /bin/ln
很明顯,不引用外部共享庫的要比引用外部共享庫的大10倍左右,這是因為引用共享庫的程序是不完整的程序,它依靠外部共享庫來提供運行所需的許多函數。
三、動態裝入器
如果程序中引用外部共享庫,那么需要一個動態裝入器(dynamic loader),它負責裝載共享庫并解析客戶程序函數引用,在linux中,這個裝載器是ld.so,也有可能是ld-linux.so.2或者ld-lsb.so.1,它實際上是你在 ln 的 ldd 清單中看到的作為共享庫相關性列出的 ld-linux.so.2 庫。下面我們迅速查看一下動態裝入器如何在系統上找到適當的共享庫。
動態裝入器找到共享庫要依靠兩個文件 : /etc/ld.so.conf 和 /etc/ld.so.cache。如果你對 /etc/ld.so.conf 文件進行 cat 操作,您可能會看到一個與下面類似的清單:
[root@M2_test_192.168.8.93_61618_A code]# cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
[root@M2_test_192.168.8.93_61618_A code]# cat /etc/ld.so.conf.d/usr_local_lib.conf
/usr/local/lib
ld.so.conf 文件包含一個所有目錄(/lib 和 /usr/lib 除外,它們會自動包含在其中)的清單,動態裝入器將在其中查找共享庫。但是在動態裝入器能“看到”這一信息之前,必須將它轉換到 ld.so.cache 文件中。可以通過運行 ldconfig 命令做到這一點:
[root@M2_test_192.168.8.93_61618_A code]# ldconfig
當 ldconfig 操作結束時,您會有一個的 /etc/ld.so.cache 文件,它反映您對 /etc/ld.so.conf 所做的更改。從這一刻起,動態裝入器在尋找共享庫時會查看您在 /etc/ld.so.conf 中指定的所有新目錄。
四、ldconfig 技巧
要查看 ldconfig 可以“看到”的所有共享庫,請輸入:
[root@M2_test_192.168.8.93_61618_A code]# ldconfig -p | less
413 libs found in cache `/etc/ld.so.cache'
libz.so.1 (libc6,x86-64) => /lib64/libz.so.1
libz.so.1 (libc6,x86-64) => /usr/lib64/libz.so.1
libz.so.1 (libc6) => /lib/libz.so.1
libz.so.1 (libc6) => /usr/lib/libz.so.1
libz.so (libc6,x86-64) => /lib64/libz.so
libz.so (libc6,x86-64) => /usr/lib64/libz.so
libz.so (libc6) => /lib/libz.so
libz.so (libc6) => /usr/lib/libz.so
libxml2.so.2 (libc6,x86-64) => /usr/lib64/libxml2.so.2
libwrap.so.0 (libc6,x86-64) => /lib64/libwrap.so.0
libwrap.so (libc6,x86-64) => /usr/lib64/libwrap.so
libvolume_id.so.0 (libc6,x86-64) => /lib64/libvolume_id.so.0
libuuid.so.1 (libc6,x86-64) => /lib64/libuuid.so.1
libuuid.so.1 (libc6) => /lib/libuuid.so.1
..... (還有很多)
還有另一個方便的技巧可以用來配置共享庫路徑。有時候你希望告訴動態裝入器在嘗試任何 /etc/ld.so.conf 路徑以前先嘗試使用特定目錄中的共享庫。在您運行的較舊的應用程序不能與當前安裝的庫版本一起工作的情況下,這會比較方便。要指示動態裝入器首先檢查某個目錄,請將 LD_LIBRARY_PATH 變量設置成您希望搜索的目錄。多個路徑之間用逗號分隔;例如:
[root@M2_test_192.168.8.93_61618_A code]# export LD_LIBRARY_PATH="/usr/lib/old:/opt/lib"
導出 LD_LIBRARY_PATH 后,如有可能,所有從當前 shell 啟動的可執行程序都將使用 /usr/lib/old 或 /opt/lib 中的庫,如果仍不能滿足一些共享庫相關性要求,則轉回到 /etc/ld.so.conf 中指定的庫。