新聞中心

EEPW首頁 > 學習方法與實踐 > MIPS匯編語言的特點

MIPS匯編語言的特點

——
作者: 時間:2007-10-24 來源:嵌入式在線論壇 收藏
是CPU二進制指令的可讀寫版本。我們在后面將有單獨的一章來講述。從來沒有接觸過的讀者在閱讀本書時可能會有一些迷惑 。 

大多數(shù)匯編語言都是非常古板的,都是一些寄存器號碼。但是工具鏈(toolchains)可以使得使用微處理機語言變得簡單。工具鏈至少允許程序員引用一些助記符,而嚴格的匯編語言要求嚴格的數(shù)字編碼。大多我們都是用比較熟悉的C預處理器。C預處理器會把C風格的注解去掉,而得到一個可用的匯編代碼。 

有C預處理器的幫助,匯編程序都是用助記符來表示寄存器。助記符同時也代表了每個寄存器的用法(我們將在2.2節(jié)介紹這一點)。 

對於熟悉匯編語言但不熟悉的讀者,下面是一些例子。 

/* this is a comment */ 
#so is this 

entrypoint: #this's a label 

addu $1, $2, $3 # (registers) $1 = $2 + $3 

與大多數(shù)匯編語言一樣, MIPS匯編語言也是以行為單位的。每一行的結束是一個指令的結束,并且忽略任何“#”之后的內(nèi)容,認為是注釋。在一行里可以有多條指令。指令之間要用分號“;”隔開。 

一個符號(label)是一個后面跟著冒號“:”的字。符號可以是任何字符串的組合。符號被用來定義一段代碼的入口和定義數(shù)據(jù)段的一個存儲位置。 

如上所示,許多指令都是3個操作數(shù)/符(operand)。目標寄存器在左側(注意,這一點與Intetel x86 正相反)。一般而言,寄存器結果和操作符的順序與C語言或其他符號語言的方式是一致的。 例如: 

subc $1, $2, $3 

意味著: 

$1 = $2 - $3; 

這方面我們就先講這么多。 

2.2 寄存器 

對於一個程序,可以有32個通用寄存器,分別為:$0-$31。其中,兩個,也只有兩個的使用不同于其他。 

$0:不管你存放什么值,其返回值永遠是零。 

$31:永遠存放著正常函數(shù)調(diào)用指令(jal)的返回地址。請注意call-by-registe的jalr指令可以使用任何寄存器來存放其返回地址。當然,如不用$31,看起來程序會有點古怪。 

其他方面,所有的寄存器都是一樣的。可以被用在任何一個指令中(你也可以用$0作為一個指令的目標寄存器。當然不管你存入什么數(shù)據(jù),數(shù)據(jù)都消失了。) 

MIPS體系結構下,程序計數(shù)器不是一個寄存器,其實你最好不要去那樣想。在一個具有流水線的CPU中,程序計數(shù)器的值在一個給定的時刻有多個可選值。這一點有點迷惑人。jal指令的返回地址跟隨其后的第二條指令。 

... 
jal printf 
move $4, $6 
xxx # return here after call 

上述的解釋是有道理的,因為緊跟蹤jal指令后面的指令,由於在delay slot(延遲位置)上--請記住,關于延遲位置的規(guī)則是該指令將在轉(zhuǎn)移目標(如上述的printf)之前執(zhí)行。延遲位置指令經(jīng)常被用來傳遞函數(shù)調(diào)用的參數(shù)。 

MIPS里沒有狀態(tài)碼。CPU狀態(tài)寄存器或內(nèi)部都不包含任何用戶程序計算的結果狀態(tài)信息。 

hi和lo是與乘法運算器相關的兩個寄存器大小的用來存放結果的地方。它們并不是通用寄存器,除了用在乘除法之外,也不能有做其他用途。但是,MIPS里定義了一些指令可以往hi和lo里存入任何值。想一想我們會發(fā)現(xiàn),這是非常有必要的當你想要恢復一個被打斷的程序時。 

浮點運算協(xié)處理器(浮點加速器,F(xiàn)PA),如果存在的話,有32個浮點寄存器。按匯編語言的簡單約定講,是從$f0到$31。 

實際上,對於MIPS I和MIPS II的機器,只有16個偶數(shù)號的寄存器可以用來做數(shù)學計算。當然,它們可以既用來做單精度(32位)和雙精度(64位)。當你做一個雙精度的運算時,寄存器$f1存放$f0的余數(shù)。奇數(shù)號的寄存器只用來作為寄存器與FPA之間的數(shù)據(jù)傳送。 

MIPS III CPU有32個FP寄存器。但是為了保持軟件與過去的兼容性,最好不要用奇數(shù)號的寄存器。 

2.2.1 助記符與通用寄存器的用法 

我們已經(jīng)描述了一些體系結構方面的內(nèi)容,下面來介紹一些軟件方面的內(nèi)容。 

寄存器編號 助記符 用法 
0 zero 永遠返回值為0 
1 at 用做匯編器的暫時變量 
2-3 v0, v1 子函數(shù)調(diào)用返回結果 
4-7 a0-a3 子函數(shù)調(diào)用的參數(shù) 
8-15 t0-t7 暫時變量,子函數(shù)使用時不需要保存與恢復 
24-25 t8-t9 
16-25 s0-s7 子函數(shù)寄存器變量。子函數(shù)必須保存和恢復使用過的變量在函數(shù)返回之前,從而調(diào)用函數(shù)知道這些寄存器的值沒有變化。 
26,27 k0,k1 通常被中斷或異常處理程序使用作為保存一些系統(tǒng)參數(shù) 
28 gp 全局指針。一些運行系統(tǒng)維護這個指針來更方便的存取“static“和”extern"變量。 
29 sp 堆棧指針 
30 s8/fp 第9個寄存器變量。子函數(shù)可以用來做楨指針 
31 ra 子函數(shù)的返回地□ 
'7d 

雖然硬件沒有強制性的指定寄存器使用規(guī)則,在實際使用中,這些寄存器的用法都遵循一系列約定。這些約定與硬件確實無關,但如果你想使用別人的代碼,編譯器和操作系統(tǒng),你最好是遵循這些約定。 

寄存器約定用法引人了一系列的寄存器約定名。在使用寄存器的時候,要盡量用這些約定名或助記符,而不直接引用寄存器編號。 

1996年左右,SGI開始在其提供的編譯器中使用新的寄存器約定。這種新約定可以用來建立使用32位地址或64位地址的程序,分別叫 "n32"和"n64"。我們暫時不討論這些,將會在第10章詳細討論。 

寄存器名約定與使用 

*at: 這個寄存器被匯編的一些合成指令使用。如果你要顯示的使用這個寄存器(比如在異常處理程序中保存和恢復寄存器),有一個匯編directive可被用來禁止匯編器在directive之后再使用at寄存器(但是匯編的一些宏指令將因此不能再可用)。 

*v0, v1: 用來存放一個子程序(函數(shù))的非浮點運算的結果或返回值。如果這兩個寄存器不夠存放需要返回的值,編譯器將會通過內(nèi)存來完成。詳細細節(jié)可見10.1節(jié)。 


*a0-a3: 用來傳遞子函數(shù)調(diào)用時前4個非浮點參數(shù)。在有些情況下,這是不對的。請參考10.1細節(jié)。 

* t0-t9: 依照約定,一個子函數(shù)可以不用保存并隨便的使用這些寄存器。在作表達式計算時,這些寄存器是非常好的暫時變量。編譯器/程序員必須注意的是,當調(diào)用一個子函數(shù)時,這些寄存器中的值有可能被子函數(shù)破壞掉。 

*s0-s8: 依照約定,子函數(shù)必須保證當函數(shù)返回時這些寄存器的內(nèi)容必須恢復到函數(shù)調(diào)用以前的值,或者在子函數(shù)里不用這些寄存器或把它們保存在堆棧上并在函數(shù)退出時恢復。這種約定使得這些寄存器非常適合作為寄存器變量或存放一些在函數(shù)調(diào)用期間必須保存原來值。 

* k0, k1: 被OS的異常或中斷處理程序使用。被使用后將不會恢復原來的值。因此它們很少在別的地方被使用。 

* gp: 如果存在一個全局指針,它將指向運行時決定的,你的靜態(tài)數(shù)據(jù)(static data)區(qū)域的一個位置。這意味著,利用gp作基指針,在gp指針32K左右的數(shù)據(jù)存取,系統(tǒng)只需要一條指令就可完成。如果沒有全局指針,存取一個靜態(tài)數(shù)據(jù)區(qū)域的值需要兩條指令:一條是獲取有編譯器和loader決定好的32位的地址常量。另外一條是對數(shù)據(jù)的真正存取。為了使用gp, 編譯器在編譯時刻必須知道一個數(shù)據(jù)是否在gp的64K范圍之內(nèi)。通常這是不可能的,只能靠猜測。一般的做法是把small global data (小的全局數(shù)據(jù))放在gp覆蓋的范圍內(nèi)(比如一個變量是8字節(jié)或更小),并且讓linker報警如果小的全局數(shù)據(jù)仍然太大從而超過gp作為一個基指針所能存取的范圍。 

并不是所有的編譯和運行系統(tǒng)支持gp的使用。 

*sp: 堆棧指針的上下需要顯示的通過指令來實現(xiàn)。因此MIPS通常只在子函數(shù)進入和退出的時刻才調(diào)整堆棧的指針。這通過被調(diào)用的子函數(shù)來實現(xiàn)。sp通常被調(diào)整到這個被調(diào)用的子函數(shù)需要的堆棧的最低的地方,從而編譯器可以通過相對於sp的偏移量來存取堆棧上的堆棧變量。詳細可參閱10.1節(jié)堆棧使用。 

* fp: fp的另外的約定名是s8。如果子函數(shù)想要在運行時動態(tài)擴展堆棧大小,fp作為楨指針可以被子函數(shù)用來記錄堆棧的情況。一些編程語言顯示的支持這一點。匯編編程員經(jīng)常會利用fp的這個用法。C語言的庫函數(shù)alloca()就是利用了fp來動態(tài)調(diào)整堆棧的。 

如果堆棧的底部在編譯時刻不能被決定,你就不能通過sp來存取堆棧變量,因此fp被初始化為一個相對與該函數(shù)堆棧的一個常量的位置。這種用法對其他函數(shù)是不可見的。 

* ra: 當調(diào)用任何一個子函數(shù)時,返回地址存放在ra寄存器中,因此通常一個子程序的最后一個指令是jr ra. 

子函數(shù)如果還要調(diào)用其他的子函數(shù),必須保存ra的值,通常通過堆棧。 

對於浮點寄存器的用法,也有一個相應的標準的約定。我們將在7.5節(jié)。在這里,我們已經(jīng)介紹了MIPS引入的寄存 


關鍵詞: MIPS 匯編語言

評論


相關推薦

技術專區(qū)

關閉