1. 程式人生 > >Linux 動態庫與靜態庫

Linux 動態庫與靜態庫

轉載自:http://blog.chinaunix.net/uid-23069658-id-3142046.html

今天我們主要來說說Linux系統下基於動態庫(.so)和靜態(.a)的程式那些貓膩。在這之前,我們需要了解一下原始碼到可執行程式之間到底發生了什麼神奇而美妙的事情。

      在Linux作業系統中,普遍使用ELF格式作為可執行程式或者程式生成過程中的中間格式。ELF(Executable and Linking Format,可執行連線格式)是UNIX系統實驗室(USL)作為應用程式二進位制介面(Application BinaryInterface,ABI)而開發和釋出的。工具介面標準委員會(TIS)選擇了正在發展中的ELF標準作為工作在32位Intel體系上不同作業系統之間可移植的二進位制檔案格式。本文不對ELF檔案格式及其組成做太多解釋,以免沖淡本文的主題,大家只要知道這麼個概念就行。以後再詳解Linux中的ELF格式。原始碼到可執行程式的轉換時需要經歷如下圖所示的過程:

l 編譯是指把用高階語言編寫的程式轉換成相應處理器的組合語言程式的過程。從本質上講,編譯是一個文字轉換的過程。對嵌入式系統而言,一般要把用C語言編寫的程式轉換成處理器的彙編程式碼。編譯過程包含了C語言的語法解析彙編碼的生成兩個步驟。編譯一般是逐個檔案進行的,對於每一個C語言編寫的檔案,可能還需要進行預處理。

l 彙編是從組合語言程式生成目標系統的二進位制程式碼(機器程式碼)的過程。機器程式碼的生成和處理器有密切的聯絡。相對於編譯過程的語法解析,彙編的過程相對簡單。這是因為對於一款特定的處理器,其組合語言和二進位制的機器程式碼是一一對應的。彙編過程的輸入是彙編程式碼,這個彙編程式碼可能來源於編譯過程的輸出,也可以是直接用匯編語言書寫的程式。

l 連線是指將彙編生成的多段機器程式碼組合成一個可執行程式。一般來說,通過編譯和彙編過程,每一個原始檔將生成一個目標檔案。聯結器的作用就是將這些目標檔案組合起來,組合的過程包括了程式碼段、資料段等部分的合併,以及新增相應的檔案頭。

GCC是Linux下主要的程式生成工具,它除了編譯器、彙編器、聯結器外,還包括一些輔助工具。在下面的分析過程中我會教大家這些工具的基本使用方法,Linux的強大之處在於,對於不太懂的命令或函式,有一個很強大的“男人”時刻stand by your side,有什麼不會的就去命令列終端輸入:man [命令名或函式名],然後阿拉神燈就會顯靈了。

對於最後編譯出來的可執行程式,當我們執行它的時候,作業系統又是如何反應的呢?我們先從巨集觀上來個總體把握,如圖2所示:

作為UNIX作業系統的一種,Linux的作業系統提供了一系列的介面,這些介面被稱為系統呼叫(System Call)。在UNIX的理念中,系統呼叫"提供的是機制,而不是策略"。C語言的庫函式通過呼叫系統呼叫來實現,庫函式對上層提供了C語言庫檔案的介面。在應用程式層,通過呼叫C語言庫函式和系統呼叫來實現功能。一般來說,應用程式大多使用C語言庫函式實現其功能,較少使用系統呼叫。

那麼最後的可執行檔案到底是什麼樣子呢?前面已經說過,這裡我們不深入分析ELF檔案的格式,只是給出它的一個結構圖和一些簡單的說明,以方便大家理解。

ELF檔案格式包括三種主要的型別:可執行檔案、可重定向檔案、共享庫。

1.可執行檔案(應用程式)

可執行檔案包含了程式碼和資料,是可以直接執行的程式。

2.可重定向檔案(*.o)

可重定向檔案又稱為目標檔案,它包含了程式碼和資料(這些資料是和其他重定位檔案和共享的object檔案一起連線時使用的)。

*.o檔案參與程式的連線(建立一個程式)和程式的執行(執行一個程式),它提供了一個方便有效的方法來用並行的視角看待檔案的內容,這些*.o檔案的活動可以反映出不同的需要。

Linux下,我們可以用gcc -c編譯原始檔時可將其編譯成*.o格式。

3.共享檔案(*.so)

也稱為動態庫檔案,它包含了程式碼和資料(這些資料是在連線時候被聯結器ld和執行時動態聯結器使用的)。動態聯結器可能稱為ld.so.1,libc.so.1或者 ld-linux.so.1。我的CentOS6.0系統中該檔案為:/lib/ld-2.12.so

一個ELF檔案從聯結器(Linker)的角度看,是一些節的集合;從程式載入器(Loader)的角度看,它是一些段(Segments)的集合。ELF格式的程式和共享庫具有相同的結構,只是段的集合和節的集合上有些不同。

那麼到底什麼是庫呢?

庫從本質上來說是一種可執行程式碼的二進位制格式,可以被載入記憶體中執行。庫分靜態庫和動態庫兩種。

靜態庫:這類庫的名字一般是libxxx.a,xxx為庫的名字。利用靜態函式庫編譯成的檔案比較大,因為整個函式庫的所有資料都會被整合進目的碼中,他的優點就顯而易見了,即編譯後的執行程式不需要外部的函式庫支援,因為所有使用的函式都已經被編譯進去了。當然這也會成為他的缺點,因為如果靜態函式庫改變了,那麼你的程式必須重新編譯。

動態庫:這類庫的名字一般是libxxx.M.N.so,同樣的xxx為庫的名字,M是庫的主版本號,N是庫的副版本號。當然也可以不要版本號,但名字必須有。相對於靜態函式庫,動態函式庫在編譯的時候並沒有被編譯進目的碼中,你的程式執行到相關函式時才呼叫該函式庫裡的相應函式,因此動態函式庫所產生的可執行檔案比較小。由於函式庫沒有被整合進你的程式,而是程式執行時動態的申請並呼叫,所以程式的執行環境中必須提供相應的庫。動態函式庫的改變並不影響你的程式,所以動態函式庫的升級比較方便。linux系統有幾個重要的目錄存放相應的函式庫,如/lib /usr/lib。

當要使用靜態的程式庫時,聯結器會找出程式所需的函式,然後將它們拷貝到執行檔案,由於這種拷貝是完整的,所以一旦連線成功,靜態程式庫也就不再需要了。然而,對動態庫而言,就不是這樣。動態庫會在執行程式內留下一個標記指明當程式執行時,首先必須載入這個庫。由於動態庫節省空間,linux下進行連線的預設操作是首先連線動態庫,也就是說,如果同時存在靜態和動態庫,不特別指定的話,將與動態庫相連線。

        OK,有了這些知識,接下來大家就可以弄明白我所做的事情是幹什麼了。都說例子是最好老師,我們就從例子入手。

   1、靜態連結庫

   我們先製作自己的靜態連結庫,然後再使用它。製作靜態連結庫的過程中要用到gcc和ar命令。

  準備兩個庫的原始碼檔案st1.c和st2.c,用它們來製作庫libmytest.a,如下:

    靜態庫檔案libmytest.a已經生成,用file命令檢視其屬性,發現它確實是歸檔壓縮檔案。用ar -t libmytest.a可以檢視一個靜態庫包含了那些obj檔案:

    接下來我們就寫個測試程式來呼叫庫libmytest.a中所提供的兩個介面print1()和print2()。

    看到沒,靜態庫的編寫和呼叫就這麼簡單,學會了吧。這裡gcc的引數-L是告訴編譯器庫檔案的路徑是當前目錄,-l是告訴編譯器要使用的庫的名字叫mytest。

    2、動態庫

    靜態庫*.a檔案的存在主要是為了支援較老的a.out格式的可執行檔案而存在的。目前用的最多的要數動態庫了。

動態庫的字尾為*.so。在Linux發行版中大多數的動態庫基本都位於/usr/lib和/lib目錄下。在開發和使用我們自己動態庫之前,請容許我先落裡羅嗦的跟大家嘮叨嘮叨Linux下和動態庫相關的事兒吧。

有時候當我們的應用程式無法執行時,它會提示我們說它找不到什麼樣的庫,或者哪個庫的版本又不合它胃口了等等之類的話。那麼應用程式它是怎麼知道需要哪些庫的呢?我們前面已幾個學了個很棒的命令ldd,用就是用來檢視一個檔案到底依賴了那些so庫檔案。

Linux系統中動態連結庫的配置檔案一般在/etc/ld.so.conf檔案內,它裡面存放的內容是可以被Linux共享的動態聯庫所在的目錄的名字。我的系統中,該檔案的內容如下:

    然後/etc/ld.so.conf.d/目錄下存放了很多*.conf檔案,如下:

    其中每個conf檔案代表了一種應用的庫配置內容,以mysql為例:

    如果您是和我一樣裝的CentOS6.0的系統,那麼細心的讀者可能會發現,在/etc目錄下還存在一個名叫ld.so.cache的檔案。從名字來看,我們知道它肯定是動態連結庫的什麼快取檔案。

對,您說的一點沒錯。為了使得動態連結庫可以被系統使用,當我們修改了/etc/ld.so.conf或/etc/ld.so.conf.d/目錄下的任何檔案,或者往那些目錄下拷貝了新的動態連結庫檔案時,都需要執行一個很重要的命令:ldconfig,該命令位於/sbin目錄下,主要的用途就是負責搜尋/lib和/usr/lib,以及配置檔案/etc/ld.so.conf裡所列的目錄下搜尋可用的動態連結庫檔案,然後建立處動態載入程式/lib/ld-linux.so.2所需要的連線和(預設)快取檔案/etc/ld.so.cache(此檔案裡儲存著已經排好序的動態連結庫名字列表)。

也就是說:當用戶在某個目錄下面建立或拷貝了一個動態連結庫,若想使其被系統共享,可以執行一下"ldconfig目錄名"這個命令。此命令的功能在於讓ldconfig將指定目錄下的動態連結庫被系統共享起來,即:在快取檔案/etc/ld.so.cache中追加進指定目錄下的共享庫。請注意:如果此目錄不在/lib,/usr/lib/etc/ld.so.conf檔案所列的目錄裡面,則再次單獨執行ldconfig時,此目錄下的動態連結庫可能不被系統共享了。單獨執行ldconfig時,它只會搜尋/lib/usr/lib以及在/etc/ld.so.conf檔案裡所列的目錄,用它們來重建/etc/ld.so.cache

因此,等會兒我們自己開發的共享庫就可以將其拷貝到/lib、/etc/lib目錄裡,又或者修改/etc/ld.so.conf檔案將我們自己的庫路徑新增到該檔案中,再執行ldconfig命令。

非了老半天功夫,終於把基礎打好了,猴急的您早已按耐不住激情的想動手嘗試了吧!哈哈。。。OK,說整咱就開整,接下來我就帶領大家一步一步來開發自己的動態庫,然後教大家怎麼去使用它。

我們有一個頭檔案my_so_test.h和三個原始檔test_a.c、test_b.c和test_c.c,將他們製作成一個名為libtest.so的動態連結庫檔案:

OK,萬事俱備,只欠東風。如何將這些檔案編譯成一個我們所需要的so檔案呢?可以分兩步來完成,也可以一步到位:

方法一:

         1、先生成目標.o檔案:

       2、再生成so檔案:

-shared該選項指定生成動態連線庫(讓聯結器生成T型別的匯出符號表,有時候也生成弱連線W型別的匯出符號),不用該標誌外部程式無法連線。相當於一個可執行檔案。

-fPIC:表示編譯為位置獨立的程式碼,不用此選項的話編譯後的程式碼是位置相關的所以動態載入時是通過程式碼拷貝的方式來滿足不同程序的需要,而不能達到真正程式碼段共享的目的。

方法二:一步到位。

至此,我們製作的動態庫檔案libtest.so就算大功告成了。

接下來,就是如何使用這個動態庫了。動態連結庫的使用有兩種方法:既可以在執行時對其進行動態連結,又可以動態載入在程式中是用它們。接下來,我就這兩種方法分別對其介紹。 

        +++動態庫的使用+++

        用法一:動態連結。

使用“-ltest”標記來告訴GCC驅動程式在連線階段引用共享函式庫libtest.so。“-L.”標記告訴GCC函式庫可能位於當前目錄。否則GNU聯結器會查詢標準系統函式目錄。

這裡我們注意,ldd的輸出它說我們的libtest.so它沒找到。還記得我在前面動態連結庫一節剛開始時的那堆嘮叨麼,現在你應該很明白了為什麼了吧。因為我們的libtest.so既不在/etc/ld.so.cache裡,又不在/lib、/usr/lib或/etc/ld.so.conf所指定的任何一個目錄中。怎麼辦?還用我告訴你?管你用啥辦法,反正我用的ldconfig `pwd`搞定的:

       執行結果如下:

偶忍不住又要羅嗦一句了,相信俺,我的嘮叨對大家是有好處。我為什麼用這種方法呢?因為我是在給大家演示動態庫的用法,完了之後我就把libtest.so給刪了,然後再重構ld.so.cache,對我的系統不會任何影響。倘若我是開發一款軟體,或者給自己的系統DIY一個非常有用的功能模組,那麼我更傾向於將libtest.so拷貝到/lib、/usr/lib目錄下,或者我還有可能在/usr/local/lib/目錄下新建一資料夾xxx,將so庫拷貝到那兒去,並在/etc/ld.so.conf.d/目錄下新建一檔案mytest.conf,內容只有一行“/usr/local/lib/xxx/libtest.so”,再執行ldconfig。如果你之前還是不明白怎麼解決那個“not found”的問題,那麼現在總該明白了吧。

    方法二:動態載入。

動態載入是非常靈活的,它依賴於一套Linux提供的標準API來完成。在源程式裡,你可以很自如的運用API來載入、使用、釋放so庫資源。以下函式在程式碼中使用需要包含標頭檔案:dlfcn.h

函式原型

說明

const char *dlerror(void)

當動態連結庫操作函式執行失敗時,dlerror可以返回出錯資訊,返回值為NULL時表示操作函式執行成功。

void *dlopen(const char *filename, int flag)

用於開啟指定名字(filename)的動態連結庫,並返回操作控制代碼。呼叫失敗時,將返回NULL值,否則返回的是操作控制代碼。

void *dlsym(void *handle, char *symbol)

根據動態連結庫操作控制代碼(handle)與符號(symbol),返回符號對應的函式的執行程式碼地址。由此地址,可以帶引數執行相應的函式。

int dlclose (void *handle)

用於關閉指定控制代碼的動態連結庫,只有當此動態連結庫的使用計數為0時,才會真正被系統解除安裝。2.2在程式中使用動態連結庫函式。

       dlsym(void *handle, char *symbol)

filename:如果名字不以“/”開頭,則非絕對路徑名,將按下列先後順序查詢該檔案。

   (1)使用者環境變數中的LD_LIBRARY值;

   (2)動態連結緩衝檔案/etc/ld.so.cache

   (3)目錄/lib,/usr/lib

      flag表示在什麼時候解決未定義的符號(呼叫)。取值有兩個:

       1) RTLD_LAZY : 表明在動態連結庫的函式程式碼執行時解決。

      2) RTLD_NOW :表明在dlopen返回前就解決所有未定義的符號,一旦未解決,dlopen將返回錯誤。

        dlsym(void *handle, char *symbol)

        dlsym()的用法一般如下:

       void(*add)(int x,int y); /*說明一下要呼叫的動態函式add */

add=dlsym("xxx.so","add"); /* 開啟xxx.so共享庫,取add函式地址 */

add(89,369); /* 帶兩個引數89和369呼叫add函式 */

    看我出招:

        執行結果:

使用動態連結庫,源程式中要包含dlfcn.h標頭檔案,寫程式時注意dlopen等函式的正確呼叫,編譯時要採用-rdynamic選項與-ldl選項(不然編譯無法通過),以產生可呼叫動態連結庫的執行程式碼。

相關推薦

Linux動態靜態操作

Linux命令之ar - 建立靜態庫.a檔案 用途說明 建立靜態庫.a檔案。用C/C++開發程式時經常用到,但我很少單獨在命令列中使用ar命令,一般寫在makefile中,有時也會在shell腳 本中用到。關於Linux下的庫檔案、靜態庫、動態庫以及怎樣建立和使用等相關知識,參見本文後面的相

linux動態靜態程式設計

個人覺得在linux環境下,動態庫和靜態庫的程式設計更加容易. 首先要熟悉gcc的各個引數意義 -E 預編譯 -S 編譯 -c 彙編成二進位制程式碼,-C 生成可執行檔案 1,編寫static.c 2,將static.c編譯成二進位制程式碼:gcc -c static.c&nbs

Linux 動態靜態

轉載自:http://blog.chinaunix.net/uid-23069658-id-3142046.html 今天我們主要來說說Linux系統下基於動態庫(.so)和靜態(.a)的程式那些貓膩。在這之前,我們需要了解一下原始碼到可執行程式之間到底發生了什麼神奇而美妙

動態靜態的區別(linux vs windows)

      方法庫大體上可以分為兩類:靜態庫和動態庫(共享庫)。在windows中靜態庫是以 .lib 為字尾的檔案,動態庫是以 .dll 為字尾的檔案。在linux中靜態庫是以 .a 為字尾的檔案,共

動態靜態優缺點比較

命令 編譯 現實 產生 會有 重新編譯 種類 並且 發現 動態庫與靜態庫優缺點比較 (2012-10-18 15:31) 我們在編寫一個C語言程序的時候,經常會遇到好多重復或常用的部分,如果每次都重新編寫固然是可以的,不過那樣會大大降低工作效率,並且影響代碼的

動態靜態

共享 才會 編譯 found bsp 文件 二進制 size article 轉載自:https://blog.csdn.net/sheng_bin/article/details/52961520 什麽叫庫? 庫(Library)說白了就是一段編譯好的二進制代碼,加上頭文

C++---動態靜態的區別

首先介紹一下靜態庫(靜態連結庫)、動態庫(動態連結庫)的概念,首先兩者都是程式碼共享的方式。 靜態庫:在連結步驟中,聯結器將從庫檔案取得所需的程式碼,複製到生成的可執行檔案中,這種庫稱為靜態庫,其特點是可執行檔案中包含了庫程式碼的一份完整拷貝;缺點就是被多次使用就會有多份冗餘拷貝。即靜態庫中的指

iOS-動態靜態

簡介 在企業開發中,一些核心技術或者常用框架,出於安全性和穩定性的考慮,不想被外界知道,所以會把核心程式碼打包成靜態庫,只暴露標頭檔案給程式設計師使用(比如:友盟、百度地圖等第三方的sdk)  靜態庫和動態庫的存在形式  • 靜態庫:.a 和 .framework  •

qt動態靜態編譯、應用以及pri檔案建立

一. 靜態庫的生成 1. 測試目錄: lib 2. 原始碼檔名: mywindow.h, mywindow.cpp 3. 編寫專案檔案: mywindow.pro 注意兩點: TEMPLATE = libCONFIG   += staticlib  4. 生成M

生成 iOS 動態靜態方法

支援原創,更多內容歡迎訪問部落格: 在程式開發過程中,免不了需要對程式進行封裝:比如給第三方使用者來呼叫的SDK,或者給其他開發人員來使用,同時他們又無需或者無權瞭解其中細節的時候,就需要用到動態庫封裝。 一、建立FrameWork工程 新建工程,選擇建立Fram

動態靜態相互呼叫

測試場景,Test,lib1,lib2,dll1,dll2,分為下面四種情況:1、Test->lib1->lib2lib1編譯自己的程式碼,對lib2的部分,只需要lib2的標頭檔案,對lib2的程式碼實現,使用佔位符關聯。生成Test連線的時候,把lib1的程式碼實現包含進來,再遞迴,把lib

Mac下libconv動態靜態的編譯

轉至http://www.cnblogs.com/codingking/archive/2013/01/21/2869686.html 編譯環境:gcc 庫:libconv-1.14.tar.gz 步驟: 1,下載庫並解壓 2,進入解壓目錄 3,./configure [

cmake同時生成動態靜態的方法

我的目錄結構 [[email protected] createLibrary]$ tree . ├── bin ├── build ├── CMakeLists.txt ├── include │   └── person.h ├── lib └── src ├── CMakeLi

Windows動態連結靜態

一、靜態庫(lib) ①Win32程式不能連結64位靜態庫; ②Debug下可以連結Release版的靜態庫,反之亦然; 二、動態連結庫(dll) ①32位程式依賴32位dll,dll檔案可以放在“C:\Windows”目錄下,也可以放在“C:\Windows\SysWOW64”目錄,但不能

Ubuntu下動態靜態混合連線

一、在應用程式需要連線外部庫的情況下,linux預設對庫的連線是使用動態庫,在找不到動態庫的情況下再選擇靜態庫。使用方式為: gcc test.cpp -L. -ltestlib 如果當前目錄有兩個庫libtestlib.so libtestlib.a 則肯定是連線libtestlib.so。如果要指定為連線

linux c/c++ 動態靜態的生成使用

二.介紹     從原始碼到可執行程式,通常要經過最重要的兩大步是:編譯,連結。編譯就是將原始檔生成中間檔案的過程,在linux下就是生成  .obj檔案。連結就是用連結器將,這些個中間檔案有序地”糅合“在一起,構成一個可執行檔案。通常,一個.c檔案或者.cpp原始檔編譯後,就會對應生成一個.obj檔案。  

malloc,colloc,realloc內存分配,動態靜態的生成調用

lac 輸出 初始化 clu 技術 pragma num idt return ?? 1.在main方法裏面直接定義一個很大的數組的時候。可能會出現棧溢出:錯誤代碼演示: #include<stdio.h> #include<stdlib.h&g

Linux下RabbitMQ的編譯,生成動態靜態

執行 編譯 ast lin 目錄 off href apt-get span 一、步驟 1、代碼托管處下載代碼 最新:https://github.com/alanxz/rabbitmq-c/archive/master.zip 穩定:https://g

C語言函式動態連結靜態連結

首先,函式庫就是一些事先寫好的函式的集合,是別人分享的,我們可以拿來使用的。經過一些校準和整理,就形成一份標準化的函式庫。例如glibc 函式庫有兩種提供形式:動態連結庫與靜態連結庫 早起函式庫裡的函式都是直接共享的,就是所謂的開源社群。後來函式庫商業化,就出現了靜態連結庫與動態連結庫。

Linux 動態靜態

Linux作業系統中,依據函式庫是否被編譯到程式內部,將其分為兩大類,靜態函式庫和動態函式庫。 Linux下的函式庫放在/lib或/usr/lib,標頭檔案放在/usr/include。 在既有靜態庫又有動態庫的情況下,預設使用動態庫,如果強制使用靜態庫則需要加-static選項支援。