自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

動態(tài)鏈接庫的實(shí)現(xiàn)原理是什么?

開發(fā) 前端
了解了這些就可以開始講動態(tài)庫的實(shí)現(xiàn)原理了,動態(tài)庫又叫做共享庫,我們的問題是,動態(tài)庫是怎么實(shí)現(xiàn)可以被程序之間共享的呢?

大家好,我是小風(fēng)哥,今天簡單聊聊動態(tài)鏈接庫的實(shí)現(xiàn)原理。

假設(shè)有這樣兩段代碼,第一段代碼定義了一個(gè)全量變量a以及函數(shù)foo,函數(shù)foo中引用了下一段代碼中定義的全局變量b。

圖片圖片

第二段代碼定義了全局變量b以及main函數(shù),同時(shí)在main函數(shù)中調(diào)用了第一個(gè)模塊中定義的函數(shù)foo。

接下來編譯器出場,編譯器會把這個(gè)兩個(gè)源文件編譯成對應(yīng)的目標(biāo)文件。

目標(biāo)文件中主要有兩部分,代碼段和數(shù)據(jù)段,這兩部分里面分別包含什么內(nèi)容呢?

我們定義的全局變量會被放到數(shù)據(jù)段,代碼被編譯生成的二進(jìn)制指令會被放到代碼段,第二個(gè)目標(biāo)文件也一樣。

圖片圖片

注意看第一段代碼,這里引用了一個(gè)其它模塊定義的全局變量b,這一信息記錄在第一個(gè)目標(biāo)文件,第二段代碼引用了其它模塊定義的函數(shù)foo,這一信息記錄在第二個(gè)目標(biāo)文件。

注意看第一段代碼,這里定一個(gè)全局變量a和函數(shù)foo,我們記錄下來,第二段代碼定義了全局變量b和函數(shù)main,同樣記錄下來。

圖片圖片

接著我們開始一個(gè)叫做連連看的游戲。

第一個(gè)模塊引用了變量b,變量b的定義可以在第二個(gè)模塊找到。

第二個(gè)模塊引用了函數(shù)foo,foo的定義可以在第一個(gè)模塊找到。

這個(gè)過程叫做符號解析。

圖片圖片

這里看到的引用以及定義的符號保存在所謂的符號表中。

而如果第二個(gè)模塊引用了一個(gè)叫做bar的變量,鏈接器翻遍所有其它模塊都沒找到bar這個(gè)符號的定義,而只找到了一個(gè)叫做foo的定義,這時(shí)鏈接器就會報(bào)一個(gè)叫做符合未定義的錯(cuò)誤,這個(gè)錯(cuò)誤寫c/c++的程序員一定不陌生。

圖片圖片

接下來鏈接器會把數(shù)據(jù)段合并到一起,代碼段合并到一起并確定符號的內(nèi)存地址,這個(gè)過程叫做重定位。

了解了這些就可以開始講動態(tài)庫的實(shí)現(xiàn)原理了,動態(tài)庫又叫做共享庫,我們的問題是,動態(tài)庫是怎么實(shí)現(xiàn)可以被程序之間共享的呢?

假設(shè)現(xiàn)在有兩個(gè)運(yùn)行的程序和一個(gè)動態(tài)庫liba. so,動態(tài)庫中定了一個(gè)全局變量a,第一個(gè)程序把變量a修改為了10。

圖片圖片

然后第二個(gè)程序開始運(yùn)行,第二個(gè)程序也使用該動態(tài)庫,然后把全局變量a修改為了20。

圖片圖片

這是第一個(gè)程序運(yùn)行一段時(shí)間后決定打印變量a,這時(shí)你會驚訝的發(fā)現(xiàn)變量a從10變成了20,但是為什么。

原因就是這兩個(gè)程序共享了同一個(gè)數(shù)據(jù)段,所以一個(gè)程序?qū)?shù)據(jù)的修改對另一人程序是可見的,因此動態(tài)庫中的數(shù)據(jù)段不能共享,每個(gè)程序需要有自己的數(shù)據(jù)段。

現(xiàn)在數(shù)據(jù)的問題解決了,我們來看函數(shù)。

假設(shè)動態(tài)庫liba.so需要引用外部定義的foo函數(shù),由于程序1和程序2都使用了該動態(tài)庫,因此必須定義出foo函數(shù)。

我們知道函數(shù)調(diào)用最終會被編譯器翻譯成call機(jī)器指令后跟函數(shù)地址。

圖片圖片

接下來我們需要解析出foo函數(shù)的地址到底是什么,這就是剛才我們提到的重定位,只不過動態(tài)庫將這一過程推遲到了運(yùn)行時(shí)。

由于程序1的foo函數(shù)位于內(nèi)存地址0x123這個(gè)位置,因此鏈接器將call指令后的地址修正為0x123。

這時(shí)CPU執(zhí)行這條call指令就能正確的跳轉(zhuǎn)到第一個(gè)程序的foo函數(shù)。

圖片圖片

而第二個(gè)程序的foo函數(shù)為內(nèi)存地址0x456這個(gè)位置,接下來第二個(gè)程序開始運(yùn)行,CPU開始執(zhí)行foo函數(shù),由于第二個(gè)程序的foo函數(shù)在0x456,因此我們希望CPU能跳轉(zhuǎn)到這里,但由于動態(tài)庫中call指令后跟的是0x123這個(gè)內(nèi)存地址,因此CPU執(zhí)行foo函數(shù)時(shí)依然會跳轉(zhuǎn)到第一個(gè)程序的foo函數(shù)。

圖片圖片

這時(shí)系統(tǒng)就出現(xiàn)了錯(cuò)誤。

問題出在了哪里呢?

主要是call這條機(jī)器指令,這條指令后跟了一個(gè)絕對的內(nèi)存地址,而不要忘了,這條指令或者說動態(tài)庫是要被各個(gè)程序共享的,顯然我們不能直接使用絕對地址。

該怎么辦呢?

計(jì)算機(jī)中所有問題都可以通過增加一個(gè)中間層來解決。

圖片圖片

這樣我們就摒棄了直接調(diào)用,而采用間接調(diào)用。

而我們這里對函數(shù)的討論對于全局變量的應(yīng)用也是一樣的道理,全局變量的使用也存在同樣的問題,只不過是從函數(shù)調(diào)用變成了內(nèi)存讀寫,解決問題的方法一樣,我們從直接應(yīng)用改為間接引用。

接下來我們依然以函數(shù)調(diào)用為例來講解。

那么這個(gè)中間層到底是什么呢?

答案就是got。

還記得剛才提到的每個(gè)程序都有自己的數(shù)據(jù)區(qū)嗎,這個(gè)got段就屬于數(shù)據(jù)區(qū)的一部分。

圖片圖片

got中有什么呢?got中記錄了引用的全局變量或者函數(shù)的地址,在程序運(yùn)行時(shí)鏈接器會找到foo的內(nèi)存地址,然后填到got表中,這樣通過查got表我們就能知道函數(shù)foo的內(nèi)存地址了。

接下來的問題就是當(dāng)CPU調(diào)用foo函數(shù)時(shí)怎么才能知道got表在哪里呢?

注意剛提到每個(gè)程序都有自己的數(shù)據(jù)區(qū),實(shí)際上對于動態(tài)庫來說也有自己的代碼區(qū)。

我們現(xiàn)在只需要知道每個(gè)程序運(yùn)行在自己的地址空間中,這些地址空間最終會被映射到真正的物理內(nèi)存,動態(tài)庫中的數(shù)據(jù)區(qū)會被映射到不同的內(nèi)存區(qū)域,但代碼段會被映射到同一段物理內(nèi)存中,從而實(shí)現(xiàn)共享的目的。

圖片圖片

接下來我們重點(diǎn)看進(jìn)程地址空間中的動態(tài)庫布局。

注意看,動態(tài)庫的數(shù)據(jù)區(qū)和代碼區(qū)總是相鄰的,也就是代碼區(qū)和got段的相對位置總是不變的,而不管動態(tài)庫被放到了哪個(gè)位置。

多個(gè)程序也一樣,也就是代碼區(qū)和數(shù)據(jù)區(qū)的相對位置總是固定的,這個(gè)相對位置在編譯時(shí)編譯器就能確定。

圖片圖片

現(xiàn)在foo會被編譯成call指令,而程序在加載時(shí)鏈接器會向got段中寫入foo的內(nèi)存地址,顯然兩個(gè)程序的foo地址是不一樣的。

接下來CPU開始執(zhí)行第一個(gè)程序的call指令,此時(shí)CPU會做一個(gè)相對跳轉(zhuǎn),這個(gè)跳轉(zhuǎn)距離是編譯器確定的,CPU會跳轉(zhuǎn)到got表,然后查找foo的地址發(fā)現(xiàn)是0x123,然后開始執(zhí)行0x123這個(gè)位置的函數(shù)。

圖片圖片

而如果CPU執(zhí)行第二個(gè)程序中的foo函數(shù),那么CPU同樣會進(jìn)行相對跳轉(zhuǎn),這不過這次跳轉(zhuǎn)到的是第二個(gè)程序的got表,然后發(fā)現(xiàn)foo的地址是0x456,然后開始執(zhí)行第二個(gè)程序中的foo函數(shù)。

圖片圖片

這樣我們就實(shí)現(xiàn)了執(zhí)行同一個(gè)指令但卻會跳轉(zhuǎn)到不同地址的目的,從而在不改動動態(tài)庫代碼的前提先實(shí)現(xiàn)共享。

而如果一個(gè)動態(tài)庫中引用了很多外部函數(shù)會怎么樣呢?

這樣程序在啟動時(shí)鏈接器不得不對所有函數(shù)進(jìn)行重定位,因此會拖慢程序啟動速度。

而我們知道一個(gè)程序中不是所有的函數(shù)都會被調(diào)用到,經(jīng)常調(diào)用的都是少數(shù)幾個(gè)函數(shù),為了利用這一點(diǎn)編譯鏈接系統(tǒng)使用procedure linkage table, plt來推遲重定位這個(gè)過程,也就是程序在啟動時(shí)不進(jìn)行函數(shù)重定位,而是推遲到真正調(diào)用函數(shù)時(shí),沒用調(diào)用過的函數(shù)根本就不進(jìn)行重定位,從而加快程序啟動速度。

從這個(gè)一過程我們可以看到動態(tài)庫的這種間接調(diào)用實(shí)際上會對程序性能有一定影響,但相對于動態(tài)庫帶來的好處與便捷,這點(diǎn)影響可以忽略不計(jì)。

這樣,不管動態(tài)庫被加載到內(nèi)存的哪個(gè)位置都能正確被各個(gè)程序共享。

動態(tài)庫的這個(gè)特性被稱之為位置無關(guān)代碼,簡稱position-independent code, pic,這就是為什么你在編譯生成動態(tài)庫時(shí)要加上pic編譯選項(xiàng)的原因。

圖片圖片

希望這篇對大家理解動態(tài)庫有幫助。

責(zé)任編輯:武曉燕 來源: 碼農(nóng)的荒島求生
相關(guān)推薦

2009-08-28 16:19:30

C#實(shí)現(xiàn)修改動態(tài)鏈接庫

2022-10-24 00:03:21

動態(tài)鏈接庫DLL

2012-05-08 14:48:23

LinuxUnix動態(tài)鏈接庫

2012-05-04 08:24:14

LinuxUnix

2011-06-21 18:02:14

Qt 動態(tài) 鏈接庫

2022-07-12 13:23:59

靜態(tài)鏈接庫可執(zhí)行文件C 目標(biāo)文件

2009-07-07 20:57:20

LinuxUnix動態(tài)鏈接庫

2022-06-09 09:54:45

編譯軟件開發(fā)

2022-05-03 23:44:21

Python動態(tài)鏈接庫Ctypes

2024-03-01 20:59:11

C#DLL開發(fā)

2023-11-29 08:31:20

PythonRust

2011-05-18 17:15:45

2009-08-05 16:29:18

C#調(diào)用C++動態(tài)鏈接

2023-05-09 08:24:11

JNA鏈接庫代碼

2024-04-26 00:31:24

Java動態(tài)鏈接

2009-10-29 16:36:49

VB.NET .DLL

2022-08-09 07:57:25

Linux操作系統(tǒng)Windows

2011-08-02 14:15:05

XCode 靜態(tài) 鏈接庫

2021-09-01 05:11:13

C# 動態(tài)鏈接庫

2012-01-06 10:25:50

JavaDLLC++
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號