Linux 如何解決共享庫的版本控制
換句話說,soname不是真實存在的檔案,只是在此庫中和將來呼叫此庫的檔案中儲存的一個名字,在載入是去找這個名字,使用時建立一個軟連線來指向真實檔案,這樣真實檔案的版本號就可以升級了
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
相關文章
- Linux共享連結庫錯誤的解決Linux
- 6、linux共享庫的組織Linux
- Linux下的共享庫(動態庫)和靜態庫Linux
- 資料庫版本控制完全指南資料庫
- Linux共享連結庫錯誤解決一例(轉)Linux
- jquery如何解決版本之間衝突的問題jQuery
- 華納雲 如何解決Linux 資料庫亂碼問題?Linux資料庫
- Linux共享庫、靜態庫、動態庫詳解Linux
- 大家如何解決長事務併發控制的問題?
- 如何解決醫院內外網檔案共享問題?
- linux C++ 共享庫匯出類LinuxC++
- Linux(Ubuntu版本)下配置samba實現資料夾共享的方法:LinuxUbuntuSamba
- 版本控制
- Linux庫共享檔案的查詢與判斷Linux
- Linux核心版本控制方案給你講明白Linux
- 【Linux】python版本控制和環境管理LinuxPython
- Linux系統共享庫程式設計(轉)Linux程式設計
- Win10 1909不能匿名共享如何解決_Win10 1909無法匿名共享解決教程Win10
- 沒有使用版本控制的黑暗時代——版本控制心得(一) (轉)
- Linux下共享庫問題導致無法啟動SQLPLUS的問題解決LinuxSQL
- Win10系統共享失敗提示共享依賴服務無法啟動如何解決Win10
- 不能連線資料庫如何解決資料庫
- 如何解決資料庫配置問題資料庫
- 如何解決倉庫無線覆蓋的問題?
- 版本控制工具
- Win10系統與xp系統無法共享如何解決Win10
- 使用Java和Flyway進行資料庫版本控制Java資料庫
- redhat開源版本(如centos)oracle安裝事宜RedhatCentOSOracle
- mysql 5.0版本如資料庫已存在則安裝失敗無提示MySql資料庫
- Git 版本控制工具的使用Git
- flutter中的多版本控制Flutter
- 分散式的版本控制工具分散式
- 測試版本控制的管理
- 如何解決 Linux 中“磁碟空間不足”的問題Linux
- 共享一勞永逸的資料庫編碼解決方案資料庫
- Spring Boot 整合 Flyway 實現資料庫版本控制Spring Boot資料庫
- 資料庫事務和MVCC多版本併發控制資料庫MVC
- 版本控制常見問題列表——版本控制心得(三) (轉)