Linux 下靜態(tài)鏈接和動(dòng)態(tài)鏈接的原理及應(yīng)用
我們知道一個(gè).c 文件經(jīng)過(guò)編譯、鏈接最終可以形成一個(gè)可執(zhí)行文件。
鏈接原理
當(dāng)我們的程序包含多個(gè)文件時(shí),那么這些文件是怎么形成一個(gè)目標(biāo)文件的呢?
這就要涉及到鏈接器。
鏈接器的作用就是將多個(gè)目標(biāo)文件鏈接成一個(gè)完整、可加載、可執(zhí)行的目標(biāo)文件。其輸入是一組可以重定位的目標(biāo)文件。
鏈接的主要作用有2個(gè):
符號(hào)解析:將目標(biāo)文件內(nèi)的引用符號(hào)和該符號(hào)的定義聯(lián)系起來(lái)。
將符號(hào)定義與存儲(chǔ)器的位置聯(lián)系起來(lái),修改對(duì)這些符號(hào)的引用。
目標(biāo)文件
典型的目標(biāo)文件有3種形式
- 可重定位目標(biāo)文件:這種目標(biāo)文件后綴通常為.o,這種文件包含已經(jīng)轉(zhuǎn)換成機(jī)器指令的二進(jìn)制代碼和數(shù)據(jù),但是這種文件還不能直接執(zhí)行,因?yàn)檫@些指令和數(shù)據(jù)中往往還引用其他模塊(目標(biāo)文件)中的符號(hào),這些其他模塊的符號(hào)對(duì)于本模塊來(lái)講還都是未知的,因此這些符號(hào)的解析需要鏈接器對(duì)這些模塊進(jìn)行連接,這種操作也稱為“重定位”。
- 可執(zhí)行目標(biāo)文件:這種文件同樣包含二進(jìn)制代碼和數(shù)據(jù),區(qū)別就是這些文件已經(jīng)經(jīng)過(guò)鏈接,因此這些文件是可以直接執(zhí)行的。
- 共享目標(biāo)文件:這是一種特殊類型的可重定位的目標(biāo)文件,可以在需要它的程序運(yùn)行過(guò)程或者加載時(shí),動(dòng)態(tài)的加載到內(nèi)存中運(yùn)行。這種文件的后綴通常為“.so”, 共享目標(biāo)文件又稱為“動(dòng)態(tài)庫(kù)”文件或者“共享庫(kù)”文件。
符號(hào)解析
符號(hào)解析是鏈接的主要任務(wù)之一。只有在正確的解析了符號(hào)之后才能更改引用符號(hào)的位置,從而完成重定位,生成一個(gè)可以被機(jī)器直接加載執(zhí)行的的可執(zhí)行目標(biāo)文件。每個(gè)可重定位目標(biāo)文件都有一個(gè)符號(hào)表。在這個(gè)符號(hào)表中存儲(chǔ)符號(hào)。這些符號(hào)分為3類:
本模塊中引用其他模塊所定義的全局符號(hào)。
本模塊中定義的全局符號(hào)。
本模塊中定義和引用的局部符號(hào)。
重定位
在符號(hào)解析結(jié)束后,每個(gè)符號(hào)的定義位置及大小都是已知的了。重定位操作只需要將這些符號(hào)鏈接起來(lái)。在該步驟中,鏈接器需要將所有參與鏈接的目標(biāo)文件合并,并且為每一個(gè)符號(hào)分配存儲(chǔ)內(nèi)容的運(yùn)行時(shí)地址。
重定位分為2個(gè)步驟進(jìn)行:
- 重定位段:該步將所有目標(biāo)文件中同類型的段合并,生成一個(gè)大段。比如,將所有參與鏈接的目標(biāo)文件的數(shù)據(jù)段合并,生成一個(gè)大的數(shù)據(jù)段。合并之后,程序中的指令和變量就擁有一個(gè)統(tǒng)一并且唯一的運(yùn)行時(shí)地址了。
- 重定位符號(hào)引用:由于目標(biāo)文件中相同的段已經(jīng)合并,因此程序中對(duì)符號(hào)的引用位置就都作廢了。這時(shí)鏈接器需要修改這些引用符號(hào)的地址,使其指向正確的運(yùn)行時(shí)地址。
程序庫(kù)
所謂“程序庫(kù)”就是包含了一些通用函數(shù)的數(shù)據(jù)和二進(jìn)制可執(zhí)行機(jī)器碼的文件。這些文件是目標(biāo)文件的一種,其不能單獨(dú)執(zhí)行。但是若與其他的可執(zhí)行程序結(jié)合起來(lái)就可以執(zhí)行了。
從鏈接方式上區(qū)別,程序庫(kù)可分為靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)(共享庫(kù))兩種:
- 靜態(tài)庫(kù):是在可執(zhí)行程序運(yùn)行前就已經(jīng)加入到執(zhí)行代碼中,成為執(zhí)行程序的一部分來(lái)執(zhí)行的。
- 動(dòng)態(tài)庫(kù):是在執(zhí)行程序啟動(dòng)時(shí)加載到執(zhí)行程序中,可以被多個(gè)執(zhí)行程序共享使用。
靜態(tài)庫(kù)
靜態(tài)庫(kù)是一些目標(biāo)代碼的集合。Linux下靜態(tài)目標(biāo)文件一般以.a作為目標(biāo)文件的后綴。在Linux環(huán)境下使用ar命令來(lái)創(chuàng)建一個(gè)靜態(tài)庫(kù)。靜態(tài)庫(kù)的優(yōu)點(diǎn)就是在生成時(shí)已經(jīng)編譯成可重定位的目標(biāo)文件,節(jié)省了編譯時(shí)間,并且在編譯時(shí)把代碼復(fù)制到可執(zhí)行代碼段中,這樣可執(zhí)行程序就可以單獨(dú)直接運(yùn)行,但是缺點(diǎn)也是顯而易見的,就是可執(zhí)行文件可能會(huì)變得很臃腫。
靜態(tài)庫(kù)的創(chuàng)建
本文以四則運(yùn)算來(lái)創(chuàng)建一個(gè)靜態(tài)庫(kù),該靜態(tài)庫(kù)中包含四個(gè)函數(shù):加、減、乘、除。
生成一個(gè)可重定位的目標(biāo)文件。
在Linux下使用 ar 命令創(chuàng)建一個(gè)靜態(tài)庫(kù),或者將目標(biāo)文件加入到一個(gè)已經(jīng)存在的靜態(tài)庫(kù)中。其使用方法如下:
ar rcs 靜態(tài)庫(kù)名 目標(biāo)文件1 目標(biāo)文件2 ... 目標(biāo)文件n
該命令表示將目標(biāo)文件1~n加入到指定的靜態(tài)庫(kù)中。若該靜態(tài)庫(kù)不存在,則創(chuàng)建靜態(tài)庫(kù)文件,并將庫(kù)文件的擴(kuò)展名命名為.a, 其中rcs這三個(gè)參數(shù)分別表示:把列表中的目標(biāo)文件加入到靜態(tài)庫(kù)中(r);若指定的靜態(tài)庫(kù)不存在,則創(chuàng)建該庫(kù)文件(c);更新靜態(tài)庫(kù)文件的索引,使之包含新加入的目標(biāo)文件的內(nèi)容(s)。
使用生成的 static_lib.o 目標(biāo)文件創(chuàng)建一個(gè)靜態(tài)庫(kù) static_lib.a
靜態(tài)庫(kù)的使用
創(chuàng)建的靜態(tài)庫(kù)需要鏈接到應(yīng)用程序中才能使用,為了方便引用,我們創(chuàng)建一個(gè)頭文件,使用時(shí)把該頭文件包含到應(yīng)用程序中。
編寫應(yīng)用程序
編譯
動(dòng)態(tài)庫(kù)
動(dòng)態(tài)庫(kù)又稱為共享庫(kù)或者動(dòng)態(tài)鏈接庫(kù)。在 Linux 環(huán)境下為 so 文件。動(dòng)態(tài)庫(kù)是在程序運(yùn)行時(shí)加載的。當(dāng)一個(gè)應(yīng)用程序裝載了一個(gè)動(dòng)態(tài)庫(kù)后,其他應(yīng)用程序仍可以裝載同一個(gè)動(dòng)態(tài)庫(kù)。這個(gè)被多個(gè)進(jìn)程同時(shí)使用的動(dòng)態(tài)庫(kù)在內(nèi)存中只有一個(gè)副本,因此動(dòng)態(tài)庫(kù)易于程序模塊的更新,更新庫(kù)并不影響應(yīng)用程序使用舊的、非向后兼容的版本。
創(chuàng)建動(dòng)態(tài)庫(kù)
我們依然使用以四則運(yùn)算來(lái)創(chuàng)建一個(gè)動(dòng)態(tài)庫(kù),該動(dòng)態(tài)庫(kù)中包含四個(gè)函數(shù):加、減、乘、除。
Linux 下使用 gcc 創(chuàng)建一個(gè)動(dòng)態(tài)庫(kù)。由于動(dòng)態(tài)庫(kù)可以被多個(gè)進(jìn)程共享加載,所以需要生成位置無(wú)關(guān)的目標(biāo)文件。因此需要使用 gcc 編譯器的 -fPIC 選項(xiàng),該選項(xiàng)用于生成位置無(wú)關(guān)的代碼。除了使用 -fPIC 選項(xiàng),還需要使用 -shared 選項(xiàng),該選項(xiàng)將位置無(wú)關(guān)的代碼制作為動(dòng)態(tài)庫(kù)。
創(chuàng)建動(dòng)態(tài)庫(kù)的方法如下:
動(dòng)態(tài)庫(kù)的使用
為了使應(yīng)用程序可以正確的引用該庫(kù)中的全局符號(hào),需要制作一個(gè)包含該動(dòng)態(tài)庫(kù)文件中的全局符號(hào)聲明的頭文件。
編寫一個(gè)應(yīng)用程序使用動(dòng)態(tài)庫(kù)
運(yùn)行