理清幾個點:
-
指令中存放的二進制碼
例如R型指令:
opcode:操作碼 Rm:第二源操作數 Shamt:位移量 Rn:第一源操作數 Rd:目的寄存器 11bit 5bit 6bit 5bit 5bit 例如,這里的Rm位置下的二進制碼表示的是二進制的地址(指的是第幾個寄存器),這里用的是寄存器尋址,就是說要取的值還在寄存器里,不會把它放入我們的指令中來。
-
字 word 大小依賴平台(cpu一次能處理的位數) 字節 byte 8bit 位 bit 1bit 1KB = 1024Bit、1MB = 1024KB 、1GB = 1024MB
2.1引言
計算機中的基本單詞成為指令(如ADD,SUB,CMP
指令集為計算機全部指令——也包括偽指令,不過經過匯編器會被編為基本指令。偽指令就是基本指令的變種。
指令集的兩種形式:
- 便於人們書寫的(匯編語言)
- 計算機所識別的(二進制碼)
2.2計算機硬件的操作
例如:
ADD a b c
這匯編語句對應一條指令,這條指令有且只有三個操作數。
這符合我們設計原則1:簡單源於規整。
2.3計算機硬件的操作數
LEGv8算數運算指令的操作數必須來自寄存器。
LEGv8的寄存器為64bit。
LEGv8體系下的字為32bit。
雙字為64bit。
LEGv8體系下的寄存器被限制為32個。為什么?
設計原則2:越少越快
大量的寄存器會使時鍾周期變成,因為電信號要傳的更遠。
另一個原因是指令的現在,LEGv8指令格式只給操作數分配5個bit,如果寄存器超出32個,那么指令也會隨之變長——因為需要更多的位來表示操作數。
2.3.1存儲器操作數
STUR | Rd | [基址寄存器,#偏移量] |
---|
寄存器溢出:就是指要存的變量的值多於寄存器的個數,所以我們將不常用的變量的值放在存儲器中。
2.4有符號數和無符號數
最低有效位:最右邊的一位
最高有效位:最左邊的一位
原碼表示法:符號和幅值表示法(舍棄不用)
補碼:
- 前置位為0:正數
- 前置位都為1:負數
補碼負值計算公式:
符號擴展——指從存儲器中載入寄存器的數值沒有64位,那么就要用數字去填充空缺位
- 符號擴展:就是用符號位去填充空缺
- 零擴展:用0去填充空缺位置。
2.5計算機中指令的表示
設計原則3:優秀的設計需要好的權衡和折中
因為有些指令不要三個寄存器,而需要更大位數來表示立即數和跳轉地址,但指令長度又被限制於32位,所以好把表示寄存器的位數用於表示立即數。例如無條件跳轉指令B,opcode只有6bit剩下的全部用於地址位,還有以下列舉的指令類型。
D型指令(load,store):
opcode | address | op2:操作碼的邏輯擴展 | Rn:基址寄存器 | Rt |
---|---|---|---|---|
11bit | 12bit | 2bit | 5bit | 5bit |
I型指令(ADDI,SUBI)
opcode | immediate | Rn | Rd |
---|---|---|---|
10bit | 12bit | 5bit | 5bit |
重點
計算機構建原則:
- 指令用數的形式表示
- 程序和數據一樣,存儲在存儲器上進行讀寫。
這兩個准則引發了存儲程序原理的誕生,使得計算機發揮了巨大的潛力。存儲器可以存放各種東西。
2.6邏輯操作
- 左移:LSL
- 右移:LSR
- 與:AND——操作位相同,結果位1
- 或:OR——操作位不同,結果位1
- 取反:NOT——0變1,1變0
- 異或:EOR——操作位不同時未1
2.7決策指令
CBZ | register | Lable | 如果register為0跳轉到Lable |
---|---|---|---|
CBNZ | register | Lable | 如果register不為0跳轉到Lable |
B.cond 指令 ,cond表示條件
cond用於有符號數:
- EQ:equal
- NE:not equal
- LT:less than
- LE:less equal
- GT:greater than
- GE:greater equal
cond用於無符號數:
- LO:low
- LS:low same
- HI:high
- HS:high same
LEGv8還有一下四個二進制位來表示指令執行狀態:
- 負數標志位(N)
- 零標志位(Z)
- 溢出標志位(V)
- 僅為標志位(C):若從最高位借位或者進位就設置條件嗎。
到頭設置標志位的方法:
- 用指令比較兩個寄存器大小,然后根據結果跳轉。
- 比較兩個寄存器的值,然后用第三個寄存器記錄比較是否成功。
只有ADD,ADDI,AND,ADNDI,SUB,SUBI能設置條件碼,想要設置條件碼就在指令尾加S(ADDS)
2.7.2邊界檢查
SUBS XZR, X20, X11 --X11=length
B.HS IndexOutOfBounds
檢查索引是否越界。
如果一個有符號數(非負數) 減去一個無符號數,如果有符號數>無符號數,運算中產生進位或者借位時,C置為1,
如果有符號數是一個(負數)那么大於無符號數,運算中中產生進位或者借位時,C置為1.
跳轉指令就可以憑借進位標志位(C)是否為1懸着跳轉。
2.8對函數的支持
執行函數的步驟:
- 將參數放到函數可以訪問到的地方
- 將執行控制權交給函數
- 獲得函數執行所需的存儲資源
- 執行所需的任務
- 將結果值放在調用程序可訪問的地方
- 將控制點放回調用點,因為一個函數在多個地方可能會被調用
LEGv8為過程(函數)分配寄存器的約定:
- X0~X7:作為參數寄存器用於傳遞參數和放回結果
- LR(X30):作為放回地址寄存器,用於放回原始調用點。asdas
分支和連接指令(Branch and Link instruction)
BL procedureAddress
用於跳轉到某個地址的同時將下一條指令的地址保存在寄存器中。
寄存器跳轉指令(Branch register)
BR LR
跳轉會原始調用點。
PC:保存當前真正執行的指令地址
- 程序計數器
- 指令地址寄存器
兩個都是PC的別名。
我疑惑:棧為什么要從高地址開始?
早期的系統需要考慮有限內存下的內存布局問題。具體來說,內存的一端放置了靜態代碼和靜態數據之后,剩余的區域,既需要動態數據,又需要可增長的棧,那么合理的方案就是各放一端向中間生長。現在的問題就是兩個選項:靜態內存放在哪端;棧是在靜態內存的同端還是對端。
2.8.1使用更多的寄存器
問題背景當函數需要的寄存器數量超過8個時,編譯器就要使用臨時的寄存器。
因為臨時的寄存器的寄存器可能放一些值被其他的指令所使用,所以編譯器使用臨時的寄存器不能留下痕跡(就是要回復原始數據)
這里保存和恢復舊數據使用stack,為了避免保存和恢復一個未使用過的寄存器。
LEGv8將寄存器分為兩組:
- X9~X17:不需要保留(調用者負責)
- X19~X28:需要保留(被調用者負責)
2.8.2過程(函數)嵌套
葉過程:不調用其他過程(函數)
如果存在嵌套過程,即A調用B,B調用C。
此時:
- X0X7與X9X17中所使用的寄存器由調用者負責保存
- X19~X25和LR由被調用者負責保存。
2.8.3在Stack中為新數據分配內存
這個討論的是,Stack中不僅要保存寄存器的值(寄存器的大小是固定的)還要保存局部變量的值(大小是不固定的,INT32bit,Double64bit)。
過程幀(活動記錄):Stack中包含過程所保存的的寄存器和局部變量的片段。
幀指針:指向給定過程中的寄存器和局部變量的值。
- 一種的幀指針指向過程的第一雙字。
- 另一種是在一個過程中為本地存儲器引用提供一個固定的基址。
2.8.4在堆中為新數據分配內存
堆:類似鏈表這樣的數據結構通常會在生命期內增長或者縮短,這類數據結構對應的段。
LEGv8語言中寄存器的使用約定:
2.9人機交互
- LDURB:將字節從內存中讀到寄存器的最右邊
- STURB:將寄存器最右邊八位寫入內存中
表示一個字符串的三種方法:
- 保留字符串的第一個位子用於給出字符串的長度(java)
- 附加一個指明字符串長度的變量
- 字符串最后一個位置用一個字符來標識結尾(C語言)
2.10寬立即數和地址尋址。
2.10.1寬立即數
寬立即數用於更多位的常量或地址(因為LEGv8的指令長度限制了常熟或者地址的大小)
設置寄存器中的16位:
- MOVZ:將寄存器剩下的位置置0
- MOVK:將寄存器剩余的位置不變
LSL實現要加載的16位字段。LSL后面的數(加載完16位字段后剩余的位)取決於需要64位雙字的那個象限。例如加載到第四象限,那么后面剩下48位。
例如:
被載入的寄存器 | 被載入的數字 | 第幾位開始載入 | ||
---|---|---|---|---|
MOVZ | X9 | 255 | LSL | 16 |
2.10.2分支尋址
B指令的指令格式:
- 分支指令
- 分支和鏈接指令
6bit | 26bit |
---|
CBZ等條件分支指令和依賴條件碼分支指令的格式
8bit | 19bit | 5bit |
---|
$$
程序計數器=寄存器內容+分支地址偏移量
$$
這個計算式表明:19位來表示一個地址可能不夠用了,因為一個程序不可能小於2^19。
因此我們要指定一個寄存器將它與分支地址偏移量相加,得到跳轉的地址。
這里我們要采用PC寄存器,因為條件分支轉移地址傾向於轉移到附近的指令。SPEC測試中顯示轉移的范圍來16個字以內(16條指令以內,因為一條指令的長度就是一個字)。運用這個公式我們可以轉移到2^18個字內,(只要用邏輯操作將分支偏移量*4 因為LEGv8的地址是按字節編址的)。
條件分支和跳轉指令(B)采用PC相對尋址。
分支和鏈接指令采用其他尋址方式。
2.11並行與指令:同步
同步:一個線程若需要其他線程產生的結果那么它需要停下來等待,就是一個個做。
線程同步:即當有一個線程在對內存進行操作時,其他線程都不可以對這個內存地址進行操作,直到該線程完成操作, 其他線程才能對該內存地址進行操作,而其他線程又處於等待狀態,實現線程同步的方法有很多,臨界區對象就是其中一種。
例程:某個系統對外提供的功能接口或者服務集合。
lock和unlock可以直接建立互斥區,允許當個處理器操作。
處理器對鎖單元加鎖的方法使用一個寄存器的1與該鎖(鎖在臨界區里)地址里的值交換,若寄存器為1,表面臨界區在被使用,若為0則可以被占用。
同步交換(atomic)由當個不可中斷的指令對實現。
指令對:
- LDXR:取出鎖值
- STXR:對臨界區進行操作。
STXR由兩個功能:
STXR X23, X9, [X20] -- Memory[X20] = X23 如果成功x9 = 0否則x9 = 1
- 將存儲地址寫入X20
- 將X9置為1或者0.
2.12翻譯並啟動程序
編譯器:將高級語言編譯為匯編語言
匯編器:可以識別並轉換為功能等價的機器語言
目標文件(.o,.obj)包含的內容:
- 目標托文件
- 代碼的
- 靜態數據段
- 重定位信息
- 符號表 ——程序中出現的(符號和地址)對。
- 調試信息
連接器:將改變代碼不需要重新編譯所有的,只需要編譯和匯編某一行在用連接器縫合。
工作的步驟:
- 將代碼和數據塊象征性的放入內存。
- 決定數據和指令標簽的地址
- 修補內部和外部引用。
連接器可以生成可運行的文件,內容和目標文件差不多,但不包括未解決的引用。
加載器:
- 讀取可執行文件頭來確定代碼段的地址空間
- 為代碼和數據創建一個足夠大的地址空間
- 將可執行文件的指令和數據復制到內存中
- 把主程序的參數復制到棧中
- 初始化處理器中的相關寄存器,將棧指針指向第一個空位置
- 條狀到啟動例程,該例程將參數復制到參數寄存器並且調用程序的main函數。