Linux 如何解決共用庫的版本控制

來源:互聯網
上載者:User
Linux 如何解決共用庫的版本控制

Linux 系統,也同樣面臨和Window一樣的問題,如何控制動態庫的多個版本問題。Window之前沒有處理好,為此專門有個名詞來形容這個問題 “Dll hell”,其嚴重影響軟體的升級和維護。 Dll hell 是指windows 上動態庫新版本覆蓋舊版本,但是卻不相容老版本。常常發生在程式升級之後,動態庫更新,原有程式運行不起來;或者裝新軟體,但是已有的軟體運行不起來。 同樣Linux作業系統,也有同樣的問題,那麼它是怎麼解決的呢?

Linux 為解決這個問題,引入了一套機制,如果遵守這個機制來做,就可以避免這個問題。 但是這隻事一個約定,不是強制的。但是建議遵守這個約定,否則同樣也會出現 Linux 版的Dll hell 問題。 下面來介紹一個這個機制。 這個機制是通過檔案名稱,來控制dll (shared library) 的版本。

Linux 上的Dll ,叫shared library,其有三個名字,分別又不同的目的。

第一個是共用庫本身的檔案名稱(real name),其通常包含版本號碼,常常是是這樣: libmath.so.1.1.1234 。 lib是Linux 上的庫的約定首碼,math 是共用庫名子,so 是共用庫的尾碼名,1.1.1234的是共用庫的版本號碼,其主要版本號+小版本號碼+build號。主板號,代表當前動態庫的版本,如果動態庫的介面有變化,那麼這個版本號碼就要加1;後面的兩個版本號碼(小版本號碼 和 build 號)是告訴你詳細的資訊,比如為一個hot-fix 而產生的一個版本,其小版本號碼加1,build號也應有變化。 這個檔案名稱包含共用庫的代碼。

第二個是動態庫的soname( Short for shared object name),其是應用程式載入dll 時候,其尋找共用庫用的檔案名稱。其格式為

                           lib + math+.so + ( major version number)

其只包含major version number,換句話說,也就是只要其介面沒有變,應用程式都可以用,不管你其後minor build version or build version。

問題來了,程式運行時怎麼通過soname 找個real name? Soname 存在哪裡?如果與real name 關聯起來?什麼時候存的?

這就是接下來要介紹的第三個共用庫的名字,link name,顧名思義,就是在編譯過程,link 階段用的檔案名稱。 其將sonmae 和real name 關聯起來。

第三個名字,共用庫的串連名(link name),是專門為build 階段串連而用的名字。這個名字就是lib + math +.so ,比如libmath.so。其是不帶任何版本資訊的。在共用庫編譯過程中,串連(link) 階段,編譯器將產生一個共用庫及real name,同時將共用庫的soname,寫在共用庫檔案裡的檔案頭裡面。可以用命令 readelf -d sharelibrary 去查看。

在應用程式引用共用庫時,其會用到共用庫的link name。在應用程式的link階段,其通過link名字找到動態庫,並且把共用庫的soname 提取出來,寫在自己的共用庫的頭裡面。當應用程式載入時候就會通過soname 去在給定的路徑下尋找該共用庫。

下面通過這個代碼來說明一下系統是如何做的,並且介紹系統的一些設施和工具:

代碼:       

1. File libhello.c
/* hello.c - demonstrate library use. */
#include <stdio.h>
void hello(void) 
{  printf("Hello, library world.\n");}
2. File libhello.h
/* libhello.h - demonstrate library use. */
void hello(void);
3. File main.c
/* main.c -- demonstrate direct use of the "hello" routine */
#include "hello.h"
int main(void) 
{
 hello(); 
return 0;
}

1.產生共用庫,關聯real name 和soname 。

     gcc -g -Wall -fPIC -c hello.c -o hello.o

     gcc -shared -W,soname,-libhello.so.0 -o libhello.so.0.0.0 hello.o

     將會產生共用庫libhello.so.0.0.0.

     可以用系統提供的工具查看共用庫的頭:

      readelf -d libhello.so.0.0.0 | grep libhello

ox00000000000e(SONAME)    library soname: [libhello.so.0]

2.應用程式,引用共用庫。

      先手動產生link 名字,以被後面的程式連結時用

      ln -s libhello.so.0.0.0 libhello.so.0

      gcc -g -Wall -c main.c -o main.o -I.

      gcc  -o main main.o -lhello -L.

      查看編譯出來的程式:

      readelf -d main | grep libhello

ox000000000001(NEEDED)    shared library: [libhello.so.0]

      運行該程式,需要指定共用庫的路徑。 有兩種辦法,第一種使用環境變數“LD_LIBRARY_PATH”. 兩外一種辦法就是將共用庫拷貝到系統目錄(path 環境變數指定的其中一個目錄)。

      暫停! 我們還沒有解決一個問題是,程式只知道soname,怎麼從soname 找到共用庫,即real name 檔案呢? 這需要我們定義一個link檔案,串連到共用庫本身。

      ln -s libhello.so.0.0.0 libhello.so.0

      當然這個路徑需要放到LD_LIBRARY_PATH環境變數中。

     這樣就可以運行該程式。

[Note]Linux 系統提供一個命令 ldconifg 專門為產生共用庫的soname 檔案,以便程式在載入時後通過soname 找到共用庫。 同時該命令也為加速載入共用庫,把系統的共用庫放到一個快取檔案中,這樣可以提高尋找速度。可以用下面命令看一下系統已有的被緩衝起來的共用庫。

     ld -p

3.共用庫,小版本升級,即介面不變.

   當升級小版本時,共用庫的soname 是不變的,所以需要重新把soname 的那個串連檔案指定新版本就可以。 調用ldconfig命令,系統會幫你做修改那個soname link檔案,並把它指向新的版本呢。這時候你的應用程式就自動升級了。

4.共用庫,主要版本升級,即介面發生變化。

  當升級主要版本時,共用庫的soname 就會加1.比如libhello.so.0.0.0 變為 libhello.so.1.0.0. 這時候再運行ldconfig 檔案,就會發現產生兩個串連 檔案。

    ln -s libhello.so.0---->libhello.so.0.0.0

    ln -s libhello.so.1----->libhello.so.1.0.0

儘管共用庫升級,但是你的程式依舊用的是舊的共用庫,並且兩個之間不會相互影響。

    問題是如果更新的共用庫只是增加一些介面,並沒有修改已有的介面,也就是向前相容。但是這時候它的主要版本號卻增加1. 如果你的應用程式想調用新的共用庫,該怎麼辦? 簡單,只要手工把soname 檔案修改,使其指向新的版本就可以。(這時候ldconfig 檔案不會幫你做這樣的事,因為這時候soname 和real name 的版本號碼主板本號不一致,只能手動修改)。

  比如: ln -s libhello.so.0 ---> libhello.so.1.0.0

  但是有時候,主要版本號增加,介面發生變化,可能向前不相容。這時候再這樣子修改,就會報錯,“xx”方法找不到之類的錯誤。

總結一下,Linux 系統是通過共用庫的三個不同名字,來管理共用庫的多個版本。 real name 就是共用庫的實際檔案名稱字,soname 就是共用庫載入時的用的檔案名稱。在產生共用庫的時候,編譯器將soname 綁定到共用庫的檔案頭裡,二者關聯起來。 在應用程式引用共用庫時,其通過link name 來完成,link時將按照系統指定的目錄去搜尋link名字找到共用庫,並將共用庫的soname寫在應用程式的標頭檔裡。當應用程式載入共用庫時,就會通過soname在系統指定的目錄(path or LD_LIBRARY)去尋找共用庫。

當共用庫升級時,分為兩種。一種是主板本不變,升級小版本和build 號。在這種情況下,系統會通過更新soname( ldconfig 來維護),來使用新的版本號碼。這中情況下,舊版本就沒有用,可以刪掉。

另外一種是主要版本升級,其意味著庫的介面發生變化,當然,這時候不能覆蓋已有的soname。系統通過增加一個soname(ldconfig -p 裡面增加一項),使得新舊版本同時存在。原有的應用程式在載入時,還是根據自己標頭檔的舊soname 去尋找老的庫檔案。

5.如果編譯的時候沒有指定,共用庫的soname,會怎麼樣?

  這是一個trick 的地方。第一系統將會在產生庫的時候,就沒有soname放到庫的頭裡面。從而應用程式串連時候,就把linkname 放到應用程式依賴庫裡面。或者換句話說就是,soname這時候不帶版本號碼。 有時候有人直接利用這點來升級應用程式,比如,新版本的庫,直接拷貝到系統目錄下,就會覆蓋掉已經存在的舊的庫檔案,直接升級。 這個給程式員很大程度的便利性,如果一步小心,就會調到類似windows的Dll hell 陷阱裡面。建議不要這樣做。

【Note】

  1. 指定共用庫載入的路徑。LD_LIBRARY_PATH 優先與 path 環境變數。

  2. ldd 可以查看程式,或者共用庫依賴的庫的路徑

  3. nm 查看共用庫暴露的介面

  4. ldconfig 可以自動產生soname 的串連檔案。並提供catch 加速尋找。

  5.readelf 可以查看動態庫的資訊,比如依賴的庫,本身的somae。

  6. objdump 與readelf 類似。

  7 ld The GUN linker

  8. ld.so  dynamic linker or loader

  9. as the portable GNU assembley

【Reference】

http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.