1、基本概念linux
在windows平臺和linux平臺下都大量存在着庫。
本質上來講庫是一種可執行代碼的二進制形式,能夠被操做系統載入內存執行。
因爲windows和linux的平臺不一樣(主要是編譯器、彙編器和鏈接器的不一樣),所以兩者庫的二進制是不兼容的。
本文僅限於介紹linux下的庫。數據庫
linux下的庫有兩種:靜態庫和共享庫(動態庫)。
兩者的不一樣點在於代碼被載入的時刻不一樣。
靜態庫的代碼在編譯過程當中已經被載入可執行程序,所以體積較大。
共享庫的代碼是在可執行程序運行時才載入內存的,在編譯過程當中僅簡單的引用,所以代碼體積較小。編程
庫是別人寫好的現有的,成熟的,能夠複用的代碼,你可使用但要記得遵照許可協議。
現實中每一個程序都要依賴不少基礎的底層庫,不可能每一個人的代碼都從零開始,所以庫的存在乎義非同尋常。
共享庫的好處是,不一樣的應用程序若是調用相同的庫,那麼在內存裏只須要有一份該共享庫的實例。ubuntu
靜態庫的後綴是.a,它的產生分兩步
1. 由源文件編譯生成一堆.o,每一個.o裏都包含這個編譯單元的符號表
2. ar命令將不少.o轉換成.a,成爲靜態庫
動態庫的後綴是.so,它由gcc加特定參數編譯產生。
具體方法參見後文實例。windows
在linux下,庫文件通常放在/usr/lib和/lib下,
靜態庫的名字通常爲libxxxx.a,其中xxxx是該lib的名稱
動態庫的名字通常爲libxxxx.so.major.minor,xxxx是該lib的名稱,major是主版本號, minor是副版本號緩存
ldd命令能夠查看一個可執行程序依賴的共享庫,
例如$ ldd /bin/lnlibc.so.6
=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2
=> /lib/ld- linux.so.2 (0×40000000)
能夠看到ln命令依賴於libc庫和ld-linux庫編程語言
當系統加載可執行代碼時候,可以知道其所依賴的庫的名字,可是還須要知道絕對路徑。
此時就須要系統動態載入器(dynamic linker/loader)
對於elf格式的可執行程序,是由ld-linux.so*來完成的,它前後搜索elf文件的 DT_RPATH段—環境變量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib目錄找到庫文件後將其載入內存
如:export LD_LIBRARY_PATH='pwd'
將當前文件目錄添加爲共享目錄函數
若是安裝在/lib或者/usr/lib下,那麼ld默認可以找到,無需其餘操做。
若是安裝在其餘目錄,須要將其添加到/etc/ld.so.cache文件中,步驟以下
1.編輯/etc/ld.so.conf
文件,加入庫文件所在目錄的路徑
2.運行ldconfig 目錄名字
,該命令會重建/etc/ld.so.cache文件工具
咱們一般把一些公用函數製做成函數庫,供其它程序使用。
函數庫分爲靜態庫和動態庫兩種。測試
靜態庫在程序編譯時會被鏈接到目標代碼中,程序運行時將再也不須要該靜態庫。
動態庫在程序編譯時並不會被鏈接到目標代碼中,而是在程序運行是才被載入,所以在程序運行時還須要動態庫存在。
本文主要經過舉例來講明在Linux中如何建立靜態庫和動態庫,以及使用它們。
爲了便於闡述,咱們先作一部分準備工做。
hello.h(見程序1)爲該函數庫的頭文件。
hello.c(見程序2)是函數庫的源程序,其中包含公用函數hello,該函數將在屏幕上輸出」Hello XXX!」。
main.c(見程序3)爲測試庫文件的主程序,在主程序中調用了公用函數hello。
測試機器環境:gcc version 4.8.4 ;ubuntu14.04.3
三個程序放在文件夾~/testso
中
程序1: hello.h
#ifndef HELLO_H #define HELLO_H void hello(const char *name); #endif
程序2:hello.c
#include <stdio.h> void hello(const char *name) { printf("Hello %s!\n", name); }
程序3:main.c
#include "hello.h" int main() { hello("everyone"); return 0; }
注意:這個時候,咱們編譯好的hello.o是沒法經過gcc –o 編譯的,這個道理很是簡單,
hello.c是一個沒有main函數的.c程序,所以不夠成一個完整的程序,若是使用gcc –o 編譯並鏈接它,GCC將報錯。
不管靜態庫,仍是動態庫,都是由.o文件建立的。所以,咱們必須將源程序hello.c經過gcc先編譯成.o文件。
這個時候咱們有三種思路:
1) 經過編譯多個源文件,直接將目標代碼合成一個.o文件。
2) 經過建立靜態連接庫libmyhello.a,使得main函數調用hello函數時可調用靜態連接庫。
3) 經過建立動態連接庫libmyhello.so,使得main函數調用hello函數時可調用靜態連接庫。
在系統提示符下鍵入如下命令獲得hello.o文件。
gcc -c hello.c
爲何不使用gcc –o hello hello.c
這個道理咱們以前已經說了,使用-c是什麼意思呢?這涉及到gcc 編譯選項的常識。
gcc –o
是將.c源文件編譯成爲一個可執行的二進制代碼(-o選項實際上是制定輸出文件文件名,若是不加-c選項,gcc默認會編譯鏈接生成可執行文件,文件的名稱有-o選項指定),這包括調用做爲GCC內的一部分真正的C編譯器(ccl),以及調用GNU C編譯器的輸出中實際可執行代碼的外部GNU彙編器(as)和鏈接器工具(ld)。
gcc –c
是使用GNU彙編器將源文件轉化爲目標代碼以後就結束,在這種狀況下,只調用了C編譯器(ccl)和彙編器(as),而鏈接器(ld)並無被執行,因此輸出的目標文件不會包含做爲Linux程序在被裝載和執行時所必須的包含信息,但它能夠在之後被鏈接到一個程序。
咱們運行ls命令看看是否生存了hello.o文件。
$ ls
hello.c hello.h hello.o main.c
在ls命令結果中,咱們看到了hello.o文件,本步操做完成。
同理編譯main
$gcc –c main.c
將兩個文件連接成一個.o文件。
$gcc hello.o main.o -o hello
運行
$ ./hello
Hello everyone!
完成^ ^!
下面咱們先來看看如何建立靜態庫,以及使用它。
靜態庫文件名的命名規範是以lib爲前綴,緊接着跟靜態庫名,擴展名爲.a。例如:咱們將建立的靜態庫名爲myhello,則靜態庫文件名就是libmyhello.a。在建立和使用靜態庫時,須要注意這點。建立靜態庫用ar命令。
在系統提示符下鍵入如下命令將建立靜態庫文件libmyhello.a。
$ ar rcs libmyhello.a hello.o
咱們一樣運行ls命令查看結果:
$ ls
hello.c hello.h hello.o libmyhello.a main.c
ls命令結果中有libmyhello.a。
靜態庫製做完了,如何使用它內部的函數呢?只須要在使用到這些公用函數的源程序中包含這些公用函數的原型聲明,而後在用gcc命令生成目標文件時指明靜態庫名,gcc將會從靜態庫中將公用函數鏈接到目標文件中。注意,gcc會在靜態庫名前加上前綴lib,而後追加擴展名.a獲得的靜態庫文件名來查找靜態庫文件,所以,咱們在寫須要鏈接的庫時,只寫名字就能夠,如libmyhello.a的庫,只寫:-lmyhello
在程序3:main.c中,咱們包含了靜態庫的頭文件hello.h,而後在主程序main中直接調用公用函數hello。下面先生成目標程序hello,而後運行hello程序看看結果如何。
$ gcc -o hello main.c -static -L. -lmyhello
$ ./hello
Hello everyone!
咱們刪除靜態庫文件試試公用函數hello是否真的鏈接到目標文件 hello中了。
$ rm libmyhello.a
$ ./hello
Hello everyone!
程序照常運行,靜態庫中的公用函數已經鏈接到目標文件中了。
靜態連接庫的一個缺點是,若是咱們同時運行了許多程序,而且它們使用了同一個庫函數,這樣,在內存中會大量拷貝同一庫函數。這樣,就會浪費不少珍貴的內存和存儲空間。使用了共享連接庫的Linux就能夠避免這個問題。
共享函數庫和靜態函數在同一個地方,只是後綴有所不一樣。好比,在一個典型的Linux系統,標準的共享數序函數庫是/usr/lib/libm.so。
當一個程序使用共享函數庫時,在鏈接階段並不把函數代碼鏈接進來,而只是連接函數的一個引用。當最終的函數導入內存開始真正執行時,函數引用被解析,共享函數庫的代碼才真正導入到內存中。這樣,共享連接庫的函數就能夠被許多程序同時共享,而且只需存儲一次就能夠了。共享函數庫的另外一個優勢是,它能夠獨立更新,與調用它的函數絕不影響。
咱們繼續看看如何在Linux中建立動態庫。咱們仍是從.o文件開始。
動態庫文件名命名規範和靜態庫文件名命名規範相似,也是在動態庫名增長前綴lib,但其文件擴展名爲.so。例如:咱們將建立的動態庫名爲myhello,則動態庫文件名就是libmyhello.so。用gcc來建立動態庫。
在系統提示符下鍵入如下命令,獲得動態庫文件libmyhello.so。
$ gcc -shared -fPIC -c hello.c
注:chenjim添加,原文沒有,下一行會報錯
$ gcc -shared -fPIC -o libmyhello.so hello.o
「PIC」命令行標記告訴GCC產生的代碼不要包含對函數和變量具體內存位置的引用,這是由於如今還沒法知道使用該消息代碼的應用程序會將它鏈接到哪一段內存地址空間。這樣編譯出的hello.o能夠被用於創建共享連接庫。創建共享連接庫只須要用GCC的」-shared」標記便可。
咱們照樣使用ls命令看看動態庫文件是否生成。
$ ls
hello.cpp hello.h hello.o libmyhello.so main.cpp
調用動態連接庫編譯目標文件。
在程序中使用動態庫和使用靜態庫徹底同樣,也是在使用到這些公用函數的源程序中包含這些公用函數的原型聲明,而後在用gcc命令生成目標文件時指明動態庫名進行編譯。咱們先運行gcc命令生成目標文件,再運行它看看結果。
若是直接用以下方法進行編譯,並鏈接:
$ gcc -o hello main.c -L. -lmyhello
(使用」-lmyhello」標記來告訴GCC驅動程序在鏈接階段引用共享函數庫libmyhello.so。」-L.」標記告訴GCC函數庫可能位於當前目錄)
$ ./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory
錯誤提示,找不到動態庫文件libmyhello.so。程序在運行時,會查找須要的動態庫文件,順序參考後文介紹。若找到,則載入動態庫,不然將提示相似上述錯誤而終止程序運行。有多種方法能夠解決,
(1)咱們將文件 libmyhello.so複製到目錄/usr/lib中,再試試。
$ sudo mv libmyhello.so /usr/lib
(2)既然鏈接器會搜尋LD_LIBRARY_PATH所指定的目錄,那麼咱們能夠將這個環境變量設置成當前目錄:
export LD_LIBRARY_PATH=$(pwd)
(3)sudo ldconfig ~/testso
注: 當用戶在某個目錄下面建立或拷貝了一個動態連接庫,若想使其被系統共享,能夠執行一下」ldconfig 目錄名」這個命令。此命令的功能在於讓ldconfig將指定目錄下的動態連接庫被系統共享起來,意即:在緩存文件/etc/ld.so.cache
中追加進指定目錄下的共享庫.本例讓系統共享了~/tests
目錄下的動態連接庫。
下面的這個錯誤我沒有遇到,不過也記錄下,給遇到的人:
{ 這步後我沒有成功,報錯內容以下:/hello: error while loading shared libraries: /usr/lib/libmyhello.so: cannot restore segment prot after reloc: Permission denied
google了一下,發現是SELinux搞的鬼,解決辦法有兩個:
1. chcon -t texrel_shlib_t /usr/lib/libmyhello.so
(chcon -t texrel_shlib_t 「你不能share的庫的絕對路徑」)
2. $vi /etc/sysconfig/selinux file
或者用
$gedit /etc/sysconfig/selinux file
修改SELINUX=disabled
重啓
這也進一步說明了動態庫在程序運行時是須要的。
能夠查看程序執行時調用動態庫的過程:
$ ldd hello
能夠看到它是如何調用動態庫中的函數的。
linux-vdso.so.1 => (0x00007fffe8f9b000)
libmyhello.so => /home/chenjw/testso/libmyhello.so (0x00007fbe807d5000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbe80410000)
/lib64/ld-linux-x86-64.so.2 (0x000055dc016c2000)
原文中說,使用靜態庫和使用動態庫編譯成目標程序使用的gcc命令徹底同樣,
可能由於咱們的環境不同,上文我多加了一行編譯命令gcc -shared -fPIC -c hello.c
,
因此原文的驗證,那當靜態庫和動態庫同名時,gcc命令會使用哪一個庫文件呢?
在個人環境沒有意義,add by chenjim。
最主要的是GCC命令行的一個選項:
-shared 該選項指定生成動態鏈接庫(讓鏈接器生成T類型的導出符號表,有時候也生成弱鏈接W類型的導出符號),不用該標誌外部程序沒法鏈接。至關於一個可執行文件
l -fPIC:表示編譯爲位置獨立的代碼,不用此選項的話編譯後的代碼是位置相關的因此動態載入時是經過代碼拷貝的方式來知足不一樣進程的須要,而不能達到真正代碼段共享的目的。
l -L.:表示要鏈接的庫在當前目錄中
l -ltest:編譯器查找動態鏈接庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.so來肯定庫的名稱
l LD_LIBRARY_PATH:這個環境變量指示動態鏈接器能夠裝載動態庫的路徑。
l 固然若是有root權限的話,能夠修改/etc/ld.so.conf文件,而後調用 /sbin/ldconfig來達到一樣的目的,不過若是沒有root權限,那麼只能採用輸出LD_LIBRARY_PATH的方法了。
調用動態庫的時候有幾個問題會常常碰到,有時,明明已經將庫的頭文件所在目錄 經過 「-I」 include進來了,庫所在文件經過 「-L」參數引導,並指定了「-l」的庫名,但經過ldd命令察看時,就是死活找不到你指定連接的so文件,這時你要做的就是經過修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件來指定動態庫的目錄。一般這樣作就能夠解決庫沒法連接的問題了。
ld會去找GCC命令中的參數-L
再找gcc的環境變量LIBRARY_PATH
再找內定目錄 /lib /usr/lib /usr/local/lib 這是當初compile gcc時寫在程序內的
編譯目標代碼時指定的動態庫搜索路徑;
環境變量LD_LIBRARY_PATH指定的動態庫搜索路徑;
配置文件/etc/ld.so.conf中指定的動態庫搜索路徑;
默認的動態庫搜索路徑/lib;
默認的動態庫搜索路徑/usr/lib。
LIBRARY_PATH環境變量:指定程序靜態連接庫文件搜索路徑
LD_LIBRARY_PATH環境變量:指定程序動態連接庫文件搜索路徑
應用程序一般都有固定的文件夾,系統通用程序放在/usr/bin,往後系統管理員在本地計算機安裝的程序一般放在/usr/local/bin或者/opt文件夾下。除了系統程序外,大部分我的用到的程序都放在/usr/local下,因此保持/usr的整潔十分重要。當升級或者重裝系統的時候,只要把/usr/local的程序備份一下就能夠了。
一些其餘的程序有本身特定的文件夾,好比X Window系統,一般安裝在/usr/X11中,或者/usr/X11R6。GNU的編譯器GCC,一般放置在/usr/bin或者/usr/local/bin中,不一樣的Linux版本可能位置稍有不一樣。
在C語言和其餘語言中,頭文件聲明瞭系統函數和庫函數,而且定義了一些常量。對於C語言,頭文件基本上散落於/usr/include和它的子文件夾下。其餘的編程語言的庫函數分佈在編譯器定義的地方,好比在一些Linux版本中,X Window系統庫函數分佈在/usr/include/X11,GNU C++的庫函數分佈在/usr/include/g++。這些系統庫函數的位置對於編譯器來講都是「標準位置」,即編譯器可以自動搜尋這些位置。
若是想引用位於標準位置以外的頭文件,咱們須要在調用編譯器的時候加上-I標誌,來顯式的說明頭文件所在文件夾。好比,
$ gcc -I/usr/openwin/include hello.c
會告訴編譯器除了標準位置外,還要去/usr/openwin/include看看有沒有所需的頭文件。詳細狀況見編譯器的使用手冊(man gcc)。
庫函數就是函數的倉庫,它們都通過編譯,重用性不錯。一般,庫函數相互合做,來完成特定的任務。好比操控屏幕的庫函數(cursers和ncursers庫函數),數據庫讀取庫函數(dbm庫函數)等。
系統調用的標準庫函數通常位於/lib以及/usr/lib。C編譯器(精確點說,鏈接器)須要知道庫函數的位置。默認狀況下,它只搜索標準C庫函數。
庫函數文件一般開頭字母是lib。後面的部分標示庫函數的用途(好比C庫函數用c標識, 數學庫函數用m標示),小數點後的後綴代表庫函數的類型:
.a 指靜態連接庫
.so 指動態連接庫
去/usr/lib看一下,你會發現,庫函數都有動態和靜態兩個版本。
與頭文件同樣,庫函數一般放在標準位置,但咱們也能夠經過-L標識符,來添加新的搜索文件夾,-l指定特定的庫函數文件。好比
$ gcc -o x11fred -L/usr/openwin/lib x11fred.c -lX11
上述命令就會在編譯期間,連接位於/usr/openwin/lib文件夾下的libX11函數庫,編譯生成x11fred。
靜態連接庫(Static Libraries)
最簡單的函數庫就是一些函數的簡單集合。調用庫函數中的函數時,須要在調用函數中include定義庫函數的頭文件。咱們用-l選項添加標準函數庫以外的函數庫。