上一篇序章我談了談 程序員爲啥要懂底層計算機結構 ,有人贊同也有人反對,可是這並不影響 LZ 對深刻理解計算機系統研究的熱情。這篇博客以案例驅動的模式,經過跟蹤一個簡單 Hello World 程序的生命週期開始系統的學習,包括它被程序員建立,到在系統上運行,輸出簡單的消息,而後終止。LZ 將沿着這個程序的聲明週期,先簡要的介紹一些逐步出現的關鍵概念、專業術語以及組成部分。後面將會詳細展開。html
咱們知道計算機系統是由硬件和軟件組成的。它們共同工做來運行應用程序。雖然系統的實現方式隨着時間不斷變化,可是系統內在的概念卻沒有改變。全部計算機系統都有類似的硬件和軟件組件,它們執行這類似的功能,咱們只有深刻了解這些組件是如何工做的,以及這些組件是如何影響程序的正確性和性能的,才能寫出高質量的代碼。linux
#include <stdio.h> int main() { printf("Hello World\n"); return 0;//c標準規定建議main函數返回值爲int }
這段代碼不用多說,就是一個C語言的Hello World,程序的執行結果是打印 「Hello World」。程序員
咱們將上面的 Hello World 程序保存在一個 hello.c 的文件中,那麼它是怎麼存儲在文件中的呢?實際上它是以字節序列的方式存儲在文件中。shell
什麼是字節?一個字節由8個位組成,而一個位是由值0和1組成。也就是說 hello.c 源程序是由值0和1組成的位序列。編程
大部分的現代系統都是用 ASCII 碼構成,這種方式實際上就是用一個惟一的單字節大小的整數來表示每一個字符。下面咱們給出 hello.c 程序的 ASCII 碼錶示:windows
左邊是文件對應的16進制代碼,右邊是咱們的源程序,例如:第一個字符「#」的 ASCII 值是0x23。須要特別注意一下:每一個文本行都以一個看不見的換行符‘\n’結束的。第2行中有2個連續的0x0D 0x0A ,這是windows中特有的「換行符\r\n」 ,在linux中的是「換行符\n」。像hello.c文件這樣只由 ASCII 碼組成的文件叫作個「文本文件」,其餘全部文件都叫「二進制文件」。數組
系統中全部的信息都是由位+上下文構成。網絡
包括磁盤文件、存儲器中的程序,存儲器中存放的用戶數據以及網絡上傳送的數據都是由一串位表示。而區分不一樣數據對象的惟一方法就是咱們讀到這些對象時的上下文。好比在不一樣的上下文中,一個一樣的字節序列可能表示一個整數、浮點數、字符串或者機器指令。編輯器
做爲程序員,咱們須要瞭解數字的機器表示方式,由於它們與實際的整數和實數是不一樣的。它們是對真值的有限近視值,有時候會有意想不到的行爲表現。這個後面咱們會詳細講解。函數
hello 程序的生命週期是從一個高級 C 語言程序開始的,由於這種形式能被人讀懂。然而,計算機系統是讀不懂高級語言的。爲了在系統上運行 hello.c 程序,每條 C 語句都必需要被其餘程序轉化爲一系列的低級機器語言指令。
通常來講,要將 hello.c 變成一個可執行的目標程序,必需要通過 預處理器、編譯器、彙編器和連接器 的處理。以下:
預處理器、編譯器、彙編器和連接器 一塊兒構成了編譯系統,下面對每一個步驟分別進行解析:
①、預處理階段:預處理器 cpp 根據以字符 # 開頭的命令,修改原始的 C 程序,好比 Hello.c 中第一行 #include<studio.h> 命令告訴預處理器讀取系統文件 stdio.h 的內容,並把它直接插入到程序中。結果就獲得另外一個 C 程序,一般是以 .i 做爲文件擴展名。
②、編譯階段:編譯器 ccl 將文本文件 hello.i 翻譯成文本文件 hello.s,它包含一個彙編語言程序,彙編語言程序中的每條語句都以一種標準的文本格式確切的描述一條低級機器語言指令。彙編語言能爲不一樣高級語言的不一樣編譯器提供通用的輸出語言。
③、彙編階段:彙編器 as 將hello.s 翻譯成機器語言指令,把這些指令打包成一種叫作可重定位目標程序的格式,並將結果保存在目標文件 hello.o 中,hello.o 文件是一個二進制文件,它的字節編碼是機器預言指令而不是字符。若是咱們用文本編輯器打開 hello.o 文件,將會是一堆亂碼。
④、連接階段:在 hello.c 程序中,咱們看到程序調用了 printf 函數,它是每一個 C 編譯器都會提供的標準 C 庫中的一個函數。printf 函數存在於一個名爲 printf.o 的單獨的預編譯好了的目標文件中,而這個文件必須以某種方式合併到咱們的 hello.o 程序中。連接器 ld 就是負責處理這種合併,結果就獲得一個 hello 文件,它是一個可執行目標程序,能夠被加載到內存中,由系統運行。
這裏我作一下驗證,我在 Linux 系統上建立 hello.c 程序,而後依次執行上面的步驟:
預處理:
gcc -E hello.c -o hello.i
而後查看 hello.i
編譯階段:
gcc -S hello.i
而後查看 hello.s
上面截圖的是一個彙編程序
通過上面程序的編譯,hello.c 源程序已經被編譯成了可執行目標文件 hello,並存放在磁盤上,那麼如何運行呢?
①、系統的硬件組成
爲了理解運行 hello 程序時發生了什麼,咱們先要了解一個典型系統的硬件組織。以下圖:
咱們如今不須要對這張圖有很深刻的理解,後面會詳細進行介紹。如今先簡單的認識一下下面幾個主要部件:
1、總線:貫穿整個系統的一組電子管道,一般被設計成用來傳送定長的字節塊,也就是字。字的大小與系統相關,好比在32位操做系統當中,一個字是4個字節。
2、I/O設備:輸入/輸出(I/O)設備是系統與外部世界聯繫通道,上圖有4個I/O設備。做爲用戶輸入的鍵盤和鼠標,做爲用戶輸出的顯示器,以及用於長期存儲數據和程序的磁盤。每個I/O設備都經過一個控制器或者適配器與I/O總線相連。控制器是置於I/O設備自己的或者系統的主印刷電路板(一般稱爲主板)上的芯片組,而適配器則是一塊插在主板插槽上的卡。不管如何,它們的功能都是在 I/O 總線和 I/O 設備之間傳遞信息。
3、主存:它是計算機中的一個臨時存儲設備,在處理器執行程序的時候,用來存放程序和程序處理的數據。物理上來講,主存是由一組動態隨機存取存儲器(DRAM)組成的,邏輯上來講,它是一個線性的字節數組,每個字節都有惟一的地址(即數組索引)。
4、處理器:全稱中央處理器(CPU),是解釋(或執行)存儲在主存中指令的引擎。處理器的核心是一個字長的存儲設備(或寄存器),簡稱程序計數器(PC),在任什麼時候刻,它都會指向主存中的某條機器指令(即含有該條指令的地址)。從系統通電到斷點,處理器一直在不斷的執行程序計數器所指向指令,再更新程序計數器,使其指向下一條指令。處理器所作的操做是圍繞主存、寄存器文件以及算術/邏輯單元(ALU)進行的,寄存器文件是一個小的存儲設備,由一些1字長的寄存器組成,每一個寄存器都有惟一的名字。ALU則計算新的數據和地址值。
CPU 在指令的要求下會作以下操做:
①、加載:把一個字節或者一個字從主存複製到寄存器,以覆蓋寄存器原來的內容
②、存儲:把一個字節或者一個字從寄存器複製到主存的某個位置,以覆蓋這個位置上原來的內容
③、操做:把兩個寄存器的內容複製到 ALU,ALU 對這兩個字作算術操做,並把結果存放到一個寄存器中,以覆蓋寄存器原來的內容
④、跳轉:從指令自己中抽取一個字,並將這個字複製到程序計數器(PC)中,以覆蓋PC中原來的內容。
處理器當中提到的是指令集結構的簡單實現,不過實際上現代處理器使用了很是複雜的機制來加速程序的運行。咱們能夠這樣去區分指令集機構以及微體系結構,指令集結構描述的是每條機器代碼指令的效果,而微體系結構描述的是處理器其實是如何實現的,相似於JAVA虛擬機與JAVA虛擬機實現的關係。
②、運行 Hello World 程序
前面簡單的介紹了系統的硬件組成和操做,那麼接下來介紹咱們運行程序時到底發生了什麼。
想要在 Linux 系統中運行該可執行程序,咱們要將它的文件名輸入到稱爲外殼(shell)的應用程序中,外殼是一個命令行解釋器,它輸出一個提示符,等待你輸入一個命令,而後執行這個命令。若是該命令行的第一個單詞不是一個內置的外殼命令,那麼外殼就會假設這是一個可執行文件的名字,它將加載並運行這個文件。
初始時,外殼程序執行它的指令,等待咱們輸入一個命令。當咱們在鍵盤上輸入字符串"./hello"後,外殼程序將字符逐一讀入到寄存器中,再把它放入到存儲器中,以下圖:
PS:爲何要輸入「./hello」來執行,對於Linux系統有必定了解的人,可能知道這是運行命令的一種方法。
當咱們在鍵盤上敲回車鍵的時候,外殼程序知道咱們已經結束了命令的輸入。而後外殼執行一系列指令來加載可執行的 hello 文件,將 hello 目標文件中的代碼和數據從磁盤複製到主存。數據包括最終會被輸出的字符串「Hello World\n」,一旦目標文件中的代碼和數據被加載到主存,處理器就開始執行 hello 程序的 main 程序中的機器語言指令。這些指令將「Hello World\n」 字符串中的字節從主存複製到寄存器文件,再從寄存器文件中複製到顯示設備,最終顯示在屏幕上。
①、出現的名詞解釋:
位:"位(bit)"是電子計算機中最小的數據單位。每一位的狀態只能是0或1。
字節:8個二進制位構成1個"字節(Byte)",它是存儲空間的基本計量單位。1個字節能夠儲存1個英文字母或者半個漢字,換句話說,1個漢字佔據2個字節的存儲空間。
字:"字"由若干個字節構成,字的位數叫作字長,不一樣檔次的機器有不一樣的字長。例如一臺8位機,它的1個字就等於1個字節,字長爲8位。若是是一臺16位機,那麼,它的1個字就由2個字節構成,字長爲16位。在32位操做系統當中,一個字是4個字節,字是計算機進行數據處理和運算的單位。
ASCII:American Standard Code for Information Interchange,美國信息交換標準代碼。注意不是ASCⅡ(羅馬數字2),使用指定的7 位或8 位二進制數組合來表示128 或256 種可能的字符。標準ASCII 碼也叫基礎ASCII碼,使用7 位二進制數(剩下的1位二進制爲0)來表示全部的大寫和小寫字母,數字0 到九、標點符號, 以及在美式英語中使用的特殊控制字符。
文本文件和二進制文件:
文本文件是指以ASCII碼方式(也稱文本方式)存儲的文件,後面基於 utf-8 編碼的文本文件,utf-8是可以向後兼容ASCII,即相同的ASCII文本文件和UTF-8文本文件徹底一致。它是一種典型的順序文件,其文件的邏輯結構又屬於流式文件。
二進制文件:是基於值編碼的文件,你能夠根據具體應用,指定某個值(能夠看做是自定義編碼)。
②、內容總結
計算機是由軟件與硬件組成的,而硬件又包括了總線、I/O設備、主存以及處理器,其中信息是由位以及上下文表示的,而信息則是從I/O設備以位的形式經過總線進入主存,而後由處理器從主存將信息取出處理。
一個程序的執行,是經歷了預處理器、編譯器、彙編器以及連接器的處理以後,才最終成爲可執行的文件。
PS:有人問我《深刻理解計算機系統》這本書的PDF文檔,這裏給出下載連接:http://pan.baidu.com/s/1boOM3Tl 密碼:kfe1