標籤: CSAPP
從某種意義上來說,本書的目的就是爲了幫助你瞭解當你在系統上執行 hello 程序時,系統發生了什麼以及爲什麼會這樣。
1. 一個 hello 程序的執行過程
1.0 準備知識
爲了理解
hello
程序運行時發生什麼,需要先對一個典型系統的硬件組織有一個大致的瞭解。如下圖所示:
系統的硬件組成包括:
- 總線
貫穿整個系統的一組電子管道,負責在各個部件之間傳遞信息。總線通常被設計成傳送定長的字節塊,也就是 字(word)。 - I/O 設備
I/O 設備是系統與外部世界的聯繫通道。
每個 I/O 設備都通過一個 控制器 或 適配器 與 I/O總線相連。
控制器與適配器之間的區別主要在於它們的封裝方式。控制器是 I/O設備本身或者系統的主板上的芯片組,而適配器是一塊插在主板插槽上的卡。
- 總線
- 主存
主存是一個臨時存儲設備,在處理器執行程序時,用來存放程序和程序處理的數據。
物理上,存儲器是由一組 DRAM 芯片 組成。邏輯上,存儲器是一個線性的字節數組,每個字節有唯一的地址。 - 處理器(CPU)
是執行存儲在主存中指令的引擎,核心是 程序計數器(PC),在任何時刻,PC 都指向主存中的某條機器指令。
1.1 源代碼
此例子中的源代碼如下:
#include<stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
每個程序都是由源代碼開始的,源代碼即文本文件。
只由 ASCII 字符構成的文件稱爲 文本文件,所有其他文件稱爲 二進制文件。
1.2 源程序到可執行文件
爲了在系統上運行 hello.c 程序,每條 C 語句都必須被其他程序轉化爲一系列的
低級 機器語言 指令,然後按照一種稱爲 可執行目標程序 的格式打包起來,並以二進制磁盤文件的形式存放起來。
在 Unix 系統上,從源文件到目標程序的轉化是由 編譯器驅動程序 完成的。編譯過程如下:
編譯過程由四個階段組成:
- 預處理階段
預處理器(cpp)根據以字符 # 開頭的命令(頭文件、宏定義等),修改原始的 C 程序。比如hello.c
中第一行的#include<stdio.h>
命令告訴預處理器讀取系統頭文件stdio.h
的內容,並把它直接 插入 程序文本中。結果就得到了另一個 C 程序hello.i
。 編譯階段
編譯器(ccl)將文本文件hello.i
翻譯成文本文件hello.s
,它包含一個 彙編語言程序。如下:main: subq $8, %rsp movl $.LCO, %edi call puts movl $0, %eax addq $8, %rsp ret
彙編階段
彙編器(as)將hello.s
翻譯成機器語言指令打包成 可重定位目標程序 保存爲二進制文件hello.o
,包含了函數main
的指令編碼。鏈接階段
hello
程序調用了printf
函數,這是標準 C 庫中的一個函數,保存在一個名爲printf.o
的單獨預編譯好了的目標文件中,鏈接器以某種方式將這個文件合併到hello.o
中,結果得到名爲hello
的可執行目標文件。
- 預處理階段
1.3 運行
- 執行
hello
程序時,hello
目標文件中的代碼和數據從磁盤被複制到主存,然後處理器開始執行main
程序中的機器語言指令。
2.其他概念
2.1 高速緩存
- 爲了解決處理器和主存之間巨大的速度差異,利用程序的局部性原理,在主存和處理器之間設計一級或者多級的 高速緩存存儲器(cache),通過讓 cache 裏存放可能經常訪問的數據,大部分的內存操作能在快速的 cache 中完成。
2.2 存儲設備的層次結構
2.3 操作系統管理硬件
- 操作系統是應用程序和硬件之間插入的一層軟件,所有應用程序對硬件的操作嘗試都必須通過操作系統。
2.3.1 進程
一個程序在現代操作系統上運行時,操作系統會提供一種假象,就好像系統上只有這個程序在運行。程序看上去像是獨佔地使用處理器、主存和I/O設備。處理器看上去就像是在不間斷地一條接一條地執行程序中的指令。
併發運行:一個進程的指令和另一個進程的指令是交錯執行的。
單處理器系統每次只能執行一個進程的代碼,併發運行需要在多個進程之間來回切換,切換時,操作系統要保存當前進程的狀態信息,稱爲上下文切換,狀態信息即 上下文(context)。
2.3.2 線程
- 在現代操作系統中,一個進程實際上可以由多個稱爲 線程 的執行單元組成,每個線程都運行在進程的上下文中,並共享同樣的代碼和全局數據。
2.3.3 虛擬內存
- 虛擬內存 是一個抽象概念,它爲每一個進程提供了一個假象,即每個進程都在獨佔地使用主存。
- 每個進程看到的內存都是一致的,稱爲 虛擬地址空間。
下圖中的地址從下往上增大。
- 從低地址從下往上分別是:
- 程序代碼和數據
對所有進程來說,代碼是從同一固定地址開始,緊接着是和 C 全局變量相對應的數據位置。這段對應的就是 可執行文件 hello。 - 堆
可以在運行時動態地擴展和收縮。 - 共享庫
存放像 C 標準庫和數學庫這樣的共享庫的代碼和數據。 - 棧
程序用棧來實現函數調用,和堆一樣,可以在程序運行時動態地擴展和收縮。比如,調用一個函數時,棧就會增長;從一個函數返回時,棧就會收縮。 - 內核虛擬內存
不允許程序讀寫這個區域或者直接調用內核代碼定義的函數。相反,它們必須調用內核來執行這些操作。
- 程序代碼和數據
2.3.4 文件
- 文件就是字節序列,僅此而已。
- 每個 I/O設備 都可以看成是文件。
- 系統中所有的輸入輸出都是通過使用一小組稱爲 Unix I/O的系統調用函數調用讀寫文件來實現的。
2.4 Amdahl 定律
- 該定律的主要思想是,當我們對系統的某個部分加速時,其對系統整體性能的影響取決於該部分的重要性和加速程度。
若系統執行某應用程序需要時間
Tol−d ,假設系統某部分所需執行時間與該時間的比例爲a ,而該部分性能提升比例爲k ,即該部分初始所需時間爲aTold ,現在所需時間爲(aTold)/k 。因此,總的執行時間應爲
Tnew=(1−a)Told+(aTold)/k=Told[(1−a)+a/k]
由此,可以計算加速比S=Told/Tnew 爲
S=1/[(1−a)+a/k] 由公式可以看出,要想加速整個系統,必須提升全系統中相當大部分的速度。
2.5 併發與並行
- 併發:同時具有多個活動的系統,如處理器不同的核心之間同時工作。
- 並行:用併發來使一個系統運行得更快,如處理器得一個核心同時併發運行多個程序。
- 三個層次:
- 線程級併發
- 指令級並行
- 單指令、多數據並行
2.6 計算機中得抽象
- 文件 是對 I/O設備得抽象。
- 虛擬內存 是對程序存儲器的抽象。
- 進程 是對一個正在運行的程序的抽象。
- 虛擬機 是對整個計算機的抽象,包括操作系統、處理器和程序。
3. 小結
計算機是由硬件和系統軟件組成的,它們共同協作以運行應用程序。計算機內部的信息被表示爲一組組的位,它們依據上下文有不同的解釋方式。程序被其他程序翻譯成不同的形式,開始時是 ASCII 文本,然後被編譯器和鏈接器翻譯成二進制可執行文件。
處理器讀取並解釋存放在主存裏的二進制指令。因爲計算機花費了大量時間在內存、I/O設備和CPU寄存器之間複製數據,所以將系統中的存儲設備劃分成層次結構————CPU寄存器在頂端接着是多層的硬件高速緩存存儲器、DRAM主存和磁盤存儲器。在層次模型中,位於更高層的存儲設備比低層的存儲設備要快,單位比特造價也高。層次結構中較高層次的存儲設備可以作爲較低層次的存儲設備的高速緩存。通過理解和運用這種存儲層次結構的知識,程序員可以優化C程序的性能。
操作系統內核是應用程序和硬件之間的媒介。它提供三個基本抽象:1)文件是對I/O設備的抽象;2)虛擬內存是對主存和磁盤的抽象;3)進程是處理器、主存和I/O設備的抽象。
最後,網絡提供了計算機系統之間通信的手段。從特殊角度看,網絡就是一種I/O設備。