思維導圖
預計閱讀時間: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 之間的資料拷貝上,所以形成了金字塔模式的儲存模型。
作業系統核心是應用程式和硬體之間的媒介,通過作業系統提供的抽象介面,方便應用程式可以控制不同廠商的同種硬體。
完
《本章完》,期待各位道友指出文章的不足之處。
轉載請註明出處~~