關於《深入理解計算機系統》 程式碼是怎麼執行的

hongmingover發表於2018-09-27

關於《深入理解計算機系統》

“這本書的中譯名為“深入理解計算機系統”,我非常,十分,以及百分之一百二十地不滿意。我這麼說的原因在於這個譯法完全扭曲了書的本意。如果直譯原書名,應該是類似於“以程式設計師的視角理解計算機系統”,何來“深入”二字。可能在國內編輯看來,這是講系統的,用C和組合語言的,因此很“深入”,但我認為這隻能說明國內技術氛圍的淺薄。因為事實上,這是一本入門級別的書,這本書其實並不“深入”,它談論的內容還是相對比較淺的。但“淺”不代表“淺薄”,寫一本面向初學者的好書往往是非常困難的,因此無論是SICP還是CSAPP,它的作者都是頂尖學府的教授,結合多年教學經驗而寫出來的。

CMU把這本書作為“Introduction to Computer System”課程的教材,是面向計算機專業低年級學生的“計算機系統介紹(導論)”,可能這些學生只是剛剛瞭解如何使用一門高階語言——如Java進行程式設計,對於計算機系統的工作方式等話題可謂一點都沒有接觸過,而CSAPP對讀者的背景也只要求是“一些程式設計經驗”而已。這本書的話題覆蓋面很廣,從計算機的基本組成,二進位制資料表示方式,到機器級別的指令,CPU工作方式,儲存結構和優化,作業系統的虛擬記憶體管理,程式執行方式,I/O,網路、到(較底層)程式效能優化和並行程式開發等等。所以,它其實覆蓋了“計算機組成原理”,“作業系統”等許多課程的內容,其中的許多話題都能再次展開,繼續深入,都能再變成一本,甚至N本經典。事實上,在高年級的計算機專業課程設定中,都會有更加縱向的內容出現。”                                                                                     ——摘自Jeffrey Zhao博文

老趙的這一番話,值得深思,這本書用怎麼說呢,並不是深入,而是涉及的知識比較廣,但又都是廣大程式猿不得不知道的知識,下面我們就來慢慢品嚐這本書吧。言歸正傳,接下來,享讀《Computer Systems: A Programmer’s Perspective》的中hello world程式都幹了些啥。

1.資訊在計算機的中表示

當我們輸入以下程式,編譯執行,計算機從螢幕輸出hello, world。整個過程計算機都怎麼運作的呢?

#include <stdio.h>

int main()

{

printf("hello, world\n");

}

我們知道,資訊在計算機中都是用0或1表示的。計算機通過這些位資訊以及上下文來解讀這些0/1。也即:計算機中的資訊=位+上下文。

我們輸入的hello程式就是由0、1組成的序列,將這些位8位組織成一個位元組,每個位元組用來表示一個文字字元。ASCII碼給出了一種字元與數字的一一對應關係。

hello, world程式以位元組方式存放於檔案中,如下圖所示。其每個字元對應一個數字,具體可參考ASCII碼錶。

圖1 Hello world程式的ASCII碼錶示

2.將程式翻譯成機器可讀的格式

因為我們輸入的hello, world程式是人可讀的,機器並不能直接識別它們。我們需要把這些文字翻譯成機器可執行的二進位制檔案。這一部分的工作是由編譯系統完成的。編譯系統由前處理器、編譯器、彙編器、聯結器四部分組成。以hello, world程式為例,各部分共同完成將原始檔編譯成二進位制可執行檔案。各個部分完成的具體工作如下:

l 前處理器:根據以#開頭的命令,修改源程式。如根據#include <stdio.h>行,前處理器讀取系統標頭檔案stdio.h中的內容,代替此行內容。源程式經過預處理後,得到另一個c程式,此程式通常以.i為字尾儲存。

l 編譯器:將預處理後的.i檔案轉換成彙編程式。編譯器將不同的高階語言(如c語言,C++語言)轉換成嚴格一致的組合語言格式進行輸出。組合語言以標準的文字格式確切的描述每機器語言指令。編譯器得到的檔案通常以.s為字尾儲存。

l 彙編器:將組合語言(.s檔案)翻譯成機器語言指令,並將這些指令打包成一種可定位目標程式格式。彙編後得到的檔案即為二進位制檔案,通常以.o為字尾。

l 連結器:hello, world程式中呼叫過printf函式,它是一個c標準庫裡的函式。Printf函式存放在一個名為printf.o的單獨預編譯的檔案中。而這個檔案必須以適當的方式併入到我們的程式中,這個工作由連結器完成。將外部的.o檔案併入後,得到一個完整的hello, world可執行檔案。可執行檔案載入到儲存器後,由系統複製執行。

圖2 編譯系統

在linux系統上,輸入編譯命令列:

Viidiot>gcc hello.c -o hello

將執行上圖所示的四個步驟,得到可執行二進位制檔案hello。

3.處理器讀取並解釋儲存在儲存器中的指令

Shell:命令列直譯器,為使用者提供了一隻與系統打交道的方式。它等待使用者的輸入,當使用者輸入一行命令後,shell先判斷它是不是一個shell內建命令,如果不是,shell會假定使用者輸入為一個可執行檔案的名字,從而去載入並執行該檔案。因此,當我們通過編譯系統將原始檔編譯成可執行二進位制檔案後,在shell中輸入我們得到的可執行二進位制檔名,shell將其從磁碟中載入到儲存器(注:我們的可執行檔案是存放在磁碟上的),並通過處理器進行解釋執行,得到最終的結果,輸出到終端(顯示器)上進行顯示。自此,我們的hello, world程式完成了其生命週期。

4.計算機系統硬體結構

為了弄清楚hello, world執行時,系統究竟發生了什麼,我們先來了解下一個典型的計算機硬體結構。

圖3 典型的計算機硬體構成

【CPU:中央處理器   ALU:算術/邏輯運算單元  PC:程式計數器  USB:統一序列介面】

 

下面簡單說一下各個部件在系統中所起的作用。

匯流排:在各個部件之間傳輸資料。現在的匯流排寬度一般為32位或者64位,即一次傳輸的資料為4位元組或者8位元組。

I/O裝置:IO裝置是系統與外界通訊的通道,如滑鼠,鍵盤,顯示器都是典型的IO裝置。

主儲存器:簡稱主存,是處理器執行程式時用於臨時存放程式及其資料。主存由一組動態隨機儲存器晶片組成。

處理器:解釋執行儲存在主存中的指令。其內部包含一個雙位元組程式計數器(PC),任何時候PC中都存放著接下來要執行的機器指令在主存中的地址。

處理器的操作主要是圍繞PC、ALU、主存來進行運作的。處理器首先從PC所指向的主存儲存單元讀取指令,解釋指令中的位,執行該指令指示的簡單操作,然後更新PC暫存器,使其指向下一條要執行的指令。CPU會執行的操作有:

載入:把一個位元組或一個字從主存複製到暫存器,覆蓋掉暫存器中原來的值。

儲存:把一個位元組或一個從暫存器複製到主存,並覆蓋主存中原來的值。

操作:把兩個暫存器的內容複製到ALU,ALU對兩個字做算術運算後存回其中的一個暫存器,該暫存器中原來的值會被覆蓋。

跳轉:從cpu執行的指令抽取一個字的內容存入PC,覆蓋掉原來的值,從而改變下一條要執行的指令,達到跳轉的目的。

在瞭解了一些基本的硬體結構,以及各個部分的作用後,我們再來看看之前的hello, world程式的執行過程。

圖4 載入可執行檔案到主存的過程

在linux系統下,我們在shell中敲入以下命令

Viidiot>./hello

由於shell沒有內建hello命令,因此shell將我們輸入的hello視為一個可執行檔案,從而通過執行一系列機器指令,將可執行檔案hello從磁碟複製到主存,如圖4所示。

注意,如果通過DMA方式載入程式,則不需要通過CPU,而是將hello可執行檔案直接從磁碟複製到主存,示意圖如圖5。

圖5 DMA方式載入程式到主存

 

可執行程式載入到主存後,cpu就執行hello程式的機器指令,而這些指令完成的工作便是將”hello,world\n”這幾個字元從主存中複製暫存器檔案中(register file),再將其從寄存寄檔案中複製到顯示裝置上進行顯示。過程示意圖如圖6所示。

圖6  cpu執行指令,將 “hello,world\n”從記憶體複製到顯示裝置

至此,hello,world程式的執行過程已經完成。

題外話:

從上面的程式例項我們可以看到,程式花費了大量的時間將資料從一個部件複製到另外一個部件。程式載入時,將hello程式的機器指令從磁碟複製到主存,程式執行時,又將其從主存複製到cpu,最後又從cpu複製到外部顯示器。將根據機械原理,大容量的儲存裝置速度比小容量儲存裝置慢,快速裝置的造價比慢速裝置的造價高。對於計算機硬體系統,CPU的速度遠高於主存的速度,而主存的速度遠高於磁碟,不同部件的速度嚴重不對等,從而快的裝置的效能沒能得到充分發揮。為解決各類裝置速度不匹配的問題,引入了快取記憶體裝置來緩解速度匹配問題。如圖7所示,為加入了快取記憶體後的系統部分結構。

圖7 快取記憶體儲存器

現代計算機為提高系統效能,一般都加入了多級快取結構。快取記憶體取樣的是靜態隨機儲存器硬體(SRAM)技術,速度快於主存(取樣動態隨機儲存器技術)。如圖8是儲存器結構金字塔,越往上速度越快,造價也更昂貴。

圖8 儲存器金字塔

作者:Viidiot   微信公眾號:linux-code

相關文章