mips中的li_MIPS学习笔记(一)
寫在前面
本文是根據"MIPS Assembly Language Programming CS50 Discussion and Project Book. Daniel J. Ellard"總結的。其中有大量的翻譯文體以及個人的看法想法,當然,內容沒有書上那么詳盡。
這一章節會涉及MIPS的變量的聲明、數據的輸入輸出、取地址、分支跳轉語句(用以實行循環、判斷等),基本上對應于任何一門高級語言的最基本操作。
簡介
機器語言
正如我們在前一章中所看到的,計算機指令可以表示為位序列。一般來說,這是程序可能的最低表示級別——每條指令都相當于CPU的單個不可分割的動作。這種表示被稱為機器語言,因為它是機器可以直接“理解”的唯一形式
匯編語言
一個高一層的表示(而且對人類來說更容易使用)稱為匯編語言。匯編語言與機器語言有著非常密切的關系,通常有一種直接的方法將匯編語言編寫的程序翻譯成機器語言。(此算法通常由一個名為匯編程序(assembler)的程序實現。)
因為機器和匯編語言關系很近,每個不同的機器體系結構通常都有自己的匯編語言(事實上,每個體系結構可能有幾個),并且每個都是唯一的。用 assember(而不是機器語言)編程的優勢在于,匯編語言更容易讓人閱讀和理解。
開始編程
在這一章節中不會正式介紹所有指令,只是為了熟悉匯編和部分算法
1. 初步了解匯編
涉及到的新指令/標簽
#
/**/
li
add
main
addi
syscall
正文
下面由一個add.asm為例開始了解匯編(語法結構等等),且暫時不嚴格要求指令之間的不同之處(后續會介紹具有類似功能的指令也會有一些差異)
1.1 注釋
在開始編寫程序的可執行語句之前,我們需要編寫一個描述程序應該做什么的注釋。
#和/**/都可以寫注釋
1.2 尋找正確的指令
由于MIPS體系結構的指令相對較少,很快你就會記住你需要的所有指令,但是隨著你開始,你需要花一些時間瀏覽指令列表,尋找那些你可以用來做你想做的事情的指令。這些可以在第二部分找到。
加法運算需要三個操作數(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 補全程序
上面兩條指令執行我們想要的計算,但它們并不構成一個完整的程序。與C類似,匯編語言程序必須包含一些附加信息,這些信息告訴匯編程序的開始和結束位置。此信息的確切形式因匯編程序而異(請注意,對于給定的體系結構,可能有多個匯編程序,而對于MIPS體系結構,可能有多個匯編程序)。本教程假設 SPIM被用作匯編和運行時環境。
(1)Label和main
標簽(Label)是內存中地址的符號名稱。在MIPS程序集中,標簽是符號名(遵循與C符號名相同的約定),后跟冒號(colon)。
使用不同形式的Label可以很好地將指令分類,在執行程序之前,我們需要告訴匯編程序從哪里開始。在SPIM中,程序的執行從帶有標簽main的位置開始。
內存中的一個位置可能有多個標簽。因此,為了告訴 SPIM應該將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匯編程序不允許將指令名用作標簽。因此,不允許使用名為add的標簽,因為存在同名指令。(當然,由于指令名都很短而且相當通用(li, lw等),因此它們通常不會生成非常描述性的標簽名。)
(2)syscall調用
程序的結尾與C類似,在C中可以調用exit函數來停止程序的執行,停止MIPS程序的一種方法是使用類似于在C中調用exit的方法。
但是,與C不同,如果忘記“調用exit”,則當程序到達主函數的末尾時,它可能不會直接退出。相反,它會在內存中出錯,將找到的任何內容解釋為執行指令。
使用 syscall告訴 SPIM它應該停止執行的程序,以及執行許多其他有用的事情的方法。syscall指令暫停程序的執行,并將控制權傳輸到操作系統。然后,操作系統查看寄存器$v0的內容,以確定程序要求它執行的操作。
這通過在執行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
新指令/標簽
.data
.text
move
syscall 1
syscall 5
syscall 10
正文
算法中我們還不知道該怎么做的部分是從用戶那里讀取數字,并打印出,這兩個操作都可以通過系統調用完成。
2.1 讀取和打印整數
在C中,我們如果要打印輸出一個數字:printf("%d",x);, 但是那么在 mips是沒有這樣的一個print指令的,唯一一個可以用于輸出的指令是syscall,在特定條件下它是可以起到輸出的作用的。它的使用要配合其他的寄存器($v0),$v0中儲存不同的值在調用syscall時會有不同的作用
$v0 = 1, syscall -> print_in (output)
$v0 = 5, syscall -> read_in (into $v0, input)
$v0 = 10, syscall -> exit
(1)syscall 1
我們現在知道怎樣將syscall的作用定義為打印輸出(即完成了%d部分),但是我們要打印輸出哪個寄存器的內容呢(即找出需要被打印的x)?在mips中syscall只能打印$a0中的內容(無法指定被打印的寄存器),為此,我們需要將x(位于某個寄存器)的值存儲至$a0寄存器,供syscall調用和輸出。MIPS中有一個move指令,它將一個寄存器的內容復制到另一個寄存器中。
# print_in: 1addi $t0, $zero,1move $a0,$t0 #不推薦使用li給$a0賦值,那樣就不是打印$t0的內容了,沒有意義
li $v0,1# 將1存儲至$v0中,提示syscall的作用為打印整數
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
# 這之后在控制臺輸入一個整數并回車,用戶輸入的數據將被存儲在$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
新指令/標簽
la
.asciiz
.ascii
.byte
syscall 4
正文
$v0 = 4, syscall -> print_string (output)
我們需要將被打印字符串 "hello world" 的地址放入$a0中,將$v0的值設為4,再進行syscall的調用輸出即可。接下來我們會學習如何定義一個(字符串)變量并使用它。
像是在C語言中一樣,MIPS中也可以對地址進行調用,而這里我們定義了變量后如果想要對這個變量調用可以通過訪問其變量名存儲的地址來實現。指令la可以獲取并存儲變量地址。
字符串“Hello World”不應該是程序的可執行部分(包含要執行的所有指令)的一部分,該部分稱為程序的文本段 (text segment)。相反,字符串應該是程序使用的數據的一部分,按照慣例,它存儲在數據段中。MIPS匯編程序允許程序員通過使用幾個匯編程序指令來指定在程序中存儲每個項的段。
為了在數據段中放置一些東西,我們需要做的就是在定義它之前放置一個· .data。.data指令和下一個 .text指令(或文件結尾)之間的所有內容都被放入數據段中。注意,默認情況下,匯編程序從文本段開始,這就是為什么即使我們沒有明確提到要使用哪個段,早期的程序仍舊工作正常。
我們還需要知道如何定義和為空結束的字符串分配空間。在MIPS匯編程序中,這可以使用 .asciiz(ASCII,以零結尾的字符串)指令來完成。對于以非空結尾的字符串,可以使用 .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
數據段中的數據被組裝到相鄰的位置。因此,有很多方法可以聲明字符串“Hello World\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 條件執行
新指令/標簽
bgt
b endif, endif 為標簽名
正文
我們將編寫的下一個程序將探討在MIPS匯編語言中實現條件執行的問題。
我們將要編寫的實際程序將從用戶那里讀取兩個數字,并打印出其中較大的一個。
下面我們先用占位符(placeholder comment)代表這個操作表示出該算法:
# 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指令有三個參數。前兩個是數字,最后一個是標簽。
如果第一個數字大于第二個數字,則應在標簽處繼續執行;
否則將在下一條指令處繼續執行。
另一方面,b指令只是分支到給定的標簽。(無條件跳轉)
占位符所在位置的程序:
# 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:一次判斷賦值后直接跳轉至結尾防止重復賦值
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#由于第一個數的獲取,此時的$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
至此,本次內容基本結束,一些更為復雜的數據結構(如數組的聲明)和算法(如對應于C語言中的switch)可以通過對下一章介紹的指令集的內容自行研究。
總結
以上是生活随笔為你收集整理的mips中的li_MIPS学习笔记(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端css之 浮动 自学日记
- 下一篇: Spark入门实战系列--1.Spark