版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、目錄第一章溫故而知新 6第二節(jié)萬變不離其宗6第3節(jié) 站得高看得遠(yuǎn) 7第4節(jié)操作系統(tǒng)的功能7不要讓CPU打盹7設(shè)備驅(qū)動(dòng)81.5 內(nèi)存不夠怎么辦? 8關(guān)于隔離81.5.2 分段91.5.3 分頁(yè)91.6眾人拾柴火焰高 10線程基礎(chǔ)10線程安全 11多線程內(nèi)部情況14第二章編譯和鏈接 152.1被隱藏了的過程 15預(yù)編譯152.1.2 編譯152.1.3 匯編152.1.4 鏈接162.2編譯器做了什么16詞法分析16語(yǔ)法分析16語(yǔ)義分析16中間語(yǔ)言生成 17目標(biāo)代碼的生成與優(yōu)化 172.3鏈接器年齡比編譯器長(zhǎng) 182.4模塊拼接一一靜態(tài)鏈接 18第三章目標(biāo)文件里有什么 183.1目標(biāo)文件的格式19
2、3.2目標(biāo)文件是什么樣的 193.3 挖掘 SimpleSection.o 203.3.3 BSS段 20其他段203.4 ELF文件結(jié)構(gòu)描述 20文件頭213.4.2 段表21重定位表 22字符串表 223.5鏈接的接口 一一符號(hào) 223.5.1 ELF符號(hào)表結(jié)構(gòu) 23特殊符號(hào) 23符號(hào)修飾與函數(shù)簽名 24弱符號(hào)和強(qiáng)符號(hào) 243.6調(diào)試信息25第4章靜態(tài)鏈接254.1空間與地址分配25相似段合并25符號(hào)地址的確定 264.2符號(hào)解析與重定位 26重定位表26符號(hào)解析 27指令修正方式 274.3 COMMON 塊27重復(fù)代碼消除 284.4.2 全局構(gòu)造與析構(gòu) 294.4.3 C+與 ABI
3、 294.5靜態(tài)庫(kù)鏈接 304.6鏈接過程控制 30鏈接過程腳本30最“小”的程序 31使用Id鏈接腳本314.6.4 Id鏈接腳本語(yǔ)法簡(jiǎn)介314.7 BFD 庫(kù)31第 5 章 WINDOWS PE/COFF315.1 Windows的二進(jìn)制文件格式 PE/COFF315.2 PE 的前身 COFF325.3鏈接指示信息 325.4調(diào)試信息325.5大家都有符號(hào)表325.6 WINDOWS 下的 ELFPE32第6章可執(zhí)行文件的裝載與進(jìn)程 336.1進(jìn)程的虛擬地址空間 336.2裝載的方式 33覆蓋裝入33頁(yè)映射346.3從操作系統(tǒng)的角度看可執(zhí)行文件的裝載 34進(jìn)程的建立 346.4 進(jìn)程虛存
4、空間的分布 356.4.1 ELF文件鏈接視圖和執(zhí)行視圖 35堆和棧36堆的最大申請(qǐng)數(shù)量 36段地址對(duì)齊36進(jìn)程棧初始化376.5 Linux內(nèi)核裝載 ELF過程簡(jiǎn)介 376.6 Windows PE 的裝載 38第7章動(dòng)態(tài)鏈接387.1為什么要?jiǎng)討B(tài)鏈接387.2簡(jiǎn)單的動(dòng)態(tài)鏈接例子 397.3地址無關(guān)代碼40固定裝載地址的困擾 40裝載時(shí)重定位 40地址無關(guān)代碼 40共享模塊的全局變量問題 42代碼段地址無關(guān)性 437.4延遲綁定(PLT). 437.5動(dòng)態(tài)鏈接相關(guān)結(jié)構(gòu) 447.5.1 “ .in”i段457.5.2 “ dyna”段45動(dòng)態(tài)符號(hào)表 45動(dòng)態(tài)鏈接重定位表 45動(dòng)態(tài)鏈接時(shí)進(jìn)程堆棧
5、初始化信息 467.6動(dòng)態(tài)鏈接的步驟和實(shí)現(xiàn) 46動(dòng)態(tài)鏈接器自舉46裝載共享對(duì)象 47重定位和初始化 477.6.4 Linux動(dòng)態(tài)鏈接器的實(shí)現(xiàn) 477.7顯示運(yùn)行時(shí)鏈接 48打開動(dòng)態(tài)庫(kù) 487.7.2 dlsym() 487.7.3 dlerror() 487.7.4 dlclose() 49第8章Linux共享庫(kù)的組織 498.1共享庫(kù)版本49共享庫(kù)兼容性 49共享庫(kù)版本命名 498.1.3 SO-NAME程序需要記錄什么 508.2符號(hào)版本50基于符號(hào)的版本機(jī)制 508.2.3 Linux中的符號(hào)版本 518.3共享庫(kù)系統(tǒng)路徑518.4共享庫(kù)的查找過程 518.5環(huán)境變量528.6共享庫(kù)的
6、創(chuàng)建與安裝 52共享庫(kù)的創(chuàng)建52共享庫(kù)的安裝53共享庫(kù)構(gòu)造和析構(gòu)函數(shù) 53共享庫(kù)腳本53第 9 章 Windows 下的動(dòng)態(tài)鏈接 549.1 dll 介紹54基地址和 RVA549.1.3 dll共享數(shù)據(jù)段549.1.4 dll的簡(jiǎn)單例子54使用模塊定義文件 559.1.8 DLL顯示運(yùn)行時(shí)鏈接 559.2符號(hào)導(dǎo)出導(dǎo)入表55導(dǎo)出表559.2.2 EXP文件56導(dǎo)入表56導(dǎo)入函數(shù)的調(diào)用 569.3 DLL優(yōu)化57重定基地址 579.3.2 序號(hào)58導(dǎo)入函數(shù)綁定 589.4 C+與動(dòng)態(tài)鏈接589.5 DLL HELL59第4部分庫(kù)與運(yùn)行庫(kù)60第10章內(nèi)存6010.1程序的內(nèi)存布局 6010.2棧與
7、調(diào)用慣例61什么是棧61調(diào)用慣例61函數(shù)返回值傳遞6310.3堆與內(nèi)存管理63什么是堆6310.3.2 Linux進(jìn)程堆管理 6310.3.3 Windows 進(jìn)程堆管理64堆分配算法64第11章運(yùn)行庫(kù)6511.1入口函數(shù)和程序初始化 65程序從 main開始執(zhí)行嗎 65入口函數(shù)是如何實(shí)現(xiàn)的 65運(yùn)行庫(kù)與I/O 6611.1.4 MSVC CRT勺入口函數(shù)初始化 6611.2 C/C+運(yùn)行庫(kù) 6711.2.1 C語(yǔ)言運(yùn)行庫(kù) 6711.2.2 C語(yǔ)言標(biāo)準(zhǔn)庫(kù) 6711.2.3 glibc 和 MSVC CRT6711.3 運(yùn)行庫(kù)與多線程 6811.3.1 CRT的多線程困擾 6811.3.2 CR
8、T 改進(jìn) 68線程局部存儲(chǔ)實(shí)現(xiàn) 6911.4 C+全局構(gòu)造和析構(gòu) 6911.4.1 glibc全局構(gòu)造和析構(gòu) 691142 MSVC的全局構(gòu)造和析構(gòu) 7011.5 fread 的實(shí)現(xiàn)7111.5.1 緩沖7111.5.2 fread_s 7111.5.3 _fread_ no lock_s 7111.5.4 _read 71文本換行 7211.5.6 fread 回顧 72第12章系統(tǒng)調(diào)用與API 7212.1系統(tǒng)調(diào)用介紹72什么是系統(tǒng)調(diào)用 72系統(tǒng)調(diào)用的弊端 7212.2系統(tǒng)調(diào)用原理 73基于INT的Linux的經(jīng)典系統(tǒng)調(diào)用實(shí)現(xiàn) 7312.2.3 Linux的新型系統(tǒng)調(diào)用機(jī)制 7312.3
9、Windows API 7412.3.1 Windows API 概覽7412.3.2 為什么要使用 Windows API ? 74第13章運(yùn)行庫(kù)的實(shí)現(xiàn)7413.1 C語(yǔ)言運(yùn)行庫(kù) 74A.1字節(jié)序74第一章溫故而知新第二節(jié)萬變不離其宗凡是單純講史的章節(jié)我全部略去。本節(jié)講的主要是由CPU、內(nèi)存和I/O之間速度不匹配而設(shè)計(jì)的硬件架構(gòu)及其 發(fā)展。這個(gè)就不用細(xì)說了 CPU最快,內(nèi)存次之,I/O更慢。由于CPU和內(nèi)存速度 還算接近,所以把CPU和內(nèi)存算作一類,I/O單獨(dú)算作一類。當(dāng)然這里說的I/O 是指I/O設(shè)備,并不是操作。隨著發(fā)展CPU頻率越來越高,處理速度越來越快,內(nèi)存跟不上節(jié)奏了,它 們之間的
10、I/O也出現(xiàn)了速度不匹配的問題。因?yàn)镮/O設(shè)備可分為高速設(shè)備和低速設(shè)備兩種,所以為高速搭配北橋,低速 搭配南橋。它們之間的關(guān)系可用下圖表示:CPU的頻率只能達(dá)到4GHz無法提升,這是由CPU制造工藝決定的,是個(gè) 瓶頸,目前還無法突破。一個(gè)CPU能力有限,那就讓多個(gè)CPU共同工作提升效率。但是這樣的CPU 陣列各部件利用率不高,于是,發(fā)展出了多核心,其他部件共享的多核 CPU設(shè) 計(jì)。說白了,原來的CPU里面每個(gè)CPU一個(gè)核心,除此之外還有圍繞這個(gè)核的其他部件。但是現(xiàn)在多核CPU除了核心彼此獨(dú)立外,其他的部件是共享的 這一節(jié)就這么點(diǎn)內(nèi)容。第3節(jié)站得咼看得遠(yuǎn)Applktlons:Web Browse
11、rVMeo PlayerWord ProcftssorEmail ClientImage Viewer從下圖可以看出計(jì)算機(jī)的結(jié)構(gòu)大概是這樣的:Development Tools:C/C+ CompilerAssemblerLibrary ToolsDebug TootsDevelopment Libraries-Operating Systeni APIRuntime LibraryCalOperating System KernelHardware最底層是硬件,它提供硬件規(guī)格描述。再往上是操作系統(tǒng)內(nèi)核,它提供系統(tǒng) 調(diào)用。再往上是運(yùn)行庫(kù),它提供各種系統(tǒng) API。再往上就是各種系統(tǒng)軟件了。這種設(shè)
12、計(jì)具有上層屏蔽下層,上層提供接口的特點(diǎn)。這一節(jié)對(duì)接口的解釋非常好。作者說接口是一種協(xié)議,協(xié)議二字比較貼切。當(dāng)然這個(gè)協(xié)議不是計(jì)算機(jī)網(wǎng)絡(luò)中的protocol。第4節(jié)操作系統(tǒng)的功能有二。1、提供抽象接口。2、管理硬件不要讓CPU打盹操作系統(tǒng)經(jīng)歷了從多道程序設(shè)計(jì)、分時(shí)操作系統(tǒng)、到多任務(wù)操作系統(tǒng)等階段 多道程序設(shè)計(jì)是指CPU空閑的時(shí)候出讓CPU以提高CPU利用率的設(shè)計(jì); 分時(shí)是指給每個(gè)程序固定的時(shí)間片執(zhí)行, 時(shí)間片一到就停止的設(shè)計(jì),不過這個(gè)時(shí) 間片是輪轉(zhuǎn)著用的,不是一個(gè)程序用完了就沒了;多任務(wù)就是現(xiàn)在操作系統(tǒng)設(shè)計(jì) 了,程序以進(jìn)程的方式存在。搶占:OS對(duì)程序執(zhí)行具有絕對(duì)的控制權(quán),OS依據(jù)一定標(biāo)準(zhǔn)判斷該剝奪
13、哪個(gè) 程序的執(zhí)行就剝奪,想讓哪個(gè)程序執(zhí)行就讓哪個(gè)程序執(zhí)行。設(shè)備驅(qū)動(dòng)GDI和directX等都是硬件的抽象,是一個(gè)中間層,它們屏蔽了硬件的具體 細(xì)節(jié),提供了通用的操作接口。LBA(Logical Block Address):因?yàn)橛脖P結(jié)構(gòu)復(fù)雜,概念繁多,尋找一個(gè)扇區(qū) 要經(jīng)過很多步驟,這個(gè)比較麻煩。與其如此,不如干脆為每個(gè)扇區(qū)配置一個(gè)邏輯 編號(hào),這樣找扇區(qū)就好像是哈希算法一樣快。1.5內(nèi)存不夠怎么辦?程序在內(nèi)存中的地址空間是需要相互隔離的。這是為了防止一個(gè)程序在無意 間修改其他程序造成意料之外的結(jié)果,另外,這也是為了信息安全。內(nèi)存利用率要高,要不然程序在內(nèi)存和硬盤之間進(jìn)行I/O操作所花費(fèi)的時(shí)間 可
14、就多了。程序運(yùn)行的地址應(yīng)該是確定的。因?yàn)槎鄶?shù)程序指令跳轉(zhuǎn)的目標(biāo)地址是固定的,如果運(yùn)行地址不確定就不能保證每次都在目標(biāo)地址上運(yùn)行,這就需要重定向進(jìn)行調(diào)整,浪費(fèi)時(shí)間。解決上述問題的辦法是使用中間層,即把程序的運(yùn)行地址與目標(biāo)地址建立一 種映射關(guān)系。關(guān)于隔離我們平時(shí)說的什么32位,64位CPU啥的都是指CPU的處理能力,從硬件 的角度講,即,計(jì)算機(jī)的地址總線的條數(shù)。從 CPU的設(shè)計(jì)上講就是CPU 次能 夠處理的二進(jìn)制位數(shù),而這個(gè)位數(shù)還有一個(gè)學(xué)名叫字長(zhǎng)。內(nèi)存的物理地址空間就是真實(shí)的內(nèi)存空間,虛擬地址空間則是應(yīng)用于進(jìn)程的邏輯地址空間。分段我在想如何從16進(jìn)制的差值一下推斷出地址空間的大???以下是我的想法。
15、1位16進(jìn)制數(shù)字代表4位2進(jìn)制數(shù)字,換句話說16進(jìn)制 數(shù)字轉(zhuǎn)換為2進(jìn)制數(shù)字是以24為單位進(jìn)行換算的。那么根據(jù)某個(gè)16進(jìn)制數(shù)字所 在位置乘以當(dāng)前權(quán)值就可以得到該位置上的16進(jìn)制數(shù)字所代表的2進(jìn)制數(shù)字。而16進(jìn)制某位的權(quán)值等于低一位的權(quán)值乘以 24,并且16進(jìn)制最低位的權(quán)值是 2°,因此可以根據(jù)這個(gè)規(guī)律換算出相應(yīng)的 2進(jìn)制數(shù)字。來看個(gè)例子。書上說從0X00000000到0X00A00000的地址空間大小就等于 |0x00A00000-0x00000000|=|A00000因?yàn)?A 是 10 所以其等價(jià)于 |1000000|,現(xiàn)在按 照上述規(guī)律進(jìn)行換算。10 X 220+0 >216
16、+0 >212+0 >28+0 >24+0 >2°=10M(byte)。分段的方法可以使各進(jìn)程彼此隔離,并且可以使程序運(yùn)行的地址確定。分段的缺點(diǎn)就是它以程序?yàn)閱挝贿M(jìn)行處理,但是根據(jù)程序運(yùn)行的局部性原 理,程序通常情況下只有一少部分需要常駐內(nèi)存,因此以程序?yàn)閱挝粨Q進(jìn)換出嚴(yán) 重影響了內(nèi)存的利用率和處理速度。分頁(yè)頁(yè)面有3種:1、虛擬頁(yè);2、內(nèi)存頁(yè);3、磁盤頁(yè)。MMU(Memory Ma nageme nt Un it)負(fù)責(zé)把虛擬地址轉(zhuǎn)換成物理地址。1.6眾人拾柴火焰高線程基礎(chǔ)使用線程的好處?1、多線程可以有效利用等待時(shí)間。因?yàn)槟尘€程陷入等待狀態(tài)后別的線程可 以繼續(xù)執(zhí)
17、行;2、多線程不會(huì)使與用戶的交互中斷。因?yàn)榭梢砸粋€(gè)線程負(fù)責(zé)與用戶交互,另一個(gè)線程負(fù)責(zé)計(jì)算;3、能夠?qū)崿F(xiàn)程序內(nèi)部并發(fā)執(zhí)行操作;4、多核CPU等硬件的潛力只有多線程才能使其充分發(fā)揮;5、在數(shù)據(jù)共享方面更高效。線程的私有存儲(chǔ)空間?1、棧;2、線程局部存儲(chǔ)(Thread Local Storage,TLS; 3、寄存器。線程真正的并發(fā)執(zhí)行和非真正并發(fā)執(zhí)行?在同一時(shí)間只有處理器核心數(shù)量大于等于執(zhí)行線程數(shù)量的時(shí)候才是真并發(fā) 執(zhí)行,除此之外都是模擬出來的。線程調(diào)度:在同一時(shí)間處理器的核心數(shù)量小于執(zhí)行線程的數(shù)量時(shí)就需要在同 一核心不斷切換來執(zhí)行線程。改變線程優(yōu)先級(jí)的3種方式1、用戶指定優(yōu)先級(jí);2、根據(jù)等待狀態(tài)
18、的頻繁程度調(diào)整優(yōu)先級(jí);3、長(zhǎng)時(shí)間 得不到執(zhí)行而被提升優(yōu)先級(jí)。可搶占執(zhí)行線程和不可搶占執(zhí)行線程:線程的各種狀態(tài)完全由操作系統(tǒng)來控 制這就叫可搶占,就像某線程的時(shí)間片用完進(jìn)入就緒態(tài)一樣, 這就是由操作系統(tǒng) 來控制的。除此之外的就是不可搶占線程。不可搶占線程主動(dòng)放棄執(zhí)行的時(shí)機(jī):1、線程等待某事件發(fā)生時(shí)。2、線程主 動(dòng)放棄時(shí)間片。因?yàn)榫瓦@倆條件所以不可搶占線程調(diào)度的時(shí)機(jī)是確定的Linux下的多線程:不像 Windows那樣把線程和進(jìn)程分得那樣清楚, Linux 是以任務(wù)為單位的,如果某幾個(gè)任務(wù)的執(zhí)行是做同一件事的各個(gè)部分, 那么這幾 個(gè)任務(wù)就可以看成是線程,而這件事就可以看成是進(jìn)程。 所以Linux下
19、的線程和 進(jìn)程是動(dòng)態(tài)的概念。Linux下的fork函數(shù):fork是叉子的意思,我不知道為啥 Linux用它來給函 數(shù)命名。它的作用就是復(fù)制任務(wù),新任務(wù)和原任務(wù)共享同一塊內(nèi)存空間, 并且是 寫時(shí)復(fù)制。所謂寫時(shí)復(fù)制就是寫的時(shí)候才從內(nèi)存空間里面復(fù)制出一塊給你寫, 原 內(nèi)存空間內(nèi)容不變。讀的時(shí)候新舊任務(wù)讀同一塊內(nèi)存空間。Linux下的exec函數(shù):fork產(chǎn)生的是本任務(wù)的鏡像,也就是復(fù)制品。兩個(gè)同 樣的任務(wù)完成同樣的功能是浪費(fèi)啊,所以fork是個(gè)半成品函數(shù),必須搭配別的函數(shù)才有用,這個(gè)函數(shù)就是exec函數(shù)。Exec函數(shù)用來執(zhí)行別的可執(zhí)行文件,換 句話說就是干別的事。所以可以把fork理解成在一塊內(nèi)存空
20、間上創(chuàng)造出個(gè)接口 給exec執(zhí)行新任務(wù)。Linux下的clone函數(shù):我對(duì)它的理解就是fork和exec二合一,clone的作 用就是產(chǎn)生新線程。線程安全要知道線程安全就得知道啥叫線程不安全。所謂線程不安全就是指多個(gè)線程 同時(shí)訪問共享數(shù)據(jù)造成結(jié)果的不確定性。原子操作:絕對(duì)不會(huì)被打斷的操作。因?yàn)樵邮腔瘜W(xué)反應(yīng)中的最小微粒不可 再分所以拿這個(gè)來比擬原子操作。它適用于簡(jiǎn)單應(yīng)用環(huán)境。解決線程不安全的通用方法是鎖。線程同步:一開始我還以為是多個(gè)線程一起訪問某個(gè)資源呢,其實(shí)不然,線程同步是解決線程訪問同一數(shù)據(jù)資源的解決方式,保證了同一時(shí)間只有同一線程訪問數(shù)據(jù)資源,從而保證了線程安全。鎖一一二元信號(hào)量:最簡(jiǎn)
21、單的鎖機(jī)制。只允許一個(gè)線程獨(dú)占,一旦有線程占 用,鎖就呈現(xiàn)占用狀態(tài),其他線程無法訪問資源。否則,非占用狀態(tài),可以接受 線程。鎖一一多元信號(hào)量:就是它允許多個(gè)線程同步訪問資源,比二元信號(hào)量高能 一些。我感覺信號(hào)量就像管道。一個(gè)線程想訪問資源它就必須首先獲取一個(gè)管道, 這樣原來的管道數(shù)就少 1于是信號(hào)量首先減1。但是如果信號(hào)量減1以后成為 負(fù)值,說明原來的管道數(shù)為0,即原來就已經(jīng)沒有管道了,那么此時(shí)信號(hào)量機(jī)制 就只能讓該線程等待了,這就是 P原語(yǔ)。而如果一個(gè)線程用完了資源想要釋放, 那么它必須歸還它所使用的管道,那么管道總數(shù)應(yīng)該加1,即信號(hào)量加1。正因?yàn)樾盘?hào)量已經(jīng)加1,如果此時(shí)的信號(hào)量值為小于1,
22、那說明在加1之前管道總量 就已經(jīng)透支了,而且先前那些因?yàn)闆]有獲得管道的線程還在那等著呢。正好有個(gè) 線程歸還了管道,V原語(yǔ)趕緊從那些等待的線程中找一個(gè)出來把管道給它,這就是在信號(hào)量值小于1的情況下喚醒線程的意思。鎖一一互斥量(Mutex):信號(hào)量與互斥量的區(qū)別是一個(gè)信號(hào)量可以被一個(gè)線 程獲取并釋放給另一個(gè)線程使用,正如 V原語(yǔ)的操作。而互斥量始終都是一個(gè) 線程,上鎖是這個(gè)線程,這個(gè)線程不執(zhí)行完就不解鎖。鎖一一臨界區(qū):獲取臨界區(qū)的鎖為進(jìn)入臨界區(qū),釋放鎖為離開臨界區(qū)。它的 作用對(duì)象是某一位以進(jìn)程,一旦某進(jìn)程進(jìn)入臨界區(qū),其他進(jìn)程就無法進(jìn)入。除此 之外,臨界區(qū)與互斥量相同。鎖一一讀寫鎖:互斥量、臨界區(qū)和
23、信號(hào)量適用于讀寫都非常頻繁的場(chǎng)合,而讀寫鎖適用于讀頻繁而寫不頻繁的場(chǎng)合。它的工作規(guī)律可用下表表示:鎖寫鎖狀態(tài)以共享方式獲取以獨(dú)占方式獲取自由成功成功卄享/、成功等待獨(dú)占等待等待鎖一一條件變量:相當(dāng)于一個(gè)開關(guān),它可以讓等待它的線程繼續(xù)等待也可以 讓它們繼續(xù)執(zhí)行。而這個(gè)開關(guān)需要一些其他的線程打開或關(guān)閉它??芍厝牒瘮?shù):一個(gè)函數(shù)沒有執(zhí)行完全,但是由于內(nèi)部因素或者外部調(diào)用, 又 一次開始執(zhí)行該函數(shù)。它不產(chǎn)生任何不良后果。產(chǎn)生可重入的條件:1多線程共同執(zhí)行該函數(shù)。2、函數(shù)自己直接或者間接 調(diào)用自身??芍厝牒瘮?shù)的特點(diǎn):1不使用任何(局部)靜態(tài)或全局的非 const變量。 因?yàn)槿绻褂玫脑捤蜕嫦硬倏v共享數(shù)據(jù)
24、,這樣會(huì)導(dǎo)致線程不安全。2、不返回任何(局部)靜態(tài)或全局的非const變量的指針。因?yàn)檫@同樣涉及到共享數(shù)據(jù)。3、僅依賴于調(diào)用方提供的參數(shù)。因?yàn)檫@樣可以把函數(shù)的執(zhí)行過程局限在局部。4、 不依賴于任何單個(gè)資源的鎖。單個(gè)資源的鎖不允許被中斷,這不符合可重入函數(shù) 的定義。5、不調(diào)用任何不可重入函數(shù)。這個(gè)沒啥好說的,如果調(diào)用了,可重入 函數(shù)就成了不可重入函數(shù)。可重入性質(zhì)是并發(fā)安全的強(qiáng)力保證可在多線程環(huán)境下 大膽使用。過度優(yōu)化:P53這個(gè)例子就是說本來2個(gè)x+結(jié)果是2,但是經(jīng)過上鎖以后卻是1,這證明即使通過鎖機(jī)制也不能完全保障計(jì)算正確,這是計(jì)算機(jī)內(nèi)部工作 機(jī)制造成的線程不安全。CPU對(duì)程序的優(yōu)化可能導(dǎo)致線
25、程不安全,因?yàn)樗鼤?huì)調(diào)整程序語(yǔ)句執(zhí)行順序 以達(dá)到CPU所謂的優(yōu)化,這有時(shí)候很麻煩。Volatile關(guān)鍵字可以阻止這種優(yōu)化。1、它阻止編譯器為提高程序執(zhí)行速度將一個(gè)變量緩存到寄存器內(nèi)而不寫回。2、它阻止編譯器調(diào)整語(yǔ)句執(zhí)行順序。這兩件事就是volatile所做的具體工作。但是, volatile能管住編譯器管不了 CPU,CPU還是能對(duì)指令進(jìn)行動(dòng)態(tài)調(diào)整。P54舉了一個(gè)double-check的例子,雖然現(xiàn)在我對(duì)這個(gè)沒有多深的理解,但 是從這個(gè)例子中我看到作者是怎么分析的。它是將各個(gè)語(yǔ)句內(nèi)部實(shí)際所進(jìn)行的操 作都列出來進(jìn)行分析的,這個(gè)值得我學(xué)習(xí)。雖然volatile管不了 CPU,但是CPU有CPU相當(dāng)
26、于volatile的指令,一般這個(gè)指令叫做barrier。163多線程內(nèi)部情況線程分為內(nèi)核級(jí)線程和用戶級(jí)線程,內(nèi)核級(jí)線程是用戶直接接觸不到的,用 戶只能接觸到用戶級(jí)線程。3種內(nèi)核級(jí)線程與用戶級(jí)線程的模型。1、一對(duì)一模型:就是每個(gè)用戶級(jí)線程都對(duì)應(yīng)一個(gè)內(nèi)核級(jí)線程,但反過來不是,因?yàn)閮?nèi)核級(jí)線程可能沒有用戶級(jí)線程與之對(duì)應(yīng)。一般直接使用API或者系統(tǒng)調(diào)用創(chuàng)建的線程均為一對(duì)一模型。它的優(yōu)點(diǎn):真正實(shí)現(xiàn)線程的并發(fā)執(zhí)行,線程之間彼此互不影響。它的缺點(diǎn):1、許多操作系統(tǒng)限制了內(nèi)核級(jí)線程的數(shù)量導(dǎo)致用戶級(jí)線程數(shù)量 受限。2、許多操作系統(tǒng)用在內(nèi)核級(jí)線程調(diào)度上的開銷較大,主要為上下文切換 開銷,致使用戶級(jí)線程執(zhí)行效率低下
27、。2、多對(duì)一模型:多個(gè)用戶級(jí)線程對(duì)應(yīng)同一個(gè)內(nèi)核級(jí)線程,線程的切換由用戶級(jí)代碼決定。作者說多處理器對(duì)提升處理速度沒有明顯幫助,這是當(dāng)然的了, CPU處理的是內(nèi)核級(jí)線程,而這個(gè)模型就在那擺著,CPU也只能按照這個(gè)模式來處理。再說了,一個(gè)線程只能在一個(gè)核上跑,你再多給幾個(gè)核也沒用啊。它的優(yōu)點(diǎn):它比一對(duì)一模型快,還有高效的上下文切換和近似無限制的線程 數(shù)量。它的缺點(diǎn):只要有一個(gè)線程阻塞,對(duì)應(yīng)于同一個(gè)內(nèi)核級(jí)線程的其他線程也無 法執(zhí)行,該內(nèi)核級(jí)線程也阻塞,這很好理解,因?yàn)橹挥幸粭l通路。3、多對(duì)多模型:是上面二者的合體。很顯然它能克服上述二者的缺點(diǎn),同 理多處理器也無法顯著提升它的執(zhí)行效率。第二章編譯和鏈接
28、2.1被隱藏了的過程以前學(xué)的程序的執(zhí)行過程是編輯、編譯、鏈接、執(zhí)行。今天這本書把這個(gè)過 程更加細(xì)化了,它以C語(yǔ)言中的helloworld程序?yàn)槔M(jìn)行說明,講的大概是從編 譯到鏈接的過程。也是包括4步:1、預(yù)處理;2、編譯;3、匯編;4、鏈接。從這個(gè)順序可以 看出在C語(yǔ)言中預(yù)處理是在編譯之前。預(yù)編譯預(yù)編譯是個(gè)獨(dú)立的過程,不同于源文件的.cpp格式和頭文件的.h格式,預(yù)編 譯得到的文件后綴是.i或者.ii。預(yù)編譯的主要?jiǎng)幼骶褪翘幚泶a中以#開頭的指令,具體可見P64這些步驟。 因?yàn)楹暌呀?jīng)展開所以.i文件不包含任何宏定義??梢愿鶕?jù).i文件查看宏定義和文 件包含是否正確。預(yù)編譯需要預(yù)編譯器。編譯編譯的
29、過程是把預(yù)處理得到的文件進(jìn)行詞法分析、語(yǔ)法分析、語(yǔ)義分析和優(yōu) 化后生成相應(yīng)的匯編代碼文件。匯編匯編階段是通過匯編器完成的,其作用就是把匯編指令轉(zhuǎn)換成機(jī)器指令。匯 編結(jié)束以后生成目標(biāo)文件.obj鏈接鏈接簡(jiǎn)而言之就是把目標(biāo)文件鏈接在一起生成可執(zhí)行文件的過程,但是實(shí)際上這是一個(gè)非常復(fù)雜的過程,并不像看上去那么簡(jiǎn)單。2.2編譯器做了什么編譯的過程可以分為掃描、語(yǔ)法分析、語(yǔ)義分析、源代碼優(yōu)化、代碼生成、 目標(biāo)代碼優(yōu)化等6步。詞法分析這一過程是交給掃描器執(zhí)行的,目的是把程序語(yǔ)句劃分成若干記號(hào)。這些記號(hào)一般包括:1、關(guān)鍵字;2、標(biāo)識(shí)符;3、字面量(數(shù)字,字符串等);4、特殊符號(hào)(加號(hào),等號(hào)等)。此外,掃描器
30、還將標(biāo)識(shí)符放到符號(hào)表,將字面量放到文字表中以備后用。詞法分析需要此法掃描器。語(yǔ)法分析它是對(duì)詞法分析產(chǎn)生的各種記號(hào)進(jìn)行語(yǔ)法分析,并產(chǎn)生一顆語(yǔ)法樹語(yǔ)句內(nèi)容含義的區(qū)分,語(yǔ)法的檢查等都是在此階段完成的。語(yǔ)法分析需要語(yǔ)法分析器。語(yǔ)義分析語(yǔ)義分析需要語(yǔ)義分析器。語(yǔ)義分析就是分析該語(yǔ)句的意思,就是它能做什么,有啥用。編譯器所能做的包括靜態(tài)語(yǔ)義分析和動(dòng)態(tài)語(yǔ)義分析。靜態(tài)語(yǔ)義:編譯期能夠確定的語(yǔ)義,它主要包括類型和聲明的匹配,類型的 轉(zhuǎn)換等。我想C+中的靜態(tài)綁定應(yīng)該也屬于靜態(tài)語(yǔ)義吧。動(dòng)態(tài)語(yǔ)義:運(yùn)行期能夠確定的語(yǔ)義以及相關(guān)問題, 比如說異常處理。我同時(shí) 在想C+中的動(dòng)態(tài)綁定應(yīng)該屬于動(dòng)態(tài)語(yǔ)義。語(yǔ)義分析對(duì)語(yǔ)法樹各節(jié)點(diǎn)進(jìn)
31、行了類型標(biāo)記和類型轉(zhuǎn)換,還更新了符號(hào)表里的 符號(hào)類型。224中間語(yǔ)言生成編譯器有很多層次的優(yōu)化,源碼級(jí)別的優(yōu)化是其中一個(gè)層次。源碼級(jí)的優(yōu)化需要源碼級(jí)優(yōu)化器。這個(gè)優(yōu)化是把語(yǔ)法樹轉(zhuǎn)換成中間代碼,并在中間代碼上進(jìn)行的。常見的中間代碼有三地址碼和 P代碼。中間代碼將編譯器分成了前端和后端,前端負(fù)責(zé)產(chǎn)生與機(jī)器無關(guān)的中間代 碼,后端負(fù)責(zé)把中間代碼轉(zhuǎn)換成目標(biāo)代碼??缙脚_(tái)的編譯器并不是放在任意一個(gè)平臺(tái)上都絕對(duì)能用,只不過它能支持的 平臺(tái)很多而已。這是因?yàn)榫幾g器使用同一個(gè)前端,而針對(duì)不同的平臺(tái)使用不同的 后端。目標(biāo)代碼的生成與優(yōu)化編譯器的后端包括代碼生成器和目標(biāo)代碼優(yōu)化器。代碼生成器將中間代碼轉(zhuǎn)換成目標(biāo)代碼,該
32、過程依賴于目標(biāo)機(jī)器。目標(biāo)代碼優(yōu)化器對(duì)目標(biāo)代碼進(jìn)行優(yōu)化, 比如選擇合適的尋址方式,以移位代 替數(shù)乘等?,F(xiàn)在的編譯器非常復(fù)雜,上述提到的這些方面也變得非常復(fù)雜。變量和函數(shù)的地址都是在最終鏈接的時(shí)候才確定的,然后變成可執(zhí)行文件。2.3鏈接器年齡比編譯器長(zhǎng)作者把鏈接比喻為拼圖的拼接。2.4模塊拼接一一靜態(tài)鏈接將源代碼模塊組裝起來的過程就是鏈接。鏈接的過程包括:1、地址和空間分配;2、符號(hào)決議;3、重定位等。.obj文件即目標(biāo)文件和庫(kù)一起鏈接成可執(zhí)行文件。庫(kù)是由一些常用的代碼編譯成的目標(biāo)文件的包, 是一個(gè)集合。最常見的庫(kù)是 運(yùn)行時(shí)庫(kù),是支持程序運(yùn)行的基本函數(shù)的集合。每個(gè)目標(biāo)文件都是單獨(dú)編譯的。模塊A想要
33、調(diào)用模塊B的C函數(shù),A必須要知道C的地址,但是現(xiàn)在A不 知道C的地址,但是A給C留了位置,等到鏈接器鏈接時(shí)再在這個(gè)位置上填上 C的地址。如果C的地址被改動(dòng)了, A中所有調(diào)用C的地方都需要進(jìn)行相應(yīng)的 更改,這些都可藉由鏈接器完成。這是靜態(tài)鏈接的基本功能和作用。在鏈接的過程中需要對(duì)目標(biāo)文件中定義在其他目標(biāo)文件中的函數(shù)和變量的調(diào)用指令進(jìn)行重新調(diào)整,注意這里說的是指令!書中舉的例子意在說明,當(dāng)目標(biāo) 文件A調(diào)用目標(biāo)文件B中的變量C時(shí),因?yàn)闀簳r(shí)無法知道C的位置,所以指令 先把表示C的位置置為某一值,等到鏈接的時(shí)候再把這值修正為 C的地址,這 一過程叫做重定位,像C這樣的位置被稱為重定位入口。第三章目標(biāo)文件
34、里有什么.obj是目標(biāo)文件,所以可以知道目標(biāo)文件是指編譯后生成的文件,目標(biāo)文件幾乎和可執(zhí)行文件相同只是稍微有點(diǎn)不同而已。其不同之處在于有些符號(hào)和地址沒有被調(diào)整。3.1目標(biāo)文件的格式正是因?yàn)槟繕?biāo)文件與可執(zhí)行文件幾乎相同,所以它們的存儲(chǔ)格式是一樣的, 可以把它們近似看成同一種文件。Linux下的動(dòng)態(tài)鏈接庫(kù)格式為.so,Windows和Linux下的靜態(tài)鏈接庫(kù)格式分 別為.lib和a靜態(tài)鏈接庫(kù)是一個(gè)文件,該文件包含了很多目標(biāo)文件,它是一個(gè)整體。Linux下的可執(zhí)行文件是按照ELF格式存儲(chǔ)的,ELF標(biāo)準(zhǔn)包含4種文件,請(qǐng) 看P81。我所熟悉的 Windows下的DLL就屬于共享目標(biāo)文件。3.2目標(biāo)文件是
35、什么樣的目標(biāo)文件一般包含了哪些內(nèi)容?編譯后的機(jī)器指令代碼、數(shù)據(jù)、連接所需的信息、符號(hào)表、調(diào)試信息、字符串等。目標(biāo)文件把信息按照屬性的不同分段存儲(chǔ)。寫到這里我感覺這書上說的與老 師課上講的程序在內(nèi)存中的分段方法有些相似。在目標(biāo)文件中,編譯后的機(jī)器指令代碼放在代碼段(Code Section)中,段名一般為.code和.text。全局變量和靜 態(tài)變量放在數(shù)據(jù)段(Data Section)中,段名一般為.data。BSS段(Block Started By Symbo I)用來存儲(chǔ)未初始化的靜態(tài)變量和全局變量。話雖如此bss中并沒有這些變量的內(nèi)容,它只是為這些變量按照所占空間大小預(yù) 留空間而已。由于
36、這些變量默認(rèn)就是 0,所以壓根沒必要再為它們分配一個(gè)數(shù)據(jù)0,也沒有必要讓它們待在data段中。因此bss的作用是為這些變量預(yù)留空間。另外目標(biāo)代碼還有一個(gè)文件頭用來保存該目標(biāo)文件的信息,它里面還有一個(gè)段表。源代碼被編譯以后生成兩種段數(shù)據(jù)段和指令段,.code.text屬于指令 段.data.bss屬于數(shù)據(jù)段。這樣分主要有3點(diǎn)好處:1、防止程序被有意無意篡改。這是因?yàn)橹噶疃沃蛔x,數(shù)據(jù)段可讀寫。2、提高了緩存命中率。3、節(jié)省內(nèi)存空間。因?yàn)橹噶疃慰杀欢鄠€(gè)副本共享,但是副本可以擁有自己 的數(shù)據(jù)段。3.3 挖掘 SimpleSection.o原來目標(biāo)文件中的段還有只讀數(shù)據(jù)段(.rodata)、注釋信息段(
37、.comment)、 堆棧提示段(.note.GNU-stack)。從書中所給的例子來看一個(gè)ELF文件只有4個(gè)段是由內(nèi)容的, 即.data .text、.rodata .comment。從圖3-3可以看出在內(nèi)存中,從低地址到高地址是按照ELF header text、data rodata comment、other data的順序存放的。3.3.3 BSS段由本小節(jié)可知,全局變量可能因?yàn)檎Z(yǔ)言和編譯器的不同不一定存放在bss段,但是靜態(tài)變量一定存放在bss段。雖說bss存放的是未初始化的靜態(tài)和全局變量,但是有些變量如果被初始化為0,它也會(huì)被放在bss中,這是編譯器的優(yōu)化,有時(shí)候這種優(yōu)化會(huì)帶來麻
38、煩。其他段表3-2列出了其他段及意義。此外,這個(gè)段還可以自定義。3.4 ELF文件結(jié)構(gòu)描述圖3-4展示了 ELF的層次結(jié)構(gòu)。最重要的兩個(gè)部分就是ELF文件頭和段表。ELF文件頭描述整個(gè)文件的基 本屬性,段表描述各段的信息。文件頭清單3-2清楚地描述了 ELF文件頭的信息,P95黑體部分列舉了 ELF文件 頭包含的信息。ELF文件兼容各平臺(tái),它的文件結(jié)構(gòu)和相關(guān)參數(shù)定義在” /usr/include/elf.h里, 它有32位和64位兩種。表3-3展示了 elf.h的自定義變量體系。表3-4展示了 ELF文件頭結(jié)構(gòu)成員含義。ELF魔數(shù):ELF文件頭的第一個(gè)字段是Magic,包含16bytes,對(duì)應(yīng)
39、于Elf32_Ehdr中的e_ident成員。Magic用來表示平臺(tái)的各種屬性。14個(gè)字節(jié)是所有ELF文件都相同的標(biāo)識(shí)碼,分別對(duì)應(yīng) del、E、L、F,這 四個(gè)字節(jié)就是ELF魔數(shù)。操作系統(tǒng)通過確認(rèn)魔術(shù)是否正確以決定是否加載可執(zhí) 行文件。第5個(gè)字節(jié)用來表示ELF文件是32位的還是64位的。第6個(gè)字節(jié)用來表示ELF字節(jié)序。第7個(gè)字節(jié)用來表示ELF文件版本號(hào)。后面的9個(gè)字節(jié)用來預(yù)留,有些平臺(tái)可能用來作為擴(kuò)展標(biāo)志。Elf32_Ehdr中的e_type成員表示ELF文件類型,ELF總共有三種文件類型 如表3-5所示。操作系統(tǒng)是通過判斷文件類型而不是擴(kuò)展名來確定 ELF文件類 型的。Elf32_Ehdr中
40、的e_machine成員表示ELF文件的平臺(tái)屬性。雖然 ELF遵循 統(tǒng)一標(biāo)準(zhǔn)但不代表同一 ELF文件可以在不同平臺(tái)上使用。段表它用來表示各個(gè)段的信息,ELF文件中的段是由段表決定的。一個(gè)ELF文件不僅僅包含像data text、bss這樣的段,還包括其他的輔助性段。段表是一個(gè)Elf32_Shdr類型的結(jié)構(gòu)體數(shù)組,元素的個(gè)數(shù)代表段的個(gè)數(shù),每 個(gè)元素對(duì)應(yīng)一個(gè)段。這個(gè)Elf32_Shdr被稱為段描述符。表3-7描述了 Elf32_Shdr中各字段的意義。段的名稱對(duì)于編譯和鏈接有意義,對(duì)操作系統(tǒng)無意義。決定段的類型的是段 的類型字段,并不是段的后綴名和名稱。段的類型和段的標(biāo)志位字段決定了段的屬性。表3
41、-8展示了段的各種類型。段的標(biāo)志位表示該段在進(jìn)程虛擬地址空間中的屬性,如是否可讀。表3-9列出了段的各種屬性。表3-10列出了系統(tǒng)保留段的各種屬性。段的連接信息包括sh_link和sh_info,它們與鏈接相關(guān),如表3-11所示。重定位表目標(biāo)文件中有一個(gè)SHT_REL的.rel.text字段,它是重定位表。重定位發(fā)生在連接的過程中,這個(gè)在前面已經(jīng)講過,重定位表記錄了重定位相關(guān)信息。字符串表顧名思義,就是用來表示各種名稱的字符串的表。它是一個(gè)裝有各種字符串的表格,每個(gè)字符在表中都有一個(gè)固定的位置。這種表在ELF文件中保存為2種形式一一.strtab和.shstrtab,它們分別是字 符串表和段字
42、符串表,它們?cè)贓LF文件中都以獨(dú)立的段而存在。為了輕松地找到這個(gè)段,在ELF文件頭中包含了這兩個(gè)段的下標(biāo),名為e_shstrndx)3.5鏈接的接口符號(hào)鏈接是組合目標(biāo)文件的過程,目標(biāo)文件是根據(jù)彼此之間的地址相互引用, 從 而組合成可執(zhí)行文件的。而,這個(gè)地址可以簡(jiǎn)單地理解為目標(biāo)文件中的函數(shù)和變 量。在這里,函數(shù)和變量統(tǒng)稱為符號(hào),函數(shù)名和變量名統(tǒng)稱為符號(hào)名。鏈接器的著眼點(diǎn)主要在定義在本目標(biāo)文件和定義在其他目標(biāo)文件的全局性 符號(hào),因?yàn)橹挥羞@些涉及到目標(biāo)文件之間的組合。3.5.1 ELF符號(hào)表結(jié)構(gòu)ELF文件的符號(hào)表是一個(gè)段,段名為“ .symtab”它是一個(gè)Elf32_sym類型 的數(shù)組,每個(gè)數(shù)組元素
43、代表一個(gè)符號(hào)。在Elf32_sym結(jié)構(gòu)體中有一個(gè)32bit成員叫st_info,低4bit表示符號(hào)的類型,高28bit符號(hào)的綁定信息。綁定信息具體可見表3-15,符號(hào)類型可參見表3-16。Elf32_sym.st_shndx:如果符號(hào)定義在本目標(biāo)文件中,它表示該符號(hào)所在的 段在段表中的下標(biāo),否則它具有其他意義。st_shndx具體信息可見表3-17。Elf32_sym.st_value:每個(gè)符號(hào)都有一個(gè)對(duì)應(yīng)值,它一般為變量和函數(shù)的地址。 st_value的意義有如下幾種:1、如果符號(hào)定義在目標(biāo)文件中,并且它不是COMMON 塊類型, 則st_value代表符號(hào)在段中的偏移。2、 如果符號(hào)定義在
44、目標(biāo)文件中并且是COMMON塊類型,則st_value表示符號(hào)的對(duì)齊屬性。3、在可執(zhí)行文件中st_value表示符號(hào)的虛擬地址。特殊符號(hào)鏈接器本身自帶的,不是你定義的,定義在鏈接腳本中的,但是你可以用的, 這樣的符號(hào)是特殊符號(hào)。它們存在的時(shí)機(jī)是鏈接器鏈接生成可執(zhí)行文件時(shí),此時(shí) 鏈接器會(huì)將它們解析成正確的值,書中P110舉了幾個(gè)具有代表性的特殊符號(hào)。符號(hào)修飾與函數(shù)簽名本小節(jié)明確了函數(shù)簽名的概念。函數(shù)簽名:主要是指函數(shù)名和參數(shù)類型,其次是所在類和命名空間等。它用 于區(qū)分不同函數(shù)。編譯器和連接器會(huì)使用名稱修飾的辦法加工函數(shù)簽名使之成為修飾后名稱, 在C+中為符號(hào)名。不同的編譯器對(duì)函數(shù)簽名的修飾方法不
45、同, 這導(dǎo)致不同種類的目標(biāo)文件無法 互連。原來C+編譯器已經(jīng)默認(rèn)定義了宏 cplusplus來兼容C語(yǔ)言和C+。弱符號(hào)和強(qiáng)符號(hào)在不同目標(biāo)文件中含有相同全局性符號(hào)定義,這種情況被稱為強(qiáng)符號(hào),它會(huì)引起符號(hào)重定義。C/C+編譯器認(rèn)為未初始化的全局變量是弱符號(hào)。這個(gè)強(qiáng)弱符號(hào)是可以被定義的,所以強(qiáng)弱之別是根據(jù)定義來劃分的, 并不針 對(duì)符號(hào)的引用,P117代碼說明了這一點(diǎn)。鏈接器根據(jù)符號(hào)的強(qiáng)弱來處理和選擇定義的全局變量:1、不允許多次定義強(qiáng)符號(hào),否則報(bào)錯(cuò)。2、同一個(gè)符號(hào)在各目標(biāo)文件中出現(xiàn)了多次,但只有一個(gè)是強(qiáng)符號(hào),那么編 譯器選擇強(qiáng)符號(hào)的那個(gè)。3、如果一個(gè)符號(hào)在所有目標(biāo)文件中都是弱符號(hào),那么編譯器選擇占用
46、空間最大的一個(gè)。由此可見編譯器對(duì)于弱符號(hào)的選擇并不明顯,所以由弱符號(hào)造成的錯(cuò)誤也相對(duì)難以發(fā)現(xiàn)。強(qiáng)引用:目標(biāo)文件對(duì)于非本目標(biāo)文件的符號(hào)引用, 在鏈接成可執(zhí)行文件的過 程中,如果找不到該符號(hào)的定義,就報(bào)未定義錯(cuò)誤。弱引用:與強(qiáng)引用差不多,只不過在找不到符號(hào)時(shí)不報(bào)錯(cuò)。強(qiáng)弱引用主要用于庫(kù)的鏈接。對(duì)于未定義的弱引用,編譯器為便于識(shí)別把它 看作是某一值,一般為0。弱符號(hào)與COMMON塊聯(lián)系較密切。弱引用是可以手動(dòng)聲明的,如 P118第一段代碼所示。弱符號(hào)的作用在于提供一個(gè)默認(rèn)的庫(kù)符號(hào), 但是當(dāng)用戶想要自定義該符號(hào)的 時(shí)候,該自定義符號(hào)就獲得了更高的優(yōu)先級(jí)。 而弱引用的作用在于增強(qiáng)了程序的 可擴(kuò)展性,因?yàn)橛?/p>
47、了弱引用程序功能更強(qiáng),沒有弱引用程序也能正常運(yùn)行。3.6調(diào)試信息目標(biāo)文件和可執(zhí)行文件中都可能保存調(diào)試信息,ELF文件采用DWARF格式 保存調(diào)試信息。由于調(diào)試信息與可執(zhí)行文件最終結(jié)果無關(guān), 而且占用大量空間,所以在發(fā)布 軟件時(shí)應(yīng)該去掉這些調(diào)試信息。第4章靜態(tài)鏈接靜態(tài)鏈接是指將目標(biāo)文件鏈接在一起形成可執(zhí)行文件的過程。4.1空間與地址分配相似段合并靜態(tài)鏈接過程是把各目標(biāo)文件中的各段合并到可執(zhí)行文件中的相應(yīng)段中。鏈接器為目標(biāo)文件分配地址和空間。 這個(gè)空間有兩層含義,既包括在可執(zhí)行 文件中占有的空間也包括在虛擬地址中分配的空間。其中虛擬地址空間的分配關(guān) 系重大。靜態(tài)鏈接的過程一般分兩步一一1、空間與地
48、址分配。2、符號(hào)解析與重定位。第一步就是獲取段信息,合并段將它們映射到可執(zhí)行文件的段表信息中。 整 理符號(hào)和引用并放入全局符號(hào)表中。第二步,實(shí)際上就是鏈接,把目標(biāo)文件中的地址呀、符號(hào)呀、數(shù)據(jù)等進(jìn)行重 定位然后鏈接。VMA:Virtual Memory AddressLMA : Load Memory Address鏈接前的VMA都是0,鏈接后就有實(shí)實(shí)在在的地址了。符號(hào)地址的確定符號(hào)地址在原來的目標(biāo)文件中的每個(gè)段中都有一個(gè)偏移量,這個(gè)偏移量是固定的,所以在鏈接的過程中只要在虛擬地址的基礎(chǔ)上再加上這個(gè)偏移量就是某符 號(hào)在虛擬地址空間中的地址。4.2符號(hào)解析與重定位在空間和地址分配完成以后,鏈接器即
49、將進(jìn)行符號(hào)解析與重定位。本小節(jié)舉 了個(gè)例子,用了很多匯編代碼,有些晦澀難懂。目標(biāo)文件中使用的都是虛擬地址不是物理地址,這一點(diǎn)很重要。目標(biāo)文件的起始地址都是0。重定位表它存儲(chǔ)著與重定位相關(guān)的信息每個(gè)要被重定位的ELF段都對(duì)應(yīng)一個(gè)重定位表,重定位表本身也是一個(gè)段, 所以你也可以叫重定位表為重定位段。每一個(gè)要被重定位的地方叫做重定位入口。重定位入口的偏移表示入口在要被重定位的段中的位置。重定位表的實(shí)質(zhì)是一個(gè)Elf32_Rel的結(jié)構(gòu)體數(shù)組,每個(gè)數(shù)組元素對(duì)應(yīng)一個(gè)重 定位入口423符號(hào)解析重定位的過程伴隨著符號(hào)解析的過程。每個(gè)重定位的入口對(duì)應(yīng)一個(gè)符號(hào)引用,鏈接器會(huì)查找有所有目標(biāo)文件的符號(hào) 表所組成的全局符
50、號(hào)表,然后根據(jù)這個(gè)全局符號(hào)表進(jìn)行重定位。指令修正方式32位x86平臺(tái)下的ELF文件的重定位入口所修正的指令尋址方式只有2種:絕對(duì)近址32位尋址和相對(duì)近址32位尋址。修正的位置長(zhǎng)度為4byte&經(jīng)過絕對(duì)地址修正方式修正得到的地址是該符號(hào)的實(shí)際地址,而相對(duì)地址尋址方式得到的是符號(hào)與被修正位置的距離。4.3 COMMON 塊相同的符號(hào)定義在多個(gè)不同的目標(biāo)文件中, 但是類型各不相同,這說明它們 不是同一個(gè)變量或者函數(shù),因此不能對(duì)它們進(jìn)行相同的操作。但是鏈接器只認(rèn)符 號(hào)不認(rèn)類型,它認(rèn)為它們都一樣。這種情況主要分為3種:1、至少2個(gè)強(qiáng)符號(hào)類型不一致。2、一個(gè)強(qiáng)符號(hào)和多個(gè)弱符號(hào)類型不一致。3、至少2
51、個(gè)弱符號(hào)類型不一致。強(qiáng)符號(hào)是指定義在目標(biāo)文件中全局性符號(hào), 包括函數(shù)和變量,顯然它們?nèi)绻?有相同的多個(gè),那就是重定義,這本身就會(huì)報(bào)錯(cuò)?,F(xiàn)在的編譯器和鏈接器都支持 COMMON塊機(jī)制。它主要針對(duì)的對(duì)象是弱 符號(hào)。如果在眾多符號(hào)之中有一個(gè)符號(hào)是強(qiáng)符號(hào),那么符號(hào)所占空間與強(qiáng)符號(hào)相 同。如果弱符號(hào)大小超過強(qiáng)符號(hào),編譯器會(huì)發(fā)出警告。編譯器為什么不把未初始化的全局變量當(dāng)做未初始化的局部靜態(tài)變量處理?為什么不在bss中給它們分配空間,而非要把它們標(biāo)記為 COMMON類型 呢?因?yàn)榫幾g時(shí)編譯器不知道弱符號(hào)需要多大空間,所以這時(shí)無法為其在BSS中分配空間,只能當(dāng)做局部靜態(tài)變量處理。 但是在鏈接的時(shí)候可以確定,所
52、以鏈 接以后才在BSS中分配空間。編譯器把所有未初始化的全局變量都當(dāng)成 COMMON類型處理,這樣做是 為了與強(qiáng)類型分開,凡是非 COMMON類型的都是強(qiáng)類型。多個(gè)強(qiáng)類型的符號(hào) 會(huì)發(fā)生重復(fù)定義的錯(cuò)誤。重復(fù)代碼消除C+在很多時(shí)候會(huì)產(chǎn)生重復(fù)代碼,模版是其中最具代表性的一個(gè)。模版可以 在不同的編譯單元被實(shí)例化成相同的類型,兩個(gè)完全一樣的類是完全沒有必要 的,一個(gè)足矣。不解決代碼重復(fù)問題會(huì)導(dǎo)致:1、空間浪費(fèi)。這個(gè)根本就不用解釋。2、地址容易出錯(cuò)。因?yàn)槭嵌鄠€(gè)相同的實(shí)例嘛,就會(huì)有多個(gè)指針分別指向這些實(shí)例,但是這些實(shí)例之間沒差別,它們?cè)谶壿嬌鲜峭缓瘮?shù),這就容易造成指 針的誤指。3、指令運(yùn)行效率較低。緩存機(jī)
53、制會(huì)緩存多份重復(fù)的代碼,但是程序只會(huì)用 特定的一份,在這么多份相同的代碼中找特定的一份不好找,成功率較低,即, 緩存命中率低。解決方案:把每個(gè)編譯單元中的每個(gè)模版的不同實(shí)例分別放進(jìn)不同的段中, 并且對(duì)不同的單元都這樣做,這樣在最后鏈接的時(shí)候不同編譯單元中的相同實(shí)例 段就合并從而消除多份相同的實(shí)例。缺點(diǎn):不同的編譯單元可能使用了不同的編譯器版本或者優(yōu)化選項(xiàng),這會(huì)導(dǎo) 致實(shí)際產(chǎn)生的代碼不同,鏈接器必須選擇其中一個(gè)副本。函數(shù)級(jí)別鏈接:默認(rèn)情況下鏈接器會(huì)把所有的目標(biāo)文件鏈接在一起,不管有用的代碼還是沒用的代碼,這會(huì)導(dǎo)致可執(zhí)行文件很大。所謂函數(shù)級(jí)別鏈接就是每個(gè)編譯單元也把函數(shù)單獨(dú)放進(jìn)一個(gè)段中,在鏈接的時(shí)候
54、只鏈接那些有用的函數(shù)段。這種做法會(huì)減慢編譯和鏈接的過程,因?yàn)槎蔚臄?shù)量增加了。442全局構(gòu)造與析構(gòu)在C+中全局對(duì)象的構(gòu)造在main之前完成,析構(gòu)在main之后完成。在ELF文件中有.init和.fini兩個(gè)段。init段包含了進(jìn)程的初始化代碼,在 main之前執(zhí)行。fini段包含了進(jìn)程的終止代碼,在 main之后執(zhí)行。C+的全局構(gòu)造和析構(gòu)由此實(shí)現(xiàn)。4.4.3 C+與 ABI把不同編譯器產(chǎn)生的目標(biāo)文件鏈接在一起需要特定的條件一一相同的ABI(Applicati on Binary In terface)。ABI :符號(hào)修飾標(biāo)準(zhǔn)、變量?jī)?nèi)存布局、函數(shù)調(diào)用方式等與二進(jìn)制兼容性相關(guān) 的內(nèi)容。C語(yǔ)言間的目標(biāo)
55、文件能否互相兼容具體決定于如下幾個(gè)方面:1、內(nèi)置類型大小和存儲(chǔ)方式。2、組合類型大小和存儲(chǔ)方式。3、外部符號(hào)與用戶定義的符號(hào)之間的命名方式和解析方式。4、函數(shù)調(diào)用方式。5、堆棧分布方式。6寄存器使用方式。C+在這方面的決定因素 P141+P142介紹。C+代碼不僅對(duì)于由不同編譯器編譯得到的目標(biāo)文件不兼容,而且就算是同一編譯器的不同版本編譯得到的目標(biāo)文件也不兼容。這都是ABI鬧的。4.5靜態(tài)庫(kù)鏈接開發(fā)環(huán)境往往附帶語(yǔ)言庫(kù),這些庫(kù)是對(duì)系統(tǒng) API的封裝。大部分的C語(yǔ)言 庫(kù)函數(shù)都調(diào)用了系統(tǒng)API,少數(shù)除外。靜態(tài)庫(kù)實(shí)際上可以看成是一組目標(biāo)文件的集合。C語(yǔ)言中看似簡(jiǎn)單的庫(kù)函數(shù)和系統(tǒng)中眾多的 API存在著依賴關(guān)系。靜態(tài)鏈接的過程分為三步:1、調(diào)用C語(yǔ)言
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 單位管理制度集合大合集人員管理篇
- 單位管理制度匯編大合集人力資源管理
- 《家具導(dǎo)購(gòu)圣經(jīng)》課件
- 單位管理制度分享匯編職員管理篇十篇
- 單位管理制度分享大全職工管理十篇
- 2024教科室工作計(jì)劃
- 單位管理制度呈現(xiàn)合集職工管理篇十篇
- 《投資管理復(fù)習(xí)》課件
- 《市場(chǎng)考察報(bào)告》課件
- 《廣告效果的測(cè)定》課件
- 皖醫(yī)大兒科學(xué)習(xí)題及答案
- 劉鐵敏《金融專業(yè)英語(yǔ)》(第2版)-習(xí)題參考答案20
- 《公路工程建設(shè)監(jiān)理》課件
- 2023-2024學(xué)年黑龍江省哈爾濱一中高一(上)期末數(shù)學(xué)試卷
- 2024年管理學(xué)理論考核試題及答案
- 地理信息系統(tǒng)試卷及答案
- 干部考察延伸談話范圍
- (新)公共常識(shí)知識(shí)考試復(fù)習(xí)題庫(kù)800題(含答案)
- 叉車維修檢驗(yàn)原始記錄
- Invoice商業(yè)發(fā)票模板
- 施工過程三檢記錄表
評(píng)論
0/150
提交評(píng)論