(換句話說,soname不是真實存在的文件,只是在此庫中和未來調用此庫的文件中保存的一個名字,在加載時去找這個名字,使用時建立一個軟鏈接來指向真實文件,這樣真實文件的版本號就能夠升級了)程序員
Linux 系統,也一樣面臨和Window同樣的問題,如何控制動態庫的多個版本問題。Window以前沒有處理好,爲此專門有個名詞來形容這個問題 「Dll hell」,其嚴重影響軟件的升級和維護。 Dll hell 是指windows 上動態庫新版本覆蓋舊版本,可是卻不兼容老版本。經常發生在程序升級以後,動態庫更新,原有程序運行不起來;或者裝新軟件,可是已有的軟件運行不起來。 一樣Linux操做系統,也有一樣的問題,那麼它是怎麼解決的呢?windows
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號也應有變化。 這個文件名包含共享庫的代碼。ui
第二個是動態庫的soname( Short for shared object name),其是應用程序加載dll 時候,其尋找共享庫用的文件名。其格式爲spa
lib + math+.so + ( major version number)操作系統
其只包含major version number,換句話說,也就是隻要其接口沒有變,應用程序均可以用,無論你其後minor build version or build version。code
問題來了,程序運行時怎麼經過soname 找個real name? Soname 存在哪裏?若是與real name 關聯起來?何時存的?接口
這就是接下來要介紹的第三個共享庫的名字,link name,顧名思義,就是在編譯過程,link 階段用的文件名。 其將sonmae 和real name 關聯起來。ip
第三個名字,共享庫的鏈接名(link name),是專門爲build 階段鏈接而用的名字。這個名字就是lib + math +.so ,好比libmath.so。其是不帶任何版本信息的。在共享庫編譯過程當中,鏈接(link) 階段,編譯器將生成一個共享庫及real name,同時將共享庫的soname,寫在共享庫文件裏的文件頭裏面。能夠用命令 readelf -d sharelibrary 去查看。
在應用程序引用共享庫時,其會用到共享庫的link name。在應用程序的link階段,其經過link名字找到動態庫,而且把共享庫的soname 提取出來,寫在本身的共享庫的頭文件裏面。當應用程序加載時就會經過soname 去給定的路徑下尋找該共享庫。
下面經過這個代碼來講明一下系統是如何作的,而且介紹系統的一些設施和工具:
代碼:
/* hello.c - demonstrate library use. */
#include <stdio.h>
void hello(void)
{ printf("Hello, library world./n");}
/* libhello.h - demonstrate library use. */
void hello(void);
/* 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 能夠查看動態庫的信息,好比依賴的庫,自己的soname。
6. objdump 與readelf 相似。
7 ld The GUN linker
8. ld.so dynamic linker or loader
9. as the portable GNU assembley