小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

剖析共享程序庫(kù)

 昵稱14604338 2013-11-29


初識(shí)共享程序庫(kù)

簡(jiǎn)介: 共享程序庫(kù)通過版本號(hào)來完成對(duì)應(yīng)用程序所使用的程序庫(kù)的升級(jí),同時(shí)保留了對(duì)原有應(yīng)用程序的兼容。本文將討論此方法的實(shí)際內(nèi)幕,以及在常規(guī) Linux? 系統(tǒng)上的 /usr/lib 中有很多符號(hào)鏈接的原因。

發(fā)布日期: 2005 年 1 月 31 日 
級(jí)別: 初級(jí) 
訪問情況 : 5166 次瀏覽 
評(píng)論:  (查看 | 添加評(píng)論 - 登錄)

平均分 3 星 共 6 個(gè)評(píng)分 平均分 (6個(gè)評(píng)分)
為本文評(píng)分

共享程序庫(kù)是現(xiàn)代 UNIX? 系統(tǒng)中有效利用空間和資源的基礎(chǔ)。SUSE 系統(tǒng)中的 C 程序庫(kù)大約有 1.3 MB。為 /usr/bin 中每一個(gè)程序(我有 2,569 個(gè))制作副本將占去幾個(gè) G 的空間。

當(dāng)然這個(gè)數(shù)字有一些夸張 —— 靜態(tài)鏈接程序只合并它們使用的那部分程序庫(kù)。 盡管如此, printf() 的所有副本所占用的空間數(shù)量也會(huì)讓系統(tǒng) 顯得非常臃腫。

共享程序庫(kù)不僅可以節(jié)省磁盤空間,而且還可以節(jié)省內(nèi)存。內(nèi)核可以在內(nèi)存中保持某個(gè)共享程序庫(kù)的一個(gè)惟一副本,并在多個(gè)應(yīng)用程序間共享這個(gè)副本。所以,我們不但可以在磁盤上只有 printf() 的 一個(gè)副本,而且在內(nèi)存中也只需要一個(gè)副本。這對(duì)性能有很大的影響。

在本文中,我們將討論共享程序庫(kù)所使用的底層技術(shù),以及在共享程序庫(kù)版本號(hào)幫助下預(yù)防兼容性難題的方法,過去,本機(jī)共享程序庫(kù)實(shí)現(xiàn)也曾遇到過這些難題。首先來看一下共享程序庫(kù)的工作原理。

共享程序庫(kù)的工作原理

這個(gè)概念理解起來非常簡(jiǎn)單。擁有一個(gè)程序庫(kù);然后共享這個(gè)程序庫(kù)。但是,當(dāng)您的程序嘗試調(diào)用 printf() 時(shí),也就是說實(shí)際操作的時(shí)候,具體發(fā)生的事情卻稍微有點(diǎn)復(fù)雜。

這個(gè)過程在靜態(tài)鏈接系統(tǒng)中比在動(dòng)態(tài)鏈接系統(tǒng)中更簡(jiǎn)單。在靜態(tài)鏈接系統(tǒng)中,生成的代碼會(huì)持有對(duì)某個(gè) 函數(shù)的引用。鏈接器使用加載該函數(shù)的真實(shí)地址去替換這個(gè)引用,以便生成的二進(jìn)制代碼 在適當(dāng)?shù)奈恢脮?huì)有正確的地址。然后,在運(yùn)行代碼時(shí),只需要跳轉(zhuǎn)到相應(yīng)的地址即可。對(duì)管理員來說, 這是一項(xiàng)簡(jiǎn)單的任務(wù),因?yàn)樗试S您對(duì)只在程序中的某個(gè)位置上實(shí)際引用的那些對(duì)象進(jìn)行鏈接。

但是大部分共享程序庫(kù)都是動(dòng)態(tài)鏈接的。這具有一些更深層次的意義。其中一方面是,您不能事先預(yù)計(jì) 某個(gè)函數(shù)在調(diào)用時(shí)的確切地址?。ㄒ约办o態(tài)鏈接的共享程序庫(kù)模式,比如 BSD/OS 中的,但是它們 不在本文討論范圍之內(nèi)。)

動(dòng)態(tài)鏈接器可以為每個(gè)被鏈接的函數(shù)做相當(dāng)多的工作,所以大部分鏈接器都是不積極的。只有在函數(shù)被調(diào)用時(shí),它們才實(shí)際做一些工作。C 程序庫(kù)中有一千多個(gè)外部可見的符號(hào),有大約三千多個(gè)本地符號(hào),因此這種 方法可以節(jié)省非常多的時(shí)間。

實(shí)現(xiàn)此奇妙功能的是一個(gè)稱為 過程鏈接表(Procedure Linkage Table)(PLT)的數(shù)據(jù)塊,它是程序中 的一個(gè)表,列出了程序所調(diào)用的每一個(gè)函數(shù)。當(dāng)程序開始運(yùn)行時(shí),PLT 包含每個(gè)函數(shù)的代碼,以便查詢運(yùn)行期鏈接器,從而獲得已加載某個(gè)函數(shù)的地址。然后它會(huì)在表中填入這個(gè)條目并跳轉(zhuǎn)到那個(gè)已加載函數(shù)。當(dāng)每個(gè)函數(shù)被 調(diào)用時(shí),它的 PLT 中的條目就會(huì)被簡(jiǎn)化為一個(gè)到那個(gè)已加載函數(shù)的直接跳轉(zhuǎn)。

不過,重要的是,要注意到還有一個(gè)間接的額外層次 —— 可以通過跳轉(zhuǎn)到某個(gè)表來解析每個(gè)函數(shù)調(diào)用。

兼容性不僅是為了關(guān)聯(lián)

這意味著您最終要鏈接的程序庫(kù)最好與調(diào)用它的代碼相兼容。使用靜態(tài)鏈接的可執(zhí)行文件,可以在某種程度上 保證不會(huì)發(fā)生任何改變。如果使用動(dòng)態(tài)鏈接,就得不到這樣的保證。

當(dāng)出現(xiàn)新版本的程序庫(kù)時(shí)會(huì)怎樣?特別是新版本改變了某個(gè)給定函數(shù)的調(diào)用次序時(shí),又會(huì)怎樣?

版本號(hào)可以解決這個(gè)問題 —— 共享的程序庫(kù)將擁有一個(gè)版本號(hào)。當(dāng)一個(gè)程序鏈接到某個(gè)程序庫(kù)時(shí),程序中 會(huì)存儲(chǔ)一個(gè)它計(jì)劃支持的版本號(hào)。如果更改程序庫(kù),那么版本號(hào)就會(huì)不匹配,程序也就不會(huì)被鏈接到較 新版本的程序庫(kù)。

不過,動(dòng)態(tài)鏈接的可能優(yōu)勢(shì)之一在于修正缺陷。如果可以修正程序庫(kù)中的缺陷,而且不必重新編譯上千個(gè)程序,就 可以利用這一修正功能,這將是非常令人愉快的。有時(shí),需要鏈接到某個(gè)較新的版本。

不幸的是,這會(huì)導(dǎo)致在某些情況下,您希望鏈接到較新的版本,而在另外一些情況下,您寧愿堅(jiān)持使用較老的版本。 不過,有一個(gè)解決方案 —— 使用兩類版本號(hào):

  • 主版本號(hào)表明程序庫(kù)版本之間的潛在不兼容性。
  • 次要版本號(hào)表明只是修正了缺陷。

這樣,在大部分情形下,加載具有相同主版本號(hào)和更高次要版本號(hào)的程序庫(kù)是安全的;而加載主版本號(hào)更高的程序是不安全的行為。

為了讓用戶(和程序員)不必追蹤程序庫(kù)版本號(hào)和更新,系統(tǒng)提供了大量的符號(hào)鏈接。 通常,其模式是:

libexample.so

將是一個(gè)指向

libexample.so.N

的鏈接,其中 N 是在系統(tǒng)中可以找到的最高的  版本號(hào)。

對(duì)受支持的每一個(gè)主版本號(hào)而言,

libexample.so.N

將是一個(gè)指向

libexample.so.N.M

的鏈接,其中 M 是最高的 次要 版本號(hào)。

這樣,如果為鏈接器指定了 -lexample,那么它會(huì)去尋找 libexample.so,這是一個(gè)符號(hào)鏈接,指向某個(gè)指向最新版本的符號(hào)鏈接。 另一方面,當(dāng)加載某個(gè)現(xiàn)有程序時(shí),它將嘗試去加載 libexample.so.N, 其中 N 是它先前鏈接的版本。各得其所!

為了進(jìn)行調(diào)試,首先必須知道如何編譯

為了調(diào)試使用共享程序庫(kù)的問題,對(duì)它們?nèi)绾尉幾g有更多一些了解會(huì)對(duì)您有所幫助。

在傳統(tǒng)的靜態(tài)程序庫(kù)中,生成的代碼通常封裝在一個(gè)程序庫(kù)文件中(其名稱以 .a 結(jié)尾),然后傳遞給鏈接器。在動(dòng)態(tài)程序庫(kù)中,程序庫(kù)文件的名稱通常以 .so 結(jié)尾。 文件結(jié)構(gòu)稍有不同。

常規(guī)的靜態(tài)程序庫(kù)的格式是 ar 工具(一個(gè)非常簡(jiǎn)單的存檔程序,類似于 tar,但是更簡(jiǎn)單)所創(chuàng)建的那種格式。 相反,共享程序庫(kù)通常以更復(fù)雜的文件格式存儲(chǔ)。

在現(xiàn)代 Linux 系統(tǒng)中,這一格式通常是 ELF 二進(jìn)制格式(可執(zhí)行與可鏈接格式(Executable and Linkable Format))。 在 ELF 中,每個(gè)文件的組成包括:一個(gè) ELF 頭,隨后是零或者一些段(segments),以及零或者一些區(qū)段(sections)。  中包含文件的運(yùn)行時(shí)執(zhí)行所需要的信息,而 區(qū)段 中包含用于鏈接和重定位的重要數(shù)據(jù)。 整個(gè)文件中的每個(gè)字節(jié)每次只能由一個(gè)區(qū)段使用,不過可以存在不被任何區(qū)段所包含的孤立字節(jié)。 通常,在 UNIX 可執(zhí)行文件中,一個(gè)或多個(gè)區(qū)段會(huì)封裝在一個(gè)段內(nèi)。

ELF 格式中包含用于應(yīng)用程序和程序庫(kù)的規(guī)范。但程序庫(kù)格式要復(fù)雜得多,不僅僅是對(duì)象模塊的簡(jiǎn)單存檔。

鏈接器將所有對(duì)符號(hào)的引用進(jìn)行分類,標(biāo)識(shí)出它們是在哪個(gè)程序庫(kù)中找到的。將靜態(tài)程序庫(kù)的符號(hào)添加到最終的 可執(zhí)行文件中;然后將共享程序庫(kù)的符號(hào)放入 PLT 中,最后創(chuàng)建對(duì) FLT 的引用。在完成這些任務(wù)之后,生成的可執(zhí)行文件 會(huì)擁有一個(gè)列表,該列表列出了計(jì)劃從運(yùn)行期將加載的程序庫(kù)中找出的那些符號(hào)。

在運(yùn)行期間,應(yīng)用程序?qū)⒓虞d動(dòng)態(tài)鏈接器。實(shí)際上,動(dòng)態(tài)鏈接器本身使用與共享程序庫(kù)相同種類的版本號(hào)。 例如,在 SUSE Linux 9.1 中, /lib/ld-linux.so.2 文件是一個(gè)指向 /lib/ld-linux.so.2.3.3 的符號(hào)鏈接。另一方面,尋找 /lib/ld-linux.so.1 的程序不會(huì)嘗試使用新的版本。

然后動(dòng)態(tài)鏈接器開始進(jìn)行所有有趣的工作。它會(huì)查明某個(gè)程序先前鏈接到了哪些程序庫(kù)(以及哪個(gè)版本), 然后加載它們。加載程序庫(kù)的步驟包括:

  • 找到程序庫(kù)(它可能在系統(tǒng)中若干個(gè)目錄中的任意一個(gè)目錄中)。
  • 將程序庫(kù)映射到程序的地址空間。
  • 分配程序庫(kù)可能需要的由零填充的內(nèi)存塊。
  • 添加程序庫(kù)的符號(hào)表。

調(diào)試這一過程可能會(huì)比較困難。您可能會(huì)遇到多種問題。例如,如果動(dòng)態(tài)鏈接器不能找到某個(gè)給定的 程序庫(kù),那么它將停止加載程序。如果它找到了所有需要的程序庫(kù),但卻無法找到某個(gè)符號(hào),那么它也可能會(huì)因此而停止加載操作 (但是可能直到真正嘗試去引用那個(gè)符號(hào)時(shí)才會(huì)發(fā)生這種情形) —— 這是一種很少見的情況,因?yàn)橥ǔH绻?不存在某個(gè)符號(hào),那么在初始化鏈接的時(shí)候就會(huì)被警告。

修改動(dòng)態(tài)鏈接器的搜索路徑

當(dāng)鏈接某個(gè)程序時(shí),在運(yùn)行期您可以指定另外的搜索路徑。在 gcc 中,其 語法是 -Wl,-R/path。如果程序已經(jīng)被鏈接,那么您也可以設(shè)置環(huán)境變量 LD_LIBRARY_PATH 來改變這一行為。通常只是在應(yīng)用程序需要搜索的路徑 不是系統(tǒng)級(jí)默認(rèn)路徑的一部分時(shí)才需要這樣做,對(duì)大部分 Linux 系統(tǒng)來說,這種情況很少見。 理論上,Mozilla 用戶可以發(fā)布某個(gè)使用這個(gè)路徑設(shè)置所編譯的二進(jìn)制程序,但是他們 更傾向于發(fā)布包裝器(wrapper)腳本,在啟動(dòng)可執(zhí)行程序之前正確地設(shè)置程序庫(kù)路徑。

設(shè)置程序庫(kù)路徑可以為兩個(gè)應(yīng)用程序需要同一程序庫(kù)的不兼容版本的這種罕見情況提供一個(gè)迂回解決方案。可以使用包裝器腳本使某一應(yīng)用程序在使用特殊版本程序庫(kù)的目錄中進(jìn)行搜索。這稱不上是一個(gè) 完美的解決方案,但是在某些情況下,這是您能采用的最佳方法。

如果出于不得已的原因需要為很多程序添加某個(gè)路徑,那么也可以修改系統(tǒng)的默認(rèn)搜索路徑。通過 /etc/ld.so.conf 控制動(dòng)態(tài)鏈接器,該文件包含默認(rèn)搜索路徑的列表。 對(duì) LD_LIBRARY_PATH 中指定的任何路徑的搜索都要先于 ld.so.conf 中列出的路徑,所以用戶可以覆蓋這些設(shè)置。

大部分用戶沒有理由修改系統(tǒng)默認(rèn)程序庫(kù)搜索路徑;通常環(huán)境變量更適用于修改搜索路徑,比如 連接某個(gè)工具包中的程序庫(kù),或者使用某個(gè)程序庫(kù)的較新版本的測(cè)試程序。

使用 ldd

ldd 是調(diào)試共享程序庫(kù)問題的一個(gè)實(shí)用工具。其名稱來自 list dynamic dependencies。這個(gè)程序會(huì)查看某個(gè)給定的可執(zhí)行程序或者共享程序庫(kù),并 指出它需要加載哪些共享程序庫(kù)以及要使用哪些版本。輸出類似如下:


清單 1. /bin/sh 的依賴
				
        
$ ldd /bin/sh
        linux-gate.so.1 =>  (0xffffe000)
        libreadline.so.4 => /lib/libreadline.so.4 (0x40036000)
        libhistory.so.4 => /lib/libhistory.so.4 (0x40062000)
        libncurses.so.5 => /lib/libncurses.so.5 (0x40069000)
        libdl.so.2 => /lib/libdl.so.2 (0x400af000)
        libc.so.6 => /lib/tls/libc.so.6 (0x400b2000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
      

看到一個(gè)“簡(jiǎn)單的”的程序使用了這么多個(gè)程序庫(kù),可能會(huì)有些令人驚訝?;蛟S是 libhistory 需要 libncurses。 為了查明真相,我們只需要運(yùn)行另一個(gè) ldd 命令:


清單 2. libhistory 的依賴
				
        
$ ldd /lib/libhistory.so.4
        linux-gate.so.1 =>  (0xffffe000)
        libncurses.so.5 => /lib/libncurses.so.5 (0x40026000)
        libc.so.6 => /lib/tls/libc.so.6 (0x4006b000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000)
      

在某些情況下,可能需要為應(yīng)用程序指定另外的程序庫(kù)路徑。例如,對(duì) Mozilla 二進(jìn)制程序嘗試 運(yùn)行 ldd 所得到輸出的前幾行如下所示:


清單 3. 運(yùn)行 dll 查找不在搜索路徑中的 程序庫(kù)的結(jié)果
				
        
$ ldd /opt/mozilla/lib/mozilla-bin
		linux-gate.so.1 =>  (0xffffe000)
		libmozjs.so => not found
		libplds4.so => not found
		libplc4.so => not found
		libnspr4.so => not found
		libpthread.so.0 => /lib/tls/libpthread.so.0 (0x40037000)
      

為什么找不到這些程序庫(kù)?因?yàn)樗鼈儾辉诔R姷某绦驇?kù)搜索路徑中。實(shí)際上,它們?cè)?nbsp;/opt/mozilla/lib 中,所以,解決方案之一是將這個(gè)目錄添加到 LD_LIBRARY_PATH 中。

另一個(gè)選項(xiàng)是將路徑設(shè)置為 .,并在這個(gè)目錄下運(yùn)行 ldd, 盡管這樣做更危險(xiǎn) —— 將當(dāng)前目錄添加到程序庫(kù)路徑中與將它添加到可執(zhí)行程序路徑中一樣有著潛在的危險(xiǎn)。

在這種情況下,將這些程序庫(kù)所在的目錄添加到系統(tǒng)級(jí)搜索路徑中顯然不是一個(gè)好辦法。只有 Mozilla 需要這些程序庫(kù)。

鏈接 Mozilla

說起 Mozilla,如果您覺得自己從未見過超過幾行的程序庫(kù),那么在某種程度上,Mozilla 是一個(gè)更為典型的 大型應(yīng)用程序。現(xiàn)在您可以明白為什么 Mozilla 的啟動(dòng)需要那么長(zhǎng)時(shí)間了吧!


清單 4. mozilla-bin 的依賴性
				
        
linux-gate.so.1 =>  (0xffffe000)
libmozjs.so => ./libmozjs.so (0x40018000)
libplds4.so => ./libplds4.so (0x40099000)
libplc4.so => ./libplc4.so (0x4009d000)
libnspr4.so => ./libnspr4.so (0x400a2000)
libpthread.so.0 => /lib/tls/libpthread.so.0 (0x400f5000)
libdl.so.2 => /lib/libdl.so.2 (0x40105000)
libgtk-x11-2.0.so.0 => /opt/gnome/lib/libgtk-x11-2.0.so.0 (0x40108000)
libgdk-x11-2.0.so.0 => /opt/gnome/lib/libgdk-x11-2.0.so.0 (0x40358000)
libatk-1.0.so.0 => /opt/gnome/lib/libatk-1.0.so.0 (0x403c5000)
libgdk_pixbuf-2.0.so.0 => /opt/gnome/lib/libgdk_pixbuf-2.0.so.0 (0x403df000)
libpangoxft-1.0.so.0 => /opt/gnome/lib/libpangoxft-1.0.so.0 (0x403f1000)
libpangox-1.0.so.0 => /opt/gnome/lib/libpangox-1.0.so.0 (0x40412000)
libpango-1.0.so.0 => /opt/gnome/lib/libpango-1.0.so.0 (0x4041f000)
libgobject-2.0.so.0 => /opt/gnome/lib/libgobject-2.0.so.0 (0x40451000)
libgmodule-2.0.so.0 => /opt/gnome/lib/libgmodule-2.0.so.0 (0x40487000)
libglib-2.0.so.0 => /opt/gnome/lib/libglib-2.0.so.0 (0x4048b000)
libm.so.6 => /lib/tls/libm.so.6 (0x404f7000)
libstdc++.so.5 => /usr/lib/libstdc++.so.5 (0x40519000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x405d5000)
libc.so.6 => /lib/tls/libc.so.6 (0x405dd000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
libX11.so.6 => /usr/X11R6/lib/libX11.so.6 (0x406f3000)
libXrandr.so.2 => /usr/X11R6/lib/libXrandr.so.2 (0x407ef000)
libXi.so.6 => /usr/X11R6/lib/libXi.so.6 (0x407f3000)
libXext.so.6 => /usr/X11R6/lib/libXext.so.6 (0x407fb000)
libXft.so.2 => /usr/X11R6/lib/libXft.so.2 (0x4080a000)
libXrender.so.1 => /usr/X11R6/lib/libXrender.so.1 (0x4081e000)
libfontconfig.so.1 => /usr/lib/libfontconfig.so.1 (0x40826000)
libfreetype.so.6 => /usr/lib/libfreetype.so.6 (0x40850000)
libexpat.so.0 => /usr/lib/libexpat.so.0 (0x408b9000)
      

深入了解共享程序庫(kù)

有興趣深入了解 Linux 中的動(dòng)態(tài)鏈接的用戶有很多選擇。GNU 編譯器和鏈接器工具鏈(linker tool chain)文檔都非常好, 雖然其內(nèi)容是以 info 格式存儲(chǔ)的,而且也沒有在標(biāo)準(zhǔn)手冊(cè)頁中提及。

ld.so 的手冊(cè)頁包含有一個(gè)非常詳盡的列表,列出了改變動(dòng)態(tài)鏈接器行為的變量, 以及對(duì)過去曾經(jīng)使用的不同版本的動(dòng)態(tài)鏈接器的說明。

大部分 Linux 文檔都假定所有共享程序庫(kù)都是動(dòng)態(tài)鏈接的,因?yàn)樵?Linux 系統(tǒng)上,它們通常是這樣的。 實(shí)現(xiàn)靜態(tài)鏈接的共享程序庫(kù)需要做的工作非常多,而且大部分用戶不會(huì)因此獲得任何好處,盡管支持這個(gè)特性的系統(tǒng)的性能會(huì)有顯著改變。

如果您正在使用現(xiàn)成的預(yù)先包裝好的系統(tǒng),那么您可能不會(huì)遇到太多的共享程序庫(kù)版本 —— 系統(tǒng)可能只附帶它要鏈接的那些共享程序庫(kù)版本。另一方面,如果您做過很多次更新和源代碼構(gòu)建,那么您可能最終得到 多個(gè)版本的共享程序庫(kù),因?yàn)槔习姹疽廊粫?huì)被保留,“以防萬一”。

像平時(shí)一樣,如果想了解更多,那么就去親自實(shí)踐吧。記住,在某個(gè)系統(tǒng)上,幾乎所有程序都會(huì)引用一些相同的共享程序庫(kù),所以,如果破壞了系統(tǒng)的某個(gè)核心共享程序庫(kù),那么您就得去求助系統(tǒng)恢復(fù)工具了。


參考資料

關(guān)于作者

Peter Seebach

Peter Seebach 從很早以前就開始使用共享程序庫(kù),并一直堅(jiān)信這是最基本的方法,盡管 Recording Industry Association of America (RIAA)不同意他的觀點(diǎn)。您可以通過 developerworks@seebs.plethora.net 與 Peter 聯(lián)系。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多