日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

电脑怎样执行编程语言的?

發(fā)布時(shí)間:2023/12/18 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 电脑怎样执行编程语言的? 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

鏈接:https://www.zhihu.com/question/29227521/answer/154819061
來(lái)源:知乎
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

這個(gè)問(wèn)題真的是很大,讓我們自頂向下的解釋。


在頂層,程序員編寫(xiě)出來(lái)的都是源代碼。源代碼可以使用各種高級(jí)語(yǔ)言寫(xiě)成,例如 c/c++ c# java python 等等;也可以使用對(duì)應(yīng)平臺(tái)的低級(jí)語(yǔ)言寫(xiě)成,例如匯編。想必你已經(jīng)了解其中的過(guò)程了。

到這一步為止,距離最終機(jī)器可以執(zhí)行的指令還有一大步要走。

首先要面臨的一個(gè)問(wèn)題是:源代碼都是以人類(lèi)語(yǔ)言寫(xiě)成的。即便是能夠和機(jī)器指令一對(duì)一翻譯的匯編代碼,依然是人類(lèi)語(yǔ)言。計(jì)算機(jī)無(wú)法理解其中的含義,所以不可能執(zhí)行。

所以我們需要將人類(lèi)語(yǔ)言翻譯為計(jì)算機(jī)語(yǔ)言。計(jì)算機(jī)能聽(tīng)懂的語(yǔ)言,就叫做機(jī)器語(yǔ)言,簡(jiǎn)稱(chēng)機(jī)器碼。


在這里說(shuō)幾句題外話。

在計(jì)算機(jī)歷史的上古時(shí)代,大概是上個(gè)世紀(jì)50年代之前。那時(shí)編譯理論和形式語(yǔ)言還沒(méi)有得到發(fā)展。幾乎所有的程序都是直接由機(jī)器碼寫(xiě)成的。比如由工程師直接將二進(jìn)制機(jī)器碼和數(shù)值編寫(xiě)在打孔卡上,通過(guò)讀卡機(jī)讀入計(jì)算機(jī)存儲(chǔ)器,然后執(zhí)行。

而打孔卡長(zhǎng)這個(gè)樣子:

<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">

(來(lái)自 wiki,80列標(biāo)準(zhǔn) IBM 打孔卡,你能讀出上面是什么意思嗎?)

計(jì)算機(jī)的基本架構(gòu)雖然經(jīng)過(guò)了將近百年的發(fā)展,但是核心的模型倒是一直很穩(wěn)定,都是存儲(chǔ)程序模型。

首先將程序指令從外存(打孔卡,磁帶,硬盤(pán),軟盤(pán),光盤(pán),閃存卡,網(wǎng)絡(luò)等)讀入內(nèi)存,然后讓處理器從內(nèi)存按順序取指執(zhí)行,結(jié)果寫(xiě)回內(nèi)存中。

在那個(gè)年代,人們對(duì)程序運(yùn)行原理的理解是不存在什么障礙的。工程師怎么寫(xiě),計(jì)算機(jī)就嚴(yán)格的按照指令執(zhí)行。每一條指令對(duì)應(yīng)一個(gè)步驟。最后的到結(jié)果。

在這種條件下,程序開(kāi)發(fā)絕對(duì)是頂尖的職業(yè),首先能夠理解目標(biāo)機(jī)的架構(gòu)就需要相當(dāng)?shù)墓Ψ蛄恕F浯芜€要按照機(jī)器的方式思考,寫(xiě)出正確無(wú)誤的指令序列。

這樣的開(kāi)發(fā)過(guò)程無(wú)疑限制了計(jì)算機(jī)行業(yè)的發(fā)展。

同時(shí),即便是擅長(zhǎng)于按照機(jī)器方式思考的工程師,也認(rèn)為機(jī)器指令太難記了。如你所見(jiàn),在打孔卡上準(zhǔn)確無(wú)誤的寫(xiě)上指令真是頭疼的要死。所以工程師們開(kāi)發(fā)了一套助記符,用來(lái)指示對(duì)應(yīng)的機(jī)器碼,這樣以來(lái),程序的編寫(xiě)和 debug 就方便多了。到上世紀(jì)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 匯編指令卡中的某一頁(yè))

關(guān)于助記符的話題,暫且擱置。


回到正題。為了將人類(lèi)語(yǔ)言翻譯成機(jī)器變成機(jī)器能夠理解的語(yǔ)言,還需要進(jìn)行翻譯。就好像你不懂英語(yǔ),英語(yǔ)可以翻譯成漢語(yǔ),這樣你就能明白其中的含義。對(duì)于計(jì)算機(jī)來(lái)說(shuō),這個(gè)過(guò)程是一樣的。不過(guò)計(jì)算機(jī)對(duì)于翻譯有更高的要求。人類(lèi)之間互相翻譯語(yǔ)言,有一些微小的出入并不影響理解,計(jì)算機(jī)為了能夠準(zhǔn)確的得到結(jié)果,要求這個(gè)翻譯的過(guò)程,必須保證“將一種語(yǔ)言翻譯成涵義相同的等價(jià)的另一種語(yǔ)言”。

在早期,程序的規(guī)模還比較小,翻譯的過(guò)程可以人工的進(jìn)行。利用查表的方式,最終是可以得到等價(jià)的機(jī)器碼序列。隨著計(jì)算機(jī)科學(xué)的發(fā)展,程序規(guī)模膨脹的越來(lái)越快,人工翻譯變的沒(méi)有可行性。此時(shí)就有人提出,編寫(xiě)一套軟件來(lái)進(jìn)行這個(gè)翻譯的過(guò)程。

一開(kāi)始人們只用匯編語(yǔ)言進(jìn)行程序開(kāi)發(fā)。所以只需要將匯編語(yǔ)言翻譯為機(jī)器語(yǔ)言就可以了。這是相當(dāng)直截了當(dāng)?shù)倪^(guò)程,因?yàn)閰R編語(yǔ)言的助記符和機(jī)器指令是一一對(duì)應(yīng)的關(guān)系。所以只需要完成一個(gè)能夠自動(dòng)查表并轉(zhuǎn)換的程序即可。很快,這樣的程序就被發(fā)明了出來(lái)。我們稱(chēng)之為“匯編器”。

伴隨著匯編器的發(fā)展,工程師又開(kāi)始想要偷懶。他們認(rèn)為,既然匯編器可以將匯編指令翻譯成等價(jià)的機(jī)器碼,那么在翻譯之前一定也可以做一些預(yù)先處理的工作,將一個(gè)助記符轉(zhuǎn)換為多個(gè)助記符組成的序列。這樣以來(lái),開(kāi)發(fā)人員就可以使用較少的代碼,寫(xiě)出較多的內(nèi)容。同時(shí)將常用的一些程序結(jié)構(gòu)編寫(xiě)成對(duì)應(yīng)的助記符,在需要時(shí)就使用這個(gè)助記符,還可以幫助開(kāi)發(fā)人員減少程序出錯(cuò)的可能。簡(jiǎn)直太好了。于是,人們又在匯編器中引入了宏指令。

所謂“宏(macro)”就是一套預(yù)先定義好的指令序列。每當(dāng)匯編進(jìn)行的時(shí)候,先預(yù)處理一次將宏等價(jià)的展開(kāi),然后再進(jìn)行翻譯。如此,源程序變的更加容易理解了。


宏的引入,催生了程序結(jié)構(gòu)化表達(dá)。在今天的匯編語(yǔ)言當(dāng)中,我們也可以像使用高級(jí)語(yǔ)言的 if else for while 語(yǔ)句一樣,使用等價(jià)的結(jié)構(gòu)語(yǔ)句。只不過(guò),匯編中的結(jié)構(gòu)語(yǔ)句都是宏實(shí)現(xiàn)的。


結(jié)構(gòu)化表達(dá)給了一些計(jì)算機(jī)科學(xué)人員啟發(fā)。能不能更進(jìn)一步,使用完全結(jié)構(gòu)化,脫離某個(gè)對(duì)應(yīng)機(jī)器平臺(tái)的形式化語(yǔ)言來(lái)描述一個(gè)源程序?于是,就有了高級(jí)語(yǔ)言及其編譯器。

開(kāi)發(fā)人員利用高級(jí)語(yǔ)言編寫(xiě)程序,然后利用對(duì)應(yīng)的編譯器生成中間代碼,最后再將中間代碼變成機(jī)器碼。中間代碼可以是等價(jià)的匯編代碼,也可以是其它類(lèi)型的代碼例如 JVM 的字節(jié)碼。最終處理中間代碼的程序可以是一個(gè)對(duì)應(yīng)平臺(tái)的匯編器,也可以是一個(gè)解釋器。在這里姑且隱去這些細(xì)節(jié),將編譯的最終產(chǎn)物都視為一系列可以被執(zhí)行的二進(jìn)制機(jī)器碼。關(guān)于編譯器的更多內(nèi)容,在網(wǎng)上可以找到很多詳細(xì)的資料。在這個(gè)話題下,編譯器不是核心問(wèn)題,我就不再深入討論了。


至此,就得到了一個(gè)可以被執(zhí)行的程序了。這個(gè)文件的內(nèi)容是一系列二進(jìn)制指令和數(shù)據(jù)組成的序列。它能被裝入機(jī)器的內(nèi)存,并且可以被處理器解碼執(zhí)行。


但是,為什么是二進(jìn)制


說(shuō)回來(lái),計(jì)算機(jī)其實(shí)是長(zhǎng)期使用的一個(gè)簡(jiǎn)稱(chēng)。嚴(yán)格的講應(yīng)該叫做“電子計(jì)算機(jī)”。但是計(jì)算機(jī)的形態(tài)并不限于電子式計(jì)算機(jī)。算盤(pán),計(jì)算尺,對(duì)數(shù)計(jì)算表都可以算作廣義上的計(jì)算機(jī),同時(shí)在電子式計(jì)算機(jī)出現(xiàn)之前,它的還有一個(gè)機(jī)械式計(jì)算機(jī)的表親。

<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">

(來(lái)自 Wiki 。 查爾斯·巴貝奇 的分析機(jī)。蒸汽動(dòng)力驅(qū)動(dòng),采用十進(jìn)制,其內(nèi)存能夠存儲(chǔ)1000個(gè)50位的十進(jìn)制數(shù),相當(dāng)于20.7 KB 的 SRAM 或 DDRAM。采用打孔紙帶讀入程序,具有類(lèi)似匯編語(yǔ)言的助記符編程系統(tǒng),是圖靈完備的。很蒸汽朋克,嗯?)


可是我們并不認(rèn)為算盤(pán)以及計(jì)算尺和現(xiàn)代計(jì)算機(jī)是同一個(gè)東西。最核心的區(qū)別在于,現(xiàn)代計(jì)算系統(tǒng)是可編程的。按照這個(gè)定義,上面的分析機(jī)也是現(xiàn)代電子是計(jì)算機(jī)的鼻祖。它身上的核心模型一直繼承至今。

在分析機(jī)上,已經(jīng)實(shí)現(xiàn)了 “ 存儲(chǔ)程序計(jì)算機(jī) ”。

這也就是現(xiàn)代計(jì)算系統(tǒng)的基本概念:

  • 以運(yùn)算單元為中心
  • 采用存儲(chǔ)程序原理
  • 存儲(chǔ)器是按地址訪問(wèn)的,線性的空間
  • 控制流由程序的指令流產(chǎn)生
  • 指令由操作碼和操作數(shù)組成
  • 這一概念所描述的計(jì)算模型具有以下的過(guò)程:將完整的程序裝入存儲(chǔ)器后,運(yùn)算單元依照地址按順序的從存儲(chǔ)器中取出指令和數(shù)據(jù)且執(zhí)行。指令序列就像流水一樣“流”入運(yùn)算單元,當(dāng)指令流盡,就意味著程序結(jié)束了。

    <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">

    對(duì)于計(jì)算機(jī),自然是希望運(yùn)算的速度越快越好。所以機(jī)械式運(yùn)算很快就淘汰了。取而代之的就是電子式計(jì)算機(jī)。


    電子式計(jì)算機(jī)的硬件基礎(chǔ),就是數(shù)字電路。因?yàn)槎M(jìn)制可以很自然的表示開(kāi)和關(guān)的兩種狀態(tài),高和低的兩種狀態(tài),通和斷的兩種狀態(tài),等等。所以很快就取得了主導(dǎo)低位,其它進(jìn)制的數(shù)字電子器件淪為小眾。

    理論上,二進(jìn)制和十進(jìn)制表示的數(shù)的范圍是一樣多的。因?yàn)閷?shí)數(shù)集是一個(gè)連續(xù)同,不同進(jìn)制實(shí)質(zhì)上是對(duì)數(shù)集的不同分割。


    基于二進(jìn)制數(shù)字電子器件制造的電子式計(jì)算機(jī)自然就需要二進(jìn)制的輸入輸出。


    到了這個(gè)層次,我們基本上解釋了高級(jí)語(yǔ)言源程序是如何成為計(jì)算機(jī)可以識(shí)別的二進(jìn)制指令序列的。接下來(lái)的問(wèn)題是,計(jì)算機(jī)如何識(shí)別并執(zhí)行二進(jìn)制指令呢?


    通用處理器被稱(chēng)為“通用”,就是因?yàn)樗幌薅ㄓ谔囟ㄓ猛?。路邊上買(mǎi)一個(gè)計(jì)算器。只能計(jì)算四則運(yùn)算,而計(jì)算機(jī)還能進(jìn)行字處理,可以玩游戲看電影。都有賴(lài)于通用處理器提供的運(yùn)算能力。

    為了實(shí)現(xiàn)通用的目標(biāo),處理器在設(shè)計(jì)之初就不能對(duì)未來(lái)可能進(jìn)行的運(yùn)算進(jìn)行限制。但是未來(lái)的可能性是無(wú)窮的。處理器不可能窮盡所有可能。

    所以,處理器提供了一套它能夠支持的運(yùn)算操作的集合,稱(chēng)為“指令集”。指令集就限定了該處理器能夠進(jìn)行的所有運(yùn)算。而且這些運(yùn)算通常都是有關(guān)于數(shù)字的運(yùn)算。如果我們想解決一個(gè)任意問(wèn)題,那么首先要把這個(gè)問(wèn)題轉(zhuǎn)換為一個(gè)數(shù)字問(wèn)題,再把數(shù)字問(wèn)題的解答過(guò)程,用指令集當(dāng)中的指令求解。

    將其它問(wèn)題轉(zhuǎn)換為數(shù)學(xué)問(wèn)題的一種方法就是編碼。比如我們常見(jiàn)的 ASCII 碼表,就是把英語(yǔ)字符數(shù)字字符以及電報(bào)傳輸過(guò)程中的控制字編碼成對(duì)應(yīng)的數(shù)字。例如字符 a 就等于數(shù)字97。


    處理器的指令集同樣是經(jīng)過(guò)編碼的。所以我們才能用二進(jìn)制數(shù)字流來(lái)表示指令。

    舉個(gè)例子。在一個(gè)典型的 Intel IA-32 處理器上所支持的 x86 指令集。假設(shè)我們想將一個(gè)字節(jié)的數(shù)據(jù)從內(nèi)存移動(dòng)到 al 寄存器,不妨就讓這個(gè)數(shù)據(jù)在內(nèi)存中 0x20 (十六進(jìn)制表示的32)號(hào)字節(jié)的位置好了。那么,我們要寫(xiě)出匯編代碼:


    mov al, 30h

    將這一行代碼送入?yún)R編器,得到對(duì)應(yīng)的機(jī)器碼為:


    0xB0 0x20

    二進(jìn)制的表示為:


    1011 0000 0010 0000

    其中 0xB0 就是我們的指令,也就是執(zhí)行第 176 號(hào)指令。這條指令的意思是:從內(nèi)存中指定的位置搬移數(shù)據(jù)一個(gè)字節(jié)寬度的數(shù)據(jù)到 al 寄存器。地址由緊跟在本指令后的數(shù)給出,在這里就是 0x20。

    指令集中的每一個(gè)指令都可以這樣編碼。每一條指令都定義了一系列的操作。

    如此,只要按照順序的從存儲(chǔ)器讀入指令代號(hào)和數(shù)據(jù),就可以讓程序執(zhí)行下去。

    你又要說(shuō)了,那如果我有循環(huán),有條件判斷怎么辦?

    簡(jiǎn)單。處理器為了能順序的取指并執(zhí)行,需要知道當(dāng)前指令的下一條指令在哪里。為什么不是這一條指令在哪了?因?yàn)檫@一條指令已經(jīng)取回來(lái)了,所以它在哪里就不重要了。為了記錄當(dāng)前指令的下一條指令的位置,處理器內(nèi)部有一個(gè)存放了這個(gè)地址的電子裝置,實(shí)現(xiàn)上它是一系列門(mén)電路組成的鎖存器,叫做 IP 寄存器(也有叫做 PC 的,這里統(tǒng)稱(chēng)為 IP)。IP 的值可以在運(yùn)行時(shí)被修改。那么只要提供了能夠修改 IP 值的指令,就能改變程序的執(zhí)行流程??梢苑祷氐街暗哪硞€(gè)位置,也可以一次前進(jìn)到之后的某個(gè)位置。這個(gè)過(guò)程叫做“跳轉(zhuǎn)”。

    所謂循環(huán)和判斷,本質(zhì)上都是判斷并跳轉(zhuǎn)。

    用一個(gè)程序來(lái)做一個(gè)直觀的說(shuō)明。這個(gè)程序很簡(jiǎn)單。求出一個(gè)數(shù)組中所有數(shù)的和,然后返回這個(gè)值,如果這個(gè)值是0,則返回一個(gè) -1。

    和它等價(jià)的 C 代碼如下,這里我們將結(jié)果返回運(yùn)行時(shí):


    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)生的匯編文件長(zhǎng)什么樣子呢?長(zhǎng)這樣的:


    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

    為了便于解釋,這里隱去了很多細(xì)節(jié),并且使用了很多偽代碼。上面的匯編程序是不經(jīng)修改是無(wú)法通過(guò)編譯的。

    等價(jià)的二進(jìn)制文件又是什么樣子呢?為了方便閱讀,我稍稍整理了一下,并且加上了對(duì)應(yīng)的匯編代碼,它長(zhǎng)這個(gè)樣子:

    <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ù)應(yīng)當(dāng)分為兩列,這里有一個(gè)小錯(cuò)誤。)

    同樣的,還是省去了很多細(xì)節(jié)。綠色的部分就是機(jī)器碼。

    我完全理解使用助記符和高級(jí)語(yǔ)言的重要性。否則誰(shuí)能通過(guò)機(jī)器碼一眼看出一段程序的含義呢?


    當(dāng)程序裝入內(nèi)存以后,IP將被(另外的某個(gè)程序,可能來(lái)自操作系統(tǒng),或者其它軟件)設(shè)置為 1,意思是:下一條要讀取的指令在 1 的位置。然后處理器就開(kāi)始讀入指令。

    為什么處理器會(huì)讀入指令呢?它是收到某個(gè)信號(hào)才會(huì)讀指令嗎?簡(jiǎn)單的講,處理器從上電到掉電的整個(gè)過(guò)程當(dāng)中只做三件事情,那就是:


  • 從內(nèi)存讀取一條指令和指令攜帶的操作數(shù),同時(shí) IP + 1
  • 解碼并執(zhí)行指令
  • 回到 1

  • 所以不需要什么信號(hào)。在上一條指令將 IP 的值修改為 1 之后,處理器就已經(jīng)完成跳轉(zhuǎn),找到程序入口了。

    處理器取指,讀入第一條指令 0xce83。這里要插入一點(diǎn),Intel 的處理器采用的是小端數(shù)據(jù)格式,就是說(shuō)一個(gè)數(shù)的高位放在地址較高的地方,低位放在地址較低的位置。所以要倒過(guò)來(lái)讀,在這里就不詳細(xì)解釋了,略過(guò)。

    處理器將這條啊指令送入解碼器,解碼的結(jié)果告訴處理器,應(yīng)當(dāng)執(zhí)行“將 esp 寄存器中的值減去一個(gè)指定數(shù),該數(shù)由緊隨指令的連續(xù)四個(gè)字節(jié)指定”的操作。然后處理器通過(guò)數(shù)據(jù)總線連續(xù)讀入四個(gè)字節(jié),得出操作數(shù)應(yīng)該是 0x14(十六進(jìn)制的20)。接著就執(zhí)行了這個(gè)操作,IP + 1,繼續(xù)取出下一條指令。

    這個(gè)過(guò)程是很好理解的??傊褪沁@樣的循環(huán)。直到斷電。


    再注意一下行號(hào) 11 和 12 標(biāo)識(shí)的代碼。11 行將執(zhí)行比較 ecx 寄存器中的值和 edx 寄存器內(nèi)的值。根據(jù)不同的結(jié)果,12 行指令將有不同的行為:


    • 兩個(gè)值相同的時(shí)候,12 行指令什么也不做,IP + 1。
    • 兩個(gè)值不同的時(shí)候,12 行指令會(huì)將 Loop 標(biāo)號(hào)的地址寫(xiě)入 IP。 IP = 9。

    程序走著走著就走回去了。這就是比較與跳轉(zhuǎn)。簡(jiǎn)單吧。

    而 10 行的代碼將會(huì)使 ecx 寄存器內(nèi)的值增長(zhǎng),每次經(jīng)過(guò) 10 行都 +1,隨著循環(huán)的進(jìn)行,程序流不斷的跳轉(zhuǎn)到 9 行,然后經(jīng)過(guò) 10 行。在某一次經(jīng)過(guò)后,ecx 等增長(zhǎng)正好令 ecx = edx 成立。這時(shí)候 12 行將什么也不做,IP 指向 13,程序又繼續(xù)進(jìn)行下去了。


    接下來(lái),進(jìn)入處理器的層次來(lái)理解它如何工作的。在這里我們要討論四個(gè)問(wèn)題:

  • 指令是如何表示的?
  • 數(shù)據(jù)是如何取回的?
  • 指令是如何解碼的?
  • 指令是如何執(zhí)行的?

  • 程序運(yùn)行的過(guò)程,上面已經(jīng)提到過(guò)了。程序是完整的裝入內(nèi)存中的。運(yùn)算器能夠直接操作的只有存儲(chǔ)器中的數(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,搞錯(cuò)了,是這個(gè):

    <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">

    圖上黃色的一根粗線其實(shí)以一排并列的導(dǎo)線,在這里是 8 根導(dǎo)線并列在一起。只是看起來(lái)畫(huà)在了一起,其實(shí)是互相分開(kāi)的。

    使用 8051 及其外部擴(kuò)展存儲(chǔ)器接口電路來(lái)說(shuō)明問(wèn)題主要是為了簡(jiǎn)便。在不失準(zhǔn)確性的前提下,我依然隱去一些細(xì)節(jié),方便理解。


    訪問(wèn)存儲(chǔ)器的過(guò)程主要關(guān)注兩個(gè)問(wèn)題:

  • 送出地址
  • 取回?cái)?shù)據(jù)
  • 考慮一般的訪問(wèn)過(guò)程,當(dāng)運(yùn)算器執(zhí)行如下操作時(shí):


    mov al, 0xD0D0

    將會(huì)發(fā)生什么呢?

    首先 mov 指令指定了數(shù)據(jù)搬移的操作,第二個(gè)操作數(shù)是一個(gè)立即數(shù)參數(shù),直接給出了地址?,F(xiàn)在就要到存儲(chǔ)器當(dāng)中去找這個(gè)地方了。

    處理器不能直接操作存儲(chǔ)器的具體單元,但是它可以請(qǐng)存儲(chǔ)器將對(duì)應(yīng)單元中的數(shù)據(jù)準(zhǔn)備好,然后取回來(lái)。你肯定有過(guò)取快遞的經(jīng)歷,菜鳥(niǎo)驛站去過(guò)吧,門(mén)市點(diǎn)不會(huì)讓你親自進(jìn)倉(cāng)庫(kù)去找快遞的,但是你可以告訴快遞小哥你的單號(hào),然后他進(jìn)去幫你找到,最后把包裹交給你。內(nèi)存和這一個(gè)意思。

    處理器首先將地址放到地址總線上,地址總線就是圖上 D0-D7 和 A8-A15 這15 根導(dǎo)線組成的。

    處理器將自己的端口設(shè)置成對(duì)應(yīng)的值,就把地址放到了總線上。0xD0D0對(duì)應(yīng)的端口狀態(tài)應(yīng)該是:

    <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">

    (圖有點(diǎn)小)

    然后,處理器告訴存儲(chǔ)器,我準(zhǔn)備好取數(shù)據(jù)了,地址在總線上,請(qǐng)你準(zhǔn)備數(shù)據(jù)。具體的方式就是拉低 端口的電壓到地電位(一般就是 0V)。存儲(chǔ)器得到這個(gè)消息后,就從總線上取得地址。然后解碼這個(gè)地址,找到對(duì)應(yīng)的數(shù)據(jù),假設(shè)數(shù)據(jù)是0x11吧,然后把數(shù)據(jù)再放回總線 D7-D0上。

    處理器在發(fā)出取指指令后會(huì)等待一段時(shí)間,然后就從總線上取回?cái)?shù)據(jù)。取回的數(shù)據(jù)就當(dāng)做存儲(chǔ)器的回應(yīng)。至于這個(gè)等待的時(shí)間具體多長(zhǎng),這是兩個(gè)設(shè)備間相互約定好的。不需要關(guān)心。

    最后,將總線上取得的數(shù)據(jù) 0x11 放入 al 中,指令完成。


    可能有的讀者就很迷惑了,為什么放到總線上就能傳遞數(shù)據(jù)呢。

    真實(shí)的情況是,總線上傳遞的是電壓的信號(hào)。這也是為什么使用二進(jìn)制方便的原因。總線就是一組導(dǎo)線,在這一組導(dǎo)線上,一一對(duì)應(yīng)的連接了處理器和存儲(chǔ)器的端口。雖然電子在導(dǎo)體內(nèi)的移動(dòng)速度很慢,但是電場(chǎng)的傳播速度卻是光速。所以當(dāng)總線一端的端口建立了電位之后,另一端的電位將立刻改變。此時(shí)信號(hào)就已經(jīng)從一個(gè)器件傳遞到了另一個(gè)。器件之間信號(hào)的傳遞,依賴(lài)的就是端口上電壓的改變。器件對(duì)總線數(shù)據(jù)的讀取,就是讀取端口上電壓的高低。而二進(jìn)制可以使這個(gè)問(wèn)題變的很簡(jiǎn)單。只要端口上能夠反應(yīng)電壓的高和低區(qū)別就足以傳遞信號(hào)了,一般的,高電位的區(qū)間在 3.3V - 5.0V 之間,而低電位在 0V - 2.2V 之間。考慮到總線都是板級(jí)的傳輸,距離很近,總線上電場(chǎng)傳播所需要的時(shí)間可以忽略掉。那么一組總線傳播數(shù)據(jù)的速度就取決于其兩端端口上電位改變的速度。這可比讀卡器讀卡高了不知道哪去了。也比磁盤(pán)尋道和讀取快的多。


    在數(shù)字電路中,我們一般用 0 表示低電平,用 1 表示高電平。

    上面提到過(guò),mov 指令的編碼是 0xB0。這個(gè)編碼是什么意思呢?將其寫(xiě)作二進(jìn)制會(huì)發(fā)現(xiàn)


    0xB0 = 1011 0000

    剛剛我們已經(jīng)介紹過(guò)了。0和是表示的就是電壓的高低?,F(xiàn)在一切都清楚了。數(shù)據(jù)的編碼其實(shí)就說(shuō)說(shuō)的端口上電壓的高低狀態(tài)。如果處理器的輸入端口在讀入指令時(shí)讀入的端口情況是從 D7到D0為 高低高高 低低低低。那么就讀入了 1011 0000。


    那么我們已經(jīng)知道如何取數(shù)據(jù)了。取指令也是一個(gè)方法。只不過(guò)取指令的過(guò)程是自動(dòng)的,指令的地址總是 IP 的值。取回的指令總是送入指令解碼器當(dāng)中。

    根據(jù)讀入數(shù)據(jù)時(shí)處理器所處的不同階段,將會(huì)給讀入的數(shù)據(jù)一個(gè)不同的解釋。讀指令階段就會(huì)把數(shù)據(jù)送入解碼器。讀數(shù)據(jù)階段就會(huì)把數(shù)據(jù)送入另外的地方。


    接下來(lái),就需要進(jìn)行指令解碼。指令解碼本身也是一個(gè)非常大的話題,其實(shí)單獨(dú)拿出來(lái)也可以寫(xiě)出和本篇一樣長(zhǎng)的文章了。在這里只能概略的介紹一下。


    處理器本身要完成某些特定的運(yùn)算,在硬件上是需要某些特定結(jié)構(gòu)的電路的支持的。比如你要完成一次加法,就需要一個(gè)帶有加法器的電路。完成一次位移,就要有帶移位寄存器的電路。簡(jiǎn)單的說(shuō),任何一條指令,都需要一組特定的電路來(lái)提供支持。但是人們通過(guò)長(zhǎng)期的對(duì)數(shù)字電路的研究發(fā)現(xiàn)。幾乎所有的運(yùn)算,都可以通過(guò)有限的幾種器件的不同組合來(lái)完成。這樣的話,我們的通用運(yùn)算器當(dāng)中,可以包含一些要素器件,然后通過(guò)運(yùn)行時(shí)改變它們的連接來(lái)實(shí)現(xiàn)不同的功能。這就是我們思考指令編碼的方向。

    其實(shí)在電子式計(jì)算機(jī)剛剛誕生的時(shí)候,就已經(jīng)實(shí)現(xiàn)基本運(yùn)算器的復(fù)用了。運(yùn)算中心中包含了一組基礎(chǔ)的運(yùn)算器,它們的輸入輸出端口上同時(shí)連接了很多組不同的電路,每執(zhí)行一條指令的時(shí)候,都選擇其中特定的一組電路,使其生效,而讓剩下的電路失效。這樣在指令執(zhí)行的過(guò)程中,這一組執(zhí)行電路就可以獨(dú)占整個(gè)運(yùn)算器。當(dāng)運(yùn)算結(jié)束拿到結(jié)果后,電路再將運(yùn)算器釋放掉。就可以準(zhǔn)備下一次的運(yùn)算了。

    在早期,還沒(méi)有指令編碼技術(shù)。要使用不同的指令,必須改變電路間的硬連接。也就是要把一組插頭從這里拔下來(lái)插到另外的地方去。世界上第一臺(tái)通用電子計(jì)算機(jī) ENIAC 的操作方式就是如此。編程的方式是女工進(jìn)機(jī)房去接插頭。

    <img src="https://pic1.zhimg.com/50/v2-42cc3730a33fa5303e52eaa50dfd91ce_hd.jpg" data-rawwidth="382" data-rawheight="214" class="content_image" width="382">

    (假設(shè)我們有三條可編程指令流水線,那么如果我們想依次執(zhí)行數(shù)據(jù)轉(zhuǎn)移,異或,求和的操作,就需要連接 #1 的 move, #2 的 xor 和 #3 的 add)

    而后出現(xiàn)了指令編碼。送入的指令被解碼器解碼后,自動(dòng)啟動(dòng)一組對(duì)應(yīng)的電路。

    <img src="https://pic3.zhimg.com/50/v2-af944f1f1713ade91eb16c40e468a422_hd.jpg" data-rawwidth="403" data-rawheight="180" class="content_image" width="403">

    這樣說(shuō)也許很難讓人明白“自動(dòng)”的含義。所以我在這里實(shí)現(xiàn)一個(gè)簡(jiǎn)單的編碼指令處理器。在這里我們只實(shí)現(xiàn) 3 條指令:

    • 指令0:將輸入端的數(shù)據(jù)存入寄存器 a
    • 指令1:將輸入端的數(shù)據(jù)存入寄存器 b
    • 指令2:取寄存器 a 寄存器 b 中的值求和,將結(jié)果放入寄存器 a

    在這里我們只研究解碼,不管其它的因素,這樣就簡(jiǎn)化了問(wèn)題。不多說(shuō),直接上圖:

    <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 的部分就是指令解器。首先將指令讀入一個(gè)寄存器,然后解碼。實(shí)際的運(yùn)算器也是這樣的流程。圖中藍(lán)色的就是數(shù)據(jù)總線,寄存器內(nèi)的值分別是兩個(gè)寄存器 Qx 端口上的值。

    讓我們啟動(dòng)他它,來(lái)算一下 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 (右下兩個(gè))都被初始化為 0xFF
    • 輸入端口(左下角)上的值為 0x00
    • 指令寄存器(上方)當(dāng)中當(dāng)前的指令為 0x0F (15號(hào)),這是一條未定義的指令,所以沒(méi)有任何效果。

    首先我們要執(zhí)行


    mov a, 0x10

    當(dāng)指令讀入后,在指令輸入端將會(huì)是 0x00 的狀態(tài),譯碼輸出端口上輸出全 0,指示出目前要執(zhí)行0 號(hào)指令。同時(shí)選中了 0 號(hào)指令的執(zhí)行電路。

    數(shù)據(jù)端的輸入為 0001 0000。mov 命令的狀態(tài)下,輸入選擇器選擇輸入端口的數(shù)據(jù)放到總線上。同時(shí),寄存器 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)被存入寄存器了)

    第二條指令,我們將啟動(dò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 號(hào)輸出被選中,此時(shí)激活了 1 號(hào)指令的電路。輸入端的 0x0F 被存入了寄存器 b。而寄存器 a 中的值保持不變。

    第三條指令,2 號(hào)指令,求值。


    add

    沒(méi)有給出操作時(shí)是因?yàn)椴僮魇且呀?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 號(hào)輸出選中了。全加器完成了運(yùn)算(左側(cè)是第四位,右側(cè)是高四位),結(jié)果放上了總線,被鎖存到了加法器的輸出緩沖器當(dāng)中。

    同一時(shí)間,雙輸入選擇器也被激活。它截?cái)嗔溯斎攵丝诘倪B接而選擇加法器輸出緩沖值作為輸入,將其放上了總線。

    寄存器 a 從總線取得數(shù)據(jù),存入。完成了指令。


    看看,結(jié)果是 0x1F,恰好就是我們預(yù)期的 31。


    實(shí)際中的處理器的處理過(guò)程比這個(gè)復(fù)雜得多。這里為了方便理解,做了很多簡(jiǎn)化。但是概念都是相同的。處理器自動(dòng)的從內(nèi)存中讀入指令和數(shù)據(jù),然后解碼,啟動(dòng)對(duì)應(yīng)的電路,最后拿到結(jié)果。如此往復(fù)。


    到此為止,已經(jīng)幾乎完全說(shuō)明了計(jì)算機(jī)的運(yùn)算原理,以及高級(jí)語(yǔ)言和機(jī)器語(yǔ)言的關(guān)系。但是我們依然可以更進(jìn)一部,探究一下數(shù)字電路的構(gòu)成。編碼器是怎么運(yùn)行的?寄存器是怎么鎖存數(shù)據(jù)的?


    上面一直在說(shuō)解碼器,那么解碼器到底是什么?

    處理器內(nèi)部的指令解碼器可能非常復(fù)雜,也許是一個(gè)器件,也有可能是一組器件,或者是可編程的硬件電路(對(duì)的,硬件電路也是可以編程的,例如 FPGA)。

    而這里,我在上面的例子中使用的解碼器:74LS42 4 Lines to 10 Lines BCD to Decimal Decoder (4線10線BCD譯碼器)的內(nèi)部結(jié)構(gòu)是這樣的:

    <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 輸入端(左邊)輸入后首先連接了非門(mén)(NOT),然后進(jìn)入一個(gè)選擇矩陣,最后通過(guò)三入與非門(mén)(NAND)輸出。


    與門(mén)(AND)、或門(mén)(OR)、非門(mén)(NOT)是數(shù)字電路中,最基礎(chǔ)的三種邏輯門(mén)電路。它們的組后構(gòu)建了大量的實(shí)用器件。

    關(guān)于三種邏輯門(mén),它們的特性可以使用真值表來(lái)表示:

    <img src="https://pic2.zhimg.com/50/v2-9620ecea93ad929c25f8e95adb4a6138_hd.jpg" data-rawwidth="291" data-rawheight="231" class="content_image" width="291">

    (1 代表真,0 代表假)

    與門(mén):所有輸入全為真,輸出為真;

    或門(mén):任意一個(gè)輸入為真,則輸出真;

    非門(mén):輸出總是輸入的反。


    利用這三個(gè)門(mén)就可以做很多有趣的事情了。

    你需要理解歐姆定律,理解電阻、電容、二極管、三極管/場(chǎng)效應(yīng)管,理解與或非門(mén)電路,組合邏輯電路,時(shí)序邏輯電路,理解CPU和指令集,機(jī)器代碼。

    到了這一步,你就知道機(jī)器代碼的一堆01010010011011是怎么通過(guò)控制單元,邏輯運(yùn)算單元,寄存器,以及底下的加法器,編碼器,譯碼器,多路選擇器通過(guò)高電平低電平的脈沖跑起來(lái)的。

    你還要理解匯編器,理解編譯器和連接器,理解其中的詞法分析,語(yǔ)法分析,代碼生成,你需要理解操作系統(tǒng)。

    到了這一步你就能明白電腦怎么講你用高級(jí)語(yǔ)言編寫(xiě)的程序轉(zhuǎn)換成機(jī)器代碼的一堆01010010011011并提交給計(jì)算機(jī)執(zhí)行的了。

    涵蓋這些知識(shí)的就是電路分析,模擬電路,邏輯電路,單片機(jī),計(jì)算機(jī)組成原理,離散數(shù)學(xué),數(shù)據(jù)結(jié)構(gòu),編譯原理,操作系統(tǒng)這些計(jì)算機(jī)專(zhuān)業(yè)的課程。

    總結(jié)

    以上是生活随笔為你收集整理的电脑怎样执行编程语言的?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。