組成原理——第二章續
棧的補充
棧在程序的運行過程中起着非常重要的作用。
爲了管理棧,我們設置了兩個指針:$sp, $fp。$sp記錄的是棧最低的部分的地址,$fp記錄的是棧最高的部分的地址。在函數執行過程中所需保存的局部變量、參數、返回地址調用的過程叫做過程幀。引入$fp可以達到快速恢復棧的目的,避免了內存泄露的問題。
程序的執行體所佔有的內存區域可以分爲四大類:
- text-代碼段:用來存放指令
- Static data:靜態數據
- Dynamic data-動態數據:內部使用的堆,C的malloc(),C++和java的 new 都在堆中。
- **Stack-棧:**存儲局部變量
通常來說,堆和棧共享一個內存區域,這樣可以最大限度地利用內存空間。
字符數據-Character Data
字符編碼:
- ASCII碼:現在已經很少使用,一共可以表示128個字符
- Latin-1: 256個字符,這個是對ASCII碼的拓展
- Unicode:三十二位字符集,可以用於表示中文
- UTF-8, UTF-16, 以及一些變長的編碼
爲了處理這些不同的編碼,MIPS準備了以下指令:
完成字符串的複製功能:
以C的代碼爲例
void strcpy(char x[], char y[]){
int i;
i = 0;
while((x[i]=y[i])!='\0') i += 1;
}
假設這裏傳進來的變量是$a0 - x[] 和 $a1 - y[],i是$s0
那麼我們可以有下面的代碼:
srrcpy:
addi $sp, $sp, -4 # 壓棧,壓一個元素
sw $s0, 0($sp) # 保存s0, 這裏相當於是新開一個局部變量, 把原來這個局部變量存的東西存到內存裏。
add $s0, $zero, $zero # 給出 s0初始值, = 0
L1:
add $t1, $s0, $a1 # $t1 代表 $y[i] 的地址
lbu $t2, 0($t1) # t2 = y[i], 這裏是一次讀一個字節,當作無符號數的方法
add $t3, $s0, $a0 # t3 = x[i]
sb $t2, 0($t3) # y[i] = x[i]
beq $t2, $zero, L2 # if (t2 == '\0') goto L2
addi $s0, $s0, 1 # else s0 ++; continue;
j L1
L2:
lw $s0, 0($sp) # 恢復原來的臨時變量
addi $sp, $sp, 4 # 恢復棧區
jr $ra # 返回了
三十二位常數 32-bit Constants
結合我們前面學的知識,我們知道,32位mips中,一條語句的長度是32位,這就代表我們無法在一條語句中將一個三十二位數字賦給一個寄存器,mips爲我們提供了一種方法,下面看兩個語句:
lui $s0, constant
ori $s0, $s0, constant
其中lui代表着將constant數字賦給$s0的高16位,並將$s0的低16位清零。
ori是異或指令,我們都知道0異或x = x ,那麼異或指令也可以用做賦值,所以我們在這裏可以用ori來達到使用立即數給寄存器低16位賦值的效果。
看不懂可以再結合下面的圖看一下。
分支的地址
以beq語句爲例
可以看到跳轉的地址長度只有十六位,那是不是就表明我們程序的長度僅能在以內呢?
並不,其實跳轉的地址是,這樣可以在這個指令前後之內進行轉移如果還不夠用呢?回想一下還有一個更短的語句,叫做。
但是值得注意的是,使用該語句到達的地址也並非address中的地址,而是由PC的高四位和address*4拼接而成的,也就是說:
編譯器可以將跳轉的過遠的條件轉移語句,拆分成條件轉移語句和直接轉移語句的組合來達到遠距離轉移的目的。如果還要遠,那麼可以參考函數跳轉的方法,直接使用直接將轉移的寄存器的地址賦給$pc,這樣就可以實現任意轉移,可以實現在次方的範圍內進行轉移。但是一般情況下代碼不會超過。
尋址模式
數據尋址
- 立即數尋址:操作數放在指令裏面。
- 寄存器尋址:根據寄存器編號尋址
- 基址尋址:根據初始地址和偏移量進行尋址
指令尋址
- 相對PC尋址:PC+偏移地址
- 無條件跳轉:PC高四位和address左移兩位拼接
- 直接賦值跳轉:使用jar,直接改變pc進行跳轉
多進程調度同步機制
ll rt, offset(rs)
sc rt, offset(rs)
這倆看視頻實在是看不懂,我這裏參考了這篇博文:理解MIPS指令集中的ll (load linked) 和 sc (store conditional)指令
ll和sc指令是一種在多處理器系統中實現共享內存的原子操作的方法,且不需要爲了讓一個處理器獨佔它而鎖定它。
意思是,你用ll指令讀取一個內存中的數據並存到一個寄存器,然後在寄存器修改(或不)這個值,隨後用sc指令將它寫入到同樣的(原來的)位置。而sc指令只在你修改寄存器中的值的期間,沒有任何一個處理器改變它內存中的值 這種情況下,將值寫入。它同時需要(的副作用是)設置一個指示狀態的變量來表明是否成功寫入。(成功爲1,失敗爲0)
當新的值成功地被寫入了,那麼可以認爲這個線程在沒有別的線程干涉的情況下完成了(一個值的)讀-改-寫過程。如果失敗了,接下來就取決於程序是要放棄這個操作還是再試一次了,不過至少它(ll&sc)不會生成一個隱性的(不被察覺的)競爭危害。
基於這兩條指令的swap
try:
add $t0, $zero, $s4 # t0 = s4
ll $t0, 0($s1)
sc $t0, 0($s1)
beq $t0, $zero, try
add $s4, $zero, $t1
高級語言的編譯、程序的啓動機制
總體結構
僞指令
查看下面的語句
move $t0, $t1
blt $t0, $t1, L
實際上在mips 指令集中是沒有上面的語句的,爲了解決這一問題,我們使用了僞指令,也就是說,當遇到這樣的僞指令時,將它轉化成對應的mips指令,如下:
add $t0, $zero, $t1
slt $at, $t0, $t1
bne $at, $zero, L
這裏的$at是一個專門給彙編器預留的臨時變量。
中是沒有上面的語句的,爲了解決這一問題,我們使用了僞指令,也就是說,當遇到這樣的僞指令時,將它轉化成對應的mips指令,如下:
add $t0, $zero, $t1
slt $at, $t0, $t1
bne $at, $zero, L
這裏的$at是一個專門給彙編器預留的臨時變量。