一、CPU的組成部分
1.1、計算機五大部件
說起計算機五大件,筆者想起在大學的時候,老師問我們計算機硬件的五大部件有什么,然后聽到有同學回答說鼠標,鍵盤,顯示器什么的,把老師逗樂了,說我們計算機系的還這么小白。其實也正常,誰不是從小白慢慢成為技術大牛呢...
言歸正傳,計算機硬件的五大件分為:輸入設備,輸出設備,運算器,控制器,儲存器。輸入,輸出設備主要就是鼠標,鍵盤,顯示器還有打印機等等。儲存器主要分為內存和磁盤,內存是儲存程序運行時的指令和數據。本章主要圍繞着控制器和運算器來講,因為它們都是CPU的組成部分。
按實際硬件分,CPU內部是由寄存器,控制器,運算器,時鍾四個部分組成:
寄存器:暫存指令和數據,可以看作是一級內存;
控制器:負責把內存上的指令,數據讀入寄存器;
運算器:負責運算從內存讀入寄存器的數據;
時鍾:負責發出CPU開始計時的時鍾信號;
一個CPU可以有多個寄存器。
1.2、寄存器的分類和功能
x86系列32位CPU的寄存器主要分為累加寄存器,標志寄存器,程序計數器,基址寄存器,棧指針寄存器,通用寄存器等;累加寄存器,標志寄存器,程序計數器都只有一個,其他寄存器一般有多個。32位CPU是指寄存器可以存儲4個字節。
累加寄存器(eax)(這個是運算器):存儲執行運算的數據和運算后的數據;
標志寄存器:存儲運算處理后的CPU的狀態;
程序計數器(epc)(這個是控制器)的作用是存儲下一條指令所在的內存地址;比如程序需要實現123和456相加,並輸出結果到顯示器。程序開始運行時,程序計數器儲存程序初始的內存地址比如是0100,CPU每執行一個指令,程序計數器存儲的內存地址就會加1(程序流程分為順序執行,跳轉,循環),這里只拿順序執行,如下圖:

基址寄存器(ebp):存儲數據存儲領域基點的內存地址;
棧指針寄存器(esp):存儲棧中最高位數據的內存地址;
通用寄存器:存儲任意數據;
二、程序在CPU的執行過程
程序是指令和數據的集合,不管是C、PHP、GO等其他語言寫的程序,到最后都是編譯成機器語言,就是一系列的指令,程序運行時CPU則依次執行每一行的指令,根據運算結果來控制整個計算機的輸入還是輸出。程序運行時,是先從把程序在磁盤上復制到內存,然后根據程序計數器指定的內存地址,把程序的指令從內存上讀到指定寄存器進行運算。

CPU能識別的機器語言,像這種00111110二進制數值,在我們人類看是很難理解的,為了人類能夠方便理解機器語言,就發明了助記符,每個助記符都一一對應機器語言,這種助記符對應機器語言的方式就是匯編,匯編語言是直接反應機器語言的程序運行情況。比如助記符LD A , 207,對應的機器碼是00111110 11001111。LD A , 207的意思是把數值207寫入到A寄存器。LD 是操作碼;A,207是操作數。
下面用高級語言(比如C、PHP、GO)轉換成匯編(低級語言),通過匯編來理解程序的指令在CPU上是怎么運作的:
下面是簡單的C程序:
int add_a_and_b(int a, int b) {
return a + b;
}
int main() {
return add_a_and_b(2, 3);
}
通過匯編器轉換成匯編(省略了匯編器生成的段定義和偽指令,這里只是為了理解程序執行過程):
_add_a_and_b:
push ebp
mov ebp,esp
mov eax,dword ptr [ebp+8] ;[ebp+8]指定棧中儲存的數值2,傳人eax寄存器
add eax,dword ptr [ebp+12] ;[ebp+12]指定棧中儲存的數值3,再跟eax寄存器存儲的值相加,結果存入eax
pop ebp
ret
_main:
push ebp
mov ebp,esp
push 3
push 2
call _add_a_and_b
add esp,8
pop ebp
ret
解釋上面匯編代碼:
dword ptr 表示從指定內存地址讀出4個字節數據;
_main,_add_a_and_b是函數標簽;
操作碼 操作數 說明
push ebp 把ebp寄存器里存儲的內存地址存入棧中(32位CPU,占用4個字節)
mov ebp,esp 把esp寄存器里存儲的內存地址傳到ebp寄存器
push 3 把3放入棧中(占用4個字節)
push 2 把2放入棧中(占用4個字節)
call _add_a_and_b 調用函數
add esp,8 esp寄存器存儲的內存地址值加8(棧存儲數據是從高地址到低地址開始存儲,上面加入了3和2,故加8來清理棧中數據)
pop ebp 讀出棧中的數值存入ebp寄存器
ret 結束main函數,返回調用源
每次調用函數時,都要執行push ebp mov ebp,esp,這兩個指令,是因為函數內可能要用到ebp寄存器,所以先把ebp寄存器原來的數值存入棧中,再把當前存儲在esp上的棧頂地址傳給ebp;函數調用結束后,執行add esp,8 pop ebp來進行占空間清理,再把ebp原來的數值傳入ebp寄存器;
上面的函數調用,函數返回時,程序計數器里存儲的地址又回到函數調用的下一個指令,實現的原因是:在進行函數調用前先把函數調用的下一個指令存儲到棧中,調用完再把棧上的返回目的地址的內存地址(目的地址的內存是指函數調用完,回到下一個指令的地址。)傳入程序計數器存儲。上面的程序內存里棧空間是這樣的:

總結:計算機的運行機制其實很簡單,就是照着程序員寫好的程序(源文件),翻譯成CPU能理解的二進制指令(本地代碼)從上往下逐步的執行。如今的編程從面向過程,到面向對象,這個發展無非是越來越接近人類,從難於理解的機器碼,到用英文對應機器碼的匯編,再發展到面向對象編程,都是為了增加代碼的可讀性,可維護性。
