算機(jī)是如何工作的:人人都能懂的計(jì)算機(jī)軟硬件工作原理》第 8 章 機(jī)器碼與匯編語(yǔ)言 閱讀筆記 8)
《計(jì)算機(jī)是如何工作的人人都能懂的計(jì)算機(jī)軟硬件工作原理》第 8 章 機(jī)器碼與匯編語(yǔ)言 閱讀筆記 8我們已經(jīng)介紹了計(jì)算機(jī)的物理部分內(nèi)存、CPU和 I/O 設(shè)備。理解計(jì)算機(jī)的硬件部分很重要但是硬件只起一半作用。計(jì)算機(jī)的魔力在于軟件。軟件把計(jì)算機(jī)從固定用途的設(shè)備變成了高度靈活的設(shè)備它能輕松地實(shí)現(xiàn)新的功能本章將介紹底層軟件——機(jī)器碼和匯編語(yǔ)言。我發(fā)現(xiàn)交互方法是理解這些主題的最好方法所以本章的大部分內(nèi)容都體現(xiàn)在設(shè)計(jì)中。8.1 軟件術(shù)語(yǔ)要討論軟件需要首先介紹一些術(shù)語(yǔ)。告訴計(jì)算機(jī)做什么的指令被稱為 “軟件”?這與硬件形成了對(duì)比硬件是計(jì)算機(jī)的物理元素。完成任務(wù)的一組有序的軟件指令被稱為 “程序”?編程就是編寫這種程序的行為。術(shù)語(yǔ) “應(yīng)用程序” 有時(shí)被用作程序的同義詞盡管應(yīng)用程序往往指的是直接與人進(jìn)行交互的程序而不是與軟件或硬件交互的程序。應(yīng)用程序也可能由多個(gè)程序構(gòu)成。app 這個(gè)詞大約在 2008 年開(kāi)始流行其中含有的其他意思將在第 13 章中介紹。另一個(gè)指代一組軟件指令的名稱是 “計(jì)算機(jī)代碼” 或 “代碼”?。CPU 執(zhí)行 “機(jī)器碼”?而軟件開(kāi)發(fā)人員一般用高級(jí)編程語(yǔ)言編寫源代碼。術(shù)語(yǔ) “源代碼” 是指由開(kāi)發(fā)人員最初編寫的程序文本。這種代碼一般不會(huì)用 CPU 能直接理解的形式來(lái)編寫因此在其能被計(jì)算機(jī)運(yùn)行之前還需要進(jìn)行其他處理。我將在第 9 章詳細(xì)介紹源代碼和高級(jí)編程語(yǔ)言但是現(xiàn)在我們先來(lái)看看軟件的基礎(chǔ)機(jī)器碼。機(jī)器碼是二進(jìn)制機(jī)器語(yǔ)言指令形式的軟件。如第 7 章所述CPU 的架構(gòu)決定了 CPU 能理解的指令。就像人類語(yǔ)言是用詞匯表構(gòu)成的一樣機(jī)器語(yǔ)言是用 CPU 系列已知的指令列表構(gòu)成的。詞匯表中的詞語(yǔ)排列成句子來(lái)表達(dá)含義CPU 指令排列成程序后也具有相同功能。不管程序最初是如何編寫的編寫程序的方法有很多種?它最終都需要在 CPU 上作為一系列機(jī)器語(yǔ)言指令來(lái)執(zhí)行。正如你所期望的CPU 指令可以歸結(jié)為一系列 0 和 1就像計(jì)算機(jī)處理的所有其他事情一樣。這里值得重復(fù)一下不論程序最初是如何編寫的也不論用的是哪種編程語(yǔ)言更不論使用的是什么技術(shù)最后程序都會(huì)變成一系列的 0 和 1表示 CPU 可以執(zhí)行的指令。幾年前我有一項(xiàng)工作是關(guān)于診斷軟件故障的。通常我要分析發(fā)生在其他公司編寫的軟件中的問(wèn)題。我沒(méi)有這類軟件的源代碼也沒(méi)有很多關(guān)于它應(yīng)如何工作的相關(guān)信息但是我的工作就是確定軟件故障的原因我有個(gè)同事對(duì)此很淡然他常常提醒我 “這就是個(gè)代碼”?。換句話說(shuō)故障軟件就是一組被 CPU 解釋為指令的 1 和 0。如果 CPU 可以理解代碼那么你也可以。8.2 機(jī)器指令示例我認(rèn)為切入機(jī)器碼主題的最簡(jiǎn)單方法就是看示例。我們來(lái)看看 ARM 系列處理器能理解的特定機(jī)器指令。你可能還記得大多數(shù)智能手機(jī)上都能發(fā)現(xiàn) ARM 處理器所以你的手機(jī)可能也可以理解這條指令。我們的示例指令告訴處理器把數(shù)字 4 移動(dòng)到寄存器 r7 中r7 是 ARM 處理器通用寄存器中的一個(gè)。回憶一下我們之前討論的計(jì)算機(jī)硬件寄存器是 CPU 中的一種小容量存儲(chǔ)位置。執(zhí)行這個(gè)操作的 ARM 指令的二進(jìn)制形式如下我們來(lái)看看 ARM CPU 是如何理解這條指令的如圖 8-1 所示。注意我們跳過(guò)了無(wú)關(guān)的一些位。圖 8-1 ARM 指令條件部分指示了在什么條件下該指令應(yīng)該執(zhí)行。1110 意味著這條指令是無(wú)條件執(zhí)行的所以 CPU 應(yīng)該總是執(zhí)行它。雖然本例中不是這樣的但是有些指令只需要在特定條件下執(zhí)行。接下來(lái)的兩位本例中為 00與我們的討論無(wú)關(guān)所以跳過(guò)它們。立即數(shù)位告訴我們將要訪問(wèn)的是寄存器中的值還是指令本身指定的值稱為立即數(shù)?。本例中立即數(shù)位為 1要訪問(wèn)的是指令中指定的數(shù)。如果立即數(shù)位是 0那么被訪問(wèn)寄存器將由指令中的其他位來(lái)指定。操作碼表示 CPU 要執(zhí)行的操作。本例中操作碼是 mov意思是 CPU 要移動(dòng)數(shù)據(jù)。目標(biāo)寄存器 0111 告訴我們把值移動(dòng)到寄存器 r70111 是十進(jìn)制的 7中。最后立即數(shù)值 00000100 是十進(jìn)制的 4這是我們想要移動(dòng)到 r7 中的數(shù)。簡(jiǎn)單概括一下這個(gè)二進(jìn)制序列告訴 ARM CPU 把數(shù)字 4 移動(dòng)到 r7 寄存器中。CPU 總是用二進(jìn)制處理各種事情但是絕大多數(shù)人很難處理這些 0 和 1?,F(xiàn)在我們用十六進(jìn)制表示同樣的指令以便讓它更容易讀懂現(xiàn)在是不是好點(diǎn)啦好吧也許不是。這種格式比二進(jìn)制格式更加緊湊也更容易區(qū)分但它的含義仍然不明顯。對(duì)我們而言幸運(yùn)的是還有另一種方法來(lái)表示這條指令匯編語(yǔ)言。匯編語(yǔ)言是一種編程語(yǔ)言它的每一條語(yǔ)句都直接表示一條機(jī)器語(yǔ)言指令。每種類型的機(jī)器語(yǔ)言都有對(duì)應(yīng)的匯編語(yǔ)言—如 x86 匯編語(yǔ)言、ARM 匯編語(yǔ)言等。匯編語(yǔ)言語(yǔ)句由一個(gè)表示 CPU 操作碼的助記符和其他必需的操作碼比如寄存器或數(shù)值組成。助記符是人類可讀的操作碼形式它允許匯編語(yǔ)言程序員在代碼中用 mov 而不是 1101。之前討論過(guò)的相同的 ARM 指令也可以用如下匯編語(yǔ)言語(yǔ)句表示與相應(yīng)的二進(jìn)制和十六進(jìn)制表示相比這個(gè)語(yǔ)句當(dāng)然是表示 “把 4 移動(dòng)到 r7 寄存器中的” 更好的方式最起碼對(duì)人類來(lái)說(shuō)更容易閱讀。也就是說(shuō)匯編語(yǔ)言語(yǔ)句只是為了人類方便。CPU 從不會(huì)執(zhí)行文本格式的指令它只處理二進(jìn)制格式的指令。如果程序員用匯編語(yǔ)言編寫程序那么在計(jì)算機(jī)執(zhí)行該程序之前匯編指令仍然必須轉(zhuǎn)換成機(jī)器碼。這要用 “匯編器” 來(lái)完成匯編器是一種把匯編語(yǔ)言語(yǔ)句轉(zhuǎn)換成機(jī)器碼的程序。匯編語(yǔ)言文本文件被送入?yún)R編器匯編器輸出的就是包含機(jī)器碼的二進(jìn)制目標(biāo)文件如圖 8-2 所示。圖 8-2 匯編器把匯編語(yǔ)言轉(zhuǎn)換成機(jī)器碼8.3 用機(jī)器碼計(jì)算階乘現(xiàn)在我們已經(jīng)查看了單條 ARM 指令接下來(lái)我們看看如何把多條指令組合在一起來(lái)執(zhí)行有用的任務(wù)。我們來(lái)看一個(gè)計(jì)算整數(shù)階乘的 ARM 機(jī)器碼。你可能還記得在數(shù)學(xué)課上學(xué)過(guò)n 的階乘寫作 n!是小于或等于 n 的正整數(shù)的乘積。例如4 的階乘為4!4×3×2×124現(xiàn)在我們已經(jīng)有了階乘的定義讓我們來(lái)看看用 ARM 機(jī)器碼如何實(shí)現(xiàn)階乘算法。為了簡(jiǎn)單起見(jiàn)我們不查看完整的程序代碼只看執(zhí)行階乘算法的部分。我們假設(shè) n 的初始值保存在寄存器 r0 中并且當(dāng)代碼完成時(shí)計(jì)算結(jié)果也保存在 r0 中。如同計(jì)算機(jī)處理的任何其他數(shù)據(jù)一樣在 CPU 訪問(wèn)機(jī)器碼之前必須把它加載到內(nèi)存中。下面的是機(jī)器碼的 32 位4 字節(jié)十六進(jìn)制表示以及每個(gè)值的內(nèi)存地址。當(dāng)我們的代碼加載到內(nèi)存時(shí)階乘邏輯從地址 0001007c 開(kāi)始。來(lái)看看從這個(gè)地址開(kāi)始的內(nèi)存內(nèi)容。請(qǐng)注意0001007c 不是一個(gè)神奇的地址它只是恰好是本例中代碼加載的地方。還要注意內(nèi)存地址值增幅為 4因?yàn)槊總€(gè)數(shù)據(jù)值都需要 4 個(gè)字節(jié)來(lái)存放。每條 ARM 指令長(zhǎng)度都為 4 字節(jié)所以這里的數(shù)據(jù)表示 5 條 ARM 指令。把這些指令看作十六進(jìn)制的值并不能讓我們深入理解它們的含義所以讓我們對(duì)這些指令進(jìn)行譯碼以便理解這個(gè)程序。在下面的清單中我已經(jīng)把十六進(jìn)制的數(shù)值轉(zhuǎn)換成相應(yīng)的匯編語(yǔ)言助記符。如果你好奇的話把機(jī)器語(yǔ)言手動(dòng)翻譯成匯編語(yǔ)言不是你需要了解的我們有完成這項(xiàng)功能的軟件稱為 “反匯編器”??,F(xiàn)在這本書(shū)就充當(dāng)了你的反匯編器。下面列出了每條指令以及與之配對(duì)的匯編語(yǔ)句CPU 按順序執(zhí)行這些指令直到遇到分支指令比如 ble 或 bne?分支指令會(huì)讓它跳轉(zhuǎn)到程序的另一個(gè)部分。地址 00010090 標(biāo)記著階乘邏輯的結(jié)束。一旦到達(dá)這個(gè)地址階乘運(yùn)算結(jié)果就已經(jīng)保存到 r0 了。此時(shí)CPU 執(zhí)行位于地址 00010090 處的指令。你可能好奇這些指令是如何表示階乘的計(jì)算的。對(duì)大多數(shù)人來(lái)說(shuō)粗略地看一下這些指令不足以理解其所含的意思。采取循序漸進(jìn)的方式在每條指令執(zhí)行時(shí)跟蹤寄存器的值能幫助你理解這個(gè)程序。我將給你提供一些必需的背景資料然后你可以嘗試評(píng)估這個(gè)程序是如何工作的。為了理解這個(gè)程序你首先需要對(duì)每條指令的說(shuō)明。在表 8-1 中我已經(jīng)給出了這個(gè)程序中每條指令的解釋。在這個(gè)表中我使用了寄存器的占位符名稱比如 Rd 和 Rn。當(dāng)你查看匯編代碼時(shí)你將看到實(shí)際使用的寄存器名稱比如 r0 和 r3。代碼中列出的操作數(shù)順序?qū)?yīng)于表 8-1 中的操作數(shù)順序。例如subs r3r0#1 表示從存儲(chǔ)在 r0 中的值中減去 1并把結(jié)果存放到 r3 中。表 8-1 一些 ARM 指令的解釋分支和狀態(tài)寄存器分支指令實(shí)際并不查看前一條指令的數(shù)值結(jié)果。和大多數(shù) CPU 一樣ARM 處理器也有一個(gè)寄存器專門跟蹤狀態(tài)。這個(gè)狀態(tài)寄存器有 32 位每個(gè)位對(duì)應(yīng)一個(gè)特定的狀態(tài)標(biāo)志。例如位 31 是 N 標(biāo)志當(dāng)指令結(jié)果為負(fù)數(shù)時(shí)該標(biāo)志置 1。只有某些指令會(huì)影響這些標(biāo)志狀態(tài)。例如subs 指令會(huì)改變標(biāo)志的狀態(tài)。如果某個(gè)減法運(yùn)算結(jié)果是負(fù)的那么 N 標(biāo)志會(huì)置 1否則它會(huì)清零。其他指令包括分支指令則查看狀態(tài)標(biāo)志位來(lái)決定該做什么。這看上去是一種迂回的方式但實(shí)際上它簡(jiǎn)化了像 bne 這樣的指令——處理器可以根據(jù)單個(gè)位的值進(jìn)行分支或不進(jìn)行分支?。8.4 總結(jié)本章討論了機(jī)器碼即特定于 CPU 的一系列指令它們?cè)趦?nèi)存中以字節(jié)表示。你學(xué)習(xí)了如何對(duì)示例 ARM 處理器指令進(jìn)行編碼以及如何用匯編語(yǔ)言表示該指令。你了解到匯編語(yǔ)言是一種源代碼尤其是人類可讀形式的機(jī)器碼。我們還了解了怎樣組合多條匯編語(yǔ)言語(yǔ)句來(lái)執(zhí)行有用的操作。第 9 章將討論高級(jí)編程語(yǔ)言。這種語(yǔ)言提供了CPU 指令集的抽象允許開(kāi)發(fā)人員編寫更容易理解且可以在不同計(jì)算機(jī)硬件平臺(tái)之間移植的源代碼。