第一章:計(jì)算機(jī)系統(tǒng)漫游
這本書是為這樣一些程序員而寫的,他們希望通過了解這些部件如何工作以及如何影響程序的準(zhǔn)確性和性能,來提高自身的技能.
系統(tǒng)中所有的信息--包括磁盤文件,存儲(chǔ)器中的程序,存儲(chǔ)器中存放的用戶數(shù)據(jù)以及網(wǎng)絡(luò)上傳送的數(shù)據(jù),都是由一串比特表示的.
c語言是貝爾實(shí)驗(yàn)室的Dennis Ritchie于1969-1973年間創(chuàng)建的.美國國家標(biāo)準(zhǔn)化組織在1989年頒布了ANSI C的標(biāo)準(zhǔn).該標(biāo)準(zhǔn)定義了C語言和一系列函數(shù)庫,即所謂的標(biāo)準(zhǔn)C庫.
c和unix操作系統(tǒng)關(guān)系密切.c從開始就是作為一種用于unix系統(tǒng)的程序語言開發(fā)出來的.unix內(nèi)核的大部分,以及所有它支持的工具和函數(shù)庫都是用c語言編寫的.
c是一個(gè)小而簡(jiǎn)單的語言,c語言的設(shè)計(jì)是由一個(gè)人完成的,其結(jié)果就是這是一個(gè)簡(jiǎn)潔明了,沒有什么冗贅的設(shè)計(jì).
c是為實(shí)踐目的設(shè)計(jì)的.c是設(shè)計(jì)用來實(shí)現(xiàn)unix操作系統(tǒng)的.它是系統(tǒng)編程的首選.同時(shí)它非常適用于應(yīng)用級(jí)程序的編寫.
預(yù)處理器,編譯器,匯編器和連接器一起構(gòu)成了編譯系統(tǒng).
匯編語言是非常有用的,因?yàn)樗鼮椴煌母呒?jí)語言的不同編譯器提供了通用的輸出語言.
GNU環(huán)境包括EMAC編譯器,GCC編譯器,GDB調(diào)試器,匯編器,連接器,處理二進(jìn)制文件的工具以及其他一些部件.
shell是一種命令行解釋器,它輸出一個(gè)提示符,等待你輸入一行命令,然后執(zhí)行這個(gè)命令.如果該命令的第一個(gè)單詞不是一個(gè)內(nèi)置的shell命令,那么shell就會(huì)假設(shè)這是一個(gè)可執(zhí)行文件的名字,要加載和執(zhí)行該文件.
每個(gè)I/O設(shè)備都是通過一個(gè)控制器或適配器于I/O總線連接起來的.控制器和適配器之間的區(qū)別主要在它們的組成方式.控制器是I/O設(shè)備本身中或是主板上的芯片組,而適配器則是一塊插在主板插槽上的卡.
主存是臨時(shí)存儲(chǔ)設(shè)備,在處理器執(zhí)行程序時(shí),它被用來存放程序和程序處理的數(shù)據(jù).物理上來說,主存是由一組DRAM芯片組成的,邏輯上來說,存儲(chǔ)器是由一個(gè)線性的字節(jié)數(shù)組組成的.
中央處理單元簡(jiǎn)稱處理器,是解釋存儲(chǔ)在主存中指令的引擎.處理器的核心是一個(gè)被稱為程序計(jì)數(shù)器的字長(zhǎng)大小的存儲(chǔ)設(shè)備.在任何一個(gè)時(shí)間點(diǎn)上,PC都指向主存中的某條機(jī)器語言指令.
從系統(tǒng)通電開始,直到系統(tǒng)斷電,處理器一直在不假思索的重復(fù)執(zhí)行相同的基本任務(wù):從程序計(jì)數(shù)器指向的存儲(chǔ)器處讀取指令,解釋指令中的位,執(zhí)行指令中的簡(jiǎn)單操作,然后更新程序計(jì)數(shù)器指向下一條指令,而這條指令并不一定在存儲(chǔ)器中和剛剛執(zhí)行的指令相鄰.
利用稱為DMA(直接存儲(chǔ)器存取)的技術(shù),數(shù)據(jù)可以不通過處理器而直接從磁盤到達(dá)主存.
常字符串的顯示過程:存儲(chǔ)器到寄存器文件,再從寄存器文件到顯示設(shè)備.
一個(gè)典型的寄存器文件只存儲(chǔ)幾百字節(jié)的信息,主存里可存放幾百萬字節(jié).然而,處理器從寄存器文件中讀數(shù)據(jù)比從主存中讀取要快幾乎100倍.
L1和L2高速緩存是用一種叫做靜態(tài)隨機(jī)訪問存儲(chǔ)器的硬件技術(shù)實(shí)現(xiàn)的.
我們可以把操作系統(tǒng)看成是應(yīng)用程序和硬件之間插入的一層軟件.
操作系統(tǒng)有兩個(gè)基本功能:防止硬件被失控的應(yīng)用程序?yàn)E用;在控制復(fù)雜而又通常廣泛不同的低級(jí)硬件設(shè)備方面,為應(yīng)用程序提供簡(jiǎn)單一致的方法.
程序在現(xiàn)代系統(tǒng)上運(yùn)行時(shí),操作系統(tǒng)會(huì)提供一種假象,就好像系統(tǒng)上只有這個(gè)程序是在運(yùn)行的.這些假象是通過進(jìn)程的概念來實(shí)現(xiàn)的,進(jìn)程是計(jì)算機(jī)科學(xué)中最重要和最成功的概念之一.
在現(xiàn)代系統(tǒng)中,一個(gè)進(jìn)程實(shí)際上可以由多個(gè)稱為線程的執(zhí)行單元組成.每個(gè)線程都運(yùn)行在進(jìn)程的上下文中,并共享同樣的代碼和全局?jǐn)?shù)據(jù).
虛擬存儲(chǔ)器是一個(gè)抽象概念,它為每個(gè)進(jìn)程提供了一個(gè)假象,好像每個(gè)進(jìn)程都在獨(dú)占的使用主存.每個(gè)進(jìn)程看到的存儲(chǔ)器都是一致的.
每
個(gè)進(jìn)程看到的虛擬地址空間由大量準(zhǔn)確定義的區(qū)組成,從最低的地址開始:程序代碼和數(shù)據(jù)(代碼和數(shù)據(jù)區(qū)是由可執(zhí)行目標(biāo)文件直接初始化的);堆(作為調(diào)用像
malloc和free這樣的c標(biāo)準(zhǔn)庫函數(shù)的結(jié)果,堆可以在運(yùn)行時(shí)動(dòng)態(tài)的擴(kuò)展和收縮);共享庫(共享庫的概念非常強(qiáng)大,但是也是個(gè)相當(dāng)難懂的概念);棧
(位于用戶虛擬地址空間頂部的是用戶棧,編譯器用它來實(shí)現(xiàn)函數(shù)的調(diào)用);內(nèi)核虛擬存儲(chǔ)器(內(nèi)核是操作系統(tǒng)總是駐留在存儲(chǔ)器中的部分).
虛擬存儲(chǔ)器的運(yùn)作需要硬件和操作系統(tǒng)軟件之間的精密復(fù)雜的相互合作,包括對(duì)處理器生成的每個(gè)地址的硬件翻譯.基本思想是把一個(gè)進(jìn)程虛擬存儲(chǔ)器的內(nèi)容存儲(chǔ)在磁盤上,然后用主存作為磁盤的高速緩存.
Linux逐漸發(fā)展成為一個(gè)技術(shù)和文化現(xiàn)象,通過和GNU項(xiàng)目的力量結(jié)合,Linux項(xiàng)目發(fā)展成為了一個(gè)完整的,符合Posix標(biāo)準(zhǔn)的Unix操作系統(tǒng)的版本,包括內(nèi)核和所有支撐的基礎(chǔ)設(shè)施.
操作系統(tǒng)內(nèi)核是應(yīng)用程序和硬件之間的媒介,它提供三個(gè)基本的抽象概念:文件是I/O設(shè)備的抽象概念:虛擬存儲(chǔ)器是對(duì)主存和磁盤的抽象概念:進(jìn)程是處理器,支撐和I/O設(shè)備的抽象概念.//
第二章:信息的表示和處理
2的n次方的二進(jìn)制表示為1后面加n個(gè)0;
指針變量將用到機(jī)器的全字長(zhǎng),long int*也是這樣;
可移植性的一個(gè)方面就是使程序?qū)Σ煌瑪?shù)據(jù)不敏感;
c標(biāo)準(zhǔn)對(duì)不同類型的數(shù)字范圍設(shè)置了下限,但是沒有上限;
存儲(chǔ)數(shù)據(jù)分大端法和小端法;
反匯編器是一種確定可執(zhí)行程序文件所表示的指令序列的工具;
不同的機(jī)器使用不同的且不兼容的指令和編碼方式;
布爾的and和異或分別相當(dāng)于模2的乘法和加法;
c標(biāo)準(zhǔn)沒有明確定義使用那種類型的右移;
c和c++中的有符號(hào)樹是默認(rèn)的;
c的標(biāo)準(zhǔn)并沒有要求用二進(jìn)制補(bǔ)碼來表示有符號(hào)數(shù);
c庫中的文件<limits.h>定義了一組常量,來限定運(yùn)行編譯器的這臺(tái)機(jī)器的不同整型數(shù)據(jù)的范圍;
printf沒有使用任何輸出變量類型的信息(how);
無符號(hào)和有符號(hào)數(shù)混合運(yùn)算,將有符號(hào)數(shù)轉(zhuǎn)化為無符號(hào)數(shù);
一種有名的用來執(zhí)行二進(jìn)制補(bǔ)碼的非的技術(shù)是取反并加一;
編譯器用移位和加法運(yùn)算的組合來代替乘以常數(shù)因子的乘法;
單精度浮點(diǎn)的符號(hào),指數(shù)和有效數(shù)字分別是1,8,23.雙精度為1,11,52.有效數(shù)字的第一位總是1,因此我們不需要表示它;
IEEE浮點(diǎn)格式定義了4種不同的舍入方式,默認(rèn)是找到最接近的匹配.這四種方式為:向偶數(shù)舍入,向0舍入,向上舍入,向下舍入;
浮點(diǎn)寄存器使用一種特殊的80位的擴(kuò)展精度格式;
浮點(diǎn)數(shù)取非就是簡(jiǎn)單的將其符號(hào)位取反;
第三章:程序的機(jī)器級(jí)表示
匯編程序員可以看到程序計(jì)數(shù)器,整數(shù),浮點(diǎn)數(shù)寄存器和條件碼寄存器;
匯編代碼只是將存儲(chǔ)器看成是一個(gè)很大且按字節(jié)尋址的數(shù)組;
對(duì)標(biāo)量數(shù)據(jù)類型,匯編代碼也不區(qū)分有符號(hào)和無符號(hào)數(shù),不區(qū)分各種類型的指針,甚至不區(qū)分指針和整數(shù);
程序存儲(chǔ)器包含程序的目標(biāo)代碼,操作系統(tǒng)需要的一些信息,用來管理過程調(diào)用和返回的運(yùn)行時(shí)棧,以及用戶分配的存儲(chǔ)器塊;
IA32 CPU有8個(gè)32位值的寄存器,前6個(gè)是通用的,后2個(gè)保存棧指針和幀指針;
最頻繁使用的指令是執(zhí)行數(shù)據(jù)傳送的指令;
局部變量通常是保存在寄存器中;
除了右移操作,所有的指令都不區(qū)分有符號(hào)數(shù)和無符號(hào)數(shù);
IA32程序用程序棧來支持過程的調(diào)用,棧用來傳遞過程參數(shù),存儲(chǔ)返回信息,保存寄存器以供以后的恢復(fù)之用,以及用于本地存儲(chǔ);
為單個(gè)過程分配的那部分棧稱為棧幀,它的最頂端是以兩個(gè)指針定界的;
寄存器%eax可以用來返回值,如果函數(shù)要返回整數(shù)或指針的話;
IA32中%eax,%edx和%ecx被劃分為調(diào)用者保存寄存器,其余三個(gè)為被調(diào)用者保存寄存器;
IA32也仍然在不斷增加新的指令類,來支持處理多媒體應(yīng)用的需要;
許多計(jì)算機(jī)系統(tǒng)要求某種數(shù)據(jù)類型的對(duì)象必須是某個(gè)值的k倍,這種對(duì)其限制簡(jiǎn)化了處理器和存儲(chǔ)器系統(tǒng)之間的接口硬件設(shè)計(jì);
&操作符可以應(yīng)用到任何左值類的c表達(dá)式上,包括變量,結(jié)構(gòu),聯(lián)合和數(shù)組元素;
用高級(jí)語言編寫的程序可在許多不同的機(jī)器上編譯執(zhí)行,而匯編代碼是于特定機(jī)器密切相關(guān)的;
1997-PentiumII 1999-PentiumIII 2001-Pentium4;
美國商標(biāo)局不允許用數(shù)字作為商標(biāo);
Intel現(xiàn)在稱其指令集為IA32;
匯編代碼非常接近于機(jī)器代碼;
匯編代碼只是將存儲(chǔ)器看成是一個(gè)很大的,按字節(jié)尋址的數(shù)組;
機(jī)器實(shí)際執(zhí)行的程序只是對(duì)一系列指令進(jìn)行編碼的字節(jié)序列,機(jī)器對(duì)產(chǎn)生這些指令的源代碼一無所知;
call過程調(diào)用 leave為返回準(zhǔn)備棧 ret從過程調(diào)用中返回;
指針提供一種統(tǒng)一方式,能夠遠(yuǎn)程訪問數(shù)據(jù)結(jié)構(gòu);
malloc函數(shù)返回一個(gè)通用指針;
指針也可以指向函數(shù),這提供了一個(gè)很強(qiáng)大的存儲(chǔ)和傳遞代碼引用的功能(how);
數(shù)年來,AMD的策略一致是在技術(shù)上緊跟Intel后面,生產(chǎn)性能稍低但是價(jià)格更便宜的處理器;
浮點(diǎn)數(shù)使用一組完全不同的指令和寄存器(相對(duì)整數(shù)來說);
IA32加了一條限制,傳送指令的兩個(gè)操作數(shù)不能都指向存儲(chǔ)器的位置,第一個(gè)是源操作數(shù),第二個(gè)是目標(biāo)操作數(shù);
局部變量通常是保存在寄存器中的,這樣就能更快的訪問到它;
加載有效地址(load effective address)指令leal實(shí)際上是movl指令的變形,但目標(biāo)操作數(shù)必須是一個(gè)寄存器;
編譯器產(chǎn)生的代碼中會(huì)用一個(gè)寄存器存放多個(gè)程序值,還會(huì)在寄存器之間傳送程序值;
匯編代碼提供了實(shí)現(xiàn)非順序控制流的較低層次的機(jī)制,這是通過借助條件碼寄存器來完成的;
當(dāng)執(zhí)行PC相關(guān)的尋址時(shí),程序計(jì)數(shù)器的值是跳轉(zhuǎn)指令后面的那條指令的地址,,而不是跳轉(zhuǎn)指令本身的地址;
switch語句不僅提高了c代碼的可讀性,而且通過使用一種跳轉(zhuǎn)表的數(shù)據(jù)結(jié)構(gòu)使得實(shí)現(xiàn)更加高效,跳轉(zhuǎn)表是一個(gè)數(shù)組,表項(xiàng)i是一個(gè)代碼段的地址,這個(gè)代碼段實(shí)現(xiàn)的是當(dāng)開關(guān)索引值等于i時(shí)程序應(yīng)該采取的動(dòng)作;
數(shù)據(jù)傳遞,局部變量的分配了釋放是通過操縱程序棧來實(shí)現(xiàn)的;
IA32程序用程序棧來支持過程調(diào)用;
當(dāng)對(duì)一個(gè)局部變量使用地址操作符&的時(shí)候,該變量不能保存在寄存器中;
call指令的效果是將返回地址入棧,并跳轉(zhuǎn)到被調(diào)用過程的起始處.返回地址是緊跟在程序中call后面的那條指令的地址.ret指令從棧中彈出地址并跳轉(zhuǎn)到那個(gè)位置;
在較老的IA32處理器模型中,整數(shù)乘法指令要花費(fèi)30個(gè)時(shí)鐘周期,所以編譯器要盡可能的避免使用它,而大多數(shù)新近的處理器模型中,乘法指令只需要3個(gè)時(shí)鐘周期,所以不一定會(huì)進(jìn)行這樣的優(yōu)化;
c和c++都要求程序顯式的用free函數(shù)來釋放已經(jīng)分配的空間,在Java中,釋放是由運(yùn)行時(shí)系統(tǒng)通過一個(gè)稱為垃圾回收的進(jìn)程自動(dòng)完成的;
寄存器溢出是IA32一個(gè)很常見的問題,因?yàn)樘幚砥鞯募拇嫫鲾?shù)量太少了;
結(jié)構(gòu)的實(shí)現(xiàn)類似于數(shù)組的實(shí)現(xiàn),因?yàn)榻Y(jié)構(gòu)的所有組成部分都存放在存儲(chǔ)器中連續(xù)的區(qū)域內(nèi),而指向結(jié)構(gòu)的指針就是結(jié)構(gòu)的第一個(gè)字節(jié)的地址;
struct數(shù)據(jù)類型的構(gòu)造函數(shù)是c提供的于c++和java對(duì)象最為接近的東西;
蠕蟲是這樣一種程序,它可以自己運(yùn)行,并且能夠?qū)⒁粋€(gè)完全有效的自己傳播到其他的機(jī)器上.與此相應(yīng)的,病毒是這樣一段代碼,它能將自己添加都包括操作系統(tǒng)在內(nèi)的其他程序中,但是它不能獨(dú)立的運(yùn)行;//
(14 15 16節(jié)未看)
第五章:優(yōu)化程序性能
編寫高效的程序需要兩類活動(dòng):第一,我們必須選擇一組最好的算法和數(shù)據(jù)結(jié)構(gòu);第二,我們必須編寫出編譯器能夠有效優(yōu)化以轉(zhuǎn)成高效可執(zhí)行代碼的源代碼;
事實(shí)上,編譯器只能執(zhí)行有限的程序轉(zhuǎn)換,而且妨礙優(yōu)化的因素還會(huì)阻礙這種轉(zhuǎn)換,妨礙優(yōu)化的因素就是程序行為中那些嚴(yán)重依賴于執(zhí)行環(huán)境的方面;
編譯器優(yōu)化程序的能力受幾個(gè)因素的限制,包括:要求它們絕不能改變正確的程序行為:它們對(duì)程序行為,對(duì)使用它們的環(huán)境了解有限;需要很快的完成編譯工作;
編譯器必須假設(shè)不同的指針可能會(huì)指向存儲(chǔ)器中同一個(gè)位置,這造成了一個(gè)主要的妨礙優(yōu)化的因素,這也是可能嚴(yán)重限制編譯器產(chǎn)生優(yōu)化代碼機(jī)會(huì)的程序的一個(gè)方面;
編譯器會(huì)假設(shè)最糟的情況,并保持所有的函數(shù)調(diào)用不變;
代碼移動(dòng)的優(yōu)化包括識(shí)別出要執(zhí)行多次但是計(jì)算結(jié)果不會(huì)改變的計(jì)算,因而我們可以將計(jì)算移動(dòng)到代碼前面的,不會(huì)被多次求值的部分;
c中的字符串是以null結(jié)尾的字符序列,strlen必須一步一步的檢查這個(gè)序列,直到遇到null字符;
過程的調(diào)用會(huì)帶來相當(dāng)大的開銷,而且妨礙大多數(shù)形式的程序優(yōu)化;
消除不必要的存儲(chǔ)器引用,具體是用一些臨時(shí)變量,這些變量很可能被存在寄存器中(如果沒有溢出的話);
在匯編代碼級(jí),看上去似乎是一次是執(zhí)行一條指令,每條指令都包括從寄存器或存儲(chǔ)器取值,執(zhí)行一個(gè)操作,并把結(jié)果存回到一個(gè)寄存器或存儲(chǔ)器位置.在實(shí)際的處理器中,是同時(shí)對(duì)多條指令求值的.在某些設(shè)計(jì)中,可以有80或更多條指令在處理中.
P6
微體系結(jié)構(gòu)是自20世紀(jì)90年代后期以來許多廠商生產(chǎn)的高端處理器的典型.在工業(yè)界稱為超標(biāo)量,意思是它可以在每個(gè)時(shí)鐘周期執(zhí)行多個(gè)操作,而且是亂序的,
整個(gè)設(shè)計(jì)有兩個(gè)主要部分:ICU(Instruction Control Unit,指令控制單元)和EU(Execution
Unit,執(zhí)行單元).
ICU從指令高速緩存中讀取指令,指令高速緩存是一個(gè)特殊的高速緩存存儲(chǔ)器,它包含最近訪問的指令;
指令解碼邏輯接收實(shí)際的程序指令,并將它們轉(zhuǎn)換成一組基本的操作;
加載和存儲(chǔ)單元通過數(shù)據(jù)高速緩存訪問存儲(chǔ)器,這是一個(gè)高速存儲(chǔ)器,包含最近訪問的數(shù)據(jù)值;
退役單元紀(jì)錄正在進(jìn)行的處理,并確保遵守機(jī)器級(jí)程序的順序語意.退役單元控制著寄存器的更新;
任何對(duì)程序狀態(tài)的更新都只會(huì)在指令退役時(shí)才發(fā)生,只有在處理器能夠確信這條指令的所有分支都預(yù)測(cè)正確了,才能這樣做;
執(zhí)行時(shí)間的范圍從基本整數(shù)操作的一個(gè)周期,到加載,存儲(chǔ),整數(shù)乘法和更常見的浮點(diǎn)操作的幾個(gè)周期,到除法和其他復(fù)雜操作的許多個(gè)周期;
處理器的幾個(gè)功能單元被流水線化了,這意味著在前一個(gè)操作完成之前,它們就可以開始一個(gè)新的操作;
浮點(diǎn)乘法器要求連續(xù)的操作之間至少要一兩個(gè)周期,而兩個(gè)除法器根本就沒有流水線化;
標(biāo)記可以與并不會(huì)寫道寄存器文件中的中間值相關(guān)聯(lián);
循環(huán)展開本身只會(huì)幫助整數(shù)求和情況中代碼的性能,因?yàn)槲覀兊钠渌闆r是被功能單元的執(zhí)行時(shí)間限制的;
編譯器可以很容易的執(zhí)行循環(huán)展開,只要優(yōu)化級(jí)別設(shè)置得足夠高(例如,優(yōu)化選項(xiàng)-O2)許多編譯器都能例行公事的做到這一點(diǎn),在命令行上以'-funroll-loops'調(diào)用GCC,它會(huì)執(zhí)行循環(huán)展開;
有時(shí)候,我們能夠通過使用指針,而不是數(shù)組改進(jìn)一個(gè)程序的性能;編譯器對(duì)數(shù)組代碼應(yīng)用非常高級(jí)的優(yōu)化,而對(duì)指針只應(yīng)用最小限度的優(yōu)化,為了可讀性的緣故,通常數(shù)組代碼更可取一些;
循環(huán)分割:我們可以通過將一組合并操作分割成兩個(gè)或更多的部分,并在最后合并結(jié)果來提高性能;
對(duì)于整數(shù)數(shù)據(jù)類型的情況,總共只有八個(gè)整數(shù)據(jù)傳區(qū)可用.其中兩個(gè)指向棧中的區(qū)域;
八
個(gè)整數(shù)和八個(gè)浮點(diǎn)寄存器的限制是IA32指令集的不幸產(chǎn)物,前面講到國的重命名消除了寄存器名字和寄存器數(shù)據(jù)實(shí)際位置之間的聯(lián)系.在現(xiàn)代處理器中,寄存器
名字之簡(jiǎn)單的用來標(biāo)識(shí)在功能單元之間傳遞的程序值.IA32只提供了很少量的這樣的標(biāo)識(shí)符,限制了在程序中能表達(dá)的并行性的數(shù)量;
某個(gè)與及其相關(guān)的因素將浮點(diǎn)乘法能達(dá)到的CPE限制在了1.5,而不是理論極限值的1.0;
程序優(yōu)化的通用原則對(duì)各種不同的機(jī)器都適用,即使某種特殊的特性組合導(dǎo)致最優(yōu)性能依賴于特殊的機(jī)器;
到目前,臺(tái)式機(jī)或服務(wù)器中幾乎每個(gè)處理器都支持投機(jī)執(zhí)行;
一個(gè)存儲(chǔ)器讀的結(jié)果依賴于一個(gè)非常近的存儲(chǔ)器的寫叫做讀寫相關(guān),它導(dǎo)致了處理速度的下降;
movl %edx,(%ecx)被翻譯成兩個(gè)操作:storeaddr指令計(jì)算存儲(chǔ)操作的地址,創(chuàng)建一個(gè)存儲(chǔ)緩沖區(qū)中的條目,并設(shè)置該條目的地址字段;storedata指令設(shè)置該條目的數(shù)據(jù)字段;
通常,處理器/存儲(chǔ)器接口是處理器設(shè)計(jì)中最復(fù)雜的部分之一.不查閱詳細(xì)的文檔和使用機(jī)器分析工具,我們只能給出實(shí)際行為的一個(gè)假象的描述;
由于存儲(chǔ)器操作占到了程序很大一部分,存儲(chǔ)器子系統(tǒng)優(yōu)化成以獨(dú)立的存儲(chǔ)器操作來提供更大的并行性;
優(yōu)化程序性能的基本策略:
1.高級(jí)設(shè)計(jì),為手邊的問題選擇適當(dāng)?shù)乃惴ê蛿?shù)據(jù)結(jié)構(gòu);
2.基本編碼原則,消除函數(shù)的連續(xù)調(diào)用,在可能時(shí),將計(jì)算移到循環(huán)外.考慮有選擇的妥協(xié)程序的模塊性以獲得更大的效率;消除不必要的存儲(chǔ)器引用,引入中間變量來保存中間結(jié)果,只有在最后的值計(jì)算出來的時(shí)候,才將結(jié)果存放到數(shù)組或全局變量中;
3.低級(jí)優(yōu)化.嘗試各種于數(shù)組代碼相對(duì)的指針形式;通過展開循環(huán)降低循環(huán)開銷;通過諸如迭代分割之類的技術(shù),找到使用流水線化的功能單元的方法;
Unix系統(tǒng)提供了一個(gè)剖析程序GPROF.這個(gè)程序產(chǎn)生兩種形式的信息.首先,它確定程序中每個(gè)函數(shù)花費(fèi)了多少CPU時(shí)間.其次,它計(jì)算每個(gè)函數(shù)被調(diào)用的次數(shù),以調(diào)用函數(shù)來分析;
庫函數(shù)qsort是際遇快速排序算法進(jìn)行排序的;
剖析是工具箱中一個(gè)很有用的工具,但是它不應(yīng)該是唯一一個(gè),即使測(cè)量不是很準(zhǔn)確,特別是對(duì)較短的運(yùn)行時(shí)間來說.結(jié)果只適用于被測(cè)試的那些特殊的數(shù)據(jù);
Amdahl定律的主要觀點(diǎn):要想大幅度提高整個(gè)系統(tǒng)的速度,我們必須提高整個(gè)系統(tǒng)很大一部分的速度;
Amdahl
定律描述了一個(gè)改進(jìn)任何過程的通用原則.除了適用于提高計(jì)算機(jī)系統(tǒng)的速度外,它還能指導(dǎo)一個(gè)公司試著降低生產(chǎn)剃須刀的成本,或是指導(dǎo)一個(gè)學(xué)生改進(jìn)他或她的
平均績(jī)點(diǎn).或許它在計(jì)算機(jī)世界里最有意義,在計(jì)算機(jī)世界中,我們通常將性能提高一倍或更多.只有通過優(yōu)化系統(tǒng)很大一部分才能獲得這么高的提高率;
第六章:存儲(chǔ)器層次結(jié)構(gòu)
RAM分SRAM和DRAM
DDR-SDRAM:雙倍數(shù)據(jù)速率同步DRAM.通過時(shí)鐘的兩個(gè)邊沿作為控制信號(hào),從而使DRAM的速度加倍.
ROM是以它們能被重編程的次數(shù)和對(duì)它們進(jìn)行重新編程的機(jī)制來區(qū)分的:PROM,EPROM,EEDROM.閃存是基于EEPROM的;
存儲(chǔ)在ROM設(shè)備中的程序通常被稱為固件,當(dāng)一個(gè)計(jì)算機(jī)系統(tǒng)通電以后,它會(huì)運(yùn)行存儲(chǔ)在ROM中的固件;
總線是一組并行的導(dǎo)線,能攜帶數(shù)據(jù),地址和控制信號(hào);
系統(tǒng)總線將CPU連接到I/O橋接器,存儲(chǔ)器總線將I/O橋接器連接到主存,I/O橋接器也將系統(tǒng)總線和存儲(chǔ)器總線連接到I/O總線;
扇區(qū)包含相等數(shù)量的數(shù)據(jù)位,扇區(qū)之間由一些間隙分隔開,間隙用來存儲(chǔ)標(biāo)識(shí)扇區(qū)的格式化位;
多個(gè)盤面時(shí),任何時(shí)刻,所有讀寫頭都位于同一柱面上;
磁盤是以扇區(qū)大小的塊來讀寫數(shù)據(jù)的;
磁盤中有一個(gè)小的硬件/固件設(shè)備,稱為磁盤控制器,維護(hù)著邏輯塊號(hào)和實(shí)際磁盤扇區(qū)之間的映射關(guān)系;
在磁盤可以存儲(chǔ)數(shù)據(jù)之前,它必須被磁盤控制器格式化,這包括標(biāo)識(shí)扇區(qū)的信息填寫扇區(qū)之間的間隙,標(biāo)識(shí)出表面有故障的柱面并且不適用它們,以及在每個(gè)區(qū)中預(yù)留出一組柱面作為備用;
在使用存儲(chǔ)器映射I/O的系統(tǒng)中,地址空間中有一塊地址是為與I/O設(shè)備通信保留的,每個(gè)這樣的地址稱為一個(gè)I/O端口;
現(xiàn)代計(jì)算機(jī)頻繁使用基于SRAM的高速緩存;
有良好局部性的程序比局部性差的程序運(yùn)行得更快;
代碼區(qū)別于程序數(shù)據(jù)的一個(gè)重要屬性是在運(yùn)行時(shí)不能修改;
局部變量的反復(fù)引用是好的,步長(zhǎng)為1的引用模式是好的;
如果你的程序需要的數(shù)據(jù)是存儲(chǔ)在CPU寄存器中的,那么在執(zhí)行期間,在零個(gè)周期內(nèi)就能訪問到它們;如果存儲(chǔ)在高速緩存中,需要1-10個(gè)周期;如果存儲(chǔ)在主存中,需要50-100個(gè)周期;而如果存儲(chǔ)在磁盤上,需要大約20 000 000個(gè)周期;
具有良好局部性的程序傾向于一次又一次的訪問相同的數(shù)據(jù)項(xiàng)集合,或是傾向于訪問鄰近的數(shù)據(jù)項(xiàng)集合;
特別的,我們將注意力集中在CPU和主存之間作為緩存區(qū)域的高速緩存存儲(chǔ)器上,因?yàn)樗鼈儗?duì)應(yīng)用程序性能影響最大;
隨機(jī)訪問存儲(chǔ)器(random-access memory,RAM)分為兩類--靜態(tài)的和動(dòng)態(tài)的.靜態(tài)RAM(SRAM)比動(dòng)態(tài)RAM(DRAM)更快,但也貴得多;
SRAM將每個(gè)位存儲(chǔ)在一個(gè)雙穩(wěn)態(tài)的存儲(chǔ)器單元里;每個(gè)單元是用一個(gè)六晶體管電路來實(shí)現(xiàn)的.這個(gè)電路有這樣一個(gè)屬性,它可以無限期的保持在兩個(gè)不同的電壓配置或狀態(tài)之一;
DRAM存儲(chǔ)器可以制造的非常密集--每個(gè)單元由一個(gè)電容和一個(gè)訪問晶體管組成;
只要有電,SRAM就是持續(xù)的,與DRAM不同,它不需要刷新.SRAM的存取比DRAM快.SRAM對(duì)諸如光和電噪音這樣的干擾不敏感.代價(jià)是SRAM單元比DRAM單元使用更多的晶體管,因而沒那么密集,而且更貴,功耗更大;
電路設(shè)計(jì)者將DRAM組織成二維陣列而不是線性數(shù)組的一個(gè)原因是降低芯片上地址管腳的數(shù)量;
DRAM芯片包裝在存儲(chǔ)器模塊中,它插到主板的擴(kuò)展槽上;二維陣列組織的缺點(diǎn)是必須分兩步發(fā)送地址,這增加了訪問時(shí)間;
增
強(qiáng)的DRAM:FPM DRAM(fast page mode DRAM,快頁模式DRAM);EDO DRAM(extended data out
DRAM,擴(kuò)展數(shù)據(jù)輸出DRAM);SDRAM(synchronous DRAM, 同步DRAM);DDR SDRAM(double
data-rate synchronous DRAM,雙倍數(shù)據(jù)速率同步);Rambus DRAM;
一些系統(tǒng)在固件中提供了少量的輸入和輸出函數(shù)--例如,PC的BIOS例程;I/O橋接器將系統(tǒng)總線的電信號(hào)翻譯成存儲(chǔ)器總線的電信號(hào);
磁盤是由一個(gè)或多個(gè)疊放在一起的盤片組成的,它們放在一個(gè)密封的包裝里,正個(gè)裝置通常別叫做磁盤驅(qū)動(dòng)器,雖然我們通常簡(jiǎn)稱位磁盤;
對(duì)扇區(qū)的訪問時(shí)間有三個(gè)主要部分:尋道時(shí)間,旋轉(zhuǎn)時(shí)間,傳送時(shí)間;
從存儲(chǔ)器中讀一個(gè)512字節(jié)扇區(qū)大小的塊的時(shí)間對(duì)SRAM來說大約是256ns,對(duì)DRAM來說大約是4000ns.磁盤訪問時(shí)間比SRAM大約大4000倍;比DRAM大約大2500倍;
當(dāng)
操作系統(tǒng)想要執(zhí)行一個(gè)I/O操作時(shí),例如讀一個(gè)磁盤扇區(qū)的數(shù)據(jù)到主存,操作系統(tǒng)會(huì)發(fā)送一個(gè)命令到磁盤控制器,讓它讀某個(gè)邏輯塊號(hào).控制器上的固件執(zhí)行一個(gè)
快速表查找,將一個(gè)邏輯塊號(hào)翻譯成一個(gè)(盤面,磁道,扇區(qū))的三元組,這個(gè)三元組唯一的標(biāo)識(shí)了對(duì)應(yīng)的物理扇區(qū).控制器上的硬件解釋這個(gè)三元組,將I/O頭
移動(dòng)到適當(dāng)?shù)闹?等待扇區(qū)移動(dòng)到I/O頭下,將I/O頭感知到的位放到控制器上的一個(gè)小緩沖區(qū)中,然后將它們拷貝到主存中;
像圖形卡,監(jiān)視器,鼠標(biāo),鍵盤,這樣的設(shè)備都是通過諸如Intel的PCI(peripheral component interconnect外圍設(shè)備互聯(lián))總線這樣的I/O總線連接到CPU和主存的;
雖然I/O總線比系統(tǒng)總線和存儲(chǔ)器總線慢,但是它快頁容納種類繁多的第三方I/O設(shè)備;
USB控制器是一個(gè)將設(shè)備連接到USB的電路,USB的吞吐率可以達(dá)到12Mbit/s,是為慢速或中速串行設(shè)備設(shè)計(jì)的;
圖形卡包含硬件和軟件邏輯,它們負(fù)責(zé)代表CPU在顯示器上畫像素;
磁盤控制器包含硬件和軟件邏輯,它們用來代表CPU讀寫磁盤;
當(dāng)一個(gè)設(shè)備連接到總線時(shí),它與一個(gè)或多個(gè)端口相關(guān)聯(lián);
邏輯塊的概念不僅能夠提供給操作系統(tǒng)一個(gè)更簡(jiǎn)單的接口,還能夠提供一層抽象,使得磁盤更加健壯;
磁盤中有一個(gè)小的硬件/固件設(shè)備,稱為磁盤控制器,維護(hù)著邏輯塊號(hào)和實(shí)際磁盤扇區(qū)之間的映射關(guān)系;
操作系統(tǒng)用主存來緩存磁盤文件系統(tǒng)中最近被使用的磁盤塊;
對(duì)于取指令來說,循環(huán)有好的時(shí)間和空間局部性,循環(huán)體越小,循環(huán)次數(shù)越多,局部性越好;
一般而言,層次結(jié)構(gòu)中較低層的設(shè)備的訪問時(shí)間較長(zhǎng),因此為了補(bǔ)償這些較長(zhǎng)的訪問時(shí)間,傾向于使用較大的塊;
在一個(gè)有虛擬存儲(chǔ)器的系統(tǒng)中,DRAM主存作為存儲(chǔ)在磁盤上的數(shù)據(jù)塊的緩存,是有操作系統(tǒng)軟件和CPU上的地址翻譯硬件共同管理的;
高速緩存確定一個(gè)請(qǐng)求是否命中,然后抽取出被請(qǐng)求的字的過程,分為三步:組選擇;行匹配;字抽取;
沖突不命中在真實(shí)的程序中很常見,會(huì)導(dǎo)致令人困惑的性能問題;
即使程序有良好的空間局部性,而我們的高速緩存中也有足夠的空間來存放兩個(gè)數(shù)組的塊,每次引用還是會(huì)導(dǎo)致沖突不命中,這是因?yàn)檫@些塊被映射到了同一個(gè)高速緩存組;
直寫高速緩存通常是非寫分配的,寫回高速緩存通常是寫分配的;
只保存指令的高速緩存稱為i-cache,只保存程序數(shù)據(jù)的高速緩存稱為d-cache.一個(gè)典型的桌面系統(tǒng)CPU芯片本身就包括一個(gè)L1 i-cache和一個(gè)L1 d-cache;
一方面,較大的高速緩存可能會(huì)提高緩存命中率;另一方面,使得大存儲(chǔ)器運(yùn)行得更快總是要難一些的;
現(xiàn)代系統(tǒng)通常會(huì)折中,使高速緩存塊包含4-8個(gè)字;
傳統(tǒng)上,努力爭(zhēng)取時(shí)鐘頻率的高性能系統(tǒng)會(huì)選擇直接映射L1高速緩存,而在較低層上使用比較小的相關(guān)度,但是沒有固定的規(guī)則;
一般而言,高速緩存越往下層,越可能使用寫回而不是直寫;
因?yàn)橐恍锌偸谴鎯?chǔ)一個(gè)塊,術(shù)語行和塊通?;Q使用,例如,系統(tǒng)專家總是說高速緩存的行大小,實(shí)際上他們指的是塊大小;
理
解存儲(chǔ)器層次結(jié)構(gòu)本質(zhì)的程序員能夠利用這些知識(shí),編寫出更有效的程序,無論具體的存儲(chǔ)系統(tǒng)是怎樣的.特別的,我們推薦下列技術(shù):將你的注意力集中在內(nèi)部循
環(huán)上,大部分計(jì)算和存儲(chǔ)器訪問都發(fā)生在這里;通過按照數(shù)據(jù)對(duì)象存儲(chǔ)在存儲(chǔ)器中的順序來讀數(shù)據(jù),從而使得你程序中空間局部性最大;一旦從存儲(chǔ)器中讀入一個(gè)數(shù)
據(jù)對(duì)象,就盡可能多的使用它,從而使得你程序中的時(shí)間局部性最大;記住,不命中率只是確定你代碼性能的一個(gè)因素,存儲(chǔ)器訪問數(shù)量也扮演著重要的角色,有時(shí)
候要在兩者之間做一下折中;//(6.6未看)
第七章:連接
連接器完成的兩個(gè)主要任務(wù):符號(hào)解析和重定位.
編譯器和匯編器生成地址0開始的代碼和數(shù)據(jù)節(jié).
目標(biāo)文件:可重定位目標(biāo)文件,可執(zhí)行目標(biāo)文件,共享目標(biāo)文件.
ELF
可重定位目標(biāo)文件:ELF頭以一個(gè)16字節(jié)的序列開始,這個(gè)序列描述了字的大小和生成該文件的系統(tǒng)的字節(jié)順序.
.test.:已編譯程序的機(jī)器代碼;.rodata.:只讀數(shù)據(jù);.data.:已初始化的全局變量;.bss.:為初始化的全局變
量;.symtab.:存放程序中被定義和引用的函數(shù)和全局變量的信息.
連接器上下文中有三種符號(hào):塊內(nèi)定義的全局符號(hào);塊外定義的全局符號(hào)和本地符號(hào);
定義為帶c static屬性的本地過程變量不是在棧中管理的,編譯器在.data.和.bss.中為每個(gè)定義分配空間;
任何聲明為static屬性的全局變量或函數(shù)是模塊私有的;
編譯器來保證本地符號(hào)的唯一性;
當(dāng)編譯器遇到一個(gè)不是在當(dāng)前模塊中定義的變量時(shí),它會(huì)假設(shè)該符號(hào)是在其他某個(gè)模塊中定義的,生成一個(gè)連接器符號(hào)表表目;
運(yùn)行時(shí)的存儲(chǔ)器映像:未使用->只讀段->讀寫段->運(yùn)行時(shí)堆->共享庫的存儲(chǔ)器映射區(qū)域->用戶棧->內(nèi)核虛擬存儲(chǔ)器;
加載運(yùn)行:可執(zhí)行文件中段頭表的指導(dǎo)下,加載代碼和數(shù)據(jù)->程序入口點(diǎn)_start->初始化函數(shù)->注冊(cè)函數(shù)->主函數(shù)->返回;
c的啟動(dòng)代碼對(duì)于每個(gè)c程序都是相同的,都需要跳到main函數(shù);
鏈接就是將不同部分的代碼和數(shù)據(jù)收集和組合成為一個(gè)單一文件的過程,這個(gè)文件可被加載到存儲(chǔ)器并執(zhí)行.鏈接可以執(zhí)行于編譯時(shí),也就是在源代碼被翻譯成機(jī)器代碼時(shí);也可以執(zhí)行于加載時(shí),也就是在程序被加載器加載到存儲(chǔ)器并執(zhí)行時(shí);甚至執(zhí)行于運(yùn)行時(shí),由應(yīng)用程序來執(zhí)行;
鏈接器在軟件開發(fā)中扮演著一個(gè)關(guān)鍵的角色,因?yàn)樗麄兪沟梅蛛x編譯成為了可能;
理解鏈接器將幫助你構(gòu)造大型程序;理解鏈接器將幫助你避免一些危險(xiǎn)的編程錯(cuò)誤;理解鏈接器將幫助你理解語言的作用域規(guī)則是如何實(shí)現(xiàn)的;理解鏈接器將幫助你理解其他重要的系統(tǒng)概念;理解鏈接器使你能夠開發(fā)共享庫;
目
標(biāo)文件純粹是字節(jié)塊的集合,這些塊中,有些包含程序代碼,有些則包含程序數(shù)據(jù),而其他的則包含指導(dǎo)鏈接器和加載器的數(shù)據(jù)結(jié)構(gòu).鏈接器將這些塊連接起來,確
定鏈接塊的運(yùn)行時(shí)位置,并且修改代碼和數(shù)據(jù)塊中的各種位置.鏈接器對(duì)目標(biāo)機(jī)器了解甚少,產(chǎn)生目標(biāo)文件的編譯器和匯編器已經(jīng)完成了大部分工作;
目標(biāo)
文件有三種形式:1.可重定位目標(biāo)文件
包含二進(jìn)制代碼和數(shù)據(jù),其形式可以在編譯時(shí)與其他可重定位目標(biāo)文件合并起來,創(chuàng)建一個(gè)可執(zhí)行目標(biāo)文件;2.可執(zhí)行目標(biāo)文件
包含二進(jìn)制代碼和數(shù)據(jù),其形式可以被直接拷貝到存儲(chǔ)器并執(zhí)行;3.共享目標(biāo)文件
一種特殊類型的可重定位目標(biāo)文件,可以在加載活著運(yùn)行時(shí),被動(dòng)態(tài)的加載到存儲(chǔ)器并鏈接;
各個(gè)系統(tǒng)之間,目標(biāo)文件的格式都不相同;
SUN Solaris使用的是Unix ELF(可執(zhí)行可鏈接格式).盡管我們的討論集中在ELF上,但是不管是那種格式,基本的概念是相似的;
ELF
頭以一個(gè)16字節(jié)的序列開始,這個(gè)序列描述了字的大小和生成該文件的系統(tǒng)的字節(jié)順序.ELF頭剩下的部分包含幫助鏈接器解析和解釋目標(biāo)文件的信息.其中包
括了ELF頭的大小,目標(biāo)文件的類型(可重定位,可執(zhí)行或是共享等),機(jī)器類型(IA32等),節(jié)頭部表的文件偏移,以及節(jié)頭部表中的表目大小和數(shù)量.不
同節(jié)的位置和大小是有節(jié)頭部表描述的,其中目標(biāo)文件中每個(gè)節(jié)都有一個(gè)固定大小的表目;
每個(gè)可重定位目標(biāo)文件m都有一個(gè)符號(hào)表,它包含m所定義和引
用的符號(hào)的信息.在鏈接器的上下文中,有三種不同的符號(hào):1.由m定義并能被其他模塊引用的全局符號(hào).全局鏈接器符號(hào)對(duì)應(yīng)于非靜態(tài)的c函數(shù)以及被定義為不
帶c的static屬性的全局變量;2.由其他模塊定義的并被模塊m引用的全局符號(hào).這些符號(hào)稱為外部符號(hào),對(duì)應(yīng)于定義在其他模塊中的c函數(shù)和變量;3.
只被模塊m定義和引用的本地符號(hào).有的本地鏈接器符號(hào)對(duì)應(yīng)于代static屬性的c函數(shù)和全局變量.這些符號(hào)在模塊m中的任何地方都是可見的,但是不能被
其他模塊引用.
.symtab中的符號(hào)表不包含對(duì)應(yīng)于本地非靜態(tài)程序變量的任何符號(hào).這些符號(hào)在運(yùn)行時(shí)在棧中被管理,鏈接器第此類符號(hào)不感興趣;
c程序員使用static屬性在模塊內(nèi)部隱藏變量和函數(shù)聲明;
鏈接器解析符號(hào)引用的方法是將每個(gè)引用與它輸入的可重定位目標(biāo)文件的符號(hào)表中的一個(gè)確定的符號(hào)定義聯(lián)系起來;
編譯器只允許每個(gè)模塊中的每個(gè)本地符號(hào)只有一個(gè)定義,編譯器還確保靜態(tài)本地變量,他們也也會(huì)有本地鏈接器符號(hào),擁有唯一的名字;
當(dāng)編譯器遇到一個(gè)不是在當(dāng)前模塊中定義的符號(hào)(變量或是函數(shù))時(shí),它會(huì)假定該符號(hào)是在其他模塊中定義的,生成一個(gè)鏈接器符號(hào)表表目,并把它交給鏈接器處理;
c++和java中能使用重載函數(shù),是因?yàn)榫幾g器將每個(gè)唯一的方法和參數(shù)列表組合編碼成一個(gè)對(duì)鏈接器來說是一個(gè)唯一的名字.這種編碼過程叫做毀壞,而相反的過程叫做恢復(fù);
函數(shù)和已初始化的全局變量是強(qiáng)符號(hào),未初始化的全局變量是弱符號(hào);
根據(jù)強(qiáng)弱符號(hào)的定義,Unix鏈接器使用下面的規(guī)則來處理多處定義的符號(hào):1.不允許有多個(gè)強(qiáng)符號(hào);2.如果有一個(gè)強(qiáng)符號(hào)和多個(gè)弱符號(hào),那么選擇強(qiáng)符號(hào);3.如果有多個(gè)弱符號(hào),那么從這些弱符號(hào)中任意選擇一個(gè);
所有的編譯系統(tǒng)都提供一種機(jī)制,將所有相關(guān)的目標(biāo)文件打包為一個(gè)單獨(dú)的文件,稱為靜態(tài)庫,它也可以作為鏈接器的輸入;
在Unix系統(tǒng)中,靜態(tài)庫以一種稱為存檔的特殊文件格式存放在磁盤中.存檔文件是一組連接起來的可重定位目標(biāo)文件的集合,有一個(gè)頭部描述每個(gè)成員目標(biāo)的大小和位置;
在符號(hào)解析的階段,Unix鏈接器從左到右按照它們?cè)诰幾g器驅(qū)動(dòng)程序命令行上出現(xiàn)的相同順序來掃描可重定位目標(biāo)文件和存檔文件;
一
旦鏈接器完成了符號(hào)解析這一步,它就把代碼中的每個(gè)符號(hào)引用和確定的一個(gè)符號(hào)定義(也就是它的一個(gè)輸入目標(biāo)模塊中的一個(gè)符號(hào)表表目)聯(lián)系起來.在此時(shí),鏈
接器就知道它的輸入目標(biāo)模塊中的代碼節(jié)和數(shù)據(jù)節(jié)的確切大小.現(xiàn)在就可以開始重定位步驟了,在這個(gè)步驟中,將合并輸入模塊,并為每個(gè)符號(hào)分配運(yùn)行時(shí)的地址.
重定位由兩部分組成:重定位節(jié)和符號(hào)定義(這步完成時(shí),程序中的每個(gè)指令和全局變量都有一個(gè)唯一的運(yùn)行時(shí)存儲(chǔ)器地址了);重定位節(jié)中的符號(hào)引用.
當(dāng)
匯編器生成了一個(gè)目標(biāo)模塊時(shí),它并不知道數(shù)據(jù)和代碼最終將存放在存儲(chǔ)器的什么位置.它也不知道這個(gè)模塊引用的任何外部定義的函數(shù)或者全局變量的位置.所
以,無論何何時(shí)編譯器遇到對(duì)最終位置未知的目標(biāo)引用,它就會(huì)生成一個(gè)重定位表目,告訴鏈接器在將目標(biāo)文件合并成可執(zhí)行文件時(shí)如何修改這個(gè)引用,代碼的重定
位表目放在.relo.text中,已初始化數(shù)據(jù)的重定位表目放在.relo.data中;
可執(zhí)行文件的.init節(jié)定義了一個(gè)小函數(shù),叫做_init,程序的初始化代碼會(huì)調(diào)用它;
ELF可執(zhí)行文件被設(shè)計(jì)為一個(gè)很容易加載到存儲(chǔ)器,連續(xù)的可執(zhí)行文件組塊被映射到連續(xù)的存儲(chǔ)器段.段頭表描述了這種映射關(guān)系;
加載器將可執(zhí)行目標(biāo)文件中的代碼和數(shù)據(jù)從磁盤拷到存儲(chǔ)器中,然后通過跳轉(zhuǎn)到程序的第一條指令,即入口點(diǎn),來運(yùn)行該程序.這個(gè)將程序拷貝到存儲(chǔ)器并運(yùn)行的過程叫做加載;
當(dāng)
加載器運(yùn)行時(shí),它創(chuàng)建了一個(gè)存儲(chǔ)器映像.在可執(zhí)行文件中段頭表的知道下,加載器將可執(zhí)行文件的相關(guān)內(nèi)容拷貝到代碼和數(shù)據(jù)段,接下來,加載器跳轉(zhuǎn)到程序的入
口點(diǎn),也就是符號(hào)_start的地址._start地址處的啟動(dòng)代碼是在目標(biāo)文件ctrl.o中定義的,對(duì)所有的c程序都是一樣的.在從.text
和.init節(jié)中調(diào)用了初始化例程后,啟動(dòng)代碼調(diào)用atexit例程,這個(gè)程序附加了一系列在應(yīng)用調(diào)用exit函數(shù)時(shí)應(yīng)該調(diào)用的程序.exit函數(shù)運(yùn)行
atexit注冊(cè)函數(shù),然后通過調(diào)用_exit將控制返回給操作系統(tǒng),接著,啟動(dòng)代碼調(diào)用應(yīng)用程序的main程序,這就開始執(zhí)行我們的c代碼了,在應(yīng)用程
序返回之后,啟動(dòng)代碼調(diào)用_exit程序,它將控制返回給操作系統(tǒng);
Unix系統(tǒng)中的每個(gè)程序都運(yùn)行在一個(gè)進(jìn)程上下文中,這個(gè)進(jìn)程上下文有自己的
虛擬地址空間,當(dāng)shell運(yùn)行一個(gè)程序時(shí),父shell進(jìn)程生成一個(gè)子進(jìn)程,他是父進(jìn)程的一個(gè)復(fù)制品.子進(jìn)程通過execve系統(tǒng)調(diào)用啟動(dòng)加載器.加載
器刪除子進(jìn)程已有的虛擬存儲(chǔ)器段,并創(chuàng)建一組新的代碼,數(shù)據(jù),堆和棧段.新的棧和堆段被初始化為零.通過將虛擬地址空間中的頁映射到可執(zhí)行文件的頁大小的
組塊,新的代碼和數(shù)據(jù)段被初始化為可執(zhí)行文件的內(nèi)容.最后,加載器跳轉(zhuǎn)到_start地址,它最終會(huì)調(diào)用應(yīng)用的main函數(shù).除了一些頭部信息,在加載過
程中沒有任何從磁盤到存儲(chǔ)器的數(shù)據(jù)拷貝.直到CPU引用一個(gè)被映射的虛擬頁,才會(huì)進(jìn)行拷貝,此時(shí),操作系統(tǒng)利用它的頁面調(diào)度機(jī)制自動(dòng)將頁面從磁盤傳送到存
儲(chǔ)器;
共享庫是致力于解決靜態(tài)庫缺陷的一個(gè)現(xiàn)代創(chuàng)新產(chǎn)物.共享庫是一個(gè)目標(biāo)模塊,在運(yùn)行時(shí),可以加載到任意的存儲(chǔ)器地址,并在存儲(chǔ)器中和一個(gè)程序鏈接起來.這個(gè)過程稱為動(dòng)態(tài)鏈接,是由一個(gè)叫做動(dòng)態(tài)鏈接器的程序來執(zhí)行的;
在存儲(chǔ)器中,一個(gè)共享庫的.text節(jié)只有一個(gè)副本可以被不同的正在運(yùn)行的進(jìn)程共享;
生成動(dòng)態(tài)鏈接的可執(zhí)行文件時(shí),鏈接器拷貝了一些重定位和符號(hào)表信息,它們使得運(yùn)行時(shí)可以解析對(duì)動(dòng)態(tài)庫中代碼和數(shù)據(jù)的引用;
加
載器將可執(zhí)行文件的內(nèi)容映射到存儲(chǔ)器,并運(yùn)行這個(gè)程序.鏈接器還可能生成部分鏈接的可執(zhí)行程序,這樣的文件中有未解析的到定義在共享庫中的程序和數(shù)據(jù)的引
用.在加載時(shí),加載器將部分鏈接的可執(zhí)行文件映射到存儲(chǔ)器,然后調(diào)用動(dòng)態(tài)鏈接器,它通過加載共享庫和重定位程序中的引用來完成鏈接;
(7.7 7.11 7.12未理解)
第十章:虛擬存儲(chǔ)器
存儲(chǔ)器很容易被破壞.如果某個(gè)進(jìn)程不小心寫了另一個(gè)進(jìn)程使用的存儲(chǔ)器,那么進(jìn)程可能以某種完全和程序邏輯無關(guān)的令人迷惑的方式失敗;
為
了更加有效地管理存儲(chǔ)器并且少出錯(cuò),現(xiàn)代系統(tǒng)提供了一種對(duì)主存的抽象概念,叫做虛擬存儲(chǔ)器.虛擬存儲(chǔ)器是硬件異常,硬件地址翻譯,主存,磁盤文件和內(nèi)核軟
件的完美交互,它為每個(gè)進(jìn)程提供了一個(gè)大的,一致的,私有地址空間.通過一個(gè)很清晰的機(jī)制,虛擬存儲(chǔ)器提供了三個(gè)重要的能力:它將主存看成是一個(gè)存儲(chǔ)在磁
盤上的地址空間的高速緩存,在主存中只保存活動(dòng)區(qū)域,并根據(jù)需要在磁盤和主存之間來回傳送數(shù)據(jù),通過這種方式,它高效地使用了主存;它為每個(gè)進(jìn)程提供了一
個(gè)一致的地址空間,從而簡(jiǎn)化了存儲(chǔ)器管理;它保護(hù)了每個(gè)進(jìn)程的地址空間不被其他進(jìn)程破壞;
虛擬存儲(chǔ)器是危險(xiǎn)的.每次應(yīng)用程序引用一個(gè)變量,間接引用一個(gè)指針,或者調(diào)用一個(gè)諸如malloc這樣的動(dòng)態(tài)分配包程序時(shí),它就會(huì)和虛擬存儲(chǔ)器發(fā)生交互;
本章的前一部分描述虛擬存儲(chǔ)器是如何工作的,后一部分描述的是應(yīng)用程序如何使用和管理虛擬存儲(chǔ)器;
計(jì)算機(jī)系統(tǒng)的主存被組織成一個(gè)由M個(gè)連續(xù)的字節(jié)大小的單元組成的數(shù)組,每個(gè)字節(jié)都有一個(gè)唯一的物理地址;
早起的PC使用物理地址尋址,為通用計(jì)算設(shè)計(jì)的現(xiàn)代處理器使用的是虛擬尋址;
CPU芯片上叫做MMU的專用硬件,利用存放在主存中的查詢表來動(dòng)態(tài)的翻譯虛擬地址,該表的內(nèi)容由操作系統(tǒng)管理的;
一個(gè)地址空間的大小是由表示最大地址所需要的位數(shù)來描述的;
這就是虛擬存儲(chǔ)器的思想.主存中的每個(gè)字節(jié)都有一個(gè)選自虛擬地址空間的虛擬地址,和一個(gè)選自物理地址空間的物理地址;
概念上而言,虛擬存儲(chǔ)器被組織為一個(gè)存放在磁盤上的N個(gè)連續(xù)的字節(jié)大小的單元組成的數(shù)組.每字節(jié)都有唯一的虛擬地址,這個(gè)唯一的虛擬地址是作為到數(shù)組的索引的;
VM系統(tǒng)通過將虛擬存儲(chǔ)器分割為稱為虛擬頁的大小固定的塊,來處理這個(gè)問題.每個(gè)虛擬頁的大小為2的P次方字節(jié).類似的,物理存儲(chǔ)器被分割為物理頁,大小也是2的p次方字節(jié).
在任意時(shí)刻,虛擬頁的集合都分為三個(gè)不相交的子集:
未分配的:VM系統(tǒng)還未分配的頁,未分配的塊沒有任何數(shù)據(jù)和它們相關(guān)聯(lián),因此就不占用任何磁盤空間;
緩存的:當(dāng)前緩存在物理存儲(chǔ)器中的已分配頁;
未緩存的:沒有緩存在物理存儲(chǔ)器中的已分配頁;
DRAM比SRAM要慢大約10倍,而磁盤要比DRAM慢大約100000多倍.因此,DRAM緩存中的不命中比起SRAM緩存中的不命中要昂貴的多;
因?yàn)榇蟮牟幻刑幜P和訪問第一字節(jié)的開銷,虛擬頁趨向于很大,典型的是4-8KB;
比起硬件對(duì)SRAM緩存,操作系統(tǒng)對(duì)DRAM緩存使用了更復(fù)雜精密的替換算法,最后,因?yàn)閷?duì)磁盤的訪問時(shí)間很長(zhǎng),DRAM緩存總是使用寫回,而不是直寫;
同
任何緩存一樣,虛擬存儲(chǔ)器系統(tǒng)必須有某種方法來判定一個(gè)虛擬頁是否放在DRAM中的某個(gè)地方.如果是,系統(tǒng)還必須確定這個(gè)虛擬頁存放在哪個(gè)物理頁中.如果
不命中,系統(tǒng)必須判斷這個(gè)虛擬頁存放在磁盤的哪個(gè)位置,在物理存儲(chǔ)器中選擇一個(gè)犧牲頁,并將虛擬頁從磁盤拷貝到DRAM中,替換這個(gè)犧牲頁;
這些
功能是由許多軟硬件聯(lián)合提供的,包括:操作系統(tǒng)軟件,MMU中的地址翻譯硬件,和一個(gè)存放在物理存儲(chǔ)器中叫做頁表的數(shù)據(jù)結(jié)構(gòu).頁表將虛擬頁映射到物理頁.
每次地址翻譯硬件將一個(gè)虛擬地址轉(zhuǎn)換為物理地址時(shí),都會(huì)讀取頁表.操作系統(tǒng)負(fù)責(zé)維護(hù)頁表的內(nèi)容,以及在磁盤與DRAM之間來回傳送頁;
頁表就是一個(gè)PTE的數(shù)組.虛擬地址空間中的每個(gè)頁在頁表中的一個(gè)固定偏移量處都有一個(gè)PTE;
因?yàn)镈RAM緩存是全相關(guān)聯(lián)的,任意物理頁都可以包含任意虛擬頁;
地址翻譯硬件將虛擬地址作為一個(gè)索引;
虛擬存儲(chǔ)器是在20世紀(jì)60年代早起發(fā)明的,遠(yuǎn)在CPU-存儲(chǔ)器之間的差距的加大引發(fā)產(chǎn)生SRAM緩存之前;
虛擬存儲(chǔ)器工作的相當(dāng)好,這主要?dú)w功于我們的老朋友局部性;
操作系統(tǒng)為每個(gè)進(jìn)程提供了一個(gè)獨(dú)立的頁表,因而也就是一個(gè)獨(dú)立的虛擬地址空間;
多個(gè)虛擬頁面可以映射到同一個(gè)共享物理頁面上;
獨(dú)立的地址空間允許每個(gè)進(jìn)程為它的存儲(chǔ)器映像使用相同的基本格式,而不管代碼和數(shù)據(jù)實(shí)際存放在物理存儲(chǔ)器的何處;
這樣的一致性極大的簡(jiǎn)化了鏈接器的設(shè)計(jì)和實(shí)現(xiàn),允許鏈接器生成全鏈接的可執(zhí)行文件,這些可執(zhí)行文件是獨(dú)立于物理存儲(chǔ)器中代碼和數(shù)據(jù)的最終位置的;
一般而言,每個(gè)進(jìn)程都有自己的私有代碼,數(shù)據(jù),堆以及棧區(qū)域,是不和其他進(jìn)程共享的.在這種情況中操作系統(tǒng)創(chuàng)建頁表,將相應(yīng)的虛擬頁映射到不同的物理頁;
然而,在一些情況中,還是需要進(jìn)程來共享代碼和數(shù)據(jù)的.例如,每個(gè)進(jìn)程必須調(diào)用相同的操作系統(tǒng)內(nèi)核代碼,而每個(gè)c程序都會(huì)調(diào)用標(biāo)準(zhǔn)庫中的程序;
當(dāng)一個(gè)運(yùn)行在用戶進(jìn)程中的程序要求額外的堆空間時(shí),操作系統(tǒng)分配一個(gè)適當(dāng)?shù)臄?shù)次和連續(xù)的虛擬存儲(chǔ)器頁面,并且將它們映射到物理存儲(chǔ)器中任意位置的k個(gè)任意的物理頁面.由于頁表工作的方式,操作系統(tǒng)沒有必要分配k個(gè)連續(xù)的物理存儲(chǔ)器頁面.頁面可以隨機(jī)的分散在物理存儲(chǔ)器中;
有趣的一點(diǎn)是加載器從不真正的從磁盤中拷貝任何數(shù)據(jù)到存儲(chǔ)器中.當(dāng)每個(gè)頁面第一次被引用時(shí),虛擬存儲(chǔ)器系統(tǒng)將自動(dòng)并按需的把數(shù)據(jù)從磁盤上調(diào)入到存儲(chǔ)器,頁面引用或者是當(dāng)CPU取一條指令時(shí),或者是當(dāng)一條正在執(zhí)行的指令引用一個(gè)存儲(chǔ)器位置時(shí);
任
何現(xiàn)代計(jì)算機(jī)系統(tǒng)必須為操作系統(tǒng)提供手段來控制對(duì)存儲(chǔ)器系統(tǒng)的訪問.不應(yīng)該允許一個(gè)用戶進(jìn)程修改它的只讀文本段,而且也不應(yīng)該允許它讀或者修改任何內(nèi)核中
的代碼和數(shù)據(jù)結(jié)構(gòu).不應(yīng)該允許它讀或者寫其他進(jìn)程的私有存儲(chǔ)器,并且不允許它修改任何與其他進(jìn)程共享的虛擬頁面,除非所有的共享者都顯式的允許它這么做;
運(yùn)行在內(nèi)核模式中的進(jìn)程可以訪問的任何頁面,但是運(yùn)行在用戶模式中的進(jìn)程只能訪問那些SUP為0的頁面;
CPU中的一個(gè)控制寄存器,頁表基址寄存器指向當(dāng)前的頁表;
MMU利用VPN來選擇適當(dāng)?shù)腜TE.例如vpn0選擇pte0等.將頁表?xiàng)l目中的PPN和虛擬地址中的VPO串聯(lián)起來,就得到相應(yīng)的物理地址,注意,因?yàn)槲锢砗吞摂M頁面都是P字節(jié)的,所以PPO和VPO是相同的;
當(dāng)出項(xiàng)頁面命中時(shí),CPU硬件執(zhí)行的步驟:
處理器生成一個(gè)虛擬地址,并把它傳送給MMU;
MMU生成PTE地址,并從高速緩存/主存請(qǐng)求得到它;
高速緩存/主存向MMU返回PTE;
MMU構(gòu)造物理地址,并把它傳送給高速緩存/主存;
高速緩存/主存返回所請(qǐng)求的數(shù)據(jù)字給處理器;
頁面命中完全由硬件來處理的,而處理缺頁要求硬件和操作系統(tǒng)內(nèi)核協(xié)作完成的;
第一步到第三步同上
PTE中的有效位是零,所以MMU觸發(fā)了一次異常,傳遞CPU中的控制到操作系統(tǒng)內(nèi)核中的缺頁異常處理程序;
缺頁處理程序確定出物理存儲(chǔ)器中的犧牲頁面,如果這個(gè)頁面已經(jīng)被修改了,則把它頁面換出到磁盤;
缺頁處理程序頁面調(diào)入新的頁面,并更新存儲(chǔ)器中的PTE;
缺頁處理程序返回到原來的進(jìn)程,驅(qū)使導(dǎo)致缺頁的指令重新啟動(dòng),CPU將引起缺頁的指令重新發(fā)送給MMU.因?yàn)樘摂M頁面現(xiàn)在緩存在物理存儲(chǔ)器中,所以就會(huì)命中,在MMU執(zhí)行了如上的步驟,主存就會(huì)將所請(qǐng)求的自返回給處理器;
MMU中包括了一個(gè)關(guān)于PTE的小的緩存,稱為TLB(翻譯后備緩沖器)
用于組選擇和行匹配的索引和標(biāo)記字段是從虛擬地址中的虛擬頁號(hào)中提取出來的;
用來壓縮頁表的常用方法是使用 層次結(jié)構(gòu)的頁表;
一級(jí)頁表轉(zhuǎn)中的每個(gè)PTE負(fù)責(zé)映射虛擬地址空間中的一個(gè)4MB的組塊;
如果組塊i中的每個(gè)頁面都未分配,那么一級(jí)PTE i就為空,然而,如果在組塊i中至少有一個(gè)頁是分配了的,那么一級(jí)PTE i就指向一個(gè)二級(jí)頁表的基址;
二級(jí)頁表中的每個(gè)PTE都負(fù)責(zé)映射一個(gè)4KB的虛擬存儲(chǔ)器頁面,每個(gè)一級(jí)和二級(jí)頁表都是4KB的;
這
種方法從兩個(gè)方面減少了存儲(chǔ)器要求:第一,如果一級(jí)頁表中的一個(gè)PTE是空的,那么相應(yīng)的二級(jí)頁表就根本不會(huì)存在;第二,只有一級(jí)頁表需要總是在主存中,
虛擬存儲(chǔ)器系統(tǒng)可以在需要的時(shí)候創(chuàng)建并頁面調(diào)入或調(diào)出二級(jí)頁表,這就減少了主存的壓力.只有最經(jīng)常使用的二級(jí)頁表才需要緩存在主存中;
為了方便,我們用索引它的VPN來標(biāo)識(shí)每個(gè)PTE,但是要記住這些VPN并不是頁表的一部分,也不在存儲(chǔ)器中;
posted on 2010-09-30 10:40
何克勤 閱讀(562)
評(píng)論(0) 編輯 收藏