LINUX總結第13篇:LINUX下動態庫及版本號控制

前言 html

針對同一動態組件的不一樣版本連接和加載。

1、概念         
         DLL HELL字面意思是DLL"災難",是因爲com組件(動態庫)升級引發的程序不能運行的狀況。
        緣由
         有三種可能的緣由致使了DLL Hell的發生:
                一是由使用舊版本的DLL替代原來一個新版本的DLL而引發的。這個緣由最廣泛,是Windows 9X用戶一般遇到的DLL錯誤之一。
                二是由新版DLL中的函數無心發生改變而引發。儘管在設計DLL時候應該向下兼容,然而要保證DLL徹底向下兼容倒是不能的。
                三是由新版DLL的安裝引入一個新的Bug。

2、linux下的解決方案——命名規範       
       Linux 上的Dll ,叫sharedlibrary。Linux 系統面臨和Window同樣的問題,如何控制動態庫的多個版本問題。爲解決這個問題,Linux 爲解決這個問題,引入了一套命名機制,若是遵照這個機制來作,就能夠避免這個問題。可是這隻事一個約定,不是強制的。可是建議遵照這個約定,不然一樣也會出現 Linux 版的Dll hell 問題。

Real Name        
          首先是共享庫自己的文件名:共享庫的命名必須如 libname.so.x.y.z最前面使用前綴」lib」,中間是庫的名字和後綴」.so」,最後三個數字是版本號。x是主版本號(Major Version Number),y是次版本號(Minor Version Number),z是發佈版本號(Release Version Number)。

主版本號(不兼容):重大升級,不一樣主版本的庫之間的庫是不兼容的。因此若是要保證向後兼容就不能刪除舊的動態庫的版本。

次版本號(向下兼容): 增量升級,增長一些新的接口但保留原有接口。高次版本號的庫向後兼容低次版本號的庫。

發佈版本號(相互兼容):庫的一些諸如錯誤修改、性能改進等,不添加新接口,也不更改接口。主版本號和次版本號相同的前提下,不一樣發佈版本之間徹底兼容。

SO-NAME       
         嚴格遵照上述規定,確實能避免動態庫由於版本衝突的問題,可是讀者可能有疑問:在程序加載或運行的時候,動態連接器是如何知道程序依賴哪些庫,如何選擇庫的不一樣版本?
Solaris和Linux等採用SO-NAME( Shortfor shared object name )的命名機制來記錄共享庫的依賴關係。每一個共享庫都有一個對應的「SO-NAME」(共享庫文件名去掉次版本號和發佈版本號)。好比一個共享庫名爲libtest.so.3.8.2,那麼它的SO-NAME就是libtest.so.3。

在Linux系統中,系統會爲每一個共享庫所在的目錄建立一個跟SO-NAME相同的而且指向它的軟鏈接(Symbol Link)。這個軟鏈接會指向目錄中主版本號相同、次版本號和發佈版本號最新的共享庫。也就是說,好比目錄中有兩個共享庫版本分別爲:/lib/libtest.so.3.8.2和/lib/libtest.so.3.7.5,麼軟鏈接/lib/libtest.so.3指向/lib/libtest.so.3.8.2。

創建以SO-NAME爲名字的軟鏈接的目的是,使得全部依賴某個共享庫的模塊,在編譯、連接和運行時,都使用共享庫的SO-NAME,而不須要使用詳細版本號。在編譯生產ELF文件時候,若是文件A依賴於文件B,那麼A的連接文件中的」.dynamic」段中會有DT_NEED類型的字段,字段的值就是B的SO-NAME。這樣當動態連接器進行共享庫依賴文件查找時,就會依據系統中各類共享庫目錄中的SO-NAME軟鏈接自動定向到最新兼容版本的共享庫。

★  readelf -d sharelibrary 能夠查看so-name
★  Linux提供了一個工具——ldconfig,當系統中安裝或更新一個共享庫時,須要運行這個工具,它會遍歷默認全部共享庫目錄,好比/lib,/usr/lib等,而後更新全部的軟連接,使她們指向最新共享庫。

Link Name 
       當咱們在編譯器裏使用共享庫的時候,如用GCC的「-l」參數連接共享庫libtXXX.so.3.8.1,只須要在編譯器命令行指定 -l XXX 便可,省略了前綴和版本信息。編譯器會根據當前環境,在系統中的相關路徑(每每由-L參數指定)查找最新版本的XXX庫。這個XXX就是共享庫的「連接名」。不一樣類型的庫可能有相同的連接名,好比C語言運行庫有靜態版本(libc.a)也動態版本(libc.so.x.y.z)的區別,若是在連接時使用參數」-lc」,那麼鏈接器就會根據輸出文件的狀況(動態/靜態)來選擇合適版本的庫。eg. ld使用「-static」參數時嗎,」-lc」會查找libc.a;若是使用「-Bdynamic」(默認),會查找最新版本的libc.so.x.y.z。

更詳細能夠參見 linux

http://www.linuxidc.com/Linux/2012-04/59071.htm 程序員


代碼:         windows

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. 性能

     能夠用系統提供的工具查看共享庫的頭: ui

      readelf -d libhello.so.0.0.0 | grep libhello spa

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.

(這裏我會出問題。由於:執行gcc  -o main main.o -lhello -L.命令的時候,默認會找libhello.so這個文件,可是顯然沒有,直接出錯。我又執行:

ln -s libhello.so.0.0.0 libhello.so 以後,才能夠,到如今,沒明白爲何?)

      查看編譯出來的程序:

      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

運行程序:

./main這樣,直接運行是不行了。動態庫必須在運行的時候,也指定路徑。因此,請將.so文件,複製到/lib或者/usr/lib下,而後執行 ldconfig /lib命令就OK了。

ldconfig是一個動態連接庫管理命令
爲了讓動態連接庫爲系統所共享,還需運行動態連接庫的管理命令--ldconfig
ldconfig  命令的用途,主要是在默認搜尋目錄(/lib和/usr/lib)以及動態庫配置文件/etc/ld.so.conf內所列的目錄下,搜索出可共享的動態 連接庫(格式如前介紹,lib*.so*),進而建立出動態裝入程序(ld.so)所需的鏈接和緩存文件.緩存文件默認爲  /etc/ld.so.cache,此文件保存已排好序的動態連接庫名字列表.
ldconfig一般在系統啓動時運行,而當用戶安裝了一個新的動態連接庫時,就須要手工運行這個命令.)

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 能夠查看動態庫的信息,好比依賴的庫,自己的soname。

  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

相關文章
相關標籤/搜索