CSAPP讀書(shū)筆記:第1章 計(jì)算機(jī)系統(tǒng)漫游

計(jì)算機(jī)是由硬件和軟件系統(tǒng)組成的。本章的內(nèi)容主要講述一個(gè)hello程序的生命周期:從被程序員創(chuàng)建開(kāi)始,到在系統(tǒng)上運(yùn)行,輸出簡(jiǎn)單的消息,最后終止。hello.c程序如下所示:

#include <stdio.h> 
int main()
{
    printf("hello, word\n");
    return 0;
}

1.1 信息就是位+上下文

hello程序的生命周期從一個(gè)源程序(源文件)開(kāi)始,即程序員通過(guò)編輯器創(chuàng)建并保存的文件。源程序?qū)嶋H上就是一個(gè)由0和1組成的位(又稱(chēng)為比特)序列,8個(gè)為被組織成一組,稱(chēng)為字節(jié)。每個(gè)字節(jié)表示程序中的某些文本字符。
大部分的線(xiàn)代計(jì)算機(jī)系統(tǒng)都是用ASCII標(biāo)準(zhǔn)來(lái)表示文本字符,這種方式實(shí)際上就是用一個(gè)唯一的單字節(jié)大小的整數(shù)值來(lái)表示每個(gè)字符。

圖1.1 hello程序的ASCII文本表示

hello.c程序是以字節(jié)序列的方式存儲(chǔ)在文件中的,每一個(gè)字節(jié)都有一個(gè)整數(shù)值,對(duì)應(yīng)于某些字符。像hello.c這樣只有ASCII字符構(gòu)成的文件被稱(chēng)為文本文件,其他所有文件都被稱(chēng)為二進(jìn)制文件。
系統(tǒng)中的所有信息,包括磁盤(pán)文件、內(nèi)存中的程序、內(nèi)存中存放的用戶(hù)數(shù)據(jù)以及網(wǎng)絡(luò)上傳送的數(shù)據(jù),都是由一串比特表示的。區(qū)分不同數(shù)據(jù)對(duì)象的唯一方法是我們讀到這些數(shù)據(jù)對(duì)象時(shí)的上下文。也就是說(shuō),信息=比特位+上下文。

1.2 程序編譯

hello程序的生命周期從一個(gè)能夠被人讀懂的高級(jí)C語(yǔ)言程序開(kāi)始,為了在系統(tǒng)上運(yùn)行hello.c,程序中的每條C語(yǔ)句都必須被轉(zhuǎn)化為一系列的低級(jí)機(jī)器語(yǔ)言指令。然后這些指令會(huì)被打包成可執(zhí)行目標(biāo)程序,并以二進(jìn)制磁盤(pán)文件的形式存儲(chǔ)。編譯過(guò)程由編譯器驅(qū)動(dòng)程序完成,主要分為四個(gè)階段:預(yù)處理器、編譯器、匯編器和鏈接器。

圖1.2 編譯系統(tǒng)

  • 預(yù)處理階段。預(yù)處理器(cpp)根據(jù)以字符#開(kāi)頭的命令,修改原始的C程序。該步驟會(huì)讀取頭文件中的內(nèi)容,并將其插入程序文本中,得到以.i作為擴(kuò)展名的另一個(gè)C程序。
  • 編譯階段。編譯器(ccl)將文本文件hello.i翻譯成文本文件hello.s的匯編語(yǔ)言程序。匯編語(yǔ)言為不同高級(jí)語(yǔ)言的不同編譯器提供了通用的輸出語(yǔ)言。例如,C編譯器和Fortran編譯器輸出文件用的都是一樣的匯編語(yǔ)言。
  • 匯編階段。匯編器(as)將hello.s翻譯成機(jī)器語(yǔ)言指令,并把這些指令打包為一種叫做可重定位目標(biāo)程序的格式,保存在hello.o中。hello.o是一個(gè)二進(jìn)制文件,包含的是函數(shù)main的指令編碼,如果在文本編輯器中打開(kāi)它,看到的將是一堆亂碼。
  • 鏈接階段。hello程序調(diào)用了printf函數(shù),這是C標(biāo)準(zhǔn)庫(kù)中的一個(gè)函數(shù),被存放在一個(gè)已經(jīng)預(yù)編譯好了的目標(biāo)文件中。鏈接器(ld)的作用是要將這個(gè)目標(biāo)文件以某種方式合并到hello文件中,得到一個(gè)可執(zhí)行目標(biāo)文件,使其可以被加載到內(nèi)從中執(zhí)行。

了解編譯系統(tǒng)如何工作是大有益處的,這可以幫住我們:
1. 優(yōu)化程序性能。例如:while循環(huán)比f(wàn)or循環(huán)有效嗎?一個(gè)switch語(yǔ)句是否總比一系列if-else高效?等等。
2. 理解鏈接時(shí)出現(xiàn)的錯(cuò)誤。靜態(tài)變量和全局變量的區(qū)別是什么?靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的區(qū)別是什么?
3. 避免安全漏洞。避免緩沖區(qū)溢出。理解數(shù)據(jù)與控制信息存儲(chǔ)在程序棧上的方式會(huì)引起的后果。

1.3 處理器讀取并解釋存儲(chǔ)在內(nèi)存中的指令

1.3.1 系統(tǒng)的硬件組成

1. 總線(xiàn)
總線(xiàn)是一組貫穿整個(gè)系統(tǒng)的電子管道,它攜帶信息字節(jié),并負(fù)責(zé)在各個(gè)部件之間傳遞。通常,總線(xiàn)被設(shè)計(jì)成傳送定長(zhǎng)的字節(jié)快,也就是(word)。字中的字節(jié)數(shù)(即字長(zhǎng))依據(jù)系統(tǒng)的不同而不同,目前大多數(shù)機(jī)器的字長(zhǎng)要么是4個(gè)字節(jié)(32位),要么是8個(gè)字節(jié)(64位)。
2. I/O設(shè)備
I/O(輸入/輸出)設(shè)備是系統(tǒng)與外部世界的聯(lián)系通道。包括作為用戶(hù)輸入的鍵盤(pán)鼠標(biāo),作為用戶(hù)輸出的顯示器,以及用于長(zhǎng)期存儲(chǔ)數(shù)據(jù)和程序的磁盤(pán)驅(qū)動(dòng)器(簡(jiǎn)單來(lái)說(shuō)就是磁盤(pán))。每個(gè)I/O設(shè)備都通過(guò)一個(gè)控制器或者適配器與I/O總線(xiàn)相連??刂破髋c適配器的區(qū)別主要在于裝配方式??刂破魇荌/O設(shè)備本身或者系統(tǒng)的主板上的芯片組,而適配器則是一塊插在主板插槽上的卡。它們的功能是在I/O總線(xiàn)與I/O設(shè)備之間傳遞信息。

圖1.3 一個(gè)典型系統(tǒng)的硬件組成

3. 主存
主存是一個(gè)臨時(shí)的存儲(chǔ)設(shè)備,在處理器執(zhí)行程序時(shí),用來(lái)存放程序和程序處理的數(shù)據(jù)物理上說(shuō),主存是由一組動(dòng)態(tài)隨機(jī)存取存儲(chǔ)器(DRAM)芯片組成的。邏輯上說(shuō),存儲(chǔ)器是一個(gè)線(xiàn)性的字節(jié)數(shù)組,每個(gè)字節(jié)都有唯一的地址(數(shù)組索引),這些地址是從零開(kāi)始的。
4. 處理器
中央處理單元(CPU),即處理器,是解釋?zhuān)ɑ驁?zhí)行)存儲(chǔ)在主存中指令的引擎。其核心是一個(gè)大小為一個(gè)字長(zhǎng)的寄存器,稱(chēng)為程序計(jì)數(shù)器(PC)。在任一時(shí)刻,PC都指向主存中的某條機(jī)器語(yǔ)言指令。從系統(tǒng)通電開(kāi)始,直到系統(tǒng)端電,處理器一直不斷地執(zhí)行程序計(jì)數(shù)器指向的指令,再更新程序計(jì)數(shù)器,使其指向下一條指令。這個(gè)執(zhí)行模型由指令集架構(gòu)決定。執(zhí)行操作主要圍繞主存、寄存器文件算數(shù)/邏輯單元(ALU)進(jìn)行。其中寄存器文件是一個(gè)小的存儲(chǔ)設(shè)備,由一些單字長(zhǎng)的寄存器組成,每個(gè)寄存器都有唯一的名字。ALU負(fù)責(zé)計(jì)算新的數(shù)據(jù)和地址值。主要操作有如下幾種:

  • 加載:從主存復(fù)制一個(gè)字節(jié)或者一個(gè)字到寄存器,以覆蓋寄存器原來(lái)的內(nèi)容。
  • 存儲(chǔ):從寄存器復(fù)制一個(gè)字節(jié)或者一個(gè)字到主存的某個(gè)位置,以覆蓋這個(gè)位置上原來(lái)的內(nèi)容。
  • 操作:把兩個(gè)寄存器的內(nèi)容復(fù)制到ALU,ALU對(duì)這兩個(gè)字做算術(shù)運(yùn)算,并將結(jié)果放到一個(gè)寄存器中,以覆蓋該寄存器中原來(lái)的內(nèi)容。
  • 跳轉(zhuǎn):從指令本身中抽取一個(gè)字,并將這個(gè)字復(fù)制到程序計(jì)數(shù)器(PC)中,以覆蓋PC中原來(lái)的值。

1.3.2 運(yùn)行hello程序

程序執(zhí)行分為如下幾個(gè)步驟:
1. 從鍵盤(pán)讀取hello命令
在Unix系統(tǒng)中運(yùn)行可執(zhí)行文件,需要將文件名輸入到命令行解釋程序 shell中。初始時(shí),shell程序等待我們輸入一個(gè)命令,當(dāng)在鍵盤(pán)上輸入字符串“./hello”后,shell程序?qū)⒆址鹨蛔x入寄存器,再把它放到內(nèi)存中。如圖1.4所示:

圖1.4 從鍵盤(pán)讀取hello命令

注:shell是一個(gè)命令行解釋器,它輸出一個(gè)提示符,等待輸入一個(gè)命令行,然后執(zhí)行這個(gè)命令。如果該命令行的第一個(gè)單詞不是一個(gè)內(nèi)置的shell命令,那么shell就假設(shè)這是一個(gè)可執(zhí)行文件的名字,它會(huì)加載并運(yùn)行這個(gè)文件。

2. 從磁盤(pán)加載可執(zhí)行文件到主存
當(dāng)我們?cè)阪I盤(pán)上敲擊回車(chē)鍵時(shí),shell程序就知道我們已經(jīng)結(jié)束了命令的輸入,然后shell會(huì)執(zhí)行一些列指令來(lái)加載可執(zhí)行的hello文件,這些指令將hello目標(biāo)文件中的代碼和數(shù)據(jù)從磁盤(pán)上復(fù)制到主存中。

圖1.5 從磁盤(pán)加載可執(zhí)行文件到主存

3. 將輸出字符串從存儲(chǔ)器寫(xiě)到顯示器
一旦目標(biāo)程序被加載到主存中,處理器就開(kāi)始執(zhí)行hello程序中的機(jī)器語(yǔ)言指令。這些指令將“hello, word\n”字符串中的字節(jié)從主存復(fù)制到寄存器文件中,再?gòu)募拇嫫魑募袕?fù)制到顯示設(shè)備,最終顯示在屏幕上。
圖1.6 將輸出字符串從存儲(chǔ)器寫(xiě)到顯示器

1.4 高速緩存與存儲(chǔ)設(shè)備的層次結(jié)構(gòu)

1. 高速緩存
根據(jù)機(jī)械原理,較大的存儲(chǔ)設(shè)備要比較小的存儲(chǔ)設(shè)備運(yùn)行得慢,而快速設(shè)備的造價(jià)遠(yuǎn)高于同類(lèi)的低速設(shè)備。例如,一個(gè)典型的寄存器文件只能存儲(chǔ)幾百字節(jié)的信息,而主存里可以存儲(chǔ)幾十億字節(jié)的信息。然而,處理器從寄存器讀取數(shù)據(jù)比從主存中讀取數(shù)據(jù)幾乎快100倍。這種處理器與主存之間的差距還在持續(xù)增大,加快處理器的運(yùn)行速度比加快主存的運(yùn)行速度要快得多和便宜得多。
針對(duì)處理器與主存之間的差異,系統(tǒng)設(shè)計(jì)者采用了更小更快的存儲(chǔ)設(shè)備,稱(chēng)為高速緩存存儲(chǔ)器(cache memory,簡(jiǎn)稱(chēng)為cache或高速緩存)作為暫時(shí)的集結(jié)區(qū)域,存放處理器近期可能會(huì)需要的信息。典型的系統(tǒng)中的高速緩存存儲(chǔ)器如圖1.7所示。

圖1.7 高速緩存存儲(chǔ)器

L1高速緩存容量較大,訪(fǎng)問(wèn)速度集合和訪(fǎng)問(wèn)寄存器文件一樣快。L2高速緩存L1高速緩存容量更大些,而進(jìn)程訪(fǎng)問(wèn)L2高速緩存比訪(fǎng)問(wèn)L1高速緩存的時(shí)間長(zhǎng)5倍,但仍然比訪(fǎng)問(wèn)主存的時(shí)間快5到10倍。L1高速緩存L2高速緩存靜態(tài)隨機(jī)訪(fǎng)問(wèn)存儲(chǔ)器(SRAM)實(shí)現(xiàn)。通過(guò)這個(gè)樣的層次組合,系統(tǒng)就可以獲得一個(gè)很大的存儲(chǔ)器,同時(shí)訪(fǎng)問(wèn)速度也很快。原因是利用了高速緩存的局部性原理,即程序具有訪(fǎng)問(wèn)局部區(qū)域內(nèi)的數(shù)據(jù)和代碼的趨勢(shì)。通過(guò)讓高速緩存存放可能經(jīng)常訪(fǎng)問(wèn)的數(shù)據(jù),大部分的內(nèi)存操作都能在快速的高速緩存中完成。
2. 存儲(chǔ)器的層次結(jié)構(gòu)
在處理器和一個(gè)較大較慢的存儲(chǔ)設(shè)備之間插入一個(gè)較小較快的存儲(chǔ)設(shè)備,是一種非常有效的提速手段?,F(xiàn)有的計(jì)算機(jī)系統(tǒng)也被組織成了這種存儲(chǔ)器層次結(jié)構(gòu),如圖1.8所示。在這個(gè)層次結(jié)構(gòu)中,從上至下,設(shè)備的速度越來(lái)越慢,但是容量越來(lái)越大,單位字節(jié)的造價(jià)也越來(lái)越便宜。在這個(gè)存儲(chǔ)器結(jié)構(gòu)中,上一層的存儲(chǔ)器作為低一層存儲(chǔ)器的高速緩存。

圖1.8 存儲(chǔ)器層次結(jié)構(gòu)示例

1.5 操作系統(tǒng)管理硬件

像hello word或者shell這類(lèi)應(yīng)用程序,都沒(méi)有直接訪(fǎng)問(wèn)顯示器、鍵盤(pán)、磁盤(pán)或者主存,它們依靠操作系統(tǒng)提供的服務(wù)來(lái)控制這些設(shè)備。操作系統(tǒng)可以看做是應(yīng)用程序和硬件之間的一層軟件,所有應(yīng)用對(duì)于硬件的訪(fǎng)問(wèn)都要通過(guò)操作系統(tǒng)。操作系統(tǒng)的基本功能包括:

  1. 防止硬件被失控的應(yīng)用程序?yàn)E用。
  2. 向應(yīng)用程序提供簡(jiǎn)單一致的機(jī)制,來(lái)控制復(fù)雜而又通常大不相同的低級(jí)硬件設(shè)備。

操作系統(tǒng)通過(guò)幾個(gè)基本的抽象概念來(lái)實(shí)現(xiàn)上述兩種功能:進(jìn)程、虛擬內(nèi)存文件。其中,文件是對(duì)I/O設(shè)備的抽象表示,虛擬內(nèi)存是對(duì)主存和磁盤(pán)I/O設(shè)備的抽象表示,進(jìn)程是對(duì)處理器、主存和I/O設(shè)備的抽象表示。

1.5.1 進(jìn)程與線(xiàn)程

進(jìn)程是操作系統(tǒng)對(duì)一個(gè)正在運(yùn)行的程序的一種抽象。在一個(gè)系統(tǒng)上,可以同時(shí)運(yùn)行多個(gè)進(jìn)程,這些進(jìn)程都好像在獨(dú)占整個(gè)系統(tǒng)的資源,這是通過(guò)處理器在進(jìn)程之間來(lái)回切換實(shí)現(xiàn)的。操作系統(tǒng)將這種交錯(cuò)執(zhí)行的機(jī)制稱(chēng)為上下文切換。操作系統(tǒng)保持跟蹤進(jìn)程運(yùn)行所需要的所有狀態(tài)信息,這種狀態(tài)信息就叫上下文,包括PC和寄存器文件的當(dāng)前值以及主存的內(nèi)容等等。任一時(shí)刻,單個(gè)處理器系統(tǒng)只能執(zhí)行一個(gè)進(jìn)程的代碼,當(dāng)操作系統(tǒng)決定把控制器從當(dāng)前進(jìn)程轉(zhuǎn)到某個(gè)其他進(jìn)程時(shí),會(huì)進(jìn)行上下文切換。也就是保存當(dāng)前進(jìn)程的上下文,回復(fù)新進(jìn)程的上下文,然后將控制器傳遞到新進(jìn)程,新進(jìn)程就會(huì)從它上次停止的地方開(kāi)始執(zhí)行。
從一個(gè)進(jìn)程到另一個(gè)進(jìn)程的轉(zhuǎn)換是由操作系統(tǒng)內(nèi)核管理的,內(nèi)核是操作系統(tǒng)代碼常駐內(nèi)存的部分。應(yīng)用程序通過(guò)系統(tǒng)調(diào)用(system call)指令執(zhí)行需要操作系統(tǒng)執(zhí)行的某些操作。
一個(gè)進(jìn)程可以包含多個(gè)執(zhí)行單元,被稱(chēng)為線(xiàn)程。每個(gè)線(xiàn)程都運(yùn)行在進(jìn)程的上下文中,它們共享該進(jìn)程的代碼和全局?jǐn)?shù)據(jù),比進(jìn)程效率更高。

1.5.2 虛擬內(nèi)存

虛擬內(nèi)存是一個(gè)抽象概念,它為每個(gè)進(jìn)程共了一個(gè)假象,即每個(gè)進(jìn)程都獨(dú)占地使用主存。每個(gè)進(jìn)程看到的內(nèi)存都是一致的,成為虛擬地址空間。主要部分如下圖所示:

圖1.9 進(jìn)程的虛擬地址空間

  • 程序代碼和數(shù)據(jù)
  • 共享庫(kù)
  • 內(nèi)核虛擬內(nèi)存

1.5.3 文件

文件就是字節(jié)序列。每個(gè)I/O設(shè)備,包括磁盤(pán)、鍵盤(pán)、顯示器,都可以看成是文件。文件這個(gè)簡(jiǎn)單而精致的概念非常強(qiáng)大,它為應(yīng)用程序提供了一個(gè)統(tǒng)一的視圖,來(lái)看待系統(tǒng)中可能含有的各種類(lèi)型的I/O設(shè)備。系統(tǒng)中的所有輸入輸出都是通過(guò)一組成為Unix I/O的系統(tǒng)函數(shù)調(diào)用讀寫(xiě)文件來(lái)實(shí)現(xiàn)的。
網(wǎng)絡(luò)也可以被看做是一個(gè)I/O設(shè)備,當(dāng)系統(tǒng)從主存復(fù)制一串字節(jié)數(shù)據(jù)到網(wǎng)絡(luò)適配器時(shí),數(shù)據(jù)經(jīng)過(guò)網(wǎng)絡(luò)到達(dá)另一臺(tái)計(jì)算機(jī),而不是到達(dá)本地磁盤(pán)。相應(yīng)的,系統(tǒng)也可以讀取其他機(jī)器發(fā)送過(guò)來(lái)的數(shù)據(jù),并復(fù)制到自己的主存。如今,從一臺(tái)主機(jī)復(fù)制信息到另一臺(tái)主機(jī)已經(jīng)成為了計(jì)算機(jī)系統(tǒng)最重要的用途之一。比如,電子郵件、即時(shí)通訊等等。

1.6 重要主題

系統(tǒng)是硬件和系統(tǒng)軟件互相交織的集合體,它們必須協(xié)同合作以實(shí)現(xiàn)運(yùn)行應(yīng)用程序的最終目的。接下來(lái)我們會(huì)強(qiáng)調(diào)幾個(gè)貫穿計(jì)算機(jī)系統(tǒng)所有方面的重要概念。
1. Amdahl定律
Amdahl定律的思想是:當(dāng)我們對(duì)系統(tǒng)的某個(gè)部分加速時(shí),其對(duì)系統(tǒng)整體性能的影響,取決于該部分的重要程度與加速程度。公式如下,其中a代表該部分所需執(zhí)行時(shí)間占總執(zhí)行時(shí)間的比例,k代表該部分性能提升的比例:
S=\frac{1}{(1-a)+\frac{a}{k}}
可見(jiàn),要想顯著加速整個(gè)系統(tǒng),必須提升全系統(tǒng)中相當(dāng)大部分的速度。
2. 并發(fā)和并行
并發(fā)是指一個(gè)同時(shí)具有多個(gè)活動(dòng)的系統(tǒng);而并行指的是用并發(fā)來(lái)使一個(gè)系統(tǒng)運(yùn)行的更快。按照系統(tǒng)的抽象層次,由高到低可以分為三個(gè)層次的并發(fā):

  • 線(xiàn)程級(jí)并發(fā)
  • 指令集并發(fā)
  • 單指令、多數(shù)據(jù)并行
    3. 計(jì)算機(jī)系統(tǒng)中的抽象
  • 文件 是對(duì)I/O設(shè)備的抽象。
  • 虛擬內(nèi)存 是對(duì)程序存儲(chǔ)器的抽象。
  • 進(jìn)程 是對(duì)一個(gè)正在運(yùn)行的程序的抽象。
  • 虛擬機(jī) 是對(duì)整個(gè)計(jì)算機(jī)的抽象,包括操作系統(tǒng)、處理器和程序。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容