解決Linux動態庫版本兼容問題


說道“動態庫版本兼容”,很多人頭腦中首先蹦出的就是“Dll Hell”。啊,這曾經讓人頭疼的難題。時至今日,這個難題已經很好地解決了。
 
在進一步討論之前來思考一個問題:Linux下為什么沒有讓人頭痛的“DllHell”?
回答這個問題,非常easy,因為——Linux下根本沒有dll!
 
哈哈,當然這只是個玩笑,接下來展開一下這個話題,很多有動態庫的系統都會面臨這個難題,但各自解決的思路卻各不相同。
 
Dll hell是指windows 上動態庫新版本覆蓋舊版本,但是卻不兼容老版本。常常發生在程序升級之后,動態庫更新,原有程序運行不起來;或者裝新軟件,但是已有的軟件運行不起來。
 
一、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 即可,省略了前綴和版本信息(也就是說可以指定詳細的版本信息??,有機會測試一下假設指定libuser.so.1.1.2,然后/usr/lib里只有libuser.so.1或其它版本Linux能否智能的查找)。編譯器會根據當前環境,在系統中的相關路徑(往往由-L參數指定)查找最新版本的XXX庫。這個XXX就是共享庫的“鏈接名”。不同類型的庫可能有相同的鏈接名,比如C語言運行庫有靜態版本(libc.a)也動態版本(libc.so.x.y.z)的區別,如果在鏈接時使用參數”-lc”,那么連接器就會根據輸出文件的情況(動態/靜態)來選擇合適版本的庫。eg. ld使用“-static”參數時嗎,”-lc”會查找libc.a;如果使用“-Bdynamic”(默認),會查找最新版本的libc.so.x.y.z。
 
更詳細可以參見
http://www.linuxidc.com/Linux/2012-04/59071.htm
 
 
.Net下的解決方案——Manifest文件
.Net框架中,一個程序集(Assembly)有兩種類型:應用程序程序集(.exe)與庫程序集(DLL動態鏈接庫)。一個程序集包括一個或個文件,所以需要一個清單文件(Manifest文件)來描述程序集。Manifest文件描述了程序集的名字,版本號以及程序集的各種資源,同時也描述了該程序集的運行所依賴的資源,包括DLL以及其他資源文件等。Manifest是一個XML文件。每個DLL有自己的Manifest。對於應用程序而言,manifest文件可以和可執行文件在同一目錄,也可以作為一個資源嵌入到可執行文件內部(Embed Manifest)。
 
XP以前的windows版本,在執行可執行文件是不會考慮manifest文件的。它會直接到system32目錄下查找可執行文件鎖依賴的DLL。在這種情況下,manifest是多余的。XP之后的操作系統,在執行可執行文件時則會首先讀取程序集的manifest文件,獲得該可執行文件需要調用的DLL列表,操作系統再根據DLL的manifest文件去尋找對應的DLL調用。
 
 
Windows的解決方案——COM組件
采用標准COM組件,有很多好處:面向接口和對象編程語言無關性,采用二進制標准,可以實現跨語言調用版本升級方便,增加新接口,組件升級后老客戶程序不用重新編譯位置透明,客戶程序不用關心組件的位置重用方便,通過包容和聚合可以快速重用已有組件我們可以看到標准COM組件非常強大,但是很多時候我們並不需要標准COM組件的所有特性,比如我們不希望引入注冊表,也不希望引入COM運行庫,我們希望我們的程序是完全“綠色”的。這時我們就會采用“COM思想架構“開發非標准的COM組件。


轉自 http://blog.sina.com.cn/s/blog_5cf54f0e0101cpct.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM