[轉載]CS:APP筆記+每章總結(2017-08-26 23:31:27

可以借鑑!
原文地址:CS:APP筆記+每章總結作者:唳天飛鷹
CS:APP(Computer Systems:A Programmmer’s Perspective),中文譯名:深入理解計算機系統,可謂是一本非常經典的書了,有人甚至說是程序員必看書籍之一。

由於前輩們(尤其是嘉哥,在嘉哥的相關技術書籍裏發現的這本書)對這本書的讚譽不斷,所以我也找了本看看,只有一個感覺:太厚了!在讀第一章的時候,還是很容易入戲的,可是越到後來越是難以入戲啊,各種硬件知識有木有,都出來處理器設計了(書中仿照IA32體系,也就是我們平時所說的x86,自己搞了個Y86指令集,而且用各種組合電路來實現這個指令集,暈死,有木有,有木有!),總的來說,書中確實涉及到不少方面的知識,而且很多知識對於我們理解程序和硬件的結合是特別有幫助的!

前言:計算機系統漫遊
這個是我唯一讀的比較輕鬆的地方,嘿嘿!寫一下我作的筆記吧!
1。1信息就是位+上下文
程序源文件就是由bit組成的。
1。2程序被其他程序翻譯成不同的格式
源文件--目標文件---可執行文件
gcc還可以將c程序翻譯成彙編代碼gcc -s test test.c
gcc還可以對程序進行一定的編譯優化:gcc -O2 -o test test.c
1。3瞭解編譯系統如何工作的好處
優化程序的性能、理解鏈接時出現的各種錯誤、避免安全漏洞
1。4處理器讀並解釋存儲在存儲器中的指令
硬件組成:
cpu: pc 、寄存器堆、ALU、總線接口
總線:傳送定長的字節塊,被稱爲字
I/o設備:控制器(一般在主板上)+適配器
主存:由DRAM芯片組成
1。5高速緩存
L1和L2
1。6層次結構存儲設備
寄存器--》L1--》L2--》DRAM--》本地二級存儲--》遠程二級存儲
1。7操作系統管理硬件
[轉載]CS:APP筆記+每章總結

進程、線程、虛擬存儲器、文件(將一切看作文件)

進程的虛擬地址空間:

[轉載]CS:APP筆記+每章總結

前言部分總結:
計算機系統是由硬件和系統軟件組成的,它們共同協作以運行應用程序。計算機內部的信息被表示爲一組組的位,它們依據不同的上下文又有不同的解釋方式。程序被其他程序翻譯成不同的形式,開始時是ASCII文本,然後被編譯器和鏈接器翻譯成二進制可執行文本。
處理器讀取並解釋存放在主存裏的二進制指令。因爲計算機花費了大量的時間在存儲器、I/O設備和CPU寄存器之間拷貝數據,所以系統中的存儲設備就被按層次排列,CPU寄存器在頂部,接着是多層硬件高速緩存存儲器、DRAM主存儲器和磁盤存儲器。在層次模型中位於更高層的存儲設備比低層的存儲設備要快,單位比特造價也更高。程序員通過理解和運用這種存儲層次結構的知識,可以優化他們C程序性能。
操作系統內核是應用程序和硬件之間的媒介。它提供三個基本的抽象概念:文件是對I/O設備的抽象概念;虛擬存儲器是對主存和磁盤的抽象概念;進程是處理器、主存和I/O設備的抽象概念。
最後,網絡提供了計算機系統之間通信的手段。從某個系統的角度來看,網絡就是一種I/O設備。

第一部分:程序結構和執行
我們對計算機系統的探索是從學習計算機本身開始的,它由處理器和存儲器子系統組成。在覈心部分,我們需要方法來表示基本數據類型,比如整數和實數去處的近似值。然後,我們考慮機器級指令如何操作這樣的數據,編譯器如何將C程序翻譯成這樣的指令。接下來,我們研究幾種實現處理器的方法,來更好地瞭解如何使用硬件資源來執行指令。一旦我們理解了編譯器和機器級代碼,我們就能通過編寫可以高效理解編譯的源代碼,來分析如何最大化程序的性能。我們以存儲器子系統的設計來結束本部分,這是現代計算機系統最複雜的部分之一。

第2章:信息的表示和處理

2。1信息的存儲
大多數計算機使用8位的塊,或叫做字節,來作爲最小的可尋址的存儲器單位,而不是訪問存儲器中單獨的位,這就產生了虛擬存儲器。學過操作系統的同學,應該還記得虛擬地址是怎麼和物理地址對應起來的。
信息是按二進制的位存儲的,但是我們還應該掌握二進制、十進制和十六進制的轉換。
還要注意,不同的硬件系統的,信息的存儲也是有一些差別的,比如我們所熟悉的大端存儲和小端存儲。小端法指最低有效字節在最前面的低位,大端法指最高有效字節在最前面的低位。
在信息系統中,還有一個特殊的運算體系,那就是布爾代數體系,並且由此產生了布爾環(主要用於高質量的CD等,比如CD被磨損了,仍能正常尋址,都是布爾環的功勞)。

2。2整數表示
C支持多種整型數據類型--表示有限範圍的整數。
無符號數和二進制補碼編碼表示。
有符號數和無符號數的轉換:存儲的位不變,不過編譯器解釋不同了。

2。3整數運算
無符號數的加法。
二進制補碼的加法。
無符號的乘法。
乘以2的冪。移位實現,左移
除以2的冪,移位實現,右稱,只需要一個時鐘週期。
浮點數:具體想想計算機組成原理那塊吧,規格化和非規格化、溢出

第2章小結:
計算機將信息編碼爲位(比特),通常組織成字節序列。有不同的編碼方式用來表示整數、實數和字符串。不同的計算機模型在編碼數字和多字節數據中的字節順序上使用不同的約定。
C語言被設計成包容多種不同字長和數字編碼的實現。雖然高端機器逐漸開始使用64位字長,但是目前大多數機器仍使用32位字長。大多數機器對整數使用二進制補碼編碼,而對浮點數使用IEEE編碼。在位級上理解這些編碼,並且理解算術運算的數學特性,對於編寫能在全部數值上正確運算的程序來說,是很重要的的。
C語言的標準規定在無符號和有符號整數之間進行強制類型轉換時,基本的位模型不應改變。C語言中隱式的強制類型轉換會得到許多程序員無法預計的結果,常常導致程序錯誤。
由於編碼長度有限,計算機運算與傳統整數和實數相比,具有非常不同的屬性。當超出表示範圍時,有限長度能夠引起數值溢出。當浮點數非常接近0.0時,從而轉換成0時,浮點數也會下溢。
和大多數其他程序一樣,C語言實現的有限整數運算和真實的整數運算相比有一些特殊的屬性。例如,由於溢出,表達式xx有可能會得到一個負數。但是,無符號數和二進制補碼的運算都能滿足環的屬性。這就允許編譯器做很多的優化。例如,用(x<<3)-x取代表示7x時,我們就利用了結合性、交換性和分配性,還利用了移位和乘以2的冪之間的關係。
我們已經看到了幾種使用位級運算和算術運算組合的聰明方法。
浮點數表示通過將數字編碼爲x*2^y的形式來近似的表示實數。最常見的浮點數表示是由IEEE標準754定義的。必須非常小心地使用浮點數,因爲浮點數運算的範圍和精度非常有限,而且浮點數並不遵守普遍的算術屬性,比如結合性。

第3章程序的機器級表示

3.1歷史觀點
這一節簡要地回顧了Intel的體系結構。Intel處理器從1978年那個想法簡單的16位處理器發展而來,現在已經成爲桌面計算機的主流機器。隨着新特性的加入,體系結構也在相應地成長,從16位體系結構轉變成了支持32位(甚至64位和128位)數據和地址的結構。

3.2程序編碼
假如我們寫一個C程序,有兩個文件p1.c和p2.c,然後我們用Unix命令行編譯這些代碼:
unix> gcc -O2 -o p p1.c p2.c
命令gcc表明的就是GNU C編譯器GCC。編譯選項-O2告訴編譯器使用第二級優化。通常,提高優化級別會使最終程序運行得更快,但是編譯時間可能會變長,對代碼進行調試會更困難。第二級優化是性能優化和使用方便之間的一種很好的妥協。
這個命令實際上調用了一系列程序,將源代碼轉化爲可執行代碼。首先,C預處理器會擴展源代碼,插入所有#include命令指定的文件,並擴展所有宏。其次,編譯器產生兩個源文件的彙編代碼,名爲p1.s和p2.s。接下來,彙編器會將彙編代碼轉化成二進制目標代碼文件p1.o和p2.o。最後,鏈接器將兩個目標文件與實現標準Unix庫函數的代碼合併,併產生最終的可執行文件。

3.3數據格式
由於是從16位體系結構擴展成32位的,Intel使用術語“字”表示16位數據類型。因此,稱32位數爲“雙字”,稱64位爲“四字”。
普通整數和長整數都是雙字的,無論它們是否有符號。
字符類型是單字節。
浮點數有三種形式:單精度(4字節)爲float,雙精度(8字節)爲double。

3.4訪問信息
一個IA32中央處理器單元(CPU)包含一組八個存儲32位值的寄存器,這些寄存器用來存儲整數數據和指針。字操作指令可以獨立地讀或者寫前四個寄存器的兩個低位字節。

 操作數指示符:大多數指令有一個或多個操作數,指示出執行一個操作中要引用的源數據值,以及放置結果的目的位置。各種操作數的可能性被劃分爲三種類型:立即數、寄存器、存儲器引用。
有多種不同的尋址模式,允許不同形式的存儲器引用。這類尋址方式比較多,參考:http://baike.baidu.com/view/889427.htm

3.5算術和邏輯操作
加載有效地址指令:leal,實際上是movl指令的變形,它從存儲器讀數據到寄存器。
一元和二元操作符:一元操作,只有一個操作數,既作源,也作目的。二元操作,第二個操作數既是源又是目的。
移位操作:先給出移位量,然後是待移位的值。

3.6控制
程序執行的一個很重要的部分就是控制被執行操作的順序。
條件碼:CF、ZF、SF、OF等。
一些跳轉指令根據這些條件碼進行跳轉。

3.7過程
一個過程調用包括將數據和控制從代碼的一部分傳遞到另一部分。
IA32使用棧幀結構來支持過程調用。棧用來傳遞過程參數、存儲返回信息、保存寄存器以供以後恢復之用,以及用於要地存儲。爲單個過程分配的那部分棧稱爲棧幀。

3.8數組分配和訪問
C中數組是一種將標題型數據聚焦成更大數據類型的方式。C用來實現數組的方式非常簡單。C的一個不同尋常的特點是可以對數組中的元素產生指針,並對這些指針進行運算。

動態分配的數組:C只支持大小在編譯時就能知道的多維數組,在許多應用程序中,我們需要代碼能夠對動態分配的任意大小的數組進行操作,在C中用malloc和calloc兩個函數。

3.9異類的數據結構
C提供了兩種將不同的類型的對象結合到一直來創建數據類型的機制:結構和聯合。
C的結構聲明創建一個數據類型,將可能不同類型的對象聚合到一個對象中,結構的各個組成部分是用名字來引用的。結構的實現類似於數組的實現,因爲結構的所有組成部分都存放在存儲器中連續的區域內,而指向結構的指針就是結構第一個字節的地址。編譯器保存關於每個結構類型的信息,指示每個域的字節偏移。它以這些偏移作爲存儲器引用指令字節中的位移,從而產生對結構元素的引用。
聯合提供了一種方式,能夠規避C的類型系統,允許以多種類型來引用一個對象。聯合聲明的語法與結構的語法一樣,只不過語義相差較大。它們不是用不同的域來引用不同的存儲器塊,而是引用同一存儲器塊。

3.10對齊
許多計算機系統對基本數據類型的可允許地址做出了一些限制,要求某種類型的對象的地址必須是某個值K(通常是2、4、8)的倍數。這種對齊限制簡化了處理器和存儲器系統之間接口的硬件設計。

3.11綜合:理解指針
指針是C語言的一個重要特色。它們提供一種統一方式,能夠遠程訪問數據結構。
1)每個指針都有一個值。這個值是指定類型對象的地址。特殊的NULL(0)值表示該指針沒有指向任何地方。
2)指針是用&運算符創建的。
3)*操作符用於指針的間接引用。
4)數組與指針是緊密聯繫的。
5)指針也可以指向函數。

3.12現實生活:使用GDB調試器
GNU的調試器GDB提供了許多有用的特性來支持對機器級程序的運行時評估的分析。
GDB常用命令:
開始和停止:quit run kill
斷點:break sum(sum is a function) break *0x80483c3(0x80483c3是地址) delete 1(刪除斷點1) delete(刪除所有斷點)
執行:stepi (執行一步) stepi 4(執行4步) nexti (與stepi相似,但是會不會進入函數調用)
 continue(繼續執行) finish(運行直到當前函數返回)
 檢查代碼:disas(檢查當前代碼) disas sum(檢查sum函數)
檢查數據:print $eax(打印寄存器)

3.13.存儲器的越界引用和緩衝區溢出
  C對於數組引用不進行任何邊界檢查,而且局部變量和狀態信息都存放在棧中。這兩種情況結合到一起,就能導致嚴重的程序錯誤,一個對越界數組元素的寫操作破壞了存儲在棧中的狀態信息。
  另一種特別常見的狀態破壞稱爲緩衝區溢出。通常,在棧中分配某個字節數組來保存一個字符串,但是字符串的長度超出了爲數組分配的空間。

3.14小結
  在本章中,我們窺視了高級語言提供的抽象層下面的東西,以瞭解機器級編程。通過讓編譯器產生機器級的彙編代碼表示,我們瞭解了編譯器和它的優化能力,以及機器代碼、它的數據類型和它的指令集。
  彙編語言和C代碼差別很大。在彙編語言程序中,各種數據類型之間的差別很小。程序是以指令序列來表示的,每條指令都完成一個單獨的操作。部分程序狀態,如寄存器和運行時棧,對程序員來說是直接可見的。僅提供了低級操作來支持數據處理和程序控制。編譯器必須用多條指令來產生和操作各種數據結構,來實現像條件、循環和過程這樣的控制結構。我們講述了C和如何編譯C的許多不同之處。我們看到C缺乏邊界檢查,使得許多程序容易出現緩衝區溢出,而這已經使許多系統容易受到入侵者的惡意攻擊。
  我們只分析了C到IA32的映射,但是我們講的大多數內容對其他語言和機器組合來說也是類似的。例如編譯C++與編譯C就非常相似,實際上,C++的早期實現就只是簡單的執行了從C++到C的源到源的轉換,並對結果運行C編譯器,產生目標代碼。C++的對象用結構來表示,類似於C的struct。C++的方法是用指向實現方法的代碼的指針來表示的。相比而言,java的實現方式完全不同。java的目標代碼是一種特殊的二進制表示稱爲java字節碼。這種代碼可以看成是虛擬機的機器級程序。正如它的名字暗示的那樣,這種機器並不是直接用硬件實現的。相反,軟件解釋器處理字節碼,模擬虛擬機的行爲。這種方法的優點是相同的java字節碼可以在許多不同的機器上執行。

第四章:處理器體系結構
  本章依照X86指令集自己造了個Y86指令集,並根據這個指令集設計了一個CPU。
  由於本章偏向硬件,所以在此筆記做的比較粗糙。
4.1Y86指令集
  大體意思是說Y86是X86的精簡版。
4.2邏輯設計和硬件控制語言HCL
在這節中講到了邏輯門、組合電路和HCL布爾表達式、字級的組合電路和HCL整數表達式、集合關係、存儲器和時鐘控制
4.3Y86的順序實現
本節主要講了將處理組織成階段、SEQ的硬件結構、SEQ的時序、SEQ+:重新安排計算階段
4.4流水線的通用原理
流水線的一個重要特徵是增加了吞吐量,也就是單位時間內服務的顧客總數,不過它也會輕微地增加執行時間,也就是服務一個用戶需要的時間。
流水線也是存在侷限性的:不一致的劃分;深度流水線,收益反而下降;帶反饋的流水線。

4.5流水線的實現
插入流水線寄存器、對信號進行重新排列和標號、流水線冒險、用暫停來避免數據冒險;用前遞來避免數據冒險;加載和使用數據冒險

4.6小結
我們已經看到,指令集體系結構(ISA)在處理器行爲(就指令集合及其編碼而言)和如何實現處理器之間提供了一層抽象。ISA提供了程序執行的一種順序說明,也就是一條指令執行完了,下一條指令纔會開始。
基於ISA指令集,並且大大簡化其數據類型、地址模式和指令編碼,我們定義出了Y86指令集,得到的ISA既有RISC指令集的屬性,也有CISC指令集的屬性。然後,我們將不同指令組織放到六個階段中處理,在此,根據被執行指令的不同,每個階段中的操作也不相同。從此,我們構造了SEQ處理器,其中每個時鐘週期推進一條指令通過每個階段。通過重新排列各個階段,我們創建了SEQ+設計,其中第一個階段選擇程序計數器的值,它被用來取出當前指令。
流水線化通過讓不同的階段並行操作,改進了系統的吞吐量性能。在任意一個給定的時間,多條指令被處理。在引入這種並行性的過程中,我們必須非常小心,以提供與程序的順序執行相同的用戶可見的、程序級行爲。我們通過往SEQ+中添加流水線寄存器,並重新安排週期來創建PIPE-流水線,介紹了流水線化。然後,我們添加了前遞邏輯,加速了將結果從一條指令發送到另一條指令,從而提高了流水線的性能。有幾種特殊情況需要額外的流水線控制邏輯來暫停或取消一些流水線階段:
在本章中,我們學習了有關處理器設計的幾個重要的經驗:
1)管理複雜性是首要問題。我們想要優化使用硬件資源,在最小的成本下獲得最大的性能。爲了實現這個目的,我們創建了一個非常簡單而一致的框架,來處理所有不同指令類型。有了這個框架,我們就能夠在處理不同指令類型的邏輯中間共享硬件單元。
2)我們不需要直接實現ISA。ISA的直接實現意味着一個順序的設計。爲了獲得更高的性能,我們想運用硬件能力以同時執行許多操作,這就導致要使用流水線化的設計。通過仔細的設計和分析,我們能夠處理各種流水線冒險,因此運行一個程序的整體效果,同用ISA模型獲得的效果完全一致。
3)硬件設計人員必須非常謹慎小心。一旦芯片被製造出來,就幾乎不可能改正任何錯誤了。一開始就使設計正確是非常重要的。意思就是,仔細地分析各種指令類型和組合情況,甚至於那些看上去沒有意義的情況,例如彈出棧指針。必須用系統的模擬測試程序徹底地測試設計。

第五章:優化程序性能
編寫高效程序需要兩類活動:第一,我們必須選擇一組最好的算法和數據結構;第二,我們必須編寫出編譯器效優化以轉換成高效可執行代碼的源代碼。

5.1優化編譯器的能力和侷限性
編譯器優化程序的能力受幾個因素的限制,包括:要求它們絕不能改變正確的程序行爲;它們對程序行爲、對使用它們的環境瞭解有限;需要很快地完成編譯工作。
編譯器必須假設不同的指針可能會指向存儲器中同一位置。這就造成了一個主要的妨礙化的因素。

5.2表示程序性能
我們需要一種方法來表示程序性能,它能指憮們改進代碼。對許多程序都很有用的度量標準是每元素的週期數(CPE)。這種度量標準幫助我們在更詳細的級別上理解迭代程序的循環性能。

5.3消除循環中的低效率
在循環中不變的求值運算拿到循環外。這種優化稱爲代碼移動優化。

5.4減少過程調用
過程調用會造成想法大的開銷,而且妨礙大多數形式的程序優化。
對於性能至關重要的程序來說,爲了速度,經常必須要損害一些模塊性的抽象性。

5.5消除不必要的存儲器引用
可以把一個在循環中經常要用到的存儲器的值用臨時變量來代替。

5.6理解現代處理器
這節講解了PentiumIII處理器一個簡單的工作模型。
整個設計分爲兩個部分:ICU(指令控制單元)和EU(執行單元)。前者負責從存儲器中讀出指令序列,並根據這些指令序列生成一組針對程序數據的基本的操作;而後者執行這些操作。
ICU從指令高速緩存中讀取指令,指令高速緩存是一個特殊的高速緩存存儲器,它包含最近訪問的指令。不過當程序遇到轉移時,程序有兩種可能的前進方向:一種是選擇轉移,控制被傳遞到轉移目標;另一種是不選擇轉移,控制被傳遞到指令序列的下一條指令。現代處理器採用了一種稱爲轉移預測的技術,在這種技術中處理器會預測是否選擇轉移,同時還預測轉移的目標地址。使用一種投機執行的技術。

5.7降低循環的開銷
本節主要幾種不太常用的降低循環開銷的方法。比如在一個循環中連續進行幾個操作。

5.10提高並行性
循環分割

5.11小結
雖然關於代碼優化的大多數論述都描述了編譯器是如何能生成高效代碼的,但是應用程序員有很多方法來協助編譯器完成這項任務。沒有任何編譯器能用一人好的算法或數據結構代替低效率的算法或數據結構,因此程序設計的這些方面仍然應該是程序員主要關心的。我們還看到妨礙優化的因素,例如存儲器別名和過程調用,嚴重限制了編譯器執行大量優化的能力。同樣,程序員必須對消除這些妨礙優化的負主要責任。
除些之外,我們還研究了一系列技術,包括循環展開、迭代分割以及指針運算。隨着我們對優化的深入,研究彙編代碼以及試着理解機器是如何執行計算的變得重要起來。對於現代、亂序處理器上的執行,分析程序是如何在有無限處理資源但是功能單元的執行時間和發射時間與目標處理器相符的機器上執行的,收穫良多。爲了精練這個分析,我們還應該考慮諸如功能單元數量和類型這樣的資源約束。
包含條件轉移可與存儲器系統複雜交互的程序,比我們首先考慮的簡單循環程序,更加難以分析和優化。基本策略是使循環更容易預測,並試着減少存儲和加載操作之間的相互影響。
當處理大型程序時,將我們的注意力集中在最耗時的部分變得很重要。代碼剖析程序和相關的工具能幫助我們系統地評價和改進程序性能。我們描述了GPROF,一個標準的Unix剖析工具。也還有更加複雜完善的剖析程序可用,例如Intel的VTUNE程序開發系統。這些工具可以在過程級分解執行時間,測量程序每個基本塊的性能。基本塊是沒有條件操作的指令序列。

第六章:存儲器的層次結構

6.1存儲技術
靜態RAM(SRAM):一個又穩態的存儲單元
動態RAM(DRAM) : DRAM將每個位存儲爲對電容充電。
磁盤存儲:構造:每個盤片有兩個面,表面覆蓋着磁性記錄材料;磁盤容量:記錄密度、磁道密度、面密度;磁盤操作:尋道時間、旋轉時間、傳送時間;邏輯磁盤塊:盤面、磁道、扇區。

6.2局部性
局部性通常有兩種形式:時間局部性和空間局部性。
局部性小結:
1)重要引用同一個變量的程序有良好的時間局部性。
2)對於具有步長爲k的引用模式的程序,步長越小,空間局部性越好。
3)對於取指令來說,循環有好的時間和空間局部性。循環越小,循環迭代次數越多,局部性越好。

6.3存儲器的層次結構
整個計算機體系的存儲器層次系統:寄存器-》L1級緩存—》L2級緩存-》DRAM主存-》本地二級緩存-》遠程二級緩存。
緩存體系:數據總是塊爲傳送單元。
緩存命中:
緩存不命中:覆蓋一個現存的塊的過程被稱爲替換。替換策略:隨機替換、LRU(最近最少使用)。

6.4高速緩存存儲器
現在系統中,L1級高速緩存一般是位於CPU芯片中,L2高速緩存連接到存儲器總線或連接到它自己的高速緩存總線。
緩存與被緩存的映射關係:直接映射、組相聯、全相聯。
有關寫的問題:寫命中:直寫法、寫回法。
寫不命中:寫分配-》加載相應的存儲器到緩存 非寫加法:避開高速緩存,直接更新存儲器。

6.5小結
基本存儲技術包括RAM(隨機存儲器)、ROM(非易失性存儲器)和磁盤。RAM有兩種基本類型。SRAM(靜態RAM)快一些,但是也貴一些,它既可以用做CPU芯片上的高速緩存,也可以用做芯片外的高速緩存。動態RAM(DRAM)慢一點,也便宜一些,用做主存和圖形幀緩衝區。非易失性存儲器,也稱爲只讀存儲器(ROM),即使是在關電的時候,也能保持它們的信息,它們用來存儲固件。磁盤是非易失性存儲設備,以每個位很低的成本保存大量的數據。代價是較長的訪問時間。
一般而言,較快的存儲技術每個位會更貴,而且容量較小。這些技術的價格和性能屬性正在動態地以不同的速度變化着。特別地,DRAM和磁盤訪問時間滯後於CPU週期時間。系統通過將存儲器組織成存儲設備的層次經結構來彌補這些差異,在這個層次結構中,較小、較快的設備在頂部,較大、較慢的設備部。因爲編寫良好的程序有好的局部性,大多數數據都可以從較高層得到服務,結果就是存儲系統能以較高層的速度運行,但卻有較低層的成本和容量。
程序員可以通過編寫良好空間和時間局部性的程序來動態地改進程序的運行時間。利用基於SRAM的高速緩存存儲器特別重要,主要從L1高速緩存存取數據的程序能比從存儲器取數據的程序運行快過一個數量級。

第二部分:在系統上運行程序
鏈接器把我們程序的各個部分聯合成一個單獨的文件,處理器可以將這個文件加載到存儲器,並且執行它。現代操作系統與硬件合作,爲每個程序提供一種幻像,好像這個程序是在獨佔地使用處理器和主存,而實際上在任何時文,系統上都有多個程序在運行。這部分,你將很好地理解程序和硬件之間的交互關係。本部分旨在拓寬你對系統的瞭解,使你牢固地掌握程序和操作系統之間的交互關係。你將學習到如何使用操作系統提供的服務來構建系統級程序。

第7章鏈接
鏈接就是將不同部分的代碼和數據收集和組合成一個單一文件的過程,這個文件可被加載到存儲器並執行。鏈接可以執行於編譯時,也就是在源代碼被翻譯成機器代碼時,也可以執行於加載時,也就是在程序被加載器加載到存儲器並執行時;甚至可以執行於運行時,由應用程序來執行。

7.1編譯器驅動程序
大多數編譯系統提供編譯驅動程序,它爲用戶,根據需求調用語言預處理器、編譯器、彙編器和鏈接器。
我們拿GNU的GCC編譯驅動程序來說一下,這裏我們有一個文件main.c,首先驅動程序會運行C預處理器,將main.c翻譯成一個ASCII碼的蹭文件main.i,接下來,驅動程序運行C編譯器,將main.i翻譯成一個ASCII彙編語言文件main.s,隨後驅動程序運行彙編器,將main.s翻譯成一個可重定位的目標文件main.o。
在main.c中引用了swap.c,那麼驅動程序會經過相同的過程把swap.c翻譯成swap.o,最後,驅動程序會運行鏈接器程序,將main.o和swap.o以及一些必要的系統目標文件組合起來,創建了個可執行的目標文件。

7.2靜態鏈接
靜態鏈接器以一組可重定位目標文件和命令行參數作爲輸入,生成一個完全鏈接的可以加載和運行的可執行文件作爲輸出。
爲了創建可執行文件,鏈接器必須完成兩個主要任務:
1)符號解析。目標文件定義和引用符號。符號解析的目的是將每個符號引用和一個符號定義聯繫起來。
2)重定位。編譯器和彙編器生成從地址零開始的代碼和數據節。鏈接器通過把每個符號定義與一個存儲器位置聯繫起來,然後修改所有對這些符號的引用,使得它們指向這個存儲器位置,從而重定位這些節。

7.3目標文件
目標文件有三種形式:
1)可重定位目標文件。包含二進制代碼和數據,其形式可以在編譯時與其他可重定位目標文件合併起來,創建一個可執行目標文件。
2)可執行目標文件。包含二進制代碼和數據,其形式可以被直接拷貝到存儲器並執行。
3)共享目標文件。一種特殊類型的可重定位目標文件,可以在加載或者運行時,被動態地加載到存儲器並鏈接。

7.4可重定位的目標文件(ELF)
可重定位目標文件
ELF頭、.text、.rodata、.data、.bss、.symtab、.rel.text、.rel.data、.debug、.line、.strtab、節頭部表。
.text:已編譯程序的機器代碼。
.rodata:只讀數據。
.data:已初始化的全局C變量。
.bss:未初始化的全局C變量。
.symtab:一個符號表,它存放在程序中被定義和引用的函數和全局變量的信息。
.rel.text:當鏈接器把這個目標文件和其他文件結合時,.text節中的許多位置都需要修改。一般而言,任何調用外部函數或者引用全局變量的指令都需要修改。另一方面,調用本地函數的指令則不需要修改。
.rel.data:被模塊定義或引用的全局變量信息。
.debug:一個調試符號表,其有些表目是程序中定義的局部變量和類型定義,有些表目是程序中定義和引用的全局變量,有些是原始的C源文件。只有以-g選項調用編譯驅動程序時,纔會生成這個表。
 .line:原始C源程序中的行號和.text節中機器指令之間的映射。
 .strtab:一個字符表,其內容包括.symtab和.debug節中的符號表,以及節頭部中的節名字。

7.5符號和符號表
每個可重定位的目標模塊m都有一個符號表,它包含m所定義和引用的符號的信息。在鏈接器的上下文中,有三種不同的符號:
1)由m定義並能被其他模塊引用的全局符號。全局鏈接器符號對應於非靜態的C函數以及被定義爲不帶C的static屬性的全局變量。
2)由其他模塊定義並被模塊m引用的全局符號。這些符號稱爲外部符號,對應於定義在其他模塊中的C函數和變量。
3)只被模塊m定義和引用的本地符號。有的本地鏈接器符號對應於帶static屬性的C函數和全局變量。這些符號在模塊m中的任何地方都是可見的,但是不能被其他模塊引用。目標文件中對應於模塊m的節和相應的源文件的名字也能獲得本地符號。

7.6符號解析
鏈接器解析符號引用的方法是將每個引用與它輸入的可重定位的目標文件符號表中的一個確定的符號定義聯繫起來。
對本地符號的引用,符號解析非常簡單明瞭。編譯器只允許每個模塊中的每個要地符號只有一個定義。編譯器還確保靜態本地變量,它們也會有本地鏈接器符號,擁有惟一的名字。
對全局符號的引用解析就棘手得多。當編譯器遇到一個不是在當前模塊中定義的符號(變量或函數名)時,它會假設該符號是在其他某個模塊中定義的,生成一個鏈接器符號表表目,並把它交給鏈接器處理。如果鏈接器在它的任何輸入模塊中都找不到這個被引用的符號,它就會輸出一條錯誤信息並終止。
函數和已初始化的全局變量是強符號,未初始化的全局變量是弱符號。根據強度弱符號的定義,Unix鏈接器使用下面的規則來處理多處定義的符號:
1)不允許有多個強符號。
2)如果有一個強符號和多個弱符號,那麼選擇強符號。
3)如果有多個弱符號,那麼從這些弱符號中任意選擇一個。

靜態鏈接庫、鏈接器如何使用靜態庫來解析引用。

7.7重定位
重定位由兩步組成:
1)重定位節和符號定義。鏈接器將所有相同類型的節合併爲同一類型的新的聚合節。然後,鏈接器將運行時存儲器地址賦給新的聚合節,賦給輸入模塊定義的每個節,以及賦給輸入模塊定義的每個符號。
2)重定位節中的符號引用。鏈接器修改代碼節和數據節中對每個符號的引用,使得它們指向正確的運行時地址。爲了執行這一步,鏈接器依賴於稱爲重定位靜止的可重定位目標模塊中的數據結構。

7.8可執行目標文件
我們的C程序,開始時是一組ASCII文本,已經被轉化爲一個二進制文件,且這個二進制文件包含加載程序到存儲器並運行它所需的所有信息。
7.9加載可執行目標文件
7.10動態鏈接共享庫
7.11從應用程序中加載和鏈接共享庫

7.12小結
鏈接可以在編譯時由靜態編譯器來完成,也可以在加載時和運行時由動態鏈接器來完成。鏈接器處理稱爲目標文件的二進制文件,它有三種不同的形式:可重定位的、可執行的和共享的。可重定位的目標文件由靜態鏈接器組合成一個可執行的目標文件,它可以加載到存儲器中並執行。共享目標文件是在運行時由動態鏈接器鏈接和加載的,或者隱含地在調用程序被加載和開始執行時,或者根據需要在程序調用dlopen庫的函數時。
鏈接器的兩個主要任務是符號解析和重定位。符號解析將目標文件中的每個全局符號都綁定到一個惟一的定義,而重定位確定每個符號的最終存儲器地址,並修改對那些目標的引用。
靜態鏈接器是由像GCC這樣的編譯器調用的。它們將多個可重定位目標文件組合成一個單獨的可執行目標文件。多個目標文件可以定義相同的符號,而鏈接器用來悄悄地解析這些多處定義的規則可能在用戶程序中引入的微妙錯誤。
多個目標文件可以被連接到一個單獨的靜態庫中。鏈接器用庫來解析其他目標模塊中的符號引用。許多鏈接器通過從左到右的順序掃描來解析符號引用,這是另一個引起令迷惑的鏈接時錯誤的來源。
加載器將可執行文件的內容映射到存儲器,並運行這個程序。鏈接器還可能生成部分鏈接的可執行目標文件,這樣的文件中有未解析的到定義在共享庫的程序和數據的引用。在加載時,加載器將部分鏈接的可執行文件映射到存儲器,然後調用動態鏈接器,它通過加載共享庫和重定位程序中的引用來完成鏈接任務。
被編譯爲位置無關代碼的共享庫可以加載到任何地方,也可以在運行時被多個進程共享。爲了加載、鏈接和訪問共享庫的函數和數據,應用程序還可以在運行時使用動態鏈接器。

第八章異常控制流

8.1異常
異常是一種形式的異常控制流,它一部分是由硬件實現的,一部分是由操作系統實現的。
在任何情況中,當處理器檢測到有事件發生時,它就會通過一張異常表的跳轉表,進行一個間接過程調用,到一個專門設計用來處理這類事件的操作系統子程序—異常處理程序。
異常的類別:中斷、陷阱、故障和終止。

8.2進程
異常提供基本的構造元素,它允許操作系統提供進程的概念,有了異常機制,系統中的每個程序都是運行在某個進程的上下文中的。
進程提供給應用程序的關鍵抽象:邏輯控制流、私有地址空間。
爲了使操作系統內核提供一個無懈可擊的進程抽象,處理器必須提供一種機制,限制一個應用可以執行的指令以及它可以訪問的地址空間範圍。處理器用某個控制寄存器中的一個模式位來提供這種功能的,這個模式位表明了處理器運行在用戶模式還是內核模式。
操作系統內核利用一種稱爲上下文切換的較高級形式的異常控制流來實現多任務。

8.3系統調用和錯誤處理
Unix系統提供了大量的系統調用,當應用程序想向內核請求服務時,都可以使用這些系統調用。
當系統調用出現錯誤時,它們典型地會返回-1。

8.4進程控制
從程序員角度看,進程總是處於下面三種狀態之一:運行、停止(掛起)、終止。.
進程控制包括:獲取進程ID、創建和終止進程、回收子進程、讓進程休眠和加載並運行程序

8.5信號
一個信號就是一個消息,它通知進程一個某種類型的事件已經在系統中發生了。
信號術語:發送信號、接收信號、處理信號。
信號處理問題:當一個程序要捕捉多個信號時,一些細微的問題就產生了。
1)待處理信號被阻塞。Unix信號處理程序典型地會阻塞當前處理程序正在處理的類型的待處理信號。假設一個進程捕捉了一個SIGINT信號,並且當前正在運行它的SIGINT處理程序。如果另一個SIGINT信號傳遞到這個進程,那麼這個SIGINT將變成待處理的,但是不會被接收,直到處理程序返回。
2)待處理信號不會排隊等待。任意類型至多隻有一個待處理信號。因此,如果有兩個類型爲k的信號傳送到一個目的進程,而由於目的進程當前正在執行信號k的處理程序,所以信號k是阻塞的,那麼第二個信號就被簡單地丟棄,它不會排隊等待。關鍵思想是存在一個待處理的信號僅僅表明至少已經到達了一個信號。
3)系統調用可以被中斷。像read、write和accept這樣的系統調用潛在地會阻塞進行一段較長的時間,稱爲之慢速系統調用。在某些系統中,當處理程序捕捉到一個信號時,被中斷的慢速系統調用在信號處理程序返回時不再繼續,而是立即返回給用戶一個錯誤條件,並errno設置爲EINTR。

8.6非本地跳轉
C提供了一種形式的用戶級異常控制流,稱爲非本地跳轉,它將控制直接從一個函數轉移到另一個當前正在執行的函數,而不需要經過正常的調用-返回序列。非本地跳轉是通過setjmp和longjmp函數來提供的。

8.7操作進行的工具
Unix系統提供了大量的監控和操作進程的有用工具:
strace:打印一個程序和它的子進程調用的每個系統調用的軌跡。
ps:列出系統中當前的進程。
top:打印出關於當前進行資源使用的信息。
kill:發送一個信號給進程。
 /proc:一個虛擬文件系統,以ASCII文本格式輸出大量內核數據結構的內容,用戶程序可以讀取這些內容。

8.8小結
異常控制流發生在計算機系統的各個層次。在硬件層,異常是由處理器的事件觸發的控制流中的突變。控制流傳遞給一個軟件處理程序,該處理程序進行一些處理,然後返回控制給被中斷的控制流。
有四種不同類型的異常:中斷、故障、終止和陷阱。當一個外部I/o設備,例如定時器芯片或者一個磁盤控制器,設置了處理器芯片上的中斷管腳時,(對於任意指令)中斷會異步地發生。控制返到中斷指令的下一條指令。執行一條指令可能導致故障和終止的發生。故障處理程序會重新開始故障指令,而終止處理程序從不將控制返回給被中斷的流。最後,陷阱就是用來實現系統調用的函數調用,系統調用提供給應用到操作系統代碼的受控入口點。
在操作系統層,內核提供關於一個進程的基礎性概念。一個進程提供給應用兩個重要的抽象:1.邏輯控制流,這提供給每個程序一個假象,好像它是在獨佔地使用處理器;2.私有地址空間,它提供給每個程序一個假象,好像在獨佔地使用主存。
在操作系統和應用之間的接口處,應用可以創建子進程,等待它們的子進程暫停或者終止,運行新的程序,並捕捉來自其他進程的信號。信號處理的語義清楚地指定期望的信號處理語義。
最後,在應用層,C程序可以使用非本地跳轉來規避正常的調用/返回棧規則,並且直接從一個函數轉移到另一個函數。

第九章:測量程序執行時間

9.1計算機系統上的時間流
計算機是在兩個完全不周的時間尺度上工作的。在微觀級別,它們以每個時間週期一條或多條指令的速度執行指令,每個時鐘週期大約爲1ns;在宏觀尺度上,處理器必須響應外部事件,外部事件發生的時間尺度要以ms來度量。
我們希望處理器從一個進程切換到另一個進程,這樣用戶看上去就好像處理器在同時執行許多程序一樣。由於這個原因,計算機有一個外部計時器,它週期性地向處理器發送中斷信號。這些中斷信號之間的時間被稱爲間隔時間。
從應用程序的角度看時間,可以把時間流看成兩種時間段的交替,一種時間段里程序是活動的,另一種時間段里程序是不活動的。

9.2通過間隔計數來測量時間
操作系統也用計時器來記錄每個進程使用的累計時間,這種信息提供的是對程序執行時間不那麼準確的測量值。
操作系統維護着每個進程使用的用戶時間量和系統時間量的計數值,當計時器中斷髮生時,操作系統會確定哪個進程是活動的,並且對那個進程的一個計數值增加計時器間隔時間。

9.3週期計數器
爲了給計時測量提供更高的精確度,許多處理器還包含一個運行在時鐘週期級的計時器。這個計時器是個特殊的寄存器,每個時鐘週期它都會加1。可以用特殊的機器指令來讀這個計數器的值。不是所有的處理器都有這樣的計數器的,而且有這樣的計數器的處理器在實現細節上也各不相同。

9.4小結
本章開始時提出一個看似問題:“程序X在機器Y上運行得有多快?”不幸的是,計算機系統用來同時運行多個進程的機制使得很難獲得程序性能可靠的測量值。系統活動傾向於在兩個不同的時間尺度上進行。在微觀級別上,每條指令執行的時間以ns來衡量的。在宏觀級別上,輸入/輸出交互發生的延遲是以ms來衡量的。計算機系統通過不斷地從一個任務切換到另一個任務來利用這種差異,一次運行若干ms。
計算機系統有兩種完全不同的記錄時間流逝的方法。從宏觀角度來看,計時器中斷髮生的頻率似乎很快,但是從微觀的角度來看卻秀慢。通過間隔計數,系統能夠獲得對程序執行時間非常精力的測量值。這種方法只對長持續時間表用。週期計數器非常快,可以得到在微觀尺度上很好的測量值。對於測量絕對時間的週期計數器,上下文切換的影響能夠導致很小(在負載很輕的系統上)到很大(在負載很重的系統上)的誤差。因此,沒有方法是完美的。理解在一個特殊的系統上能夠獲得的準確度是很重要的。
取決於前面存儲器引用和條件轉移的歷史,高速緩存和轉移預測的影響可以導致執行代碼的某個片段所需的時間每次都不同。通過事先運行某些將高速緩存設置爲可預測狀態的代碼,我們可以部分地控制引起這種變化的因素,但是在有上下文切換髮生時,這些嘗試就沒有用了。因此,我們必須進行多次測量,分析結果,以確定真實的執行時間。幸運的是,所有引起變化的因素的效果都是增加執行時間,因此只需分析確定測出的時間的最小值是否是在一個準確的測量值。

第十章:虛擬存儲器

10.1物理和虛擬地址
計算機系統的主存被組織上由M個連續的字節大小的單元組成的數組。每字節都有一個惟一的物理地址。但是,爲通用計算機設計,現代處理器使用是虛擬尋址。
根據虛擬尋址,CPU通過生成一個虛擬地址(VA)來訪問主存,這個虛擬地址在被送到存儲器之前先轉換成適當的物理地址。將一個虛擬地址轉換爲物理地址的任務叫做地址翻譯。就像異常處理一樣,地址翻譯需要CPU硬件和操作系統之間的緊密合作。CPU芯片上叫做MMU(存儲器管理單元)的專用硬件,利用存放在主存中的查詢表來動態翻譯虛擬地址,該表的內容是由操作系統管理的。

10.2地址空間
地址空間是一個非負整數地址的有序集合,如果地址空間中的整數是連續的,那麼我們說它是一個線性地址空間。地址空間的概念是很重要的,因爲它清楚地區分了數據對象(字節)和它們屬性(地址)。

10.3虛擬存儲器作爲緩存的工具
在任意時刻,虛擬頁面的集合都分爲三個不相交的子集:
1)未分配的:VM系統還未分配(或者創建)的頁。未分配的塊沒有任何數據和它們相關聯,因此也就不佔用任何磁盤空間。
2)緩存的:當前緩存在物理存儲器中的已分配頁。
3)未緩存的:沒有緩存在物理存儲器(主存)中的已分配頁。

DRAM高速緩存的組織結構、頁表、頁命中、缺頁分配頁面、局部性再次搭救。
DRAM緩存是全相聯的,任何物理頁都可以包含任意虛擬頁。

10.4虛擬存儲器作爲存儲器管理的工具
VM簡化了鏈接和加載,共享代碼和數據,以及對應用分配存儲器,此外,虛擬存儲器還可以對存儲器進行相應的保護。

10.5地址翻譯
頁面命中時,CPU硬件執行的步驟。
1)處理器生成一個虛擬地址,並把它傳送給MMU。
2)MMU生成PTE(page table entry),並從高速緩存/主存請求得到它。
3)高速緩存/主存向MMU返回PTE。
4)MMU構造物理地址,並把它傳送給高速緩存/主存。
5)高速緩存/主存返回所請求的數據字給處理器。
頁面命中完全是由硬件來處理的。
和頁面命中不同,處理缺頁要求硬件和操作系統內核協作來完成。
第一步到第三步同上。
第四步:PTE中的有效位是零,所以MMU觸發了一次異常,傳遞CPU中的控制到操作系統內核中的缺頁異常處理程序。
 第五步:缺頁處理程序確定出物理存儲器中的犧牲頁,如果這個頁面已經被修改了,則把它頁面換出到磁盤。
第六步:缺頁處理程序調入新的頁面,並更新存儲器中的PTE。
第七步:缺頁處理程序返回到原來的進程,驅使導致缺頁的指令重新啓動。CPU將引起缺頁的指令重新改善給MMU。因爲虛擬頁面現在緩存在物理存儲器中,所以就會命中。

每次CPU產生一個虛擬地址,MMU就必須查閱一個PTE,以便虛擬地址翻譯爲物理地址。爲了減少開銷,很多系統在MMU中包括了一個關於PTE的小的緩存,稱爲TLB(translation lookaside buffer,翻譯後備緩衝器)。

多級頁表的出現,從兩個方面減少了存儲器要求。第一,如果一級頁表中的一個PTE是空的,那麼相應的二級頁表就不存在;第二,只有一級頁表才需要總是在主存中。

10.6動態分配
一個動態存儲器分配器維護着一個進程的虛擬存儲器區域,稱爲堆。在大多數的Unix系統中,堆是一個請求二進制的零的區域,它緊接在未初始化的bss區域後開始,並向上生長。
使用動態存儲器分配的最重要的原因是它們經常直到程序實際運行時,才知道某此數據結構的大小。
 碎片:有兩種形式的碎片,內部碎片和外部碎片。內部碎片是在一個已分配塊比有效載荷大時發生的;外部碎片是當空閒存儲器合計起來足夠滿足一個分配請求,但是沒有一個單獨的空閒塊足夠大可以來處理這個請求時發生的。

10.7小結
虛擬存儲器是對主存的一個抽象。支持虛擬存儲器的處理器通過使用一種叫做虛擬尋址的間接形式來引用主存。處理器產生一個虛擬地址,在被髮送到主存之前,這個地址被翻譯成一個物理地址。從虛擬地址空間到物理地址空間的地址翻譯要求硬件和軟件緊密合作。專門的硬件通過使用頁表來翻譯虛擬地址,而頁表的內容間由操作系統提供的。
虛擬存儲器提供三個重要的功能。第一,它在主存中自動緩存最近使用的存放磁盤上的虛擬地址空間的內容。虛擬存儲器緩存中的塊叫做頁。對磁盤上頁的引用會觸發缺頁,缺頁將控制轉移到操作系統中的一個缺頁處理程序。缺頁處理程序將頁面從磁盤拷貝到主存緩存。如果必要,將寫回被驅逐的頁。第二,虛擬存儲器簡化了存儲器管理,進而又簡化了鏈接、在進程間共享數據、進程的存儲器分配,以及程序加載。最後,虛擬存儲器通過在每條條目中加入保護位,從而簡化了存儲器保護。
地址翻譯的過程必須和系統中任意硬件緩存的操作集成在一起。大多數條目位於L1高速緩存中,但是一個稱爲TLB的頁表條目在芯片上的高速緩存,通常會消除訪問在L1上的頁表條目的開銷。
現代系統通過將虛擬存儲器組塊和磁盤上的文件組塊關聯起來,來初始化虛擬存儲器,這個過程稱爲存儲器映射。存儲器映射爲共享數據、創建新的進程以及加載程序,提供了一種高效的機制。應用可以使用mmap函數來手工地和刪除虛擬地址空間的區域。然而,大多數程序依賴於動態存儲器分配器,例如 malloc,它管理虛擬地址空間區域內一個稱爲堆的區域。動態存儲器分配器是一個有系統級感覺的應用級程序,它直接操作存儲器,而無需類型系統的很多幫助。分配器有兩種類型:顯式分配器要求應用顯式地釋放它們的存儲器塊;隱式分配器(垃圾收集器)自動釋放任何無用的和不可達的塊。
對於C程序員來說,管理和使用虛擬存儲器是一件困難和容易出錯的任務。常見的錯誤示例包括:間接引用指針,讀取未初始化的存儲器,允許棧緩衝區溢出,假設指針和它們指向的對象大小相同,引用指針而不是它所指向的對象,誤解指針運算,引用不存在的變量,以及引起存儲器泄漏。

第三部分:程序間的交互和通信
現實世界中,應用程序利用操作系統提供的服務來與I/o設備及其他程序通信。
 本部分將使你瞭解Unix操作系統提供的基本I/o服務,以及如何用這些服務來構造應用程序,例如web客戶端和服務器,它們是通過Internet彼此通信的。你將學習編寫諸如web服務器這樣的可以同時爲多個客戶端提供服務的併發程序

第十一章:系統級I/o
輸入/輸出(I/o)是在主存和外部設備之間拷貝數據的過程。輸入操作是從I/o設備拷貝數據到主存,而輸出操作是從主存拷貝數據到I/o設備。

11.1Unix I/o
在Unix中,所有的I/o設備都被模型化爲文件,而所有的輸入和輸出都被當作對相應文件的讀和寫來執行。

11.2打開和關閉文件
通過open函數來打開一個已存在的文件或者創建一個新的文件。
通過close函數關閉一個打開的文件。

11.3讀和寫文件
應用程序是通過分別調用read和write函數來執行輸入和輸出的。

11.4用Rio鈕進行健壯地讀和寫
Rio包提供了方便、健壯和高效的I/O。Rio包提供了兩類不同的函數:無緩衝的輸入輸出函數和帶緩衝的輸入函數。

11.5讀取文件元數據
應用程序能夠通過調用stat和fstat函數,檢索到關於文件的信息(有時也稱爲文件的元數據)。

11.6共享文件
可以用許多不同的方式來共享Unix文件。除非你清楚內核是如何表示打開的文件,否則文件共享的概念相當難懂。內核用三種相關的數據結構來表示打開的文件:
1)描述符表。每個進程都有它獨立的描述符表,它的表項是由進程的文件描述符來索引的。每個的描述符表項指向文件表中的一個表項。
2)文件表。打開文件的集合是由一張文件表來表示的,所有的進程共享這張表。每個文件表的表項組成包括有當前的文件位置、引用計數即當前指向該表項的描述符表項數,以及一個指向v-node表中對應表項的指針。關閉一個描述符會減少相應的文件表表項中的引用計數。內核不會刪除這個文件表表項,直到它的引用計數爲零。
3)v-node表:同文件表一樣,所有的進程共享這張v-node表。每個表項包含stat結構中的大多數信息,包括st_mode和st_size成員。

11.7小結
Unix提供了少量的系統級函數,它們允許應用程序打開、關閉、讀寫文件,提取文件的元數據,以及執行I/O重定向。Unix的讀和寫操作會出現不足值,應用程序必須正確地預計和處理這種情況。應用程序不直接調用 Unix I/O函數,而應該使用Rio包,Rio包通過反覆執行讀寫操作,直到傳送完所有的請求數據,自動處理不足值。
Unix內核使用三種相關的數據結構來表示打開的文件。描述符表中的表項指向打開文件表中表項,而打開文件表中的表項又指向v-node表中的表項。每個進程都有它自己單獨的描述符表,而 所有的進程共享同一打開文件表和v-node表。理解這些結構的一般構成就能使們清楚地理解文件共享和I/O重定向。
標準I/O庫是基於Unix I/O實現的,並提供了一組強大的高級I/O例程。對於大多數應用程序而言,標準I/O更簡單,是優於Unix I/O的選擇。然而,因爲對標準I/O和網絡文件的一些相互不兼容的限制,Unix I/O比之標準I/O更該適用於網絡應用程序。

第十二章:網絡編程

12.1客戶端-服務器編程模型
客戶端-服務器模型中的基本操作是事務。一個客戶端-服務器事務由四步組成:
1)當一個客戶端需要服務時,它向服務器發送一個請求,引起一個事務。
2)服務器收到請求後,解釋它,並以適當的方式操作它的資源。
3)服務器給客戶端發送一個響應,並等待下一個請求。
4)客戶端收到響應並處理它。

12.2網絡
客戶端和服務器通過運行在不同的主機上,並且通過計算機網絡的硬件和軟件資源來通信。
對於主機而言,網絡只是又一種I/O設備。

12.3全球的IP因特網
每臺因特網主機都運行實現TCP/IP的軟件。因特網的客戶端和服務器混合使用套接字接口函數和Unix I/O函數來進行通信。套接字函數典型地是作爲系統調用來實現的,這些系統會陷入內核,並調用各種內核模式的TCP/IP函數。
IP地址、因特網域名,因特網連接(套接字的概念)。

12.4套接字接口
套接字是連接的商戰,每個套接字都有相應的套接字地址,是由一個因特網地址和一個16位的整數端口組成的。
套接字的地址結構、connect函數、open_clientfd函數、bind函數、listen函數、open_listened函數、accept函數。

12.5web服務器
web基礎:http協議、HTML語言、URL。
http事務:http請求、http響應

12.6小結
每個網絡應用都是基於客戶端-服務器端模型的。根據這個模型,一個應用是由一個服務器和一個或多個客戶端組成的。服務器管理資源,以某種方式操作資源,爲它的客戶端服務。客戶端-服務器模型中的基本操作是客戶端-服務器事務,它是由客戶端請求和跟隨的服務器響應組成的。
客戶端和服務器通過因特網這個全球網絡來通信。從一個程序員的觀點來看,我們可以把因特網看成是一個全球範圍的主機集合,具有以下幾個屬性:每個因特網都有一個惟一的32位名字,稱爲它的IP地址;IP地址的集合映射爲一個因特網域名的集合;不同因特網主機上的進程能夠通過連接互相通信。
客戶端和服務器通過使用套接字接口建立連接。套接字是連接的端點,對應用程序來說,連接是以文件描述符的形式出現的。套接字接口提供了打開和關閉套接字描述符的函數。客戶端和服務器通過讀寫這些描述符來實現彼此間的通信。
web服務器使用HTTP協議和它們的客戶端(比如瀏覽器)彼此通信。瀏覽器向服務器請求靜態或者動態的內容。對靜態內容的請求是通過從服務器磁盤取得文件並把它返回給客戶端來服務的。
對動態內容的請求是通過在服務器上一個子進程的上下文中運行一個程序並將它的輸出返回給客戶端來服務的。CGI標準提供了一組規則,來管理客戶端如何將程序參數傳遞給服務器,服務器如何將這些參數以及其他信息傳遞給子進程,以及子進程如何將它的輸出發送回客戶端。

第十三章:併發編程
現代操作系統提供了三種基本的構造併發程序的方法:
1)進程。用這種方法,每個邏輯控制流都是一個進程,由內核來調度和維護。因爲進程有獨立的虛擬地址空間,想要和其他流通信,控制流必須使用某種顯工的進程間通信機制。
2)I/O多路複用。在這種形式的併發編程中,應用程序在一個進程的上下文中顯式地調度它們自己的邏輯流。邏輯流被模型化爲狀態機,作爲數據到達文件描述符的結果,主程序顯式地從一個狀態轉換到另一個狀態。因爲程序是一個單獨的進程,所以所有流都共享同一地址空間。
3)線程。線程是運行在個單一進程上下文中的邏輯流,由內核進行調度的。

線程中有共享變量時,要用相應的方法去同步線程,比如信號量機制。在資源調度中,會出現競爭和列鎖的現象。

13.1小結
一個併發進程帥在時間上重疊的一組邏輯流組成的。在這一章中,我們學習了三種不同的構建併發程序的機制:進程、I/O多路複用和線程。
進程是由內核自動調度的,而且因爲它們有各自獨立的虛擬地址空間,所以要實現共享數據,它們需要顯式的IPC(interprocess communictaion,ipc)機制。事件驅動程序創建它們自己的併發邏輯流,這些邏輯流被模型化爲狀態機,用I/O多路複用來顯式地調度這些流。因爲程序運行在一個單一進程中,所以在流之間共享數據速度很快而且很容易。線程是這些方法的綜合。同基於進程的流一樣,線程是內核自動調度的。同基於I/O多路複用的流一樣,線程是運行在一個單一進程的上下文中的,因此可以快速而方便地共享數據。
無論哪種併發機制,同步對共享數據的併發訪問都是一個困難的問題。提出對信號量的P和V操作就是爲了幫助解決這個問題。信號量操作可以用來提供對共享數據的互斥訪問,也對諸如生產者-消費者程序中共享緩衝區這樣的資源訪問進行調度。

寫在最後:
這篇文章是我在讀完CSAPP後所做的筆記,本來想是通過自己把各個知識點串一下,可真正做的時候才發現自己的火候不夠,可以說沒有火候,只能像小學時做筆記那樣,把我認爲重要的知識點在此羅列一下,以便查閱和更進一步的學習。
筆記中,很多地方都寫的不是很詳細,所以針對的人羣是閱讀過CSAPP的人,如果沒有閱讀過的,也可以通過閱讀此文,對其中一些不瞭解的地方,再到網上查找更詳細的信息來進一步瞭解。
總的來說,CSAPP是一本不錯的IT技術書,通過本書你會對整個計算機體系,整個程序運行機制,有一個大體的瞭解,如果你閱讀的夠深入,肯定會有詳細的瞭解。感覺本書就像是爲你以後學習打下了地基,以後學到的知識都可以在這本書裏找到支撐點。
這本書實在太厚,有幾次都想要放棄閱讀,不過還是堅持了下來,感覺讀完這本書後的感覺纔是最爽的,而真正從這本書得到的知識可能並不像作者期望的那麼多,但是確實對我有很大的幫助,尤其是第六章(存儲器的層次結構)、第七章(鏈接)、第十章(虛擬存儲器)和第十三章(併發編程),讀後感覺股股清風身邊拂過,涓涓細流餘音嫋嫋。

發佈了89 篇原創文章 · 獲贊 3 · 訪問量 7122
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章