我們寫下的一行行代碼,計算機到底是如何在執行的呢?我們以x86架構的CPU為例,簡單介紹以下CPU的執行過程。
1、高級語言
為了方便編程,目前人類已經發明了六百多種編程語言,使得我們可以用人類最容易理解的語法規則去告訴計算機完成我們想要的功能。例如我們常用的C、C++、python、perl、ruby、go、java等。這些語言被稱為高級語言,以我們人類的思維方式來進行編程。
比如,一個C語言程序員寫下了一行代碼:
int sum = a + b;
這一句很簡單的C語句,計算機卻不懂。
這時候就需要一個翻譯,負責把人類編寫的高級語言“翻譯”成計算機能看得懂的東西,這個翻譯就是編譯器。
2、編譯鏈接
上面的高級語言語句經過編譯器編譯鏈接后,生成了一個目標運行平台為x86架構的可執行程序exe/elf,使用反編譯工具IDA進行分析,可以看到這行代碼編譯后的樣子是這樣的:
8B 45 EC mov eax, [ebp+a]
03 45 E0 add eax, [ebp+b]
89 45 F8 mov [rbp+sum], eax
下面對上面的進行解釋:
mov eax a //將變量a的值存入eax寄存器中
add eax b //把變量b的值和eax寄存器的值相加,並將結果保存在eax寄存器中
mov sum eax //將計算結果從eax寄存器寫入sum變量
3、機器指令
上面的匯編指令只是為了人類理解方便的助記符,計算機同樣也不認識這玩意,那幾條指令在內存中實際上是這樣的一串數據:
十六進制:
8B 45 EC 03 45 E0 89 45 F8
十六進制是為了書寫方便,計算機真正能看到的只有二進制的比特流
10001011 01000101 11101100 00000011 01000101 11100000 10001001 01000101 11111000
接下來,計算機要做的事情就是識別這些二進制流都是什么意思,轉換成一條條的指令來執行。
在開始執行之前,先來了解一下指令格式。
4、指令格式
x86架構CPU指令集中的指令格式如下:

主要有六個部分:
[非必需] 指令前綴:我們經常用到的原子操作指令前面有一個lock前綴,就屬於指令前綴。
[必需] 操作碼:指令最核心的部分,標識這條指令是什么功能。
[非必需] ModR/M:內存/寄存器操作數字節
[非必需] SIB:索引尋址描述字節
[非必需] Displacement:常數偏移字節/半字/字
[非必需] Immediate:立即數字節/半字/字
需要注意的是,並不是每一條指令都包含上面的所有部分,許多指令只包含其中一部分字段。
根據操作碼的長度不同,指令分為單字節操作碼指令、雙字節操作碼指令、三字節操作碼指令。
5、執行指令
計算機中真正負責指令執行的核心部件是中央處理器CPU,在CPU中有一個指令寄存器IP,全稱是Instruction Pointer,在32位下,它叫EIP,在64位下它叫RIP。
下面開始執行:
指令寄存器EIP指向了第一條指令,開始讀取第一個字節:10001011,也就是0x8B。
開始指令譯碼,翻譯出這是一條什么指令。
下面是x86架構的CPU指令操作碼表:

CPU中的指令譯碼模塊發現單字節操作碼的mov指令,要往eax寄存器里面塞數據,數據從哪來呢?
再往后一看,0x45,再來譯碼:

原來是根據ebp寄存器的值+一個8位的偏移來讀取數據。
再往后讀取一個字節,就是偏移值:EC。
現在第一條指令就譯碼出來了:將ebp+0xEC位置處的4個字節的數據取出來,放到eax寄存器中。,這就是這一條指令要干的事情。
同時CPU還得出了另一個信息:這一條指令長度是3個字節,下一條指令的起始地址是在3個字節之后,隨后,指令寄存器EIP向后撥動,指向下一條指令的地址:$+3。
指令譯碼完成之后,開始來正式執行它。
執行完一條以后,又來到指令寄存器EIP指向的地方,隨后再次指令譯碼、執行,不斷重復這個過程,依次執行每一條指令。
這其實就是CPU工作最基本的原理。
擴展
上面描述的過程是CPU在硬件電路層面完成的,但這種設計思想在軟件領域也同樣適用。
大家如果去研究Java虛擬機JVM和Python的解釋器源代碼時,也會發現有相似之處:JVM和解釋器通過定義一套自己的“指令集”,然后它們的編譯器使用這套指令集將Java和Python代碼編譯成對應的程序。
運行的時候也類似,虛擬機或者解釋器不斷識別每一條指令,譯碼、執行,和CPU執行指令的過程頗有幾分相似。
C/C++語言編譯的程序,最后是直接編譯成了CPU的指令,所以跨平台能力差,如果換到ARM架構平台,原來的程序將無法執行,需要重新編譯成新的平台的程序。
而Java、Python這類語言,是自己在軟件層面的指令集,因為其自身已經開發了針對不同CPU平台的虛擬機、解釋器,所以這些語言編寫的程序移植性好,真正做到一次編寫,到處運行。
總結
我們使用高級語言C、C++編寫的程序代碼,經過編譯器的編譯鏈接,最終變成CPU可以理解的機器指令,隨后CPU在執行時通過不斷的譯碼、執行,最終實現高級語言所描述的功能。
