CSAPP =1= 電腦系統漫遊
思維導圖
預計閱讀時間:15min
閱讀書籍 《深入理解電腦系統》
參考影片 【精校中英字幕】2015 CMU 15-213 CSAPP 深入理解電腦系統 課程影片
參考文章 《深入理解電腦系統(1.1)—電腦概述》
《深入理解電腦系統(1.2)—hello world的程式是如何運行的》
《深入理解電腦系統(1.3)—金字塔形的存儲設備、作業系統的抽象概念》
原文鏈接 《旻天:
譯序
《深入理解電腦系統》最大的優點是為程式設計師描述電腦系統的實現細節,幫助其在大腦中構建一個層次型的電腦系統。從最底層的數據
在記憶體中的表示(如整數、浮點數表示),到流水線指令
的構成,之後到虛擬存儲器
,編譯系統
,動態載入庫
,再到最後的用戶態應用
。
貫穿本書的一條主線是使程式設計師在設計程式時,能充分意識到電腦系統的重要性,建立起被所寫的程式可能被執行的數據和指令的流程圖,明白當程式的執行過程中,電腦到底都發生了什麼事。從而能夠設計出一個高效的、可移植的、健壯的程式。並能夠更快地對程式進行排錯、性能調優等。
本書的主要內容是關於電腦體系結構(高級硬體設計)與編譯器和作業系統的交互,包括:
- 數據表示
- 彙編語言和彙編電腦體系結構
- 處理器設計
- 程式的性能度量和優化
- 程式的載入器、鏈接器和編譯器
- I/O 和設備的存儲器層次結構
- 虛擬存儲器
- 外部存儲管理
- 中斷、訊號和進程式控制制
電腦系統就像自然界的生態環境一樣,對每一個部分的設計都要求它能融洽地和系統內其他部分和平相處,我們不能站在一個微觀的視角去判斷某個系統部件是否最優,而是應該以電腦這個宏觀的整體來觀察和思考。
一、電腦系統漫遊
我們將通過跟蹤 hello.c
程式的生命周期來對電腦系統有個簡單的了解。它的生命周期從它被程式設計師創建開始,包括在系統上保存、運行、在螢幕上輸出資訊到最後的程式終止。
1.1 資訊就是位(bit) + 上下文(context)
hello.c
程式的生命是從一個源程式(或者叫源文件)開始的,該源文件由程式設計師編寫,並保存為hello.c
的一個由 ASCII
構成的文本文件。
這個 hello.c
也說明了一個基本的思想:系統中所有的資訊,包括磁碟文件、存儲器中的程式、存儲器中的數據以及網路上傳輸的數據,都是由一串比特(bit,或者叫做位)序列表示的,這些比特 8 個為一組,稱為位元組。
同樣的位元組序列可能表示一個整數、浮點數、字元串或者機器指令,而區分不同數據對象的唯一方法就是它們所在的上下文(context),在不同語境中表示不同的意義。
1.2 程式被其他程式翻譯成不同的格式
程式語言的設計是為了讓人可以讀懂,然而為了讓電腦可以運行這個 C 程式,就需要將每條有效的語句轉換成一系列的低級機器語言指令,並以二進位磁碟文件的形式存放起來。
在 Unix 系統中,從源文件到可執行文件的轉化是由編譯器驅動程式完成的。
unix> gcc -o hello hello.c
編譯器驅動程式的執行過程如下:
hello.c == 預處理器 ==> hello.i == 編譯器 ==> hello.s == 彙編器 ==> hello.o + printf.o == 鏈接器 ==> hello
源程式(文本文件) (cpp) 預處理程式 (ccl) 彙編語言(文本文件) (as) 可重定位目標文件(二進位) (ld) 可執行文件
- 預處理階段:預處理器(cpp)根據以
#
開頭的命令,修改原始的 C 程式。如將#include <stdio.h>
中引用的頭文件stdio.h
的內容插入到程式的開頭,來得到一個新的 C 程式。 - 編譯階段:編譯器(ccl)將預處理程式
hello.i
翻譯成彙編程式hello.s
。彙編語言程式中的每條語句都以一種標準的文本格式確切的描述了一條低級機器語言指令。彙編語言為不同高級語言的不同編譯器提供了通用的輸出語言。 - 彙編階段:彙編器(as)將
hello.s
翻譯成機器語言指令集合。並以可重定位目標程式
的格式,將結果保存到hello.o
的二進位文件中。 - 鏈接階段:如果程式調用了一下標準 C 庫中的函數,如
printf
, 而該函數存在於一個單獨的名為printf.o
的可重定位目標程式文件中。因此就必須以某種方式將其併入到我們的hello.o
文件中並得到最終的可執行目標文件hello
。
1.3 了解編譯系統如何工作是大有益處的
對於像 hello.c
這樣的簡單程式,我們可以依靠編譯系統生成正確有效的機器程式碼。但還是有一些重要的原因促使程式設計師必須知道編譯系統是如何工作的。
- 優化程式性能:比如,
- (開關)一個 switch 是不是總比一系列的 if-then-else 語句要高效?
- (循環)while循環比do循環更有效嗎?
- (數組&指針)指針引用比數組索引更有效嗎?
- (函數)一個函數調用的代價有多大?
- (參數)相對於通過引用傳遞過來的參數求和,為什麼用本地變數求和的循環會快很多倍?
- 為什麼兩個功能相近的循環,運行時間會有巨大差異?
- 理解鏈接時出現的錯誤:比如,
- (引用)鏈接器報告說它無法解析一個引用。
- (變數)靜態變數和全局變數的區別。
- (作用域)在不同的文件中定義相同名字的兩個全局變數會怎樣。
- (鏈接庫)靜態庫和動態庫的區別。
- 為什麼命令行上排列庫的順序會對程式有影響。
- 為什麼有些鏈接錯誤直到運行時才出現。
- 避免安全漏洞:近年來的緩衝區溢出錯誤造成了大多數網路和伺服器上的安全漏洞。錯誤原因大多是程式設計師忽視了編譯器用來為函數產生程式碼的堆棧規則。
1.4 處理器讀並解釋存儲在存儲器中的指令
之前編寫的 hello
程式,源碼和可執行文件都已經存放在了磁碟上。執行我們之前編寫的 hello 小程式,就可以在 shell 中看到程式的輸出:
unix> ./hello
hello, world
存放在磁碟的程式是如何運行,並在 shell 中列印資訊的呢?這時就需要理解一個典型系統的硬體組織。
1.4.1 系統的硬體組成
如圖展示的是 Inter Pentium 系統產品組的模型,但和其他系統也都大同小異。
- CPU: 中央處理單元
- ALU:算術/邏輯單元
- PC:程式計數器
- USB:通用串列匯流排
匯流排
貫穿整個系統的一組電子管道,稱作匯流排。它攜帶資訊位元組並負責在各個部件間傳遞。
通常匯流排被設計成傳遞定長的位元組塊,也就是字(word)。字中的位元組數(即字長)是一個基本的作業系統參數。
像我們平時買電腦說的 64 位處理器,指的就是 CPU 匯流排字長為 8 位元組(64 位)的 CPU。而裝系統時的 64 位作業系統,指的是可以完全的利用 CPU 64 位定址能力的作業系統,當然也可以在 64 位 CPU 的機器上裝 32 位的作業系統,只不過會大大浪費 CPU 定址能力。
I/O 設備
I/O(Input/Output,輸入/輸出)設備是系統與外界聯繫的通道。如用戶用來輸入的滑鼠、鍵盤;系統用來給用戶輸出資訊的顯示器;以及長期存儲用戶程式和數據的磁碟驅動器。
每個 I/O 設備都是通過一個控制器或適配器與 I/O 匯流排連接起來的。區別是控制器是 I/O 設備本身或系統主電路板上的晶片組。而適配器則是一塊插在主板插槽上的卡。
主存
主存是一個臨時存儲設備。在處理器執行程式時,它被用來存放程式和數據。
物理上來說,主存就是一組 DRAM(動態隨機存取存儲器)晶片組成。
邏輯上來說,存儲器是由一個線性的位元組數組組成的,每個位元組都有自己唯一的地址(從 0 開始的數組索引)。
一般來說,組成程式的每條機器指令都由不定量的位元組構成,每種語言也有所不同。比如,運行在 Inter 上的 Linux 機器中,short 類型數據為 2 個位元組,而 int、float 為 4 個位元組。
處理器
中央處理單元(CPU)簡稱處理器,是解釋(或執行)存儲在主存中指令的引擎。
處理器細分,又有:
程式計數器(PC)
處理器的核心是程式計數器(PC)的字長大小的存儲設備(或暫存器)。在任何時間點上,PC 都指向主存中的某條機器語言指令的地址。
從系統啟動開始直至斷電,處理器都在重複執行相同的簡單任務,即:
- 從 PC 當前指向的地址處讀取指令
- 解釋指令中的位
- 執行指令指示的簡單操作
- 更新 PC 到下一條指令(兩條指令並不一定相鄰)
- 重複執行 1.
暫存器文件
暫存器文件是一個很小的高速存儲設備,由一些字長大小的暫存器組成。這些暫存器每個都有唯一的名字。
算術邏輯單元(ALU)
ALU 計算新的數據和地址值。
處理器在指令的要求下可能會有以下操作:
- 載入:從主存拷貝一個位元組或一個字到暫存器,並覆蓋暫存器原來的值
- 存儲:從暫存器拷貝一個位元組或一個字到主存某個位置,並覆蓋原來的值
- 更新:拷貝兩個暫存器的值到 ALU,ALU將兩數相加並將結果存放到一個暫存器中,並覆蓋原來的值
- I/O 讀:從 I/O 設備中拷貝一個位元組或者一個字到暫存器
- I/O 寫:從暫存器中拷貝一個位元組或一個字到一個 I/O 設備
- 轉移:從指令本身抽取一個字,並將這個字拷貝到 PC 中,並覆蓋原來的值
1.4.2 執行 hello 程式流程
現在我們已經初步了解了程式的編譯過程和電腦硬體的組成,但一台電腦又是如何執行 hello 這個可執行文件的呢?
大體流程可以參考左瀟龍大佬
重繪的這版高清圖片:
1.shell 監聽用戶輸出,字元從輸入設備經由匯流排到暫存器,在從暫存器保存到主存,並在接受到回車後執行相關指令
2.shell 執行一系列指令,將 hello 可執行文件的程式碼和數據經過匯流排和 I/O 橋從磁碟 copy 到主存,完成程式的載入(可以利用 DMA 繞過 CPU 直接將硬碟數據載入進主存)
3.CPU 通過匯流排將主存中的指令逐條載入進 CPU 並翻譯程式指令,對於需要列印的字元,通過匯流排傳遞給顯示器顯示
1.5 高速快取
從上面例子我們可以看到,系統有大量的數據移動操作。比如開始的 hello 執行程式存放在磁碟上,之後程式載入
被拷貝到主存,然後程式執行
又把程式逐條拷貝到處理器,而對於其中的列印字元
,又把字元拷貝到了顯示器終端。因此對於這幾步拷貝的次數和速度還是有很大的優化空間的。
硬體開發商為了減少這種數據傳輸的時間成本,而高速快取應運而生。
高速快取被放置在處理器當中,與處理器中的暫存器文件直接進行數據交換,這樣大大減少了數據傳輸的時間成本,使得程式的運行速度可以得到數倍的提升。
1.6 形成層次結構的存儲設備
電腦領域有句名言:「電腦科學領域的任何問題都可以通過增加一個間接的中間層來解決」。
而在處理器和較慢的存儲器中間插入一個更快的存儲器這種想法也成為了一個普遍的概念。但硬體廠商經不起一個真理《越快的設備造價越高昂》,所以就需要在更快和更大之間產生一個平衡。
所以存儲結構就變成了如下的金字塔結構。
越靠近CPU的設備越小越快,但造價越高昂。
越遠離CPU的設備越大越慢,但造價越便宜。
1.7 作業系統管理硬體
為了簡化程式開發者使用硬體資源,電腦又產生了一個新的中間件 ———— 作業系統。
作業系統就是幫助應用軟體控制系統硬體的系統軟體,在應用軟體和系統硬體之間扮演者一個協調和管理的角色。
1.7.1 分層視圖
1.7.2 基本功能
- 防止硬體被失控(非法)的應用程式濫用,保護電腦
- 為不同硬體提供一套簡單一致的介面
文件
文件是作業系統對硬體中 I/O 設備的抽象,它只是一組位元組序列。任何 I/O 設備,如磁碟、鍵盤、顯示器、甚至網路都可以看成是一個文件。
虛擬存儲器
虛擬存儲器是一個抽象概念,為每個進程提供一個好似獨佔了主存的假象。由主存和文件組成。
可以看到虛擬存儲器有五個區域構成,從下往上(地址從小往大)依次是:
程式程式碼和數據(只讀和讀寫兩部分)
就是程式程式碼和數據,全局變數
運行時堆
是運行時可以動態擴展的一部分記憶體區域,它可以由malloc和free這樣的標準庫函數操作
共享庫存儲
用於存放共享庫的程式碼和數據
用戶棧
與函數的執行有密切的關係
內核虛擬存儲
內核是作業系統的一部分
進程
進程是作業系統對一個正在運行的程式的抽象,為程式提供一個好似獨佔了整個電腦的假象。由處理器和虛擬存儲器組成。
但實際上一個電腦會有多個進程同時執行,我們稱之為並發運行。每個進程交替獲得 CPU 時間並執行響應指令。
作業系統實現這種交錯執行的機制稱之為上下文切換(context switching)。而保存進程所需的所有狀態資訊就稱為上下文(context)。
執行緒
在現代系統中,一個進程實際上可以由多個稱為執行緒的執行單元組成。每個執行緒都運行在進程的上下文中,並共享同樣的程式碼和全局數據。
執行緒可以比進程更容易的共享數據,因此也更加高效。
1.8 利用網路系統和其他系統通訊
目前我們一直將系統視為一個孤立的個體,但實際上現代電腦經常通過網路和其他系統連接到一起。
而前面我們也說過,網路本就可以視為是一個 I/O 設備,它也可以被看做是一系列的位元組序列。網路適配器的作用就是給電腦輸入一堆被傳送過來的位元組序列,這裡面可能包括圖片、文字,甚至可能是程式碼等等。
總結
電腦是由硬體和軟體共同組成的。
軟體是由程式指令和數據組成的,由最初的 ASCII 文本逐步翻譯成可執行文件,可執行文件在電腦都是以二進位的形式保存的,二進位指令依據不同的上下文有不同的解釋方式。
作業系統將存在磁碟中的程式載入到主存中,CPU 再讀取並解釋主存中的二進位指令,產生程式期望的硬體效果。
因為電腦花費大量時間在存儲器和 I/O 設備到 CPU 之間的數據拷貝上,所以形成了金字塔模式的存儲模型。
作業系統內核是應用程式和硬體之間的媒介,通過作業系統提供的抽象介面,方便應用程式可以控制不同廠商的同種硬體。
完
《本章完》,期待各位道友指出文章的不足之處。
轉載請註明出處~~