写在前面

  本文是根据"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 1 into 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 1 into 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 1 into register $t1
add $t0,$t1,2 #$t0 = $t1 + 2
# exit
li $v0, 10
syscall
# 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: 1
addi $t0, $zero, 1
move $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 return value
# $a0 -syscall parameter
# start
main:
# get the first number from user, 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 from user, 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 # print out $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 for exit
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,4
syscall # exit
li $vo,10
syscall

  数据段中的数据被组装到相邻的位置。因此,有很多方法可以声明字符串“Hello World\n”并获得相同的准确输出。尝试各种方法的输入输出可以很快地熟悉这门语言。

# Method 1
.data
hello_msg: .ascii "Hello"
.ascii " "
.ascii "World"
.ascii "\n"
.byte 0 # a 0 byte
# Method 2
.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 for ASCII newline
.byte 0x0 # hex for ASCII NULL

2.3 条件执行

新指令/标签

bgt

b endif, endif 为标签名

正文

我们将编写的下一个程序将探讨在MIPS汇编语言中实现条件执行的问题。

我们将要编写的实际程序将从用户那里读取两个数字,并打印出其中较大的一个。

下面我们先用占位符(placeholder comment)代表这个操作表示出该算法:

# start
.text
main:
# Get first number from user, put into $t0.
syscall # make the syscall.
move $t0, $v0 # move the number read into $t0. # Get second number from user, 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. # Print out $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 for exit.
syscall # make the syscall.
# end

分支指令之一是 bgtbgt 指令有三个参数。前两个是数字,最后一个是标签。

如果第一个数字大于第二个数字,则应在标签处继续执行;

否则将在下一条指令处继续执行。

另一方面,b 指令只是分支到给定的标签。(无条件跳转)

占位符所在位置的程序:

# bgt, branch greater than
bgt $t0, $t1, t0_bigger # if &to>$t1, branch to t0_bigger
move $t2, $t1 # else copy $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 number from user/(console)
li $v0,5
syscall
move $t0,$v0 # the second number from user/(console)
li $v0,5 #由于第一个数的获取,此时的$v0可能不是5,需要重新赋值
syscall
move $t1,$v0 # judge
bgt $t0,$t1,int_greater # if
move $a0,$t1 # else
li $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学习笔记(一)的更多相关文章

  1. 【原创】ZYNQ学习笔记(一) HelloWorld实现

    拿过ZYNQ开发板,里面给了很多部件,果断从网上下载了手册,N多手册和原理图. 要比Spartan-6复杂多了,耐心地看了看,知道ZYNQ系列分为PS(系统)以及PL(逻辑)部分. 之前,自己一直在做 ...

  2. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  3. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  4. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  5. 2014年暑假c#学习笔记目录

    2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...

  6. JAVA GUI编程学习笔记目录

    2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...

  7. seaJs学习笔记2 – seaJs组建库的使用

    原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...

  8. CSS学习笔记

    CSS学习笔记 2016年12月15日整理 CSS基础 Chapter1 在console输入escape("宋体") ENTER 就会出现unicode编码 显示"%u ...

  9. HTML学习笔记

    HTML学习笔记 2016年12月15日整理 Chapter1 URL(scheme://host.domain:port/path/filename) scheme: 定义因特网服务的类型,常见的为 ...

随机推荐

  1. Centos-配置网络或显示当前网络接口状态-ifconfig

    ifconfig 配置网络或显示当前网络接口状态,必须由root用户执行 相关选项 -a 显示所有网络接口信息,包括活动或非活动 -s 显示活动接口简要信息 -v 如果网卡接口出现错误则返回错误信息 ...

  2. 开源 C#工作流管理平台

    { font-family: 宋体; panose-1: 2 1 6 0 3 1 1 1 1 1 } @font-face { font-family: "Cambria Math" ...

  3. Python实现的数据结构与算法之链表详解

    一.概述 链表(linked list)是一组数据项的集合,其中每个数据项都是一个节点的一部分,每个节点还包含指向下一个节点的链接.根据结构的不同,链表可以分为单向链表.单向循环链表.双向链表.双向循 ...

  4. 深入了解如何构建您的第一个多语言ASP。NET MVC 5 Web应用程序

    下载demo - 3.9 MB 介绍 这篇文章解释了如何创建一个简单的多语言ASP.NET MVC 5 Web应用程序.该应用程序将能够处理英语(美国),西班牙语和法语.英语将是默认语言.当然,扩展解 ...

  5. axio跨域请求,vue中的config的配置项。

    这是我用 vue cli 脚手架搭建的跨域.以上是可以请求到的.

  6. 第四届58topcoder编程大赛--地图路径规划

    layout: post title: 第四届58topcoder编程大赛 subtitle: 58ACM catalog: true tags: - A* 算法 - C++ - 程序设计 问题及背景 ...

  7. 2014年 实验二 B2C网上购物

    实验二 B2C网上购物 [实验目的] ⑴.熟悉虚拟银行和网上支付的应用 ⑵.熟悉并掌握消费者B2C网上购物和商家的销售处理 [实验条件] ⑴.个人计算机一台 ⑵.计算机通过局域网形式接入互联网 (3) ...

  8. 多测师讲解python _string_高级讲师肖sir

    import stringprint (string.ascii_letters )#大小写英文:'#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW ...

  9. elasticsearch练习

    elasticsearch练习 最近在学习elasticsearch,做了一些练习,分享下练习成果,es基于6.7.2,用kibana处理DSL,有兴趣的伙伴可以自己试试 1.简单查询练习 sourc ...

  10. 【最短路】CF 938D Buy a Ticket

    题目大意 流行乐队"Flayer"将在\(n\)个城市开演唱会,这\(n\)个城市的人都想去听演唱会,每个城市的票价不同,于是这些人就想是否能去其他城市听演唱会更便宜,但是去其他的 ...