沒錯!cxuan 對匯編下手了
匯編代碼是計算機(jī)的一種低級表示,它是一種低級語言,可以從字面角度去理解它,包括處理數(shù)據(jù)、管理內(nèi)存、讀寫存儲設(shè)備上的數(shù)據(jù),以及利用網(wǎng)絡(luò)通信等。編譯器生成機(jī)器碼經(jīng)過了一系列的轉(zhuǎn)換,這些轉(zhuǎn)換遵循編程語言、目標(biāo)機(jī)器的指令集 和操作系統(tǒng)。
指令集
指令集就是指揮計算機(jī)工作的指令,因為程序就是按照一定執(zhí)行順序排列的指令。因為計算機(jī)的執(zhí)行控制權(quán)由 CPU 操作,所以指令集就是 CPU 中用來計算和控制計算機(jī)的一系列指令的集合。每個 CPU 在產(chǎn)出時都規(guī)定了與硬件電路相互配合工作的指令集。
指令集有不少分類,但是一般分為兩種,一種是精簡指令集,一種是復(fù)雜指令集。具體描述如下
精簡指令集
精簡指令的英文是 reduced instruction set computer, RISC,原意是精簡指令集計算,簡稱為精簡指令集,是 CPU 的一種 設(shè)計模式,可以把 CPU 想象成一家流水線工廠,對指令數(shù)目和尋址方式都做了精簡,使其實現(xiàn)更容易,指令并行執(zhí)行程度更好,編譯器的效率更高。
常見的精簡指令集處理器包括 ARM、AVR、MIPS、PARISC、RISC-V 和 SPARC。
所以你就能理解

這本書是講啥的了。
它主要是基于 MIPS 體系結(jié)構(gòu)把馮諾依曼體系的五大組件進(jìn)行了逐一的硬件實現(xiàn) + 軟件設(shè)計介紹,更為重要的是引入了諸多并行計算的內(nèi)容,這是大部分教材中忽略或者內(nèi)容較少的,會根據(jù)這個思路把并行相關(guān)的內(nèi)容,結(jié)合 OpenMP, CUDA 和 Hadoop/Spark 整體融入到新書中,畢竟這是未來發(fā)展的趨勢
還有這本書

這本書又是講啥的。
這本書是講 RISC-V 指令集的,因為指令集的不同也區(qū)分了三個版本,三個版本???嗯,還有下面這個

這本書是講 ARM 指令集的。
所以一般在看 CASPP 的時候并發(fā)的看看這本書是非常不錯的選擇。
精簡指令集一般具有如下特征
統(tǒng)一的指令編碼 通用的寄存器,一般會區(qū)分整數(shù)和浮點數(shù) 簡單的尋址模式,復(fù)雜尋址模式被簡單指令序列來取代 支持很少偏門的類型,例如 RISC 支持字節(jié)字符串類型。
復(fù)雜指令集
復(fù)雜指令集的英文是 Complex Instruction Set Computing, CISC,是一種微處理器指令集架構(gòu),也被譯為復(fù)雜指令集。
復(fù)雜指令集包括 System/360、VAX、x86 等。
復(fù)雜指令集可以說是在精簡指令集之上作出的改變。
復(fù)雜指令集的特點是指令數(shù)目多而復(fù)雜,每條指令字長并不相等,計算機(jī)必須加以判讀,并為此付出了性能的代價。
一般來說,提升 CPU 性能的方法有如下這幾種
增加寄存器的大小 增進(jìn)內(nèi)部的并行性 增加高速緩存的大小 增加核心時脈的速度 加入其他功能,例如 IO 和計時器 加入向量處理器 硬件多線程技術(shù)
比較抽象,我們后面會組織成文章具體介紹一下。
C 編譯器會接收其他操作并把其轉(zhuǎn)換為匯編語言輸出,匯編語言是機(jī)器級別的代碼表示。我們之前介紹過,C 語言程序的執(zhí)行過程分為下面這幾步

下面我們更多的討論都是基于匯編代碼來討論。
我們?nèi)粘K佑|的高級語言,都是經(jīng)過了層層封裝的結(jié)果,所以我們平常是接觸不到匯編語言的,更不會用匯編語言來進(jìn)行編程,這就和你不知道操作系統(tǒng)的存在一樣,但其實你每個操作,甚至你雙擊一個圖標(biāo)都和操作系統(tǒng)有關(guān)系。
高級語言的抽象級別很高,但是經(jīng)過了層層抽象之后,高級語言的執(zhí)行效率肯定沒有匯編語言高,也沒有匯編語言可靠。
但是高級語言有更大的優(yōu)點是其編譯后能夠在不同的機(jī)器上運行,匯編語言針對不同的指令集有不同的表示。并且高級語言學(xué)習(xí)來更加通俗易懂,降低計算機(jī)門檻,讓內(nèi)卷更加嚴(yán)重(當(dāng)然這是開個玩笑,冒犯到請別當(dāng)真)。
話不多說,了解底層必須了解匯編語言。否則一個 synchronized 底層實現(xiàn)就能夠讓你頭疼不已。而且,天天飄著也不好,遲早要落地。
了解匯編代碼也有助于我們優(yōu)化程序代碼,分析代碼中隱含的低效率,并且這種優(yōu)化方法一旦優(yōu)化成功,將是量級的提高,而不是改改 if...else ,使用一個新特性所能比的。
機(jī)器級代碼
計算機(jī)系統(tǒng)使用了多種不同形式的抽象,可以通過一個簡單的抽象模型來隱藏實現(xiàn)細(xì)節(jié)。對于機(jī)器級別的程序來說,有兩點非常重要。
首先第一點,定義機(jī)器級別程序的格式和行為被稱為 指令集體系結(jié)構(gòu)或指令集架構(gòu)(instruction set architecture), ISA。ISA 定義了進(jìn)程狀態(tài)、指令的格式和每一個指令對狀態(tài)的影響。大部分的指令集架構(gòu)包括 ISA 用來描述進(jìn)程的行為就好像是順序執(zhí)行的,一條指令執(zhí)行結(jié)束后,另外一條指令再開始。處理器硬件的描述要更復(fù)雜,它可以同時并行執(zhí)行許多指令,但是它采用了安全措施來確保整體行為與 ISA 規(guī)定的順序一致。
第二點,機(jī)器級別對內(nèi)存地址的描述就是 虛擬地址(virtual address),它提供了一個內(nèi)存模型來表示一個巨大的字節(jié)數(shù)組。
編譯器在整個編譯的過程中起到了至關(guān)重要的作用,把 C 語言轉(zhuǎn)換為處理器執(zhí)行的基本指令。匯編代碼非常接近于機(jī)器代碼,只不過與二進(jìn)制機(jī)器代碼相比,匯編代碼的可讀性更強(qiáng),所以理解匯編是理解機(jī)器工作的第一步。
一些進(jìn)程狀態(tài)對機(jī)器可見,但是 C 語言程序員卻看不到這些,包括
程序計數(shù)器(Program counter),它存儲下一條指令的地址,在 x86-64 架構(gòu)中用%rip來表示。
程序執(zhí)行時,PC 的初始值為程序第一條指令的地址,在順序執(zhí)行程序時, CPU 首先按程序計數(shù)器所指出的指令地址從內(nèi)存中取出一條指令,然后分析和執(zhí)行該指令,同時將 PC 的值加 1 并指向下一條要執(zhí)行的指令。
比如下面一個例子。

這是一段數(shù)值進(jìn)行相加的操作,程序啟動,在經(jīng)過編譯解析后會由操作系統(tǒng)把硬盤中的程序復(fù)制到內(nèi)存中,示例中的程序是將 123 和 456 執(zhí)行相加操作,并將結(jié)果輸出到顯示器上。由于使用機(jī)器語言難以描述,所以這是經(jīng)過翻譯后的結(jié)果,實際上每個指令和數(shù)據(jù)都可能分布在不同的地址上,但為了方便說明,把組成一條指令的內(nèi)存和數(shù)據(jù)放在了一個內(nèi)存地址上。
整數(shù) 寄存器文件(register file)包含 16 個命名的位置,用來存儲 64 位的值。這些寄存器可以存儲地址和整型數(shù)據(jù)。有些寄存器用于跟蹤程序狀態(tài),而另一些寄存器用于保存臨時數(shù)據(jù),例如過程的參數(shù)和局部變量,以及函數(shù)要返回的值。這個文件是和磁盤文件無關(guān)的,它只是 CPU 內(nèi)部的一塊高速存儲單元。有專用的寄存器,也有通用的寄存器用來存儲操作數(shù)。條件碼寄存器用來保存有關(guān)最近執(zhí)行的算術(shù)或邏輯指令的狀態(tài)信息。這些用于實現(xiàn)控件或數(shù)據(jù)流中的條件更改,例如實現(xiàn) if 和 while 語句所需的條件更改。我們都學(xué)過高級語言,高級語言中的條件控制流程主要分為三種:順序執(zhí)行、條件分支、循環(huán)判斷三種,順序執(zhí)行是按照地址的內(nèi)容順序的執(zhí)行指令。條件分支是根據(jù)條件執(zhí)行任意地址的指令。循環(huán)是重復(fù)執(zhí)行同一地址的指令。順序執(zhí)行的情況比較簡單,每執(zhí)行一條指令程序計數(shù)器的值就是 + 1。 條件和循環(huán)分支會使程序計數(shù)器的值指向任意的地址,這樣一來,程序便可以返回到上一個地址來重復(fù)執(zhí)行同一個指令,或者跳轉(zhuǎn)到任意指令。
下面以條件分支為例來說明程序的執(zhí)行過程(循環(huán)也很相似)

程序的開始過程和順序流程是一樣的,CPU 從 0100 處開始執(zhí)行命令,在 0100 和 0101 都是順序執(zhí)行,PC 的值順序+1,執(zhí)行到 0102 地址的指令時,判斷 0106 寄存器的數(shù)值大于 0,跳轉(zhuǎn)(jump)到 0104 地址的指令,將數(shù)值輸出到顯示器中,然后結(jié)束程序,0103 的指令被跳過了,這就和我們程序中的 if() 判斷是一樣的,在不滿足條件的情況下,指令會直接跳過。所以 PC 的執(zhí)行過程也就沒有直接+1,而是下一條指令的地址。
一組 向量寄存器用來存儲一個或者多個整數(shù)或者浮點數(shù)值,向量寄存器是對一維數(shù)據(jù)上進(jìn)行操作。
機(jī)器指令只會執(zhí)行非常簡單的操作,例如將存放在寄存器的兩個數(shù)進(jìn)行相加,把數(shù)據(jù)從內(nèi)存轉(zhuǎn)移到寄存器中或者是條件分支轉(zhuǎn)移到新的指令地址。編譯器必須生成此類指令的序列,以實現(xiàn)程序構(gòu)造,例如算術(shù)表達(dá)式求值,循環(huán)或過程調(diào)用和返回
認(rèn)識匯編
我相信各位應(yīng)該都知道匯編語言的出現(xiàn)背景吧,那就是二進(jìn)制表示數(shù)據(jù),太復(fù)雜太龐大了,為了解決這個問題,出現(xiàn)了匯編語言,匯編語言和機(jī)器指令的區(qū)別就在于表示方法上,匯編使用操作數(shù)來表示,機(jī)器指令使用二進(jìn)制來表示,我之前多次提到機(jī)器碼就是匯編,你也不能說我錯,但是不準(zhǔn)確。
但是匯編適合二進(jìn)制代碼存在轉(zhuǎn)換關(guān)系的。
匯編代碼需要經(jīng)過 匯編器 編譯后才產(chǎn)生二進(jìn)制代碼,這個二進(jìn)制代碼就是目標(biāo)代碼,然后由鏈接器將其連接起來運行。

匯編語言主要分為以下三類
匯編指令:它是一種機(jī)器碼的 助記符,它有對應(yīng)的機(jī)器碼偽指令:沒有對應(yīng)的機(jī)器碼,由編譯器執(zhí)行,計算機(jī)并不執(zhí)行 其他符號,比如 +、-、*、/ 等,由編譯器識別,沒有對應(yīng)的機(jī)器碼
匯編語言的核心是匯編指令,而我們對匯編的探討也是基于匯編指令展開的。
與匯編有關(guān)的硬件和概念
CPU
CPU 是計算機(jī)的大腦,它也是整個計算機(jī)的核心,它也是執(zhí)行匯編語言的硬件,CPU 的內(nèi)部包含有寄存器,而寄存器是用于存儲指令和數(shù)據(jù)的,匯編語言的本質(zhì)也就是 CPU 內(nèi)部操作數(shù)所執(zhí)行的一系列計算。
內(nèi)存
沒有內(nèi)存,計算機(jī)就像是一個沒有記憶的人類,只會永無休止的重復(fù)性勞動。CPU 所需的指令和數(shù)據(jù)都由內(nèi)存來提供,CPU 指令經(jīng)由內(nèi)存提供,經(jīng)過一系列計算后再輸出到內(nèi)存。
磁盤
磁盤也是一種存儲設(shè)備,它和內(nèi)存的最大區(qū)別在于永久存儲,程序需要在內(nèi)存裝載后才能運行,而提供給內(nèi)存的程序都是由磁盤存儲的。
總線
一般來說,內(nèi)存內(nèi)部會劃分多個存儲單元,存儲單元用來存儲指令和數(shù)據(jù),就像是房子一樣,存儲單元就是房子的門牌號。而 CPU 與內(nèi)存之間的交互是通過地址總線來進(jìn)行的,總線從邏輯上分為三種
地址線 數(shù)據(jù)線 控制線

CPU 與存儲器之間的讀寫主要經(jīng)過以下幾步
讀操作步驟
CPU 通過地址線發(fā)出需要讀取指令的位置 CPU 通過控制線發(fā)出讀指令 內(nèi)存把數(shù)據(jù)放在數(shù)據(jù)線上返回給 CPU
寫操作步驟
CPU 通過地址線發(fā)出需要寫出指令的位置 CPU 通過控制線發(fā)出寫指令 CPU 把數(shù)據(jù)通過數(shù)據(jù)線寫入內(nèi)存
下面我們就來具體了解一下這三類總線
地址總線
通過我們上面的探討,我們知道 CPU 通過地址總線來指定存儲位置的,地址總線上能傳送多少不同的信息,CPU 就可以對多少個存儲單元進(jìn)行尋址。

上圖中 CPU 和內(nèi)存中間信息交換通過了 10 條地址總線,每一條線能夠傳遞的數(shù)據(jù)都是 0 或 1 ,所以上圖一次 CPU 和內(nèi)存?zhèn)鬟f的數(shù)據(jù)是 2 的十次方。
所以,如果 CPU 有 N 條地址總線,那么可以說這個地址總線的寬度是 N 。這樣 CPU 可以尋找 2 的 N 次方個內(nèi)存單元。
數(shù)據(jù)總線
CPU 與內(nèi)存或其他部件之間的數(shù)據(jù)傳送是由數(shù)據(jù)總線來完成的。數(shù)據(jù)總線的寬度決定了 CPU 和外界的數(shù)據(jù)傳輸速度。8 根數(shù)據(jù)總線可以一次傳送一個 8 位二進(jìn)制數(shù)據(jù)(即一個字節(jié))。16 根數(shù)據(jù)總線一次可以傳輸兩個字節(jié),32 根數(shù)據(jù)總線可以一次傳輸四個字節(jié)。。。。。。
控制總線
CPU 與其他部件之間的控制是通過 控制總線 來完成的。有多少根控制總線,就意味著 CPU 提供了對外部器件的多少種控制。所以,控制總線的寬度決定了 CPU 對外部部件的控制能力。
一次內(nèi)存的讀取過程
內(nèi)存結(jié)構(gòu)
內(nèi)存 IC 是一個完整的結(jié)構(gòu),它內(nèi)部也有電源、地址信號、數(shù)據(jù)信號、控制信號和用于尋址的 IC 引腳來進(jìn)行數(shù)據(jù)的讀寫。下面是一個虛擬的 IC 引腳示意圖

圖中 VCC 和 GND 表示電源,A0 - A9 是地址信號的引腳,D0 - D7 表示的是控制信號、RD 和 WR 都是好控制信號,我用不同的顏色進(jìn)行了區(qū)分,將電源連接到 VCC 和 GND 后,就可以對其他引腳傳遞 0 和 1 的信號,大多數(shù)情況下,+5V 表示1,0V 表示 0。
我們都知道內(nèi)存是用來存儲數(shù)據(jù),那么這個內(nèi)存 IC 中能存儲多少數(shù)據(jù)呢?D0 - D7 表示的是數(shù)據(jù)信號,也就是說,一次可以輸入輸出 8 bit = 1 byte 的數(shù)據(jù)。A0 - A9 是地址信號共十個,表示可以指定 00000 00000 - 11111 11111 共 2 的 10次方 = 1024個地址。每個地址都會存放 1 byte 的數(shù)據(jù),因此我們可以得出內(nèi)存 IC 的容量就是 1 KB。
如果我們使用的是 512 MB 的內(nèi)存,這就相當(dāng)于是 512000(512 * 1000) 個內(nèi)存 IC。當(dāng)然,一臺計算機(jī)不太可能有這么多個內(nèi)存 IC ,然而,通常情況下,一個內(nèi)存 IC 會有更多的引腳,也就能存儲更多數(shù)據(jù)。
內(nèi)存讀取過程
下面是一次內(nèi)存的讀取過程。

來詳細(xì)描述一下這個過程,假設(shè)我們要向內(nèi)存 IC 中寫入 1byte 的數(shù)據(jù)的話,它的過程是這樣的:
首先給 VCC 接通 +5V 的電源,給 GND 接通 0V 的電源,使用 A0 - A9來指定數(shù)據(jù)的存儲場所,然后再把數(shù)據(jù)的值輸入給D0 - D7的數(shù)據(jù)信號,并把WR(write)的值置為 1,執(zhí)行完這些操作后,即可以向內(nèi)存 IC 寫入數(shù)據(jù)讀出數(shù)據(jù)時,只需要通過 A0 - A9 的地址信號指定數(shù)據(jù)的存儲場所,然后再將 RD 的值置為 1 即可。 圖中的 RD 和 WR 又被稱為控制信號。其中當(dāng)WR 和 RD 都為 0 時,無法進(jìn)行寫入和讀取操作。
總結(jié)
此篇文章我們主要探討了指令集、指令集的分類,與匯編有關(guān)的硬件,總線都有哪些,分別的作用都是什么,然后我們以一次內(nèi)存讀取過程來連接一下 CPU 和內(nèi)存的交互過程。
原創(chuàng)不易,如有幫助還請各位讀者四連(點在、在看、分享、留言),感謝各位大佬
完
往期推薦
??

