新聞中心

EEPW首頁(yè) > 學(xué)習(xí)方法與實(shí)踐 > MIPS匯編語(yǔ)言的特點(diǎn)

MIPS匯編語(yǔ)言的特點(diǎn)

——
作者: 時(shí)間:2007-10-24 來(lái)源:嵌入式在線論壇 收藏
是CPU二進(jìn)制指令的可讀寫(xiě)版本。我們?cè)诤竺鎸⒂袉为?dú)的一章來(lái)講述。從來(lái)沒(méi)有接觸過(guò)的讀者在閱讀本書(shū)時(shí)可能會(huì)有一些迷惑 。 

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

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

對(duì)於熟悉匯編語(yǔ)言但不熟悉的讀者,下面是一些例子。 

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

entrypoint: #this's a label 

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

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

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

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

subc $1, $2, $3 

意味著: 

$1 = $2 - $3; 

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

2.2 寄存器 

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

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

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

其他方面,所有的寄存器都是一樣的??梢员挥迷谌魏我粋€(gè)指令中(你也可以用$0作為一個(gè)指令的目標(biāo)寄存器。當(dāng)然不管你存入什么數(shù)據(jù),數(shù)據(jù)都消失了。) 

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

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

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

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

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

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

實(shí)際上,對(duì)於MIPS I和MIPS II的機(jī)器,只有16個(gè)偶數(shù)號(hào)的寄存器可以用來(lái)做數(shù)學(xué)計(jì)算。當(dāng)然,它們可以既用來(lái)做單精度(32位)和雙精度(64位)。當(dāng)你做一個(gè)雙精度的運(yùn)算時(shí),寄存器$f1存放$f0的余數(shù)。奇數(shù)號(hào)的寄存器只用來(lái)作為寄存器與FPA之間的數(shù)據(jù)傳送。 

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

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

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

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

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

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

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

寄存器名約定與使用 

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

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


*a0-a3: 用來(lái)傳遞子函數(shù)調(diào)用時(shí)前4個(gè)非浮點(diǎn)參數(shù)。在有些情況下,這是不對(duì)的。請(qǐng)參考10.1細(xì)節(jié)。 

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

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

* k0, k1: 被OS的異?;蛑袛嗵幚沓绦蚴褂?。被使用后將不會(huì)恢復(fù)原來(lái)的值。因此它們很少在別的地方被使用。 

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

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

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

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

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

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

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

對(duì)於浮點(diǎn)寄存器的用法,也有一個(gè)相應(yīng)的標(biāo)準(zhǔn)的約定。我們將在7.5節(jié)。在這里,我們已經(jīng)介紹了MIPS引入的寄存 


關(guān)鍵詞: MIPS 匯編語(yǔ)言

評(píng)論


相關(guān)推薦

技術(shù)專(zhuān)區(qū)

關(guān)閉