cxuan自己的 Github 非常硬核,求各位大佬 star: https://github.com/crisxuan/bestJavaer
彙編程式碼是計算機的一種低階表示,它是一種低階語言,可以從字面角度去理解它,包括處理資料、管理記憶體、讀寫儲存裝置上的資料,以及利用網路通訊等。編譯器生成機器碼經過了一系列的轉換,這些轉換遵循程式語言
、目標機器的指令集
和作業系統
。
指令集
指令集就是指揮計算機工作的指令,因為程式就是按照一定執行順序排列的指令。因為計算機的執行控制權由 CPU 操作,所以指令集就是 CPU 中用來計算和控制計算機的一系列指令的集合。每個 CPU 在產出時都規定了與硬體電路相互配合工作的指令集。
指令集有不少分類,但是一般分為兩種,一種是精簡指令集
,一種是複雜指令集
。具體描述如下
精簡指令集
精簡指令的英文是 reduced instruction set computer, RISC
,原意是精簡指令集計算,簡稱為精簡指令集,是 CPU 的一種 設計模式
,可以把 CPU 想象成一家流水線工廠,對指令數目
和定址方式
都做了精簡,使其實現更容易,指令並行執行程度更好,編譯器的效率更高。
常見的精簡指令集處理器包括 ARM、AVR、MIPS、PARISC、RISC-V 和 SPARC。
所以你就能理解
這本書是講啥的了。
它主要是基於 MIPS 體系結構把馮諾依曼體系的五大元件進行了逐一的硬體實現 + 軟體設計介紹,更為重要的是引入了諸多平行計算的內容,這是大部分教材中忽略或者內容較少的,會根據這個思路把並行相關的內容,結合 OpenMP, CUDA 和 Hadoop/Spark 整體融入到新書中,畢竟這是未來發展的趨勢
還有這本書
這本書又是講啥的。
這本書是講 RISC-V 指令集的,因為指令集的不同也區分了三個版本,三個版本???嗯,還有下面這個
這本書是講 ARM 指令集的。
所以一般在看 CASPP 的時候併發的看看這本書是非常不錯的選擇。
精簡指令集一般具有如下特徵
- 統一的指令編碼
- 通用的暫存器,一般會區分整數和浮點數
- 簡單的定址模式,複雜定址模式被簡單指令序列來取代
- 支援很少偏門的型別,例如 RISC 支援位元組字串型別。
複雜指令集
複雜指令集的英文是 Complex Instruction Set Computing, CISC
,是一種微處理器指令集架構,也被譯為複雜指令集。
複雜指令集包括 System/360、VAX、x86 等。
複雜指令集可以說是在精簡指令集之上作出的改變。
複雜指令集的特點是指令數目多而複雜,每條指令字長並不相等,計算機必須加以判讀,併為此付出了效能的代價。
一般來說,提升 CPU 效能的方法有如下這幾種
- 增加暫存器的大小
- 增進內部的並行性
- 增加快取記憶體的大小
- 增加核心時脈的速度
- 加入其他功能,例如 IO 和計時器
- 加入向量處理器
- 硬體多執行緒技術
比較抽象,我們後面會組織成文章具體介紹一下。
C 編譯器會接收其他操作並把其轉換為組合語言
輸出,組合語言是機器級別的程式碼表示。我們之前介紹過,C 語言程式的執行過程分為下面這幾步
下面我們更多的討論都是基於彙編程式碼來討論。
我們日常所接觸的高階語言,都是經過了層層封裝的結果,所以我們平常是接觸不到組合語言的,更不會用匯編語言來進行程式設計,這就和你不知道作業系統的存在一樣,但其實你每個操作,甚至你雙擊一個圖示都和作業系統有關係。
高階語言的抽象級別很高,但是經過了層層抽象之後,高階語言的執行效率肯定沒有組合語言高,也沒有組合語言可靠。
但是高階語言有更大的優點是其編譯後能夠在不同的機器上執行,組合語言針對不同的指令集有不同的表示。並且高階語言學習來更加通俗易懂,降低計算機門檻,讓內卷更加嚴重(當然這是開個玩笑,冒犯到請別當真)。
話不多說,瞭解底層必須瞭解組合語言。否則一個 synchronized 底層實現就能夠讓你頭疼不已。而且,天天飄著也不好,遲早要落地。
瞭解彙編程式碼也有助於我們優化程式程式碼,分析程式碼中隱含的低效率,並且這種優化方法一旦優化成功,將是量級的提高,而不是改改 if...else ,使用一個新特性所能比的。
機器級程式碼
計算機系統使用了多種不同形式的抽象,可以通過一個簡單的抽象模型來隱藏實現細節。對於機器級別的程式來說,有兩點非常重要。
首先第一點,定義機器級別程式的格式和行為被稱為 指令集體系結構或指令集架構(instruction set architecture)
, ISA。ISA 定義了程式狀態、指令的格式和每一個指令對狀態的影響。大部分的指令集架構包括 ISA 用來描述程式的行為就好像是順序執行的,一條指令執行結束後,另外一條指令再開始。處理器硬體的描述要更復雜,它可以同時並行執行許多指令,但是它採用了安全措施
來確保整體行為與 ISA 規定的順序一致。
第二點,機器級別對記憶體地址的描述就是 虛擬地址(virtual address)
,它提供了一個記憶體模型來表示一個巨大的位元組陣列。
編譯器在整個編譯的過程中起到了至關重要的作用,把 C 語言轉換為處理器執行的基本指令。彙編程式碼非常接近於機器程式碼,只不過與二進位制機器程式碼相比,彙編程式碼的可讀性更強,所以理解彙編是理解機器工作的第一步。
一些程式狀態對機器可見,但是 C 語言程式設計師卻看不到這些,包括
程式計數器(Program counter)
,它儲存下一條指令的地址,在 x86-64 架構中用%rip
來表示。
程式執行時,PC 的初始值為程式第一條指令的地址,在順序執行程式時, CPU 首先按程式計數器所指出的指令地址從記憶體中取出一條指令,然後分析和執行該指令,同時將 PC 的值加 1 並指向下一條要執行的指令。
比如下面一個例子。
這是一段數值進行相加的操作,程式啟動,在經過編譯解析後會由作業系統把硬碟中的程式複製到記憶體中,示例中的程式是將 123 和 456 執行相加操作,並將結果輸出到顯示器上。由於使用機器語言難以描述,所以這是經過翻譯後的結果,實際上每個指令和資料都可能分佈在不同的地址上,但為了方便說明,把組成一條指令的記憶體和資料放在了一個記憶體地址上。
- 整數
暫存器檔案(register file)
包含 16 個命名的位置,用來儲存 64 位的值。這些暫存器可以儲存地址和整型資料。有些暫存器用於跟蹤程式狀態,而另一些暫存器用於儲存臨時資料,例如過程的引數和區域性變數,以及函式要返回的值。這個檔案
是和磁碟檔案無關的,它只是 CPU 內部的一塊高速儲存單元。有專用的暫存器,也有通用的暫存器用來儲存運算元。 條件碼暫存器
用來儲存有關最近執行的算術或邏輯指令的狀態資訊。這些用於實現控制元件或資料流中的條件更改,例如實現 if 和 while 語句所需的條件更改。我們都學過高階語言,高階語言中的條件控制流程主要分為三種:順序執行、條件分支、迴圈判斷
三種,順序執行是按照地址的內容順序的執行指令。條件分支是根據條件執行任意地址的指令。迴圈是重複執行同一地址的指令。- 順序執行的情況比較簡單,每執行一條指令程式計數器的值就是 + 1。
- 條件和迴圈分支會使程式計數器的值指向任意的地址,這樣一來,程式便可以返回到上一個地址來重複執行同一個指令,或者跳轉到任意指令。
下面以條件分支為例來說明程式的執行過程(迴圈也很相似)
程式的開始過程和順序流程是一樣的,CPU 從 0100 處開始執行命令,在 0100 和 0101 都是順序執行,PC 的值順序+1,執行到 0102 地址的指令時,判斷 0106 暫存器的數值大於 0,跳轉(jump)到 0104 地址的指令,將數值輸出到顯示器中,然後結束程式,0103 的指令被跳過了,這就和我們程式中的 if()
判斷是一樣的,在不滿足條件的情況下,指令會直接跳過。所以 PC 的執行過程也就沒有直接+1,而是下一條指令的地址。
- 一組
向量暫存器
用來儲存一個或者多個整數或者浮點數值,向量暫存器是對一維資料上進行操作。
機器指令只會執行非常簡單的操作,例如將存放在暫存器的兩個數進行相加,把資料從記憶體轉移到暫存器中或者是條件分支轉移到新的指令地址。編譯器必須生成此類指令的序列,以實現程式構造,例如算術表示式求值,迴圈或過程呼叫和返回
認識彙編
我相信各位應該都知道組合語言的出現背景吧,那就是二進位制表示資料,太複雜太龐大了,為了解決這個問題,出現了組合語言,組合語言和機器指令的區別就在於表示方法上,彙編使用運算元
來表示,機器指令使用二進位制來表示,我之前多次提到機器碼就是彙編,你也不能說我錯,但是不準確。
但是彙編適合二進位制程式碼存在轉換關係的。
彙編程式碼需要經過 彙編器
編譯後才產生二進位制程式碼,這個二進位制程式碼就是目的碼,然後由連結器將其連線起來執行。
組合語言主要分為以下三類
- 彙編指令:它是一種機器碼的
助記符
,它有對應的機器碼 - 偽指令:沒有對應的機器碼,由編譯器執行,計算機並不執行
- 其他符號,比如 +、-、*、/ 等,由編譯器識別,沒有對應的機器碼
組合語言的核心是彙編指令,而我們對彙編的探討也是基於彙編指令展開的。
與彙編有關的硬體和概念
CPU
CPU 是計算機的大腦,它也是整個計算機的核心,它也是執行組合語言的硬體,CPU 的內部包含有暫存器,而暫存器是用於儲存指令和資料的,組合語言的本質也就是 CPU 內部運算元所執行的一系列計算。
記憶體
沒有記憶體,計算機就像是一個沒有記憶的人類,只會永無休止的重複性勞動。CPU 所需的指令和資料都由記憶體來提供,CPU 指令經由記憶體提供,經過一系列計算後再輸出到記憶體。
磁碟
磁碟也是一種儲存裝置,它和記憶體的最大區別在於永久儲存,程式需要在記憶體裝載後才能執行,而提供給記憶體的程式都是由磁碟儲存的。
匯流排
一般來說,記憶體內部會劃分多個儲存單元,儲存單元用來儲存指令和資料,就像是房子一樣,儲存單元就是房子的門牌號。而 CPU 與記憶體之間的互動是通過地址匯流排
來進行的,匯流排從邏輯上分為三種
- 地址線
- 資料線
- 控制線
CPU 與儲存器之間的讀寫主要經過以下幾步
讀操作步驟
- CPU 通過地址線發出需要讀取指令的位置
- CPU 通過控制線發出讀指令
- 記憶體把資料放在資料線上返回給 CPU
寫操作步驟
- CPU 通過地址線發出需要寫出指令的位置
- CPU 通過控制線發出寫指令
- CPU 把資料通過資料線寫入記憶體
下面我們就來具體瞭解一下這三類匯流排
地址匯流排
通過我們上面的探討,我們知道 CPU 通過地址匯流排
來指定儲存位置的,地址匯流排上能傳送多少不同的資訊,CPU 就可以對多少個儲存單元進行定址。
上圖中 CPU 和記憶體中間資訊交換通過了 10 條地址匯流排,每一條線能夠傳遞的資料都是 0 或 1 ,所以上圖一次 CPU 和記憶體傳遞的資料是 2 的十次方。
所以,如果 CPU 有 N 條地址匯流排,那麼可以說這個地址匯流排的寬度是 N 。這樣 CPU 可以尋找 2 的 N 次方個記憶體單元。
資料匯流排
CPU 與記憶體或其他部件之間的資料傳送是由資料匯流排
來完成的。資料匯流排的寬度決定了 CPU 和外界的資料傳輸速度。8 根資料匯流排可以一次傳送一個 8 位二進位制資料(即一個位元組)。16 根資料匯流排一次可以傳輸兩個位元組,32 根資料匯流排可以一次傳輸四個位元組。。。。。。
控制匯流排
CPU 與其他部件之間的控制是通過 控制匯流排
來完成的。有多少根控制匯流排,就意味著 CPU 提供了對外部器件的多少種控制。所以,控制匯流排的寬度決定了 CPU 對外部部件的控制能力。
一次記憶體的讀取過程
記憶體結構
記憶體 IC 是一個完整的結構,它內部也有電源、地址訊號、資料訊號、控制訊號和用於定址的 IC 引腳來進行資料的讀寫。下面是一個虛擬的 IC 引腳示意圖
圖中 VCC 和 GND 表示電源,A0 - A9 是地址訊號的引腳,D0 - D7 表示的是控制訊號、RD 和 WR 都是好控制訊號,我用不同的顏色進行了區分,將電源連線到 VCC 和 GND 後,就可以對其他引腳傳遞 0 和 1 的訊號,大多數情況下,+5V 表示1,0V 表示 0。
我們都知道記憶體是用來儲存資料,那麼這個記憶體 IC 中能儲存多少資料呢?D0 - D7 表示的是資料訊號,也就是說,一次可以輸入輸出 8 bit = 1 byte 的資料。A0 - A9 是地址訊號共十個,表示可以指定 00000 00000 - 11111 11111 共 2 的 10次方 = 1024個地址
。每個地址都會存放 1 byte 的資料,因此我們可以得出記憶體 IC 的容量就是 1 KB。
如果我們使用的是 512 MB 的記憶體,這就相當於是 512000(512 * 1000) 個記憶體 IC。當然,一臺計算機不太可能有這麼多個記憶體 IC ,然而,通常情況下,一個記憶體 IC 會有更多的引腳,也就能儲存更多資料。
記憶體讀取過程
下面是一次記憶體的讀取過程。
來詳細描述一下這個過程,假設我們要向記憶體 IC 中寫入 1byte 的資料的話,它的過程是這樣的:
- 首先給 VCC 接通 +5V 的電源,給 GND 接通 0V 的電源,使用
A0 - A9
來指定資料的儲存場所,然後再把資料的值輸入給D0 - D7
的資料訊號,並把WR(write)
的值置為 1,執行完這些操作後,即可以向記憶體 IC 寫入資料 - 讀出資料時,只需要通過 A0 - A9 的地址訊號指定資料的儲存場所,然後再將 RD 的值置為 1 即可。
- 圖中的 RD 和 WR 又被稱為控制訊號。其中當WR 和 RD 都為 0 時,無法進行寫入和讀取操作。
總結
此篇文章我們主要探討了指令集、指令集的分類,與彙編有關的硬體,匯流排都有哪些,分別的作用都是什麼,然後我們以一次記憶體讀取過程來連線一下 CPU 和記憶體的互動過程。
原創不易,如有幫助還請各位讀者四連(點在、在看、分享、留言),感謝各位大佬
關注公眾號 程式設計師cxuan 回覆 cxuan 領取優質資料。
我自己寫了六本 PDF ,非常硬核,連結如下
我自己寫了六本 PDF ,非常硬核,連結如下
我自己寫了六本 PDF ,非常硬核,連結如下