Linux深入理解進(jìn)程和文件及內(nèi)存管理問(wèn)題
一、重談Linux下一切皆文件
我們說(shuō)了一切皆文件,對(duì)于操作系統(tǒng)來(lái)說(shuō),磁盤(pán)鍵盤(pán)顯示屏等等一系列的外設(shè)都是文件。
舉一個(gè)訪問(wèn)外設(shè)的例子:
進(jìn)程運(yùn)行,從進(jìn)程PCB中找到指針指向文件管理結(jié)構(gòu)體,然后在這個(gè)結(jié)構(gòu)體中我們可以找到struct file*類(lèi)型的指針指向一個(gè)個(gè)的文件管理結(jié)構(gòu)體struct file,在這些結(jié)構(gòu)體中都有著一個(gè)專(zhuān)門(mén)放讀寫(xiě)函數(shù)的結(jié)構(gòu)體,調(diào)用這些讀寫(xiě)函數(shù)可以訪問(wèn)到外設(shè)存放讀寫(xiě)函數(shù)的結(jié)構(gòu)體,而雖然每個(gè)外設(shè)的讀寫(xiě)方式不同,但它們僅把處理好的代碼封裝后將接口漏出,方便上方函數(shù)的統(tǒng)一調(diào)用,這樣雖然每個(gè)外設(shè)不同,但是我們通過(guò)一種求同存異的方法,將它們統(tǒng)一協(xié)調(diào)調(diào)度起來(lái)
類(lèi)似于鍵盤(pán)一類(lèi)的只有讀或者顯示屏一類(lèi)的只有寫(xiě)的外設(shè),我們也有讀或?qū)懙慕涌?,只是接口不做處理,方便統(tǒng)一
二、操作系統(tǒng)對(duì)物理內(nèi)存的管理
1、物理內(nèi)存與磁盤(pán)的數(shù)據(jù)交互
在操作系統(tǒng)的運(yùn)行機(jī)制里,物理內(nèi)存和磁盤(pán)之間的數(shù)據(jù)交換起著關(guān)鍵作用,這種交換一般就是以頁(yè)page為單位,常見(jiàn)的頁(yè)大小為 4KB,在物理內(nèi)存中,一個(gè) 4KB 大小的空間被稱(chēng)作頁(yè)框,而從磁盤(pán)加載到這個(gè)頁(yè)框里的4KB數(shù)據(jù)塊則被叫做頁(yè)
采用這種以頁(yè)為單位進(jìn)行數(shù)據(jù)交換的方式,具有顯著的優(yōu)勢(shì),一方面,能有效減少I(mǎi)O操作的次數(shù),進(jìn)而提升系統(tǒng)效率,舉例來(lái)說(shuō),如果需要讀取數(shù)據(jù),一次讀取 4KB 與分四次讀取每次 1KB 相比,前者的效率要高得多,對(duì)于硬盤(pán)來(lái)說(shuō),一次讀取 4KB 時(shí),CPU 只需與磁盤(pán)進(jìn)行一次交互,而分四次讀取 1KB 時(shí),CPU 要與磁盤(pán)進(jìn)行四次交互,且這四次操作很可能不連續(xù),這就意味著效率低下,另一方面,這種方式還遵循基于局部性原理的預(yù)加載機(jī)制,即便當(dāng)前 CPU 僅需訪問(wèn) 100 字節(jié)的內(nèi)容,操作系統(tǒng)和磁盤(pán)之間依舊會(huì)以 4KB 為單位將數(shù)據(jù)加載進(jìn)來(lái),這是因?yàn)?strong>根據(jù)經(jīng)驗(yàn),CPU 在訪問(wèn)當(dāng)前磁盤(pán)中的代碼和數(shù)據(jù)時(shí),后續(xù)有較大概率會(huì)訪問(wèn)附近空間的代碼和數(shù)據(jù),還有一方面,就是對(duì)齊,磁盤(pán)中的最小寫(xiě)入單位是頁(yè),因?yàn)橛?jì)算機(jī)硬件的設(shè)計(jì)往往遵循一定的對(duì)齊規(guī)則,這樣可以提高數(shù)據(jù)訪問(wèn)的效率內(nèi)存和磁盤(pán)控制器在設(shè)計(jì)時(shí),通常會(huì)按照特定的字節(jié)邊界來(lái)組織和傳輸數(shù)據(jù),以頁(yè)為單位進(jìn)行數(shù)據(jù)交換可以保證數(shù)據(jù)在內(nèi)存和磁盤(pán)之間的傳輸是按照硬件對(duì)齊要求進(jìn)行的,減少硬件處理的復(fù)雜性
2、操作系統(tǒng)對(duì)物理內(nèi)存的管理
操作系統(tǒng)具備感知物理內(nèi)存的能力,其對(duì)物理內(nèi)存的管理遵循先描述再組織的原則,在內(nèi)核中,struct page 結(jié)構(gòu)體承擔(dān)著描述物理內(nèi)存的重要職責(zé),一個(gè) struct page 對(duì)象對(duì)應(yīng)著一個(gè) 4KB 的內(nèi)存頁(yè)框,該結(jié)構(gòu)體中記錄了當(dāng)前頁(yè)框的諸多屬性信息,像頁(yè)框的狀態(tài)、引用計(jì)數(shù)等
操作系統(tǒng)會(huì)把物理內(nèi)存劃分成一個(gè)個(gè)的struct page對(duì)象,再用數(shù)組的形式將它們組織起來(lái),數(shù)組的下標(biāo)即為對(duì)應(yīng)的頁(yè)號(hào),若要確定一個(gè)物理地址所在的頁(yè)號(hào),只需將該物理地址除以 4096 b(4KB = 4 * 1024 = 4096 b
),或者將該地址按位與上 0xFFFFF000(以 32 位系統(tǒng)為例),把低 12 位清零,得到的結(jié)果就是該地址所在的頁(yè)號(hào)
在進(jìn)行內(nèi)存申請(qǐng)操作時(shí),系統(tǒng)會(huì)訪問(wèn) page 數(shù)組,查看 struct page 里的 flags 屬性,通過(guò)這個(gè)屬性,系統(tǒng)能夠判斷當(dāng)前頁(yè)框的狀態(tài),確定其是否已被使用,若未被使用,系統(tǒng)就會(huì)修改 flags 以表明該頁(yè)框已被申請(qǐng),此外,flags 除了能表示頁(yè)框的使用狀態(tài)外,還能指示該頁(yè)框是只讀還是可讀寫(xiě)等狀態(tài),
當(dāng)然這里所介紹的只是操作系統(tǒng)對(duì)物理內(nèi)存管理的一個(gè)基礎(chǔ)模型,實(shí)際上真正的內(nèi)存管理系統(tǒng)要復(fù)雜得多
三、文件頁(yè)緩沖區(qū)
在操作系統(tǒng)內(nèi)核中,struct file
是一個(gè)重要的數(shù)據(jù)結(jié)構(gòu),用于描述一個(gè)已打開(kāi)的文件,而 inode
這一概念是在介紹磁盤(pán)時(shí)引入的,磁盤(pán)上的每個(gè)文件都對(duì)應(yīng)著一個(gè)inode
,它存儲(chǔ)了該文件的屬性信息,struct file
和 inode
之間存在著緊密的聯(lián)系,struct file
中僅記錄了文件的少量屬性,而struct inode
結(jié)構(gòu)體則專(zhuān)門(mén)用于記錄一個(gè)文件的所有屬性,在 struct file
中有一個(gè)指針字段,它指向該文件的struct inode
對(duì)象
文件由內(nèi)容和屬性?xún)刹糠謽?gòu)成,在磁盤(pán)上,文件的屬性由 inode
存儲(chǔ),文件的內(nèi)容則由數(shù)據(jù)塊存儲(chǔ),那么,在操作系統(tǒng)內(nèi)核中,文件的內(nèi)容(數(shù)據(jù))是如何表示的呢?答案就是通過(guò)文件頁(yè)緩沖區(qū),在 struct file
結(jié)構(gòu)中有一個(gè)指向 struct address_space
結(jié)構(gòu)體的指針,在 struct address_space 結(jié)構(gòu)體中,有一個(gè) struct radix_tree_root
結(jié)構(gòu)體對(duì)象,它實(shí)際上是一種樹(shù)狀結(jié)構(gòu),即基數(shù)樹(shù)(也叫字典樹(shù)),樹(shù)中的每個(gè)節(jié)點(diǎn)都是 struct radix_tree_node
類(lèi)型,該類(lèi)型中有一個(gè)名為 slots
的 void*
類(lèi)型數(shù)組,數(shù)組中存儲(chǔ)的其實(shí)就是 struct page
對(duì)象的地址,簡(jiǎn)單來(lái)說(shuō),在 struct file
結(jié)構(gòu)體中有指向物理內(nèi)存頁(yè)框的指針,我們把這些物理內(nèi)存區(qū)域稱(chēng)為文件頁(yè)緩沖區(qū)
向文件寫(xiě)入數(shù)據(jù)的過(guò)程
當(dāng)使用C/C++庫(kù)函數(shù)向文件中寫(xiě)入數(shù)據(jù)時(shí),整個(gè)過(guò)程分為幾個(gè)階段,首先,數(shù)據(jù)有可能會(huì)被寫(xiě)入到語(yǔ)言層面的用戶緩沖區(qū),然后,在合適的時(shí)機(jī),這些數(shù)據(jù)會(huì)被從用戶緩沖區(qū)寫(xiě)入到該文件對(duì)應(yīng)的文件頁(yè)緩沖區(qū)中,最后,還是在合適的時(shí)機(jī),數(shù)據(jù)會(huì)從文件頁(yè)緩沖區(qū)被寫(xiě)入到磁盤(pán)
將物理內(nèi)存中的數(shù)據(jù)刷新到磁盤(pán)這一操作由IO子系統(tǒng)負(fù)責(zé)執(zhí)行,進(jìn)程通常無(wú)需關(guān)注具體的執(zhí)行過(guò)程,在操作系統(tǒng)中,會(huì)存在大量的IO操作,可能有很多進(jìn)程都需要將數(shù)據(jù)寫(xiě)入磁盤(pán),為了有效管理這些操作,操作系統(tǒng)會(huì)按照先描述再組織的方式對(duì)所有的IO操作進(jìn)行管理,內(nèi)核中的struct request
結(jié)構(gòu)就是專(zhuān)門(mén)用來(lái)描述一個(gè)IO操作的
在Linux操作系統(tǒng)中,每個(gè)進(jìn)程打開(kāi)的每個(gè)文件都有自己的 struct inode
對(duì)象和對(duì)應(yīng)的文件頁(yè)緩沖區(qū),也就是所謂的內(nèi)核緩沖區(qū),它們共同保障了文件操作的高效和穩(wěn)定
四、動(dòng)態(tài)庫(kù)是如何被加載的
動(dòng)態(tài)庫(kù)在進(jìn)程運(yùn)行時(shí)要被加載到內(nèi)存,一般我們常用的動(dòng)態(tài)庫(kù)是要被所有的可執(zhí)行程序動(dòng)態(tài)鏈接的,所以動(dòng)態(tài)庫(kù)在系統(tǒng)中加載完成后,會(huì)被所有的進(jìn)程所共享
在操作系統(tǒng)的進(jìn)程管理與庫(kù)使用機(jī)制中,進(jìn)程與動(dòng)態(tài)庫(kù)的交互有著獨(dú)特的方式,一個(gè)進(jìn)程在運(yùn)行過(guò)程中,是可以同時(shí)鏈接多個(gè)動(dòng)態(tài)庫(kù)的,不過(guò),當(dāng)系統(tǒng)中存在多個(gè)進(jìn)程時(shí),不能簡(jiǎn)單地認(rèn)為系統(tǒng)中必然存在多個(gè)不同的動(dòng)態(tài)庫(kù),多個(gè)進(jìn)程可能會(huì)依賴(lài)相同的動(dòng)態(tài)庫(kù),操作系統(tǒng)對(duì)動(dòng)態(tài)庫(kù)采用“先描述,再組織”的策略進(jìn)行管理,它會(huì)為每個(gè)動(dòng)態(tài)庫(kù)創(chuàng)建相應(yīng)的數(shù)據(jù)結(jié)構(gòu)來(lái)描述其屬性、位置等信息,然后將這些描述信息組織起來(lái),以便高效地進(jìn)行查找、加載和管理,憑借這種管理方式,操作系統(tǒng)對(duì)系統(tǒng)中所有動(dòng)態(tài)庫(kù)的加載狀態(tài)了如指掌
以 a.exe
為例,它在編譯鏈接階段選擇使用動(dòng)態(tài)庫(kù),當(dāng) a.exe
運(yùn)行成為 a
進(jìn)程后,CPU 會(huì)按照程序的指令順序依次執(zhí)行代碼,假設(shè)在執(zhí)行過(guò)程中遇到了一個(gè)庫(kù)函數(shù)調(diào)用,此時(shí),操作系統(tǒng)會(huì)檢查該函數(shù)所在的動(dòng)態(tài)庫(kù)是否已經(jīng)被加載到內(nèi)存中,若尚未加載,操作系統(tǒng)會(huì)負(fù)責(zé)將該動(dòng)態(tài)庫(kù)加載到內(nèi)存,這一加載過(guò)程本質(zhì)上與文件加載一致,因?yàn)閯?dòng)態(tài)庫(kù)本身也是以文件形式存在的,并且具有 inode
來(lái)標(biāo)識(shí)其在文件系統(tǒng)中的元數(shù)據(jù)
動(dòng)態(tài)庫(kù)加載完成后,操作系統(tǒng)會(huì)在 a
進(jìn)程的頁(yè)表中建立該動(dòng)態(tài)庫(kù)與 a
進(jìn)程地址空間中共享區(qū)的映射關(guān)系,這樣,當(dāng) CPU 需要執(zhí)行上述函數(shù)時(shí),就可以從代碼段跳轉(zhuǎn)到共享區(qū)去執(zhí)行動(dòng)態(tài)庫(kù)中該函數(shù)的代碼,執(zhí)行完畢后,CPU 會(huì)跳轉(zhuǎn)回代碼段,繼續(xù)執(zhí)行后續(xù)的程序指令
b.exe
同樣在編譯鏈接時(shí)采用動(dòng)態(tài)庫(kù),之后被加載到內(nèi)存成為 b
進(jìn)程,當(dāng) CPU 執(zhí)行 b
進(jìn)程的代碼并遇到上面函數(shù)調(diào)用時(shí),因?yàn)樵?a
進(jìn)程已經(jīng)將該所在的動(dòng)態(tài)庫(kù)加載到了內(nèi)存,操作系統(tǒng)不會(huì)再次重復(fù)加載該動(dòng)態(tài)庫(kù),而是直接在 b
進(jìn)程的頁(yè)表中建立該動(dòng)態(tài)庫(kù)與 b
進(jìn)程共享區(qū)的映射關(guān)系,通過(guò)這種方式,同一個(gè)動(dòng)態(tài)庫(kù)可以被多個(gè)進(jìn)程共享使用,所以動(dòng)態(tài)庫(kù)又被稱(chēng)為共享庫(kù)
關(guān)于動(dòng)態(tài)庫(kù)中的全局變量
動(dòng)態(tài)庫(kù)確實(shí)可以被多個(gè)進(jìn)程共享,但對(duì)于動(dòng)態(tài)庫(kù)中的全局變量(例如 errno
),需要特殊的處理機(jī)制來(lái)保證各個(gè)進(jìn)程之間的數(shù)據(jù)獨(dú)立性,errno
是 C 語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供的一個(gè)全局變量,用于存儲(chǔ)最近一次庫(kù)函數(shù)調(diào)用失敗時(shí)的錯(cuò)誤碼
如果簡(jiǎn)單地讓所有進(jìn)程共享 errno
,會(huì)引發(fā)嚴(yán)重的問(wèn)題,例如,當(dāng) a
進(jìn)程調(diào)用庫(kù)函數(shù)失敗,errno
被設(shè)置為 1,此時(shí)如果 b
進(jìn)程也使用這個(gè)共享的 errno
,就會(huì)導(dǎo)致 b
進(jìn)程錯(cuò)誤地獲取到 a
進(jìn)程的錯(cuò)誤碼,這顯然不符合邏輯
實(shí)際上,操作系統(tǒng)采用了寫(xiě)時(shí)拷貝技術(shù)來(lái)解決這個(gè)問(wèn)題,當(dāng)某個(gè)進(jìn)程要修改 errno
時(shí),操作系統(tǒng)會(huì)通過(guò)引用計(jì)數(shù)來(lái)判斷該動(dòng)態(tài)庫(kù)是否被多個(gè)進(jìn)程共享,如果該動(dòng)態(tài)庫(kù)被多個(gè)進(jìn)程共享,操作系統(tǒng)會(huì)為該進(jìn)程復(fù)制一份動(dòng)態(tài)庫(kù)中相關(guān)數(shù)據(jù)(包括 errno
)的副本,而不是直接修改共享的數(shù)據(jù),這樣,每個(gè)進(jìn)程都有自己獨(dú)立的 errno
副本,從而保證了各個(gè)進(jìn)程之間的錯(cuò)誤碼不會(huì)相互干擾,只有當(dāng)進(jìn)程對(duì)數(shù)據(jù)進(jìn)行寫(xiě)操作時(shí)才會(huì)發(fā)生拷貝,而在只讀的情況下,多個(gè)進(jìn)程仍然可以共享同一份動(dòng)態(tài)庫(kù)數(shù)據(jù),從而充分發(fā)揮了動(dòng)態(tài)庫(kù)共享的優(yōu)勢(shì)
五、深入理解地址
1、程序地址
在一個(gè)程序編譯好后形成了可執(zhí)行文件,在它的內(nèi)部是有地址的概念的,這里程序內(nèi)部的地址我們稱(chēng)為邏輯地址,我們計(jì)算機(jī)一般采用的是平坦模式編址,平坦模式編址是一種簡(jiǎn)化的內(nèi)存編址模型,在這種模式下,整個(gè)內(nèi)存空間被視為一個(gè)連續(xù)的、線性的地址空間,程序可以直接訪問(wèn)這個(gè)連續(xù)地址空間內(nèi)的任意內(nèi)存位置,而不需要像分段模式那樣進(jìn)行復(fù)雜的段地址和偏移地址組合計(jì)算,在平坦模式中,內(nèi)存地址是一個(gè)單一的、連續(xù)的數(shù)值,從 0 開(kāi)始一直到系統(tǒng)所支持的最大內(nèi)存地址
32位下的4GB內(nèi)存地址
2、進(jìn)程地址
我們知道,可執(zhí)行程序內(nèi)部采用的是邏輯地址(也叫虛擬地址)進(jìn)行編址,物理內(nèi)存本身有其固定的物理地址,無(wú)論可執(zhí)行程序是否加載,物理內(nèi)存的地址體系是一直存在的,當(dāng)可執(zhí)行程序被加載到內(nèi)存后,程序中的每一條指令和數(shù)據(jù)都會(huì)對(duì)應(yīng)一個(gè)物理地址,這是通過(guò)地址映射機(jī)制實(shí)現(xiàn)的
那么,CPU 是如何知道可執(zhí)行程序的第一條指令位置呢?在編譯生成可執(zhí)行程序時(shí),除了生成代碼段、數(shù)據(jù)段等程序內(nèi)容外,還會(huì)生成一個(gè)文件頭,這個(gè)文件頭包含了諸多重要信息,其中就有可執(zhí)行程序的入口地址,此地址是邏輯地址(虛擬地址)
在 CPU 中有一個(gè)關(guān)鍵的寄存器,即程序計(jì)數(shù)器—PC 寄存器,它存儲(chǔ)著接下來(lái)要執(zhí)行指令的地址,實(shí)際上,在程序啟動(dòng)階段,不會(huì)立刻把整個(gè)可執(zhí)行程序加載到內(nèi)存(可以想象我們打游戲的時(shí)候不是打開(kāi)游戲就能玩的,需要等待加載)而是先將可執(zhí)行文件的頭部加載進(jìn)來(lái),操作系統(tǒng)讀取文件頭,從中獲取可執(zhí)行程序的入口地址,并將該地址設(shè)置到 PC 寄存器中
CPU 拿到這個(gè)虛擬地址后,會(huì)借助內(nèi)存管理單元去查詢(xún)頁(yè)表,頁(yè)表記錄了虛擬地址和物理地址的映射關(guān)系,若查詢(xún)發(fā)現(xiàn)該虛擬地址對(duì)應(yīng)的頁(yè)表項(xiàng)無(wú)效,也就是此頁(yè)面尚未建立內(nèi)存映射,操作系統(tǒng)會(huì)觸發(fā)缺頁(yè)中斷,缺頁(yè)中斷發(fā)生后,操作系統(tǒng)暫停當(dāng)前程序的執(zhí)行,從磁盤(pán)把對(duì)應(yīng)的程序頁(yè)面加載到物理內(nèi)存的空閑頁(yè)框中,同時(shí)更新頁(yè)表,建立起虛擬地址到物理地址的映射,之后,恢復(fù)程序執(zhí)行,CPU 就能訪問(wèn)到物理內(nèi)存中對(duì)應(yīng)的指令了
CPU 憑借其內(nèi)置的指令集,能夠明確識(shí)別每條指令的長(zhǎng)度,在正常運(yùn)行狀態(tài)下,CPU 按照 PC 寄存器所存儲(chǔ)的地址順序執(zhí)行指令,每執(zhí)行完一條指令,PC 寄存器會(huì)自動(dòng)更新為下一條指令的地址,當(dāng)程序執(zhí)行過(guò)程中遇到函數(shù)調(diào)用指令或跳轉(zhuǎn)指令時(shí),PC 寄存器的值會(huì)被修改為新的虛擬地址,CPU 會(huì)依據(jù)這個(gè)新的虛擬地址再次查詢(xún)頁(yè)表,若發(fā)現(xiàn)頁(yè)面未在內(nèi)存中,將再次觸發(fā)缺頁(yè)中斷
由此可見(jiàn),CPU 正是通過(guò)將虛擬地址轉(zhuǎn)換為物理地址的方式,來(lái)執(zhí)行可執(zhí)行程序中的指令以及訪問(wèn)可執(zhí)行程序中的變量的,這種基于虛擬內(nèi)存和地址映射的機(jī)制,為程序提供了獨(dú)立的地址空間,增強(qiáng)了內(nèi)存管理的靈活性和安全性
3、動(dòng)態(tài)庫(kù)地址
在計(jì)算機(jī)系統(tǒng)的程序執(zhí)行機(jī)制中,可執(zhí)行程序內(nèi)部采用邏輯地址進(jìn)行編址,CPU通過(guò)將虛擬地址轉(zhuǎn)換為物理地址的方式來(lái)執(zhí)行指令
靜態(tài)庫(kù)在編譯鏈接過(guò)程中會(huì)被整合到可執(zhí)行文件中,當(dāng)可執(zhí)行程序啟動(dòng)運(yùn)行時(shí),操作系統(tǒng)會(huì)將包含靜態(tài)庫(kù)代碼的可執(zhí)行文件加載到物理內(nèi)存,之后 CPU 方可執(zhí)行其中的指令,靜態(tài)庫(kù)中的函數(shù)采用絕對(duì)編址,這是因?yàn)樗鼈円殉蔀榭蓤?zhí)行文件的組成部分,其地址在編譯鏈接階段就已經(jīng)確定下來(lái)
在可執(zhí)行程序的編譯階段,對(duì)動(dòng)態(tài)庫(kù)內(nèi)函數(shù)的引用表現(xiàn)為未解析的符號(hào),當(dāng)程序進(jìn)入運(yùn)行狀態(tài)時(shí),動(dòng)態(tài)鏈接器承擔(dān)起解析這些符號(hào)的任務(wù),由于動(dòng)態(tài)庫(kù)在運(yùn)行時(shí)能夠被加載到虛擬內(nèi)存共享區(qū)的任意位置,要將動(dòng)態(tài)庫(kù)加載到固定位置存在較大困難,這是由于一個(gè)可執(zhí)行程序往往會(huì)同時(shí)使用多個(gè)動(dòng)態(tài)庫(kù),各個(gè)動(dòng)態(tài)庫(kù)的大小不盡相同,并且每個(gè)動(dòng)態(tài)庫(kù)都采用獨(dú)立的編址方式,不同動(dòng)態(tài)庫(kù)中可能出現(xiàn)相同的編址情況,為了實(shí)現(xiàn)動(dòng)態(tài)庫(kù)在虛擬內(nèi)存共享區(qū)任意位置的加載,動(dòng)態(tài)庫(kù)內(nèi)部采用相對(duì)編址的方法,對(duì)于動(dòng)態(tài)庫(kù)中的函數(shù),僅需明確其在庫(kù)中的偏移量即可,鑒于庫(kù)中函數(shù)的偏移量是已知的(在編譯動(dòng)態(tài)庫(kù)時(shí),編譯器會(huì)按照一定的規(guī)則對(duì)庫(kù)中的代碼和數(shù)據(jù)進(jìn)行布局,編譯器知道每個(gè)函數(shù)在代碼段中的起始位置以及函數(shù)內(nèi)部代碼的長(zhǎng)度),動(dòng)態(tài)庫(kù)便能夠被加載到虛擬內(nèi)存共享區(qū)的任意位置
在此機(jī)制下,操作系統(tǒng)只需記錄每個(gè)動(dòng)態(tài)庫(kù)在虛擬內(nèi)存中的起始地址,當(dāng)需要執(zhí)行某個(gè)動(dòng)態(tài)庫(kù)函數(shù)時(shí),通過(guò)將該函數(shù)所在動(dòng)態(tài)庫(kù)的起始地址與該函數(shù)的偏移量相加,即可得到該函數(shù)在程序地址空間中的虛擬地址,隨后,依據(jù)此虛擬地址查詢(xún)頁(yè)表,找到該函數(shù)在物理內(nèi)存中的對(duì)應(yīng)地址,進(jìn)而執(zhí)行該庫(kù)函數(shù),GCC 編譯器中的 -fPIC
選項(xiàng),其作用就是讓編譯器在生成動(dòng)態(tài)庫(kù)文件時(shí),直接使用偏移量對(duì)庫(kù)中的函數(shù)進(jìn)行編址,從而實(shí)現(xiàn)代碼的位置無(wú)關(guān)性
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
linux系列之常用運(yùn)維命令整理筆錄(小結(jié))
這篇文章主要介紹了linux系列之常用運(yùn)維命令整理筆錄(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01Kloxo-MR VPS主機(jī)控制面板-安裝使用及中文設(shè)置方法
這篇文章主要介紹了Kloxo-MR VPS主機(jī)控制面板-安裝使用及中文設(shè)置方法,需要的朋友可以參考下2017-07-07簡(jiǎn)單談?wù)凩inux內(nèi)核定時(shí)器
內(nèi)核定時(shí)器用于控制某個(gè)函數(shù)(定時(shí)器處理函數(shù))在未來(lái)的某個(gè)特定時(shí)間執(zhí)行.內(nèi)核定時(shí)器注冊(cè)的處理函數(shù)只執(zhí)行一次.處理過(guò)后即失效.2017-10-10CentOS 6.2 下升級(jí)安裝為MySQL 5.5的方法
使用系統(tǒng)CentOS 6.2本來(lái)已經(jīng)系統(tǒng)自帶安裝了mysql 5.1,但是奈何5.1不支持utf8mb4字符集,只能想辦法將Mysql升級(jí)到5.52014-11-11Linux、CentOS下安裝zip與unzip指令功能(服務(wù)器)
這篇文章主要介紹了Linux、CentOS下安裝zip與unzip指令的操作方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2019-11-11