type
status
date
slug
summary
tags
category
icon
password
MIPS 的小知識原則一:簡單有利於規律性原則二:小就是快endianess(字節序)加載常數MIPS 真實的長相原則三:好的設計需要好的妥協表達一些 bit operation表達 true/false (流程控制)GPT同學的說明Supporting Procedures in Computer Hardware (活用記憶體, 2.8)人類文字 遇上 記憶體當遇到了需要 32-bit 大小的常數JumpBranchesMIPS Addressing Mode Summary進階主題Parallelism and Instructions: Synchronization具體要怎麼翻譯和執行 Program — 以 C 為例CompilerAssembler (匯編器)LinkerLoaderDynamically Linked LibrariesJava 的語言方法Arrays versus Pointers
大綱:


從大綱可以很明顯的感受到,這一章主要是在講「程式語言」,所以提到了硬體、數字的表示法、邏輯操作、 MIPS 等等的,第三章才會提到更底層的事情,另外 Signed bit 我們這邊不提,因為以前 Digital System 弄過了
MIPS 的小知識
原則一:簡單有利於規律性
MIPS 實際上的相加要怎麼達成

MIPS 的指令表一部分

可以發現到, MIPS 要將一坨數字相加,不會把全部寫在一起,因為這樣做的話就要動用到許多的設定,去符合所有的格式。只用單一一種格式,可以讓整個指令集更加簡潔
- Design Principle 1: Simplicity favors regularity.
原則二:小就是快
在 MIPS 裡面, Register 最多只能用到 32 位元,不然會造成 clock cycle 變長
書也有提到,越小越快不是絕對的,是一個大方向的原則
另外,在 MIPS 裡面的 variable 都會使用 $ 來表示

另外,記憶體大概是長這個樣子


endianess(字節序)
指定 array 的 endianess 有分成兩種,假設今天我們要儲存一個16位數字 0x1234
- 大端 (Big-endian)
34 |
12 |
- 小端(Little-endian)
12 |
34 |
我們的 MIPS 是小端模式
加載常數
有時候我們會使用一些常見的常數(例如上面 Array 的例子,我們常常要使用 4 這個數字),那我們就會先加載到記憶體,有以下兩種方法
- 先放到 $t0
- 使用 addi
這個做主要是希望把 common case 做到能快就快
MIPS 真實的長相


- 每一個格子代表 field
以上我們稱為 instruction format,而實際的數字指令我們稱為 Machine language,取名原因很好想像,沒有意外只有機器看得懂
更多的 MIPS Fields 的說明
反正等到我想要深入了解再來看

原則三:好的設計需要好的妥協
看到剛剛我們的指令設計,我們會發現,我們沒有辦法做超過 的加法,因此有了這個法則
所以我們沒有辦法使用這個指令,同時還是要保持指令的位元長度,因此解決方案是:

我們設計了另一種 formate,長得像上面這樣

更多 MIPS 的 Example

表達一些 bit operation

就邏輯
表達 true/false (流程控制)
課本有點囉嗦,我們看看 GPT 同學給我們的簡介
GPT同學的說明
在 MIPS 中實現 if/else 結構通常涉及以下幾個步驟:
- 條件評估:首先,需要計算條件表達式的值。這通常是通過比較指令來完成的,如
beq
(branch if equal)、bne
(branch if not equal)、slt
(set on less than)等。
- 分支指令:一旦條件表達式被評估,接下來會使用分支指令來決定執行流程的方向。如果條件為真,則執行 if 塊中的代碼,否則跳轉到 else 塊。
- 代碼塊執行:根據條件評估的結果,處理器會執行 if 塊或 else 塊中的代碼。
- 流程合並:無論是 if 塊還是 else 塊執行完畢後,控制流需要合並到這兩塊之後的代碼。
以下是一個簡單的 if/else 結構在 MIPS 組合語言中的實現示例:
在這個例子中,
bne
指令用於評估條件($t0 是否不等於 $t1)。如果條件為真(即,值不相等),執行跳轉到 else_block
標籤的代碼。否則,處理器將繼續執行 if_block
中的代碼。 j end_if_else
指令確保在執行完 if 塊之後跳過 else 塊,進而合並流程。Supporting Procedures in Computer Hardware (活用記憶體, 2.8)
為了讓程式更快,在硬體的設計上就會加上一些小巧思,例如在記憶體上就有 default 的設定

另外,為了更加活用這兩個記憶體,我們有 jal(jump-and-link)還有 jr(jump register)這兩個指令,以下是 GPT 同學帶給我們的解釋:
jal ProcedureAddress
(Jump and Link):- 這條指令用於調用程序(如函數或過程)。
- 當執行
jal
指令時,它會跳轉到指定的地址(在這裡是ProcedureAddress
)來執行該程序。 - 同時,“link”部分的作用是將調用點後的下一條指令的地址保存到
$ra
(返回地址寄存器,也就是寄存器31)中。這樣做的目的是為了讓被調用的程序知道在完成執行後需要返回到哪裡。
- 返回地址 (Return Address):
- 存儲在
$ra
寄存器中的地址稱為“返回地址”。這對於程序控制流程來說非常重要,因為同一程序可能會從多個不同的地方被調用。 - 當一個程序或函數需要返回到它被調用的地方時,就會使用這個返回地址。
jr
(Jump Register):- 這條指令用於從程序或函數中返回。
jr
執行一個無條件跳轉到某個寄存器(通常是$ra
)中指定的地址。- 這意味著當程序完成其任務後,它會使用
jr
指令跳轉回到$ra
中存儲的地址,也就是調用它的地方。
那假設我們需要用到更多的記憶體,我們就會使用到 stack 的結構

以及 stack 在動作的時候想像起來是什麼樣子

另外,下表是 MIPS 的記憶體 table


關於 stack 要新增資料時的行為

關於 heap 要新增資料時的行為

人類文字 遇上 記憶體
我們都知道,在電腦裡面要使用 ASCII Code 之類的東西電腦才看得懂,那我們要怎麼去操作他們?
在 MIPS 中,當我們想要讀取或者寫入文字(人類的)時候,會用以下的指令
lb(load byte) 還有 sb(saved byte)
當遇到了需要 32-bit 大小的常數
平常的指令結構最大就是 16-bit 的指令 + 16-bit 的常數
但假如遇到了需要 32-bit 的情境,我們應該要做一些甚麼呢?
詳細的閱讀過程:

- 閱讀 upper 16-bit (使用 lui <要暫時記住的地方>, <前 16-bit 數字>)
- 閱讀後 16-bit 的數字 (ori <要暫時寄住的地方>, <前 16-bit 儲存的地方>, <後 16-bit>)
Jump
就跳到指定的記憶體位置:

Branches
指的是在類似 for 迴圈或者是 if/else 的流程控制
另外,書中寫到
Program counter = Register + Branch address
也很好想像,因為例如 for 迴圈的流程控制,就是把那個 i 記在記憶體,經過 if/else 判斷之後 jump 到特定的位置,而這種作法又稱為 PC-relative addressing (PC 是 program counter)
MIPS Addressing Mode Summary

我們這邊參考 GPT 同學給出來的內容:
當然,以下是對MIPS架構中這五種定址模式的解釋,以及每種模式的一個示例:
- 立即定址 (Immediate Addressing)
- 解釋:操作數直接包含在指令中作為一個常數。這種模式常用於簡單的數學運算或賦值操作。
- 範例:
addi $t0, $t1, 10
,這裡10
是立即數,將$t1
寄存器的值與10
相加,結果存儲在$t0
寄存器中。
- 寄存器定址 (Register Addressing)
- 解釋:操作數是處於寄存器中的值。這是最基本的定址方式,常用於寄存器間的數據傳輸和運算。
- 範例:
add $t0, $t1, $t2
,這裡$t1
和$t2
是寄存器,將它們的值相加,結果存儲在$t0
寄存器中。
- 基址或偏移定址 (Base or Displacement Addressing)
- 解釋:操作數位於記憶體中,其地址是一個寄存器的值(基址)與指令中的一個常數(偏移量)相加得到的。
- 範例:
lw $t0, 8($t1)
,這裡從$t1
寄存器加上偏移量8
計算出的地址讀取數據,然後將數據加載到$t0
寄存器中。
- PC相對定址 (PC-relative Addressing)
- 解釋:用於分支指令,其中分支的目標地址是程序計數器(PC)當前值加上指令中的一個常數(偏移量)。
- 範例:
beq $t0, $t1, label
,這裡如果$t0
和$t1
的值相等,程序會跳轉到label
指定的地址,該地址是當前 PC 值加上到label
的偏移量。
- 偽直接定址 (Pseudodirect Addressing)
- 解釋:用於跳轉指令,其中跳轉的目標地址是指令中的26位地址與程序計數器高位的組合。
- 範例:
j 0x00400000
,這裡跳轉到地址0x00400000
。該地址是指令中的26位與 PC 的高位結合而形成的。
每種定址模式都有其特定的應用場景,是MIPS指令集靈活處理各種操作和控制流程的基礎。
進階主題
Parallelism and Instructions: Synchronization
這個章節在討論「同步」這件事情,因為有時候十個工作,彼此之間是相關的,假如沒處理好就會打架,所以要討論實務上在記憶體要怎麼處理這件事情
當然,我可以為您介紹 MIPS 指令集中的
load linked
(LL)和 store conditional
(SC)指令,這兩種指令通常一起使用,用於實現在多處理器環境中的原子操作。- Load Linked (LL)
- 功能:
load linked
指令從指定的記憶體地址讀取一個值並將其加載到寄存器中。它的特殊之處在於它會標記所讀取的記憶體位置,以便於後續的store conditional
指令使用。 - 用途:這個指令主要用於在執行條件存儲之前檢查記憶體位置的狀態。如果在
load linked
和相應的store conditional
執行之間,該記憶體位置被更改,則store conditional
操作將失敗。
- Store Conditional (SC)
- 功能:
store conditional
指令嘗試將一個值從寄存器存儲到之前由load linked
指令讀取的記憶體地址。這個存儲操作只有在自上一次load linked
執行以來,目標記憶體地址沒有被修改的情況下才會成功。 - 用途:
store conditional
用於實現互斥和其他同步機制。它允許處理器確定自從最後一次讀取記憶體位置以來,該位置是否已被其他處理器更改。
LL
和 SC
指令的組合使用允許在多核或多處理器系統中安全地進行記憶體操作,這對於防止競爭條件和數據競爭至關重要。例如,它們常用於實現鎖,其中一個處理器通過 LL
讀取鎖的狀態,並嘗試通過 SC
更改它。如果在這個過程中其他處理器已經修改了鎖的狀態,SC
將會失敗,這樣就保證了原子性操作。具體要怎麼翻譯和執行 Program — 以 C 為例

Compiler
負責將 「高階語言」(high level language) 轉為 「組合語言」(assembly language)
高階語言: 因為可以用短短幾行,把一大坨組合語言搞定,生產效率比較 high
組合語言: 跟 binary machine code 有一對一關係的語言,例如 MIPS
Assembler (匯編器)
最主要的功能就是把組合語言換成機器碼,組合語言的 IDE 的感覺
不過這個 Assembler 是依照平台不同變的
Linker
假如有人更改了一行 C 語言,整個幾千行的 C 語言都要一起重新編譯,那非常的浪費效能
因此,我們會希望它一行一行獨立分開編譯,但也會因此出現彼此不知道指的是誰,因此,Linker 的角色就是把獨立的一行一行的程式在機械碼的情況下接起來。
到剛剛的階段,整個 Machine code 是準備可以跑的狀態
Loader
這是 Unix 的構造,它會執行以下的步驟,準備讀到記憶體,然後啟動它
- 讀取可執行檔案的 head,以確定文本和數據段的大小。
- 創建足夠大的空間來容納文本和數據。
- 將指令和數據從可執行檔案複製到記憶體中。
- 將參數(如果有的話)複製到主程序的 stack 上。
- 初始化機器寄存器,並將堆棧指針設置為第一個空閒位置。
- 跳轉到一個啟動例程,該例程將參數複製到參數寄存器中並調用程序的主例程。當主例程返回時,啟動例程通過退出系統調用結束程序。
Dynamically Linked Libraries
我的理解是,跟把 function 寫在程式碼裡面不一樣,我們是用外部的 function ,好處是可以省儲存空間,以及可以再利用,缺點是第一次 load 的時候會慢一點,而 Unix 和 Windows 系統都有大量用到這個東西。
我的理解是跟 Python 的 import 差不多

通過懶惰程序鏈接實現的動態鏈接庫。 (a) 首次呼叫DLL函數時的步驟。 (b) 在後續呼叫中,尋找函數、重新映射它和鏈接它的步驟被跳過。如我們將在第5章中看到,操作系統可以通過使用虛擬內存管理來重新映射,從而避免複製所需的函數。
Java 的語言方法

一個Java程序首先被編譯成Java字節碼的二進制版本,所有地址都由編譯器定義。
現在,這個Java程序已經準備好在解釋器上運行,該解釋器被稱為Java虛擬機(JVM)。
JVM在程序運行時鏈接到Java庫中所需的方法。
為了實現更高的性能,JVM可以調用即時編譯器(JIT),它選擇性地將方法編譯成運行它的機器上的本機機器語言。
總之跟 C 不一樣, Java 的程式,只要有 JVM 都可以到處執行,它有一些特別的機制:
- Java 的程式碼會被 Compiler 轉換為 .class 文件
- JVM 負責及時編譯這些 .class 文件
- JIT 負責讓編譯速度變得更快
- JVM 同時還會處理記憶體分配
Arrays versus Pointers
很好想像的事:假如要儲存一串東西,並且要擁有一個好的彈性,那 pointer 肯定比 array 還要方便,缺點是新手比較難以想像和入門,因此這個章節在用 MIPS 跟你解釋
- 作者:Q蛇
- 链接:/article/a25a4149-aeb3-433f-b677-b4cb9628b4a9
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。