电脑怎样执行编程语言的?
鏈接:https://www.zhihu.com/question/29227521/answer/154819061
來源:知乎
著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。
這個問題真的是很大,讓我們自頂向下的解釋。
在頂層,程序員編寫出來的都是源代碼。源代碼可以使用各種高級語言寫成,例如 c/c++ c# java python 等等;也可以使用對應平臺的低級語言寫成,例如匯編。想必你已經(jīng)了解其中的過程了。
到這一步為止,距離最終機器可以執(zhí)行的指令還有一大步要走。
首先要面臨的一個問題是:源代碼都是以人類語言寫成的。即便是能夠和機器指令一對一翻譯的匯編代碼,依然是人類語言。計算機無法理解其中的含義,所以不可能執(zhí)行。
所以我們需要將人類語言翻譯為計算機語言。計算機能聽懂的語言,就叫做機器語言,簡稱機器碼。
在這里說幾句題外話。
在計算機歷史的上古時代,大概是上個世紀50年代之前。那時編譯理論和形式語言還沒有得到發(fā)展。幾乎所有的程序都是直接由機器碼寫成的。比如由工程師直接將二進制機器碼和數(shù)值編寫在打孔卡上,通過讀卡機讀入計算機存儲器,然后執(zhí)行。
而打孔卡長這個樣子:
<img src="https://pic1.zhimg.com/50/v2-c8536a9e501f9e21ef1dd6d9ef0a6096_hd.jpg" data-rawwidth="2232" data-rawheight="1004" class="origin_image zh-lightbox-thumb" width="2232" data-original="https://pic1.zhimg.com/v2-c8536a9e501f9e21ef1dd6d9ef0a6096_r.jpg">(來自 wiki,80列標準 IBM 打孔卡,你能讀出上面是什么意思嗎?)
計算機的基本架構雖然經(jīng)過了將近百年的發(fā)展,但是核心的模型倒是一直很穩(wěn)定,都是存儲程序模型。
首先將程序指令從外存(打孔卡,磁帶,硬盤,軟盤,光盤,閃存卡,網(wǎng)絡等)讀入內(nèi)存,然后讓處理器從內(nèi)存按順序取指執(zhí)行,結果寫回內(nèi)存中。
在那個年代,人們對程序運行原理的理解是不存在什么障礙的。工程師怎么寫,計算機就嚴格的按照指令執(zhí)行。每一條指令對應一個步驟。最后的到結果。
在這種條件下,程序開發(fā)絕對是頂尖的職業(yè),首先能夠理解目標機的架構就需要相當?shù)墓Ψ蛄恕F浯芜€要按照機器的方式思考,寫出正確無誤的指令序列。
這樣的開發(fā)過程無疑限制了計算機行業(yè)的發(fā)展。
同時,即便是擅長于按照機器方式思考的工程師,也認為機器指令太難記了。如你所見,在打孔卡上準確無誤的寫上指令真是頭疼的要死。所以工程師們開發(fā)了一套助記符,用來指示對應的機器碼,這樣以來,程序的編寫和 debug 就方便多了。到上世紀40年代末期,就已經(jīng)有一批成熟的助記符體系了。
<img src="https://pic1.zhimg.com/50/v2-167f82b471c3e2f12622f731a9e5cbd9_hd.jpg" data-rawwidth="1330" data-rawheight="940" class="origin_image zh-lightbox-thumb" width="1330" data-original="https://pic1.zhimg.com/v2-167f82b471c3e2f12622f731a9e5cbd9_r.jpg">(ARM v7 匯編指令卡中的某一頁)
關于助記符的話題,暫且擱置。
回到正題。為了將人類語言翻譯成機器變成機器能夠理解的語言,還需要進行翻譯。就好像你不懂英語,英語可以翻譯成漢語,這樣你就能明白其中的含義。對于計算機來說,這個過程是一樣的。不過計算機對于翻譯有更高的要求。人類之間互相翻譯語言,有一些微小的出入并不影響理解,計算機為了能夠準確的得到結果,要求這個翻譯的過程,必須保證“將一種語言翻譯成涵義相同的等價的另一種語言”。
在早期,程序的規(guī)模還比較小,翻譯的過程可以人工的進行。利用查表的方式,最終是可以得到等價的機器碼序列。隨著計算機科學的發(fā)展,程序規(guī)模膨脹的越來越快,人工翻譯變的沒有可行性。此時就有人提出,編寫一套軟件來進行這個翻譯的過程。
一開始人們只用匯編語言進行程序開發(fā)。所以只需要將匯編語言翻譯為機器語言就可以了。這是相當直截了當?shù)倪^程,因為匯編語言的助記符和機器指令是一一對應的關系。所以只需要完成一個能夠自動查表并轉換的程序即可。很快,這樣的程序就被發(fā)明了出來。我們稱之為“匯編器”。
伴隨著匯編器的發(fā)展,工程師又開始想要偷懶。他們認為,既然匯編器可以將匯編指令翻譯成等價的機器碼,那么在翻譯之前一定也可以做一些預先處理的工作,將一個助記符轉換為多個助記符組成的序列。這樣以來,開發(fā)人員就可以使用較少的代碼,寫出較多的內(nèi)容。同時將常用的一些程序結構編寫成對應的助記符,在需要時就使用這個助記符,還可以幫助開發(fā)人員減少程序出錯的可能。簡直太好了。于是,人們又在匯編器中引入了宏指令。
所謂“宏(macro)”就是一套預先定義好的指令序列。每當匯編進行的時候,先預處理一次將宏等價的展開,然后再進行翻譯。如此,源程序變的更加容易理解了。
宏的引入,催生了程序結構化表達。在今天的匯編語言當中,我們也可以像使用高級語言的 if else for while 語句一樣,使用等價的結構語句。只不過,匯編中的結構語句都是宏實現(xiàn)的。
結構化表達給了一些計算機科學人員啟發(fā)。能不能更進一步,使用完全結構化,脫離某個對應機器平臺的形式化語言來描述一個源程序?于是,就有了高級語言及其編譯器。
開發(fā)人員利用高級語言編寫程序,然后利用對應的編譯器生成中間代碼,最后再將中間代碼變成機器碼。中間代碼可以是等價的匯編代碼,也可以是其它類型的代碼例如 JVM 的字節(jié)碼。最終處理中間代碼的程序可以是一個對應平臺的匯編器,也可以是一個解釋器。在這里姑且隱去這些細節(jié),將編譯的最終產(chǎn)物都視為一系列可以被執(zhí)行的二進制機器碼。關于編譯器的更多內(nèi)容,在網(wǎng)上可以找到很多詳細的資料。在這個話題下,編譯器不是核心問題,我就不再深入討論了。
至此,就得到了一個可以被執(zhí)行的程序了。這個文件的內(nèi)容是一系列二進制指令和數(shù)據(jù)組成的序列。它能被裝入機器的內(nèi)存,并且可以被處理器解碼執(zhí)行。
但是,為什么是二進制?
說回來,計算機其實是長期使用的一個簡稱。嚴格的講應該叫做“電子計算機”。但是計算機的形態(tài)并不限于電子式計算機。算盤,計算尺,對數(shù)計算表都可以算作廣義上的計算機,同時在電子式計算機出現(xiàn)之前,它的還有一個機械式計算機的表親。
<img src="https://pic3.zhimg.com/50/v2-39e7480eed57ce4010d52f631b34e451_hd.jpg" data-rawwidth="1704" data-rawheight="2272" class="origin_image zh-lightbox-thumb" width="1704" data-original="https://pic3.zhimg.com/v2-39e7480eed57ce4010d52f631b34e451_r.jpg">(來自 Wiki 。 查爾斯·巴貝奇 的分析機。蒸汽動力驅動,采用十進制,其內(nèi)存能夠存儲1000個50位的十進制數(shù),相當于20.7 KB 的 SRAM 或 DDRAM。采用打孔紙帶讀入程序,具有類似匯編語言的助記符編程系統(tǒng),是圖靈完備的。很蒸汽朋克,嗯?)
可是我們并不認為算盤以及計算尺和現(xiàn)代計算機是同一個東西。最核心的區(qū)別在于,現(xiàn)代計算系統(tǒng)是可編程的。按照這個定義,上面的分析機也是現(xiàn)代電子是計算機的鼻祖。它身上的核心模型一直繼承至今。
在分析機上,已經(jīng)實現(xiàn)了 “ 存儲程序計算機 ”。
這也就是現(xiàn)代計算系統(tǒng)的基本概念:
這一概念所描述的計算模型具有以下的過程:將完整的程序裝入存儲器后,運算單元依照地址按順序的從存儲器中取出指令和數(shù)據(jù)且執(zhí)行。指令序列就像流水一樣“流”入運算單元,當指令流盡,就意味著程序結束了。
<img src="https://pic1.zhimg.com/50/v2-4dff85953dd98440a398a57f3a7e5c8e_hd.jpg" data-rawwidth="543" data-rawheight="223" class="origin_image zh-lightbox-thumb" width="543" data-original="https://pic1.zhimg.com/v2-4dff85953dd98440a398a57f3a7e5c8e_r.jpg">對于計算機,自然是希望運算的速度越快越好。所以機械式運算很快就淘汰了。取而代之的就是電子式計算機。
電子式計算機的硬件基礎,就是數(shù)字電路。因為二進制可以很自然的表示開和關的兩種狀態(tài),高和低的兩種狀態(tài),通和斷的兩種狀態(tài),等等。所以很快就取得了主導低位,其它進制的數(shù)字電子器件淪為小眾。
理論上,二進制和十進制表示的數(shù)的范圍是一樣多的。因為實數(shù)集是一個連續(xù)同,不同進制實質(zhì)上是對數(shù)集的不同分割。
基于二進制數(shù)字電子器件制造的電子式計算機自然就需要二進制的輸入輸出。
到了這個層次,我們基本上解釋了高級語言源程序是如何成為計算機可以識別的二進制指令序列的。接下來的問題是,計算機如何識別并執(zhí)行二進制指令呢?
通用處理器被稱為“通用”,就是因為它不限定于特定用途。路邊上買一個計算器。只能計算四則運算,而計算機還能進行字處理,可以玩游戲看電影。都有賴于通用處理器提供的運算能力。
為了實現(xiàn)通用的目標,處理器在設計之初就不能對未來可能進行的運算進行限制。但是未來的可能性是無窮的。處理器不可能窮盡所有可能。
所以,處理器提供了一套它能夠支持的運算操作的集合,稱為“指令集”。指令集就限定了該處理器能夠進行的所有運算。而且這些運算通常都是有關于數(shù)字的運算。如果我們想解決一個任意問題,那么首先要把這個問題轉換為一個數(shù)字問題,再把數(shù)字問題的解答過程,用指令集當中的指令求解。
將其它問題轉換為數(shù)學問題的一種方法就是編碼。比如我們常見的 ASCII 碼表,就是把英語字符數(shù)字字符以及電報傳輸過程中的控制字編碼成對應的數(shù)字。例如字符 a 就等于數(shù)字97。
處理器的指令集同樣是經(jīng)過編碼的。所以我們才能用二進制數(shù)字流來表示指令。
舉個例子。在一個典型的 Intel IA-32 處理器上所支持的 x86 指令集。假設我們想將一個字節(jié)的數(shù)據(jù)從內(nèi)存移動到 al 寄存器,不妨就讓這個數(shù)據(jù)在內(nèi)存中 0x20 (十六進制表示的32)號字節(jié)的位置好了。那么,我們要寫出匯編代碼:
mov al, 30h
將這一行代碼送入?yún)R編器,得到對應的機器碼為:
0xB0 0x20
二進制的表示為:
1011 0000 0010 0000
其中 0xB0 就是我們的指令,也就是執(zhí)行第 176 號指令。這條指令的意思是:從內(nèi)存中指定的位置搬移數(shù)據(jù)一個字節(jié)寬度的數(shù)據(jù)到 al 寄存器。地址由緊跟在本指令后的數(shù)給出,在這里就是 0x20。
指令集中的每一個指令都可以這樣編碼。每一條指令都定義了一系列的操作。
如此,只要按照順序的從存儲器讀入指令代號和數(shù)據(jù),就可以讓程序執(zhí)行下去。
你又要說了,那如果我有循環(huán),有條件判斷怎么辦?
簡單。處理器為了能順序的取指并執(zhí)行,需要知道當前指令的下一條指令在哪里。為什么不是這一條指令在哪了?因為這一條指令已經(jīng)取回來了,所以它在哪里就不重要了。為了記錄當前指令的下一條指令的位置,處理器內(nèi)部有一個存放了這個地址的電子裝置,實現(xiàn)上它是一系列門電路組成的鎖存器,叫做 IP 寄存器(也有叫做 PC 的,這里統(tǒng)稱為 IP)。IP 的值可以在運行時被修改。那么只要提供了能夠修改 IP 值的指令,就能改變程序的執(zhí)行流程。可以返回到之前的某個位置,也可以一次前進到之后的某個位置。這個過程叫做“跳轉”。
所謂循環(huán)和判斷,本質(zhì)上都是判斷并跳轉。
用一個程序來做一個直觀的說明。這個程序很簡單。求出一個數(shù)組中所有數(shù)的和,然后返回這個值,如果這個值是0,則返回一個 -1。
和它等價的 C 代碼如下,這里我們將結果返回運行時:
int main(void) {int numbers[5] = {1, 2, 3, 4, 5};int result = 0;for (size_t i = 0; i != 5; ++i) result += numbers[i];return (result == 0 ? -1 : result); }
編譯器產(chǎn)生的匯編文件長什么樣子呢?長這樣的:
CONST SEGMENT constNumbers: 0x01, 0x02, 0x03, 0x04, 0x05 CONST ENDSTEXT SEGMENT numbers SIZE 20 BYTEmain PROCsub esp, 20 movaps xmm0, XMMWORD PTR constNumbersxor eax, eaxpush 5pop edxmovups XMMWORD PTR numbers[ebp], xmm0mov DWORD PTR numbers[ebp+16], edxmov ecx, eax Loop:add eax, DWORD PTR numbers[ebp+ecx*4]inc ecxcmp ecx, edxjne SHORT Loopor ecx, -1test eax, eaxcmove eax, ecx_main ENDP _TEXT ENDS END
為了便于解釋,這里隱去了很多細節(jié),并且使用了很多偽代碼。上面的匯編程序是不經(jīng)修改是無法通過編譯的。
等價的二進制文件又是什么樣子呢?為了方便閱讀,我稍稍整理了一下,并且加上了對應的匯編代碼,它長這個樣子:
<img src="https://pic4.zhimg.com/50/v2-8b6115a3b19af8d6b459aeb45b0223b9_hd.jpg" data-rawwidth="658" data-rawheight="307" class="origin_image zh-lightbox-thumb" width="658" data-original="https://pic4.zhimg.com/v2-8b6115a3b19af8d6b459aeb45b0223b9_r.jpg">(第8行操作數(shù)應當分為兩列,這里有一個小錯誤。)
同樣的,還是省去了很多細節(jié)。綠色的部分就是機器碼。
我完全理解使用助記符和高級語言的重要性。否則誰能通過機器碼一眼看出一段程序的含義呢?
當程序裝入內(nèi)存以后,IP將被(另外的某個程序,可能來自操作系統(tǒng),或者其它軟件)設置為 1,意思是:下一條要讀取的指令在 1 的位置。然后處理器就開始讀入指令。
為什么處理器會讀入指令呢?它是收到某個信號才會讀指令嗎?簡單的講,處理器從上電到掉電的整個過程當中只做三件事情,那就是:
所以不需要什么信號。在上一條指令將 IP 的值修改為 1 之后,處理器就已經(jīng)完成跳轉,找到程序入口了。
處理器取指,讀入第一條指令 0xce83。這里要插入一點,Intel 的處理器采用的是小端數(shù)據(jù)格式,就是說一個數(shù)的高位放在地址較高的地方,低位放在地址較低的位置。所以要倒過來讀,在這里就不詳細解釋了,略過。
處理器將這條啊指令送入解碼器,解碼的結果告訴處理器,應當執(zhí)行“將 esp 寄存器中的值減去一個指定數(shù),該數(shù)由緊隨指令的連續(xù)四個字節(jié)指定”的操作。然后處理器通過數(shù)據(jù)總線連續(xù)讀入四個字節(jié),得出操作數(shù)應該是 0x14(十六進制的20)。接著就執(zhí)行了這個操作,IP + 1,繼續(xù)取出下一條指令。
這個過程是很好理解的。總之就是這樣的循環(huán)。直到斷電。
再注意一下行號 11 和 12 標識的代碼。11 行將執(zhí)行比較 ecx 寄存器中的值和 edx 寄存器內(nèi)的值。根據(jù)不同的結果,12 行指令將有不同的行為:
- 兩個值相同的時候,12 行指令什么也不做,IP + 1。
- 兩個值不同的時候,12 行指令會將 Loop 標號的地址寫入 IP。 IP = 9。
程序走著走著就走回去了。這就是比較與跳轉。簡單吧。
而 10 行的代碼將會使 ecx 寄存器內(nèi)的值增長,每次經(jīng)過 10 行都 +1,隨著循環(huán)的進行,程序流不斷的跳轉到 9 行,然后經(jīng)過 10 行。在某一次經(jīng)過后,ecx 等增長正好令 ecx = edx 成立。這時候 12 行將什么也不做,IP 指向 13,程序又繼續(xù)進行下去了。
接下來,進入處理器的層次來理解它如何工作的。在這里我們要討論四個問題:
程序運行的過程,上面已經(jīng)提到過了。程序是完整的裝入內(nèi)存中的。運算器能夠直接操作的只有存儲器中的數(shù)據(jù)。他們之間的硬件連接如圖所示:
<img src="https://pic3.zhimg.com/50/v2-3b06f9699950184c4b4c210d145eb13c_hd.jpg" data-rawwidth="648" data-rawheight="424" class="origin_image zh-lightbox-thumb" width="648" data-original="https://pic3.zhimg.com/v2-3b06f9699950184c4b4c210d145eb13c_r.jpg">sorry,搞錯了,是這個:
<img src="https://pic2.zhimg.com/50/v2-1ad9ef2a8a07b07281896f4f040c9457_hd.jpg" data-rawwidth="800" data-rawheight="668" class="origin_image zh-lightbox-thumb" width="800" data-original="https://pic2.zhimg.com/v2-1ad9ef2a8a07b07281896f4f040c9457_r.jpg">圖上黃色的一根粗線其實以一排并列的導線,在這里是 8 根導線并列在一起。只是看起來畫在了一起,其實是互相分開的。
使用 8051 及其外部擴展存儲器接口電路來說明問題主要是為了簡便。在不失準確性的前提下,我依然隱去一些細節(jié),方便理解。
訪問存儲器的過程主要關注兩個問題:
考慮一般的訪問過程,當運算器執(zhí)行如下操作時:
mov al, 0xD0D0
將會發(fā)生什么呢?
首先 mov 指令指定了數(shù)據(jù)搬移的操作,第二個操作數(shù)是一個立即數(shù)參數(shù),直接給出了地址。現(xiàn)在就要到存儲器當中去找這個地方了。
處理器不能直接操作存儲器的具體單元,但是它可以請存儲器將對應單元中的數(shù)據(jù)準備好,然后取回來。你肯定有過取快遞的經(jīng)歷,菜鳥驛站去過吧,門市點不會讓你親自進倉庫去找快遞的,但是你可以告訴快遞小哥你的單號,然后他進去幫你找到,最后把包裹交給你。內(nèi)存和這一個意思。
處理器首先將地址放到地址總線上,地址總線就是圖上 D0-D7 和 A8-A15 這15 根導線組成的。
處理器將自己的端口設置成對應的值,就把地址放到了總線上。0xD0D0對應的端口狀態(tài)應該是:
<img src="https://pic2.zhimg.com/50/v2-d914aedb579fae88ba0d43ca7d4fc43e_hd.jpg" data-rawwidth="1155" data-rawheight="41" class="origin_image zh-lightbox-thumb" width="1155" data-original="https://pic2.zhimg.com/v2-d914aedb579fae88ba0d43ca7d4fc43e_r.jpg">(圖有點小)
然后,處理器告訴存儲器,我準備好取數(shù)據(jù)了,地址在總線上,請你準備數(shù)據(jù)。具體的方式就是拉低 端口的電壓到地電位(一般就是 0V)。存儲器得到這個消息后,就從總線上取得地址。然后解碼這個地址,找到對應的數(shù)據(jù),假設數(shù)據(jù)是0x11吧,然后把數(shù)據(jù)再放回總線 D7-D0上。
處理器在發(fā)出取指指令后會等待一段時間,然后就從總線上取回數(shù)據(jù)。取回的數(shù)據(jù)就當做存儲器的回應。至于這個等待的時間具體多長,這是兩個設備間相互約定好的。不需要關心。
最后,將總線上取得的數(shù)據(jù) 0x11 放入 al 中,指令完成。
可能有的讀者就很迷惑了,為什么放到總線上就能傳遞數(shù)據(jù)呢。
真實的情況是,總線上傳遞的是電壓的信號。這也是為什么使用二進制方便的原因。總線就是一組導線,在這一組導線上,一一對應的連接了處理器和存儲器的端口。雖然電子在導體內(nèi)的移動速度很慢,但是電場的傳播速度卻是光速。所以當總線一端的端口建立了電位之后,另一端的電位將立刻改變。此時信號就已經(jīng)從一個器件傳遞到了另一個。器件之間信號的傳遞,依賴的就是端口上電壓的改變。器件對總線數(shù)據(jù)的讀取,就是讀取端口上電壓的高低。而二進制可以使這個問題變的很簡單。只要端口上能夠反應電壓的高和低區(qū)別就足以傳遞信號了,一般的,高電位的區(qū)間在 3.3V - 5.0V 之間,而低電位在 0V - 2.2V 之間。考慮到總線都是板級的傳輸,距離很近,總線上電場傳播所需要的時間可以忽略掉。那么一組總線傳播數(shù)據(jù)的速度就取決于其兩端端口上電位改變的速度。這可比讀卡器讀卡高了不知道哪去了。也比磁盤尋道和讀取快的多。
在數(shù)字電路中,我們一般用 0 表示低電平,用 1 表示高電平。
上面提到過,mov 指令的編碼是 0xB0。這個編碼是什么意思呢?將其寫作二進制會發(fā)現(xiàn)
0xB0 = 1011 0000
剛剛我們已經(jīng)介紹過了。0和是表示的就是電壓的高低。現(xiàn)在一切都清楚了。數(shù)據(jù)的編碼其實就說說的端口上電壓的高低狀態(tài)。如果處理器的輸入端口在讀入指令時讀入的端口情況是從 D7到D0為 高低高高 低低低低。那么就讀入了 1011 0000。
那么我們已經(jīng)知道如何取數(shù)據(jù)了。取指令也是一個方法。只不過取指令的過程是自動的,指令的地址總是 IP 的值。取回的指令總是送入指令解碼器當中。
根據(jù)讀入數(shù)據(jù)時處理器所處的不同階段,將會給讀入的數(shù)據(jù)一個不同的解釋。讀指令階段就會把數(shù)據(jù)送入解碼器。讀數(shù)據(jù)階段就會把數(shù)據(jù)送入另外的地方。
接下來,就需要進行指令解碼。指令解碼本身也是一個非常大的話題,其實單獨拿出來也可以寫出和本篇一樣長的文章了。在這里只能概略的介紹一下。
處理器本身要完成某些特定的運算,在硬件上是需要某些特定結構的電路的支持的。比如你要完成一次加法,就需要一個帶有加法器的電路。完成一次位移,就要有帶移位寄存器的電路。簡單的說,任何一條指令,都需要一組特定的電路來提供支持。但是人們通過長期的對數(shù)字電路的研究發(fā)現(xiàn)。幾乎所有的運算,都可以通過有限的幾種器件的不同組合來完成。這樣的話,我們的通用運算器當中,可以包含一些要素器件,然后通過運行時改變它們的連接來實現(xiàn)不同的功能。這就是我們思考指令編碼的方向。
其實在電子式計算機剛剛誕生的時候,就已經(jīng)實現(xiàn)基本運算器的復用了。運算中心中包含了一組基礎的運算器,它們的輸入輸出端口上同時連接了很多組不同的電路,每執(zhí)行一條指令的時候,都選擇其中特定的一組電路,使其生效,而讓剩下的電路失效。這樣在指令執(zhí)行的過程中,這一組執(zhí)行電路就可以獨占整個運算器。當運算結束拿到結果后,電路再將運算器釋放掉。就可以準備下一次的運算了。
在早期,還沒有指令編碼技術。要使用不同的指令,必須改變電路間的硬連接。也就是要把一組插頭從這里拔下來插到另外的地方去。世界上第一臺通用電子計算機 ENIAC 的操作方式就是如此。編程的方式是女工進機房去接插頭。
<img src="https://pic1.zhimg.com/50/v2-42cc3730a33fa5303e52eaa50dfd91ce_hd.jpg" data-rawwidth="382" data-rawheight="214" class="content_image" width="382">(假設我們有三條可編程指令流水線,那么如果我們想依次執(zhí)行數(shù)據(jù)轉移,異或,求和的操作,就需要連接 #1 的 move, #2 的 xor 和 #3 的 add)
而后出現(xiàn)了指令編碼。送入的指令被解碼器解碼后,自動啟動一組對應的電路。
<img src="https://pic3.zhimg.com/50/v2-af944f1f1713ade91eb16c40e468a422_hd.jpg" data-rawwidth="403" data-rawheight="180" class="content_image" width="403">這樣說也許很難讓人明白“自動”的含義。所以我在這里實現(xiàn)一個簡單的編碼指令處理器。在這里我們只實現(xiàn) 3 條指令:
- 指令0:將輸入端的數(shù)據(jù)存入寄存器 a
- 指令1:將輸入端的數(shù)據(jù)存入寄存器 b
- 指令2:取寄存器 a 寄存器 b 中的值求和,將結果放入寄存器 a
在這里我們只研究解碼,不管其它的因素,這樣就簡化了問題。不多說,直接上圖:
<img src="https://pic2.zhimg.com/50/v2-87814e38f146df96bee0b3d745ef38be_hd.jpg" data-rawwidth="1182" data-rawheight="937" class="origin_image zh-lightbox-thumb" width="1182" data-original="https://pic2.zhimg.com/v2-87814e38f146df96bee0b3d745ef38be_r.jpg">最頂上的 instraction register 和 instraction decoder 的部分就是指令解器。首先將指令讀入一個寄存器,然后解碼。實際的運算器也是這樣的流程。圖中藍色的就是數(shù)據(jù)總線,寄存器內(nèi)的值分別是兩個寄存器 Qx 端口上的值。
讓我們啟動他它,來算一下 0x10 + 0x0F (16 + 15)是多少。
<img src="https://pic3.zhimg.com/50/v2-f36b90c5cb14f23d542e43e9d40779cf_hd.jpg" data-rawwidth="1182" data-rawheight="936" class="origin_image zh-lightbox-thumb" width="1182" data-original="https://pic3.zhimg.com/v2-f36b90c5cb14f23d542e43e9d40779cf_r.jpg">上電之后,我們注意到:
- 寄存器 a 和寄存器 b (右下兩個)都被初始化為 0xFF
- 輸入端口(左下角)上的值為 0x00
- 指令寄存器(上方)當中當前的指令為 0x0F (15號),這是一條未定義的指令,所以沒有任何效果。
首先我們要執(zhí)行
mov a, 0x10
當指令讀入后,在指令輸入端將會是 0x00 的狀態(tài),譯碼輸出端口上輸出全 0,指示出目前要執(zhí)行0 號指令。同時選中了 0 號指令的執(zhí)行電路。
數(shù)據(jù)端的輸入為 0001 0000。mov 命令的狀態(tài)下,輸入選擇器選擇輸入端口的數(shù)據(jù)放到總線上。同時,寄存器 a 被激活,將總線上的值存入:
<img src="https://pic1.zhimg.com/50/v2-11a49ccbdf02d9f65d7bd431bea04fce_hd.jpg" data-rawwidth="1182" data-rawheight="936" class="origin_image zh-lightbox-thumb" width="1182" data-original="https://pic1.zhimg.com/v2-11a49ccbdf02d9f65d7bd431bea04fce_r.jpg">(可以看到 0x10 已經(jīng)被存入寄存器了)
第二條指令,我們將啟動寄存器 b,然后存入數(shù)據(jù)。指令為:
mov b, 0x0f <img src="https://pic1.zhimg.com/50/v2-f10de0d8cfc7dd98ee94d89db11c4332_hd.jpg" data-rawwidth="1182" data-rawheight="936" class="origin_image zh-lightbox-thumb" width="1182" data-original="https://pic1.zhimg.com/v2-f10de0d8cfc7dd98ee94d89db11c4332_r.jpg">
instraction decoder 的 1 號輸出被選中,此時激活了 1 號指令的電路。輸入端的 0x0F 被存入了寄存器 b。而寄存器 a 中的值保持不變。
第三條指令,2 號指令,求值。
add
沒有給出操作時是因為操作是已經(jīng)隱含的指明了,就是 a 和 b:
<img src="https://pic1.zhimg.com/50/v2-727b3d793df16781896a5d97d67b7742_hd.jpg" data-rawwidth="1182" data-rawheight="936" class="origin_image zh-lightbox-thumb" width="1182" data-original="https://pic1.zhimg.com/v2-727b3d793df16781896a5d97d67b7742_r.jpg">譯碼器的 2 號輸出選中了。全加器完成了運算(左側是第四位,右側是高四位),結果放上了總線,被鎖存到了加法器的輸出緩沖器當中。
同一時間,雙輸入選擇器也被激活。它截斷了輸入端口的連接而選擇加法器輸出緩沖值作為輸入,將其放上了總線。
寄存器 a 從總線取得數(shù)據(jù),存入。完成了指令。
看看,結果是 0x1F,恰好就是我們預期的 31。
實際中的處理器的處理過程比這個復雜得多。這里為了方便理解,做了很多簡化。但是概念都是相同的。處理器自動的從內(nèi)存中讀入指令和數(shù)據(jù),然后解碼,啟動對應的電路,最后拿到結果。如此往復。
到此為止,已經(jīng)幾乎完全說明了計算機的運算原理,以及高級語言和機器語言的關系。但是我們依然可以更進一部,探究一下數(shù)字電路的構成。編碼器是怎么運行的?寄存器是怎么鎖存數(shù)據(jù)的?
上面一直在說解碼器,那么解碼器到底是什么?
處理器內(nèi)部的指令解碼器可能非常復雜,也許是一個器件,也有可能是一組器件,或者是可編程的硬件電路(對的,硬件電路也是可以編程的,例如 FPGA)。
而這里,我在上面的例子中使用的解碼器:74LS42 4 Lines to 10 Lines BCD to Decimal Decoder (4線10線BCD譯碼器)的內(nèi)部結構是這樣的:
<img src="https://pic3.zhimg.com/50/v2-6698baf03b1f6bf7d5e027d4090ca48f_hd.jpg" data-rawwidth="428" data-rawheight="414" class="origin_image zh-lightbox-thumb" width="428" data-original="https://pic3.zhimg.com/v2-6698baf03b1f6bf7d5e027d4090ca48f_r.jpg">可以看到,BCD 輸入端(左邊)輸入后首先連接了非門(NOT),然后進入一個選擇矩陣,最后通過三入與非門(NAND)輸出。
與門(AND)、或門(OR)、非門(NOT)是數(shù)字電路中,最基礎的三種邏輯門電路。它們的組后構建了大量的實用器件。
關于三種邏輯門,它們的特性可以使用真值表來表示:
<img src="https://pic2.zhimg.com/50/v2-9620ecea93ad929c25f8e95adb4a6138_hd.jpg" data-rawwidth="291" data-rawheight="231" class="content_image" width="291">(1 代表真,0 代表假)
與門:所有輸入全為真,輸出為真;
或門:任意一個輸入為真,則輸出真;
非門:輸出總是輸入的反。
利用這三個門就可以做很多有趣的事情了。
你需要理解歐姆定律,理解電阻、電容、二極管、三極管/場效應管,理解與或非門電路,組合邏輯電路,時序邏輯電路,理解CPU和指令集,機器代碼。
到了這一步,你就知道機器代碼的一堆01010010011011是怎么通過控制單元,邏輯運算單元,寄存器,以及底下的加法器,編碼器,譯碼器,多路選擇器通過高電平低電平的脈沖跑起來的。
你還要理解匯編器,理解編譯器和連接器,理解其中的詞法分析,語法分析,代碼生成,你需要理解操作系統(tǒng)。
到了這一步你就能明白電腦怎么講你用高級語言編寫的程序轉換成機器代碼的一堆01010010011011并提交給計算機執(zhí)行的了。
涵蓋這些知識的就是電路分析,模擬電路,邏輯電路,單片機,計算機組成原理,離散數(shù)學,數(shù)據(jù)結構,編譯原理,操作系統(tǒng)這些計算機專業(yè)的課程。
總結
以上是生活随笔為你收集整理的电脑怎样执行编程语言的?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 析构函数virtual与非virtual
- 下一篇: 优秀的软件企业为何倒下?