mips中的li_MIPS学习笔记(一)
寫在前面
本文是根據(jù)"MIPS Assembly Language Programming CS50 Discussion and Project Book. Daniel J. Ellard"總結(jié)的。其中有大量的翻譯文體以及個(gè)人的看法想法,當(dāng)然,內(nèi)容沒有書上那么詳盡。
這一章節(jié)會涉及MIPS的變量的聲明、數(shù)據(jù)的輸入輸出、取地址、分支跳轉(zhuǎn)語句(用以實(shí)行循環(huán)、判斷等),基本上對應(yīng)于任何一門高級語言的最基本操作。
簡介
機(jī)器語言
正如我們在前一章中所看到的,計(jì)算機(jī)指令可以表示為位序列。一般來說,這是程序可能的最低表示級別——每條指令都相當(dāng)于CPU的單個(gè)不可分割的動作。這種表示被稱為機(jī)器語言,因?yàn)樗菣C(jī)器可以直接“理解”的唯一形式
匯編語言
一個(gè)高一層的表示(而且對人類來說更容易使用)稱為匯編語言。匯編語言與機(jī)器語言有著非常密切的關(guān)系,通常有一種直接的方法將匯編語言編寫的程序翻譯成機(jī)器語言。(此算法通常由一個(gè)名為匯編程序(assembler)的程序?qū)崿F(xiàn)。)
因?yàn)闄C(jī)器和匯編語言關(guān)系很近,每個(gè)不同的機(jī)器體系結(jié)構(gòu)通常都有自己的匯編語言(事實(shí)上,每個(gè)體系結(jié)構(gòu)可能有幾個(gè)),并且每個(gè)都是唯一的。用 assember(而不是機(jī)器語言)編程的優(yōu)勢在于,匯編語言更容易讓人閱讀和理解。
開始編程
在這一章節(jié)中不會正式介紹所有指令,只是為了熟悉匯編和部分算法
1. 初步了解匯編
涉及到的新指令/標(biāo)簽
#
/**/
li
add
main
addi
syscall
正文
下面由一個(gè)add.asm為例開始了解匯編(語法結(jié)構(gòu)等等),且暫時(shí)不嚴(yán)格要求指令之間的不同之處(后續(xù)會介紹具有類似功能的指令也會有一些差異)
1.1 注釋
在開始編寫程序的可執(zhí)行語句之前,我們需要編寫一個(gè)描述程序應(yīng)該做什么的注釋。
#和/**/都可以寫注釋
1.2 尋找正確的指令
由于MIPS體系結(jié)構(gòu)的指令相對較少,很快你就會記住你需要的所有指令,但是隨著你開始,你需要花一些時(shí)間瀏覽指令列表,尋找那些你可以用來做你想做的事情的指令。這些可以在第二部分找到。
加法運(yùn)算需要三個(gè)操作數(shù)(operant)
li(load immediate value):將32位常量(32-bit constant)放入指定寄存器中
# add.asm
# begin of add.asm
li $t1,1 #load 1into register $t1
add $t0,$t1,2 #$t0 = $t1 + 2
1.3 補(bǔ)全程序
上面兩條指令執(zhí)行我們想要的計(jì)算,但它們并不構(gòu)成一個(gè)完整的程序。與C類似,匯編語言程序必須包含一些附加信息,這些信息告訴匯編程序的開始和結(jié)束位置。此信息的確切形式因匯編程序而異(請注意,對于給定的體系結(jié)構(gòu),可能有多個(gè)匯編程序,而對于MIPS體系結(jié)構(gòu),可能有多個(gè)匯編程序)。本教程假設(shè) SPIM被用作匯編和運(yùn)行時(shí)環(huán)境。
(1)Label和main
標(biāo)簽(Label)是內(nèi)存中地址的符號名稱。在MIPS程序集中,標(biāo)簽是符號名(遵循與C符號名相同的約定),后跟冒號(colon)。
使用不同形式的Label可以很好地將指令分類,在執(zhí)行程序之前,我們需要告訴匯編程序從哪里開始。在SPIM中,程序的執(zhí)行從帶有標(biāo)簽main的位置開始。
內(nèi)存中的一個(gè)位置可能有多個(gè)標(biāo)簽。因此,為了告訴 SPIM應(yīng)該將label main分配給程序的第一條指令。
# add.asm
# begin of add.asm
main:
li $t1,1 #load 1into register $t1
add $t0,$t1,2 #$t0 = $t1 + 2# end of add.asm
注意,SPIM匯編程序不允許將指令名用作標(biāo)簽。因此,不允許使用名為add的標(biāo)簽,因?yàn)榇嬖谕噶睢?當(dāng)然,由于指令名都很短而且相當(dāng)通用(li, lw等),因此它們通常不會生成非常描述性的標(biāo)簽名。)
(2)syscall調(diào)用
程序的結(jié)尾與C類似,在C中可以調(diào)用exit函數(shù)來停止程序的執(zhí)行,停止MIPS程序的一種方法是使用類似于在C中調(diào)用exit的方法。
但是,與C不同,如果忘記“調(diào)用exit”,則當(dāng)程序到達(dá)主函數(shù)的末尾時(shí),它可能不會直接退出。相反,它會在內(nèi)存中出錯(cuò),將找到的任何內(nèi)容解釋為執(zhí)行指令。
使用 syscall告訴 SPIM它應(yīng)該停止執(zhí)行的程序,以及執(zhí)行許多其他有用的事情的方法。syscall指令暫停程序的執(zhí)行,并將控制權(quán)傳輸?shù)讲僮飨到y(tǒng)。然后,操作系統(tǒng)查看寄存器$v0的內(nèi)容,以確定程序要求它執(zhí)行的操作。
這通過在執(zhí)行syscall指令之前將10(exit syscall的編號)放入 $v0來完成的
# add.asm
# begin of add.asm
main:
li $t1,1 #load 1into register $t1
add $t0,$t1,2 #$t0 = $t1 + 2# exit
li $v0,10syscall
# end of add.asm
2. 了解syscall
新指令/標(biāo)簽
.data
.text
move
syscall 1
syscall 5
syscall 10
正文
算法中我們還不知道該怎么做的部分是從用戶那里讀取數(shù)字,并打印出,這兩個(gè)操作都可以通過系統(tǒng)調(diào)用完成。
2.1 讀取和打印整數(shù)
在C中,我們?nèi)绻蛴≥敵鲆粋€(gè)數(shù)字:printf("%d",x);, 但是那么在 mips是沒有這樣的一個(gè)print指令的,唯一一個(gè)可以用于輸出的指令是syscall,在特定條件下它是可以起到輸出的作用的。它的使用要配合其他的寄存器($v0),$v0中儲存不同的值在調(diào)用syscall時(shí)會有不同的作用
$v0 = 1, syscall -> print_in (output)
$v0 = 5, syscall -> read_in (into $v0, input)
$v0 = 10, syscall -> exit
(1)syscall 1
我們現(xiàn)在知道怎樣將syscall的作用定義為打印輸出(即完成了%d部分),但是我們要打印輸出哪個(gè)寄存器的內(nèi)容呢(即找出需要被打印的x)?在mips中syscall只能打印$a0中的內(nèi)容(無法指定被打印的寄存器),為此,我們需要將x(位于某個(gè)寄存器)的值存儲至$a0寄存器,供syscall調(diào)用和輸出。MIPS中有一個(gè)move指令,它將一個(gè)寄存器的內(nèi)容復(fù)制到另一個(gè)寄存器中。
# print_in: 1addi $t0, $zero,1move $a0,$t0 #不推薦使用li給$a0賦值,那樣就不是打印$t0的內(nèi)容了,沒有意義
li $v0,1# 將1存儲至$v0中,提示syscall的作用為打印整數(shù)
syscall
(2)syscall 5
# read and print Integer
# $t0-used to hold the first number
# $t1-used to hold the second number
# $t2-used to hold the sum of the $t1 and $t2
# $v0-syscall paramenter and returnvalue
# $a0-syscall parameter
# start
main:
#get the first number fromuser, put into $to
li $v0,5 #load syscall read_int(5 represents this) into $v0
syscall #make the syscall
# 這之后在控制臺輸入一個(gè)整數(shù)并回車,用戶輸入的數(shù)據(jù)將被存儲在$v0中
move $t0,$v0 #move the number(5) read into $t0
#get the second number fromuser, put into $t1
li $v0,5 #load syscall read_int(5 represents this) into $v0
syscall #make the syscall
move $t1,$v0 #move the number(5) read into $t1
# sum, put into $t2
add $t2,$t0,$t1
# printout$t2
move $a0,$t2 # move the number to print into $a0
li $v0,1# load syscall print_int into $v0
syscall # make the syscall
# exit
li $v0,10 # syscall code 10 is forexit
syscall
# end
2.2 hello world
新指令/標(biāo)簽
la
.asciiz
.ascii
.byte
syscall 4
正文
$v0 = 4, syscall -> print_string (output)
我們需要將被打印字符串 "hello world" 的地址放入$a0中,將$v0的值設(shè)為4,再進(jìn)行syscall的調(diào)用輸出即可。接下來我們會學(xué)習(xí)如何定義一個(gè)(字符串)變量并使用它。
像是在C語言中一樣,MIPS中也可以對地址進(jìn)行調(diào)用,而這里我們定義了變量后如果想要對這個(gè)變量調(diào)用可以通過訪問其變量名存儲的地址來實(shí)現(xiàn)。指令la可以獲取并存儲變量地址。
字符串“Hello World”不應(yīng)該是程序的可執(zhí)行部分(包含要執(zhí)行的所有指令)的一部分,該部分稱為程序的文本段 (text segment)。相反,字符串應(yīng)該是程序使用的數(shù)據(jù)的一部分,按照慣例,它存儲在數(shù)據(jù)段中。MIPS匯編程序允許程序員通過使用幾個(gè)匯編程序指令來指定在程序中存儲每個(gè)項(xiàng)的段。
為了在數(shù)據(jù)段中放置一些東西,我們需要做的就是在定義它之前放置一個(gè)· .data。.data指令和下一個(gè) .text指令(或文件結(jié)尾)之間的所有內(nèi)容都被放入數(shù)據(jù)段中。注意,默認(rèn)情況下,匯編程序從文本段開始,這就是為什么即使我們沒有明確提到要使用哪個(gè)段,早期的程序仍舊工作正常。
我們還需要知道如何定義和為空結(jié)束的字符串分配空間。在MIPS匯編程序中,這可以使用 .asciiz(ASCII,以零結(jié)尾的字符串)指令來完成。對于以非空結(jié)尾的字符串,可以使用 .ascii指令(directive)
.data
hello_msg: .asciiz"hello world\n"# 這里\n依舊有效.text
main:
la $a0, hello_msg # load the addr of hello_msg into register $a0
li $v0,4syscall
# exit
li $vo,10syscall
數(shù)據(jù)段中的數(shù)據(jù)被組裝到相鄰的位置。因此,有很多方法可以聲明字符串“Hello World\n”并獲得相同的準(zhǔn)確輸出。嘗試各種方法的輸入輸出可以很快地熟悉這門語言。
# Method 1.data
hello_msg: .ascii"Hello".ascii" ".ascii"World".ascii"\n".byte 0 # a 0 byte# Method2.data
hello_msg: .byte 0x48 # hex for ASCII "H".byte 0x65 # hex for ASCII "e".byte 0x6C.byte 0x6C.byte 0x6F# ... # and so on
.byte 0xA # hex forASCII newline
.byte 0x0 # hex for ASCII NULL
2.3 條件執(zhí)行
新指令/標(biāo)簽
bgt
b endif, endif 為標(biāo)簽名
正文
我們將編寫的下一個(gè)程序?qū)⑻接懺贛IPS匯編語言中實(shí)現(xiàn)條件執(zhí)行的問題。
我們將要編寫的實(shí)際程序?qū)挠脩裟抢镒x取兩個(gè)數(shù)字,并打印出其中較大的一個(gè)。
下面我們先用占位符(placeholder comment)代表這個(gè)操作表示出該算法:
# start
.text
main:
# Get first numberfromuser, put into $t0.
syscall # make the syscall.
move $t0, $v0 # move the number read into $t0.
# Get second numberfromuser, put into $t1.
li $v0,5# load syscall read_int into $v0.
syscall # make the syscall.
move $t1, $v0 # move the number read into $t1.
# (placeholder comment)
# put the larger of $t0 and $t1 into $t2.
# Printout$t2.
move $a0, $t2 # move the number to print into $a0.
li $v0,1# load syscall print_int into $v0.
syscall # make the syscall.
# exit
li $v0,10 # syscall code 10 is forexit.
syscall # make the syscall.
# end
分支指令之一是 bgt。bgt指令有三個(gè)參數(shù)。前兩個(gè)是數(shù)字,最后一個(gè)是標(biāo)簽。
如果第一個(gè)數(shù)字大于第二個(gè)數(shù)字,則應(yīng)在標(biāo)簽處繼續(xù)執(zhí)行;
否則將在下一條指令處繼續(xù)執(zhí)行。
另一方面,b指令只是分支到給定的標(biāo)簽。(無條件跳轉(zhuǎn))
占位符所在位置的程序:
# bgt, branch greater than
bgt $t0, $t1, t0_bigger #if &to>$t1, branch to t0_bigger
move $t2, $t1 #elsecopy $t1 into $t2
b endif # and then branch to endif
# b endif:一次判斷賦值后直接跳轉(zhuǎn)至結(jié)尾防止重復(fù)賦值
t0_bigger:
move $t2, $t0 # copy $t0 into $t2
endif:
完整的程序:
# start
.text
main:
# the first numberfrom user/(console)
li $v0,5syscall
move $t0,$v0
# the second numberfrom user/(console)
li $v0,5#由于第一個(gè)數(shù)的獲取,此時(shí)的$v0可能不是5,需要重新賦值
syscall
move $t1,$v0
# judge
bgt $t0,$t1,int_greater #ifmove $a0,$t1 #elseli $v0,1 # set$v0
syscall # print(make syscall)
b endif # branch to end
int_greater:
move $a0,$t0 # ifSo-then
li $v0,1 # set$v0
syscall # print(make syscall)
endif:
# exit
li $v0,10 # set$v0
syscall # exit(make syscall)
# end
至此,本次內(nèi)容基本結(jié)束,一些更為復(fù)雜的數(shù)據(jù)結(jié)構(gòu)(如數(shù)組的聲明)和算法(如對應(yīng)于C語言中的switch)可以通過對下一章介紹的指令集的內(nèi)容自行研究。
總結(jié)
以上是生活随笔為你收集整理的mips中的li_MIPS学习笔记(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端css之 浮动 自学日记
- 下一篇: Spark入门实战系列--1.Spark