摘要:不同的進(jìn)程,調(diào)用同樣的,各自里面指向最終加載的動(dòng)態(tài)鏈接庫(kù)里面的虛擬內(nèi)存地址是不同的。實(shí)際上,在進(jìn)行程序開發(fā),一直會(huì)用到各種各樣的動(dòng)態(tài)鏈接庫(kù)。通過動(dòng)態(tài)鏈接這個(gè)方式,可以說徹底解決了這個(gè)問題。參考深入淺出計(jì)算機(jī)組成原理
把對(duì)應(yīng)的不同文件內(nèi)的代碼段,合并到一起,成為最后的可執(zhí)行文件
鏈接的方式,讓我們?cè)趯懘a的時(shí)候做到了“復(fù)用”。
同樣的功能代碼只要寫一次,然后提供給很多不同的程序進(jìn)行鏈接就行了。
“鏈接”其實(shí)有點(diǎn)兒像我們?nèi)粘I钪械?strong>標(biāo)準(zhǔn)化、模塊化生產(chǎn)。
有一個(gè)可以生產(chǎn)標(biāo)準(zhǔn)螺帽的生產(chǎn)線,就可生產(chǎn)很多不同的螺帽。
只要需要螺帽,都可以通過鏈接的方式,去復(fù)制一個(gè)出來,放到需要的地方
但是,如果我們有很多個(gè)程序都要通過裝載器裝載到內(nèi)存里面,那里面鏈接好的同樣的功能代碼,也都需要再裝載一遍,再占一遍內(nèi)存空間。
這就好比,假設(shè)每個(gè)人都有騎自行車的需要,那我們給每個(gè)人都生產(chǎn)一輛自行車帶在身邊,固然大家都有自行車用了,但是馬路上肯定會(huì)特別擁擠。
1 鏈接可以分動(dòng)、靜,共享運(yùn)行省內(nèi)存我們上一節(jié)解決程序裝載到內(nèi)存的時(shí)候,講了很多方法。說起來,最根本的問題其實(shí)就是內(nèi)存空間不夠用。
如果能夠讓同樣功能的代碼,在不同的程序里面,不需要各占一份內(nèi)存空間,那該有多好??!
就好比,現(xiàn)在馬路上的共享單車,我們并不需要給每個(gè)人都造一輛自行車,只要馬路上有這些單車,誰(shuí)需要的時(shí)候,直接通過手機(jī)掃碼,都可以解鎖騎行。
這個(gè)思路就引入一種新的鏈接方法,叫作動(dòng)態(tài)鏈接(Dynamic Link)
相應(yīng)的,我們之前說的合并代碼段的方法,就是靜態(tài)鏈接(Static Link)
在動(dòng)態(tài)鏈接的過程中,我們想要“鏈接”的,不是存儲(chǔ)在硬盤上的目標(biāo)文件代碼,而是加載到內(nèi)存中的共享庫(kù)(Shared Libraries)
這個(gè)加載到內(nèi)存中的共享庫(kù)會(huì)被很多個(gè)程序的指令調(diào)用到。
在Windows下,這些共享庫(kù)文件就是.dll文件,也就是Dynamic-Link Libary(DLL,動(dòng)態(tài)鏈接庫(kù))
用了“動(dòng)態(tài)鏈接”的意思
在Linux下,這些共享庫(kù)文件就是.so文件,也就是Shared Object(一般我們也稱之為動(dòng)態(tài)鏈接庫(kù))。
用了“共享”的意思
正好覆蓋了兩方面的含義。
2 地址無(wú)關(guān)很重要,相對(duì)地址解煩惱要在程序運(yùn)行的時(shí)候共享代碼,這些機(jī)器碼必須“地址無(wú)關(guān)”
也就是說,我們編譯出來的共享庫(kù)文件的指令代碼,是地址無(wú)關(guān)碼(Position-Independent Code)
換句話說就是,這段代碼,無(wú)論加載在哪個(gè)內(nèi)存地址,都能夠正常執(zhí)行
如果還不明白,我給你舉一個(gè)生活中的例子
如果我們有一個(gè)騎自行車的程序,要“前進(jìn)500米,左轉(zhuǎn)進(jìn)入天安門廣場(chǎng),再前進(jìn)500米”。
它在500米之后要到天安門廣場(chǎng)了,這就是地址相關(guān)的。
如果程序是“前進(jìn)500米,左轉(zhuǎn),再前進(jìn)500米”,無(wú)論你在哪里都可以騎車走這1000米,沒有具體地點(diǎn)的限制,這就是地址無(wú)關(guān)的。
大部分函數(shù)庫(kù)其實(shí)都可以做到地址無(wú)關(guān),因?yàn)樗鼈兌冀邮芴囟ǖ妮斎耄M(jìn)行確定的操作,然后給出返回結(jié)果就好了。
無(wú)論是實(shí)現(xiàn)一個(gè)向量加法,還是實(shí)現(xiàn)一個(gè)打印的函數(shù),這些代碼邏輯和輸入的數(shù)據(jù)在內(nèi)存里面的位置并不重要。
而常見的地址相關(guān)的代碼,比如絕對(duì)地址代碼(Absolute Code)、利用重定位表的代碼等等,都是地址相關(guān)的代碼
回想一下我們之前講過的重定位表。在程序鏈接的時(shí)候,我們就把函數(shù)調(diào)用后要跳轉(zhuǎn)訪問的地址確定下來了,這意味著,如果這個(gè)函數(shù)加載到一個(gè)不同的內(nèi)存地址,跳轉(zhuǎn)就會(huì)失敗。
對(duì)于所有動(dòng)態(tài)鏈接共享庫(kù)的程序來講,雖然我們的共享庫(kù)用的都是同一段物理內(nèi)存地址,但是在不同的應(yīng)用程序里,它所在的虛擬內(nèi)存地址是不同的。
沒辦法、也不應(yīng)該要求動(dòng)態(tài)鏈接同一個(gè)共享庫(kù)的不同程序,必須把這個(gè)共享庫(kù)所使用的虛擬內(nèi)存地址變成一致。
如果這樣的話,我們寫的程序就必須明確地知道內(nèi)部的內(nèi)存地址分配。
那么問題來了,我們要怎么樣才能做到,動(dòng)態(tài)共享庫(kù)編譯出來的代碼指令,都是地址無(wú)關(guān)碼呢?
動(dòng)態(tài)代碼庫(kù)內(nèi)部的變量和函數(shù)調(diào)用都很容易解決,我們只需要使用相對(duì)地址(Relative Address)
各種指令中使用到的內(nèi)存地址,給出的不是一個(gè)絕對(duì)的地址空間,而是一個(gè)相對(duì)于當(dāng)前指令偏移量的內(nèi)存地址
因?yàn)?整個(gè)共享庫(kù)是放在一段連續(xù)的虛擬內(nèi)存地址中的,無(wú)論裝載到哪一段地址,不同指令之間的相對(duì)地址都是不變的。
3 動(dòng)態(tài)鏈接的解決方案PLT和GOT
要實(shí)現(xiàn)動(dòng)態(tài)鏈接共享庫(kù),也并不困難,和前面的靜態(tài)鏈接里的符號(hào)表和重定向表類似
拿出一小段代碼來看一看。
lib.h
定義了動(dòng)態(tài)鏈接庫(kù)的一個(gè)函數(shù) show_me_the_money
lib.c
包含了lib.h的實(shí)際實(shí)現(xiàn)
show_me_poor.c
調(diào)用了 lib 里面的函數(shù)
把 lib.c 編譯成了一個(gè)動(dòng)態(tài)鏈接庫(kù),也就是 .so 文件
最終生成文件集
在編譯的過程中,指定了一個(gè) -fPIC 的參數(shù)
其實(shí)就是Position Independent Code意,也就是要把這個(gè)編譯成一個(gè)地址無(wú)關(guān)代碼
然后,我們?cè)偻ㄟ^gcc編譯 show_me_poor 動(dòng)態(tài)鏈接了 lib.so 的可執(zhí)行文件
在這些操作都完成了之后,我們把 show_me_poor 這個(gè)文件通過objdump出來看一下。
0000000000400540: 400540: ff 35 12 05 20 00 push QWORD PTR [rip+0x200512] # 600a58 <_GLOBAL_OFFSET_TABLE_+0x8> 400546: ff 25 14 05 20 00 jmp QWORD PTR [rip+0x200514] # 600a60 <_GLOBAL_OFFSET_TABLE_+0x10> 40054c: 0f 1f 40 00 nop DWORD PTR [rax+0x0] 0000000000400550 : 400550: ff 25 12 05 20 00 jmp QWORD PTR [rip+0x200512] # 600a68 <_GLOBAL_OFFSET_TABLE_+0x18> 400556: 68 00 00 00 00 push 0x0 40055b: e9 e0 ff ff ff jmp 400540 <_init+0x28> …… 0000000000400676 : 400676: 55 push rbp 400677: 48 89 e5 mov rbp,rsp 40067a: 48 83 ec 10 sub rsp,0x10 40067e: c7 45 fc 05 00 00 00 mov DWORD PTR [rbp-0x4],0x5 400685: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 400688: 89 c7 mov edi,eax 40068a: e8 c1 fe ff ff call 400550 40068f: c9 leave 400690: c3 ret 400691: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 400698: 00 00 00 40069b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0]
我們還是只關(guān)心整個(gè)可執(zhí)行文件中的一小部分內(nèi)容
在main函數(shù)調(diào)用show_me_the_money的函數(shù)的時(shí)候,對(duì)應(yīng)的代碼是這樣的:
這里后面有一個(gè)@plt的關(guān)鍵字,代表了我們需要從PLT,也就是程序鏈接表(Procedure Link Table)里面找要調(diào)用的函數(shù)。對(duì)應(yīng)的地址呢,則是400580這個(gè)地址。
那當(dāng)我們把目光挪到上面的 400580 這個(gè)地址,你又會(huì)看到里面進(jìn)行了一次跳轉(zhuǎn),
這個(gè)跳轉(zhuǎn)指定的跳轉(zhuǎn)地址,你可以在后面的注釋里面可以看到:
這里的 _GLOBAL_OFFSET_TABLE_,就是我接下來要說的全局偏移表。
在動(dòng)態(tài)鏈接對(duì)應(yīng)的共享庫(kù),我們?cè)诠蚕韼?kù)的data section里面,保存了一張全局偏移表(GOT,Global Offset Table)
雖然共享庫(kù)的代碼部分的物理內(nèi)存是共享的,但是數(shù)據(jù)部分是各個(gè)動(dòng)態(tài)鏈接它的應(yīng)用程序里面各加載一份的。
所有需要引用當(dāng)前共享庫(kù)外部的地址的指令,都會(huì)查詢GOT,來找到當(dāng)前運(yùn)行程序的虛擬內(nèi)存里的對(duì)應(yīng)位置
而GOT表里的數(shù)據(jù),則是在我們加載一個(gè)個(gè)共享庫(kù)的時(shí)候?qū)戇M(jìn)去的。
不同的進(jìn)程,調(diào)用同樣的 _lib.so_,各自GOT里面指向最終加載的動(dòng)態(tài)鏈接庫(kù)里面的虛擬內(nèi)存地址是不同的。
這樣,雖然不同的程序調(diào)用的同樣的動(dòng)態(tài)庫(kù),各自的內(nèi)存地址是獨(dú)立的,調(diào)用的又都是同一個(gè)動(dòng)態(tài)庫(kù),但是不需要去修改動(dòng)態(tài)庫(kù)里面的代碼所使用的地址,
而是各個(gè)程序各自維護(hù)好自己的GOT,能夠找到對(duì)應(yīng)的動(dòng)態(tài)庫(kù)就好了
GOT表位于共享庫(kù)自己的數(shù)據(jù)段里
GOT表在內(nèi)存里和對(duì)應(yīng)的代碼段位置之間的偏移量,始終是確定的
這樣,共享庫(kù)就是地址無(wú)關(guān)的代碼,對(duì)應(yīng)的各個(gè)程序只需在物理內(nèi)存里加載同一份代碼
而我們又要通過各個(gè)可執(zhí)行程序在加載時(shí),生成的各不相同的GOT表,找到它需要調(diào)用到的外部變量和函數(shù)的地址
這是一個(gè)典型的、不修改代碼,而是通過修改“地址數(shù)據(jù)”來進(jìn)行關(guān)聯(lián)的辦法
它有點(diǎn)像我們?cè)贑語(yǔ)言里面用函數(shù)指針來調(diào)用對(duì)應(yīng)的函數(shù),并不是通過預(yù)先已經(jīng)確定好的函數(shù)名稱來調(diào)用,而是利用當(dāng)時(shí)它在內(nèi)存里面的動(dòng)態(tài)地址來調(diào)用。4 總結(jié)
終于在靜態(tài)鏈接和程序裝載后,利用動(dòng)態(tài)鏈接把我們的內(nèi)存利用到了極致
同樣功能的代碼生成的共享庫(kù),我們只要在內(nèi)存里面保留一份就好了
這樣
不僅能夠做到代碼在開發(fā)階段的復(fù)用
也能做到代碼在運(yùn)行階段的復(fù)用。
實(shí)際上,在進(jìn)行Linux程序開發(fā),一直會(huì)用到各種各樣的動(dòng)態(tài)鏈接庫(kù)。
C語(yǔ)言的標(biāo)準(zhǔn)庫(kù)就在1MB以上。
撰寫任何一個(gè)程序可能都需要用到這個(gè)庫(kù),常見的Linux服務(wù)器里,/usr/bin下面就有上千個(gè)可執(zhí)行文件。
如果每一個(gè)都把標(biāo)準(zhǔn)庫(kù)靜態(tài)鏈接進(jìn)來的,幾GB乃至幾十GB的磁盤空間一下子就用出去了。如果我們服務(wù)端的多進(jìn)程應(yīng)用要開上千個(gè)進(jìn)程,幾GB的內(nèi)存空間也會(huì)一下子就用出去了。這個(gè)問題在過去計(jì)算機(jī)的內(nèi)存較少的時(shí)候更加顯著。
通過動(dòng)態(tài)鏈接這個(gè)方式,可以說_徹底解決了這個(gè)問題_。
就像共享單車一樣,如果仔細(xì)經(jīng)營(yíng),是一個(gè)很有社會(huì)價(jià)值的事情,但是如果粗暴地把它變成無(wú)限制地復(fù)制生產(chǎn),給每個(gè)人造一輛,只會(huì)在系統(tǒng)內(nèi)制造大量無(wú)用的垃圾。
已經(jīng)把程序怎么從源代碼變成指令、數(shù)據(jù),并裝載到內(nèi)存里面,由CPU一條條執(zhí)行下去的過程講完了。希望你能有所收獲,對(duì)于一個(gè)程序是怎么跑起來的,有了一個(gè)初步的認(rèn)識(shí)。
5 推薦閱讀想要更加深入地了解動(dòng)態(tài)鏈接,推薦你可以讀一讀《程序員的自我修養(yǎng):鏈接、裝載和庫(kù)》的第7章
里面深入地講解了,動(dòng)態(tài)鏈接里程序內(nèi)的數(shù)據(jù)布局和對(duì)應(yīng)數(shù)據(jù)的加載關(guān)系。
參考深入淺出計(jì)算機(jī)組成原理
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/76252.html
摘要:這個(gè)辦法,在現(xiàn)在計(jì)算機(jī)的內(nèi)存管理里面,就叫作內(nèi)存分頁(yè)和分段這樣分配一整段連續(xù)的空間給到程序相比分頁(yè)則是把整個(gè)物理內(nèi)存空間切成一段段固定尺寸的大小而對(duì)應(yīng)的程序所需要占用的虛擬內(nèi)存空間,也會(huì)同樣切成一段段固定尺寸的大小。 showImg(https://image-static.segmentfault.com/290/765/2907653835-5d580caf245fd_articl...
摘要:鏈接器會(huì)掃描所有輸入的目標(biāo)文件,然后把所有符號(hào)表里的信息收集起來,構(gòu)成一個(gè)全局的符號(hào)表。這是一本難得的講解程序的鏈接裝載和運(yùn)行的好書。 showImg(https://image-static.segmentfault.com/396/693/396693929-5d558865c3a7e_articlex); 既然程序最終都被變成了一條條機(jī)器碼去執(zhí)行,那為什么同一個(gè)程序,在同一臺(tái)計(jì)算...
摘要:計(jì)算機(jī)組成中的大量原理和設(shè)計(jì),都對(duì)應(yīng)著性能這個(gè)詞。時(shí)間的倒數(shù)性能計(jì)算機(jī)的性能,其實(shí)和體力勞動(dòng)很像,好比是我們要搬東西。對(duì)于計(jì)算機(jī)的性能,我們需要有個(gè)標(biāo)準(zhǔn)來衡量?;ǖ臅r(shí)間越少,自然性能就越好。 0 學(xué)習(xí)路線的知識(shí)點(diǎn)概括 showImg(https://segmentfault.com/img/remote/1460000020031616?w=3832&h=2540); 學(xué)習(xí)計(jì)算機(jī)組成原...
摘要:固有對(duì)象由標(biāo)準(zhǔn)規(guī)定,隨著運(yùn)行時(shí)創(chuàng)建而自動(dòng)創(chuàng)建的對(duì)象實(shí)例。普通對(duì)象由語(yǔ)法構(gòu)造器或者關(guān)鍵字定義類創(chuàng)建的對(duì)象,它能夠被原型繼承。 筆記說明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開的一個(gè)專欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過程的一些要點(diǎn)筆記以及感悟,完整的可以加入winter的專欄學(xué)習(xí)【原文有winter的語(yǔ)音】,如有侵權(quán)請(qǐng)聯(lián)系我,郵箱:ka...
閱讀 3568·2021-11-18 10:02
閱讀 1665·2021-10-12 10:12
閱讀 3075·2021-10-09 09:53
閱讀 5129·2021-09-09 09:34
閱讀 1030·2021-09-06 15:02
閱讀 2838·2021-08-05 10:02
閱讀 3248·2019-08-30 15:44
閱讀 3175·2019-08-28 18:04