1. 程式人生 > >彙編3-計算機程式是如何執行的

彙編3-計算機程式是如何執行的

1.引言

前面我們已經提到過暫存器以及cpu的一些基本工作原理,那麼在一個程式執行時,暫存器、cpu、記憶體等都是擔任著怎樣的角色呢?我們學習彙編的目的就是為了更好的瞭解計算機底層是如何工作的,所以我覺得有必要在學習彙編指令之前談一談一個計算機程式是如何在計算機上執行起來的。本文會盡可能詳細地描述從寫一個源程式到它被執行的過程中都計算機內部都發生了哪些事情。

2.計算機是如何工作的呢?

從巨集觀上來講,計算機是給人提供服務的,提供方便的。人類想要告訴計算機一些東西,通過什麼途徑呢?輸入裝置(如滑鼠,鍵盤,等等);計算機想要告訴人類一些東西也會通過輸出裝置(如螢幕,印表機,等等);計算機在讀入使用者的資料後,會對資料進行計算加工,這時會用到運算器

;而運算的結果可能不會立刻輸出給使用者,而是暫存起來,這時計算機會用到儲存器;而不論是運算的過程還是儲存的過程,邏輯流程的安排,步驟序列則需要控制器。人類與計算機的互動,就是通過這五大部件的通力合作展開的,或者說,計算機的工作就是通過這五大部件實現的。

現代計算機基本上都是遵照馮·諾依曼體系結構進行設計、生產建造的。而該體系結構的核心概念就是儲存程式計算機,即儲存在計算機中的程式的執行,是通過一條條指令的執行而實現的(三階段:取指令、指令譯碼、執行指令)。這是我們從微觀的角度來看待計算機是如何工作的。
————-摘自計算機是如何工作的

3.源程式到目標程式的過程

1.源程式的儲存

我們就拿大家最熟悉的程式hello world來描述這個過程。

這裡寫圖片描述
相信讀者知道計算機是以0、1串來表示資料的,所以我們的hello world程式在計算機中也是以這樣的形式存在的,8個位為一個位元組,一個位元組表示程式中的某些字元,也就是每個字元對應一個8位的整數值,這個整數值就是ascii碼。
所以我們的源程式用ascii碼就是這樣表示的:
這裡寫圖片描述
現在這些ascii碼都是十進位制方式展示在我們面前,但是在計算機當中還是一串串0、1組成的8位的串。這種以ascii碼儲存的檔案就是文字檔案,其它方式儲存的檔案都是二進位制檔案。

2.源程式的編譯
雖然我們能夠很輕鬆地看懂源程式,但是計算機卻不能理解這些是什麼東西,更不要談執行我們的hello.c了,所以必須要讓計算機也能看懂我們的意思,這時候就需要編譯系統來幫忙了。

  1. 編譯系統的作用:將源程式翻譯為一些計算機能夠理解的低階機器指令。
  2. 編譯系統的組成:前處理器、編譯器、彙編器、聯結器
    編譯系統的工作流程:
    這裡寫圖片描述
  3. 3.

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

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

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

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

在unix系統中,源程式到目的程式的轉換室友編譯器驅動程式執行的。
輸入命令:gcc hello.c -o hello
就會執行上述步驟並生成目標二進位制程式。

4.執行目標程式

現在我們已經有了目標程式了,是時候運行了。
在unix系統中輸入程式名:
當用戶輸入一行命令後,shell先判斷它是不是一個shell內建命令,如果不是,shell會假定使用者輸入為一個可執行檔案的名字,從而去載入並執行該檔案。
注:

源程式是放在磁碟中的。
載入:把一個位元組或一個字從主存複製到暫存器,覆蓋掉暫存器中原來的值。

現在我們來關注下細節,計算機硬體到底做了什麼?
這裡寫圖片描述
上圖是計算機一些基本的硬體組成。
其中一些硬體需要特別說明:

1.匯流排:在各個部件之間傳輸資料。

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

3.主儲存器:簡稱主存,是處理器執行程式時用於臨時存放程式及其資料。主存由一組動態隨機儲存器晶片組成。也就是我們通常說的記憶體,它是與磁碟不一樣的東西,就是我們計算機中的記憶體條

4.處理器:也就是cpu,解釋執行儲存在主存中的指令。其內部的pc永遠指向下一條將要被執行的指令的地址。處理器的操作主要是圍繞PC、ALU(算術\邏輯運算單元)、主存來進行運作的

CPU按照指令可能會執行的操作有:

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

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

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

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

最開始shell程式處於執行狀態,等待使用者輸入命令。當我們在shell環境下輸入“.\hello”時,輸入的字元被一個個讀入暫存器並送入主存。
這裡寫圖片描述
按下回車後,shell程式知道我們已經輸入完成。
由於我們的可執行檔案是放在磁盤裡的。所以接下來要做的就是把磁碟的程式碼和資料複製到主儲存器中(通過DMA方式載入程式,則不需要通過CPU,而是將hello可執行檔案直接從磁碟複製到主存),如下圖
這裡寫圖片描述
可執行程式載入到主存後,cpu就執行hello程式的機器指令,而這些指令完成的工作便是將”hello,world\n”這幾個字元從主存中複製暫存器檔案中(register file),再將其從寄存寄檔案中複製到顯示裝置上進行顯示,如圖:
這裡寫圖片描述
到此,hello world程式從源程式到成功執行整個流程已經完成了。

5.拓展

1.快取記憶體

其實上面的硬體組成並不符合現在的計算機,從上文可以看到記憶體是直接與暫存器進行資料交換的,這樣其實是很浪費資源的,因為現在的cpu的處理速度非常快,但是對記憶體的讀取與其比起來就顯得特別慢,如果暫存器直接與記憶體打交道,效率就會很低,所以前輩們引入了快取記憶體(cache)。快取記憶體是暫存器與主存的一個過渡,也結合了兩者的優點。快取記憶體可以存放比暫存器多的資料,讀取速度又快於主存。所以很多需要頻繁使用的資料就可以放在cache中,這樣就不用每次都去訪問主存,大大提高了效率。
後來這種在較快的處理器與較大較慢的儲存器之間插入一個過渡裝置的思想得到了普及,就形成了後來的儲存器分層結構:
這裡寫圖片描述

2.作業系統管理硬體

在我們的hello程式中,我們都沒有直接操作鍵盤、顯示器、磁碟等,這些事情都是作業系統幫我們做了。作業系統其實就是介於硬體與我們的引用程式之間的一層軟體。
這裡寫圖片描述
作業系統有兩大功能:

  1. 防止硬體被失控的程式操縱。
  2. 提供給應用程式提供操縱複雜的低階裝置一個簡單一致的方法。

每天進步一點點