深入理解計算機系統(1.1)------Hello World 是如何運行的


  上一篇序章我談了談 程序員為啥要懂底層計算機結構 ,有人贊同也有人反對也好,這都是博主的個人見解,但是博客還是要堅持學下去。這篇博客以案例驅動的模式,通過跟蹤一個簡單 Hello World 程序的生命周期開始系統的學習,包括它被程序員創建,到在系統上運行,輸出簡單的消息,然后終止。LZ 將沿着這個程序的聲明周期,先簡要的介紹一些逐步出現的關鍵概念、專業術語以及組成部分。后面將會詳細展開。

  

1、計算機系統

  我們知道計算機系統是由硬件和軟件組成的。它們共同工作來運行應用程序。雖然系統的實現方式隨着時間不斷變化,但是系統內在的概念卻沒有改變。所有計算機系統都有相似的硬件和軟件組件,它們執行着相似的功能,我們只有深入了解這些組件是如何工作的,以及這些組件是如何影響程序的正確性和性能的,才能寫出高質量的代碼。

 

2、萬能程序大法----Hello World

#include <stdio.h>

int main()
{
	printf("Hello World\n");
	return 0;//c標准規定建議main函數返回值為int 
}

  

  這段代碼不用多說,就是一個C語言的Hello World,程序的執行結果是打印 “Hello World”。

 

3、信息的表示

  我們將上面的 Hello World 程序保存在一個 hello.c 的文件中,那么它是怎么存儲在文件中的呢?實際上它是以字節序列的方式存儲在文件中。

  什么是字節?一個字節由8個位組成,而一個位是由值0和1組成。也就是說 hello.c 源程序是由值0和1組成的位序列。

  大部分的現代系統都是用 ASCII 碼構成,這種方式實際上就是用一個唯一的單字節大小的整數來表示每個字符。下面我們給出 hello.c 程序的 ASCII 碼表示:

  

  左邊是文件對應的16進制代碼,右邊是我們的源程序,例如:第一個字符“#”的 ASCII 值是0x23。需要特別注意一下:每個文本行都以一個看不見的換行符‘\n’結束的。第2行中有2個連續的0x0D 0x0A ,這是windows中特有的“換行符\r\n” ,在linux中的是“換行符\n”。像hello.c文件這樣只由 ASCII 碼組成的文件叫做“文本文件”,其他所有文件都叫“二進制文件”。

  系統中所有的信息都是由位+上下文構成。

  包括磁盤文件、存儲器中的程序,存儲器中存放的用戶數據以及網絡上傳送的數據都是由一串位表示。而區分不同數據對象的唯一方法就是我們讀到這些對象時的上下文。比如在不同的上下文中,一個同樣的字節序列可能表示一個整數、浮點數、字符串或者機器指令。

  作為程序員,我們需要了解數字的機器表示方式,因為它們與實際的整數和實數是不同的。它們是對真值的有限近視值,有時候會有意想不到的行為表現。這個后面我們會詳細講解。

 

 

4、程序的編譯

  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

  

  上面截圖的是一個匯編程序

5、程序的運行

  經過上面程序的編譯,hello.c 源程序已經被編譯成了可執行目標文件 hello,並存放在磁盤上,那么如何運行呢?

  ①、系統的硬件組成

  為了理解運行 hello 程序時發生了什么,我們先要了解一個典型系統的硬件組織。如下圖:

  

  我們現在不需要對這張圖有很深入的理解,后面會詳細進行介紹。現在先簡單的認識一下下面幾個主要部件:

  一、總線:貫穿整個系統的一組電子管道,通常被設計成用來傳送定長的字節塊,也就是字。字的大小與系統相關,比如在32位操作系統當中,一個字是4個字節

  二、I/O設備:輸入/輸出(I/O)設備是系統與外部世界聯系通道,上圖有4個I/O設備。作為用戶輸入的鍵盤和鼠標,作為用戶輸出的顯示器,以及用於長期存儲數據和程序的磁盤。每一個I/O設備都通過一個控制器或者適配器與I/O總線相連。控制器是置於I/O設備本身的或者系統的主印刷電路板(通常稱為主板)上的芯片組,而適配器則是一塊插在主板插槽上的卡。無論如何,它們的功能都是在 I/O 總線和 I/O 設備之間傳遞信息。

  三、主存:它是計算機中的一個臨時存儲設備,在處理器執行程序的時候,用來存放程序和程序處理的數據。物理上來說,主存是由一組動態隨機存取存儲器(DRAM)組成的,邏輯上來說,它是一個線性的字節數組,每一個字節都有唯一的地址(即數組索引)。

  四、處理器:全稱中央處理器(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” 字符串中的字節從主存復制到寄存器文件,再從寄存器文件中復制到顯示設備,最終顯示在屏幕上。

  

 

6、 本章總結

   ①、出現的名詞解釋:  

  :"位(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 到9、標點符號, 以及在美式英語中使用的特殊控制字符。

  文本文件和二進制文件

    文本文件是指以ASCII碼方式(也稱文本方式)存儲的文件,后面基於 utf-8 編碼的文本文件,utf-8是能夠向后兼容ASCII,即相同的ASCII文本文件和UTF-8文本文件完全一致。它是一種典型的順序文件,其文件的邏輯結構又屬於流式文件。

    二進制文件:是基於值編碼的文件,你可以根據具體應用,指定某個值(可以看作是自定義編碼)。

   ②、內容總結

  計算機是由軟件與硬件組成的,而硬件又包括了總線、I/O設備、主存以及處理器,其中信息是由位以及上下文表示的,而信息則是從I/O設備以位的形式通過總線進入主存,然后由處理器從主存將信息取出處理。

         一個程序的執行,是經歷了預處理器、編譯器、匯編器以及鏈接器的處理之后,才最終成為可執行的文件。

 

  PS:有人問我《深入理解計算機系統》這本書的PDF文檔,這里給出下載鏈接:http://pan.baidu.com/s/1boOM3Tl 密碼:kfe1


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM