CO初识mips知识点

寄存器地址:

伪指令

  • .data:用于预先存储数据的伪指令的开始标志。
  • .text:程序代码指令开始的标志。
  • .word:以字为单位存储数据。
  • .asciiz:以字节为单位存储字符串。
  • .space:申请若干个字节的未初始化的内存空间。

V0 的不同值

常用:

Service Code in $v0 Arguments Result
print integer 1 $a0 = integer to print
print float 2 $f12 = float to print
print double 3 $f12 = double to print
print string 4 $a0 = address of null-terminated string to print
read integer 5 $v0 contains integer read
read float 6 $f0 contains float read
read double 7 $f0 contains double read
read string 8 $a0 = address of input buffer $a1 = maximum number of characters to read See note below table
sbrk (allocate heap memory) 9 $a0 = number of bytes to allocate $v0 contains address of allocated memory
exit (terminate execution) 10
print character 11 $a0 = character to print See note below table
read character 12 $v0 contains character read
open file 13 $a0 = address of null-terminated string containing filename $a1 = flags $a2 = mode $v0 contains file descriptor (negative if error). See note below table
read from file 14 $a0 = file descriptor $a1 = address of input buffer $a2 = maximum number of characters to read $v0 contains number of characters read (0 if end-of-file, negative if error). See note below table
write to file 15 $a0 = file descriptor $a1 = address of output buffer $a2 = number of characters to write $v0 contains number of characters written (negative if error). See note below table
close file 16 $a0 = file descriptor
exit2 (terminate with value) 17 $a0 = termination result See note below table
Services 1 through 17 are compatible with the SPIM simulator, other than Open File (13) as described in the Notes below the table. Services 30 and higher are exclusive to MARS.
time (system time) 30 $a0 = low order 32 bits of system time $a1 = high order 32 bits of system time. See note below table
MIDI out 31 $a0 = pitch (0-127) $a1 = duration in milliseconds $a2 = instrument (0-127) $a3 = volume (0-127) Generate tone and return immediately. See note below table
sleep 32 $a0 = the length of time to sleep in milliseconds. Causes the MARS Java thread to sleep for (at least) the specified number of milliseconds. This timing will not be precise, as the Java implementation will add some overhead.
MIDI out synchronous 33 $a0 = pitch (0-127) $a1 = duration in milliseconds $a2 = instrument (0-127) $a3 = volume (0-127) Generate tone and return upon tone completion. See note below table
print integer in hexadecimal 34 $a0 = integer to print Displayed value is 8 hexadecimal digits, left-padding with zeroes if necessary.
print integer in binary 35 $a0 = integer to print Displayed value is 32 bits, left-padding with zeroes if necessary.
print integer as unsigned 36 $a0 = integer to print Displayed as unsigned decimal value.
(not used) 37-39
set seed 40 $a0 = i.d. of pseudorandom number generator (any int). $a1 = seed for corresponding pseudorandom number generator. No values are returned. Sets the seed of the corresponding underlying Java pseudorandom number generator (java.util.Random). See note below table
random int 41 $a0 = i.d. of pseudorandom number generator (any int). $a0 contains the next pseudorandom, uniformly distributed int value from this random number generator’s sequence. See note below table
random int range 42 $a0 = i.d. of pseudorandom number generator (any int). $a1 = upper bound of range of returned values. $a0 contains pseudorandom, uniformly distributed int value in the range 0 = [int] [upper bound], drawn from this random number generator’s sequence. See note below table
random float 43 $a0 = i.d. of pseudorandom number generator (any int). $f0 contains the next pseudorandom, uniformly distributed float value in the range 0.0 = f 1.0 from this random number generator’s sequence. See note below table
random double 44 $a0 = i.d. of pseudorandom number generator (any int). $f0 contains the next pseudorandom, uniformly distributed double value in the range 0.0 = f 1.0 from this random number generator’s sequence. See note below table
(not used) 45-49
ConfirmDialog 50 $a0 = address of null-terminated string that is the message to user $a0 contains value of user-chosen option 0: Yes 1: No 2: Cancel
InputDialogInt 51 $a0 = address of null-terminated string that is the message to user $a0 contains int read $a1 contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field
InputDialogFloat 52 $a0 = address of null-terminated string that is the message to user $f0 contains float read $a1 contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field
InputDialogDouble 53 $a0 = address of null-terminated string that is the message to user $f0 contains double read $a1 contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field
InputDialogString 54 $a0 = address of null-terminated string that is the message to user $a1 = address of input buffer $a2 = maximum number of characters to read See Service 8 note below table $a1 contains status value 0: OK status. Buffer contains the input string. -2: Cancel was chosen. No change to buffer. -3: OK was chosen but no data had been input into field. No change to buffer. -4: length of the input string exceeded the specified maximum. Buffer contains the maximum allowable input string plus a terminating null.
MessageDialog 55 $a0 = address of null-terminated string that is the message to user $a1 = the type of message to be displayed: 0: error message, indicated by Error icon 1: information message, indicated by Information icon 2: warning message, indicated by Warning icon 3: question message, indicated by Question icon other: plain message (no icon displayed) N/A
MessageDialogInt 56 $a0 = address of null-terminated string that is an information-type message to user $a1 = int value to display in string form after the first string N/A
MessageDialogFloat 57 $a0 = address of null-terminated string that is an information-type message to user $f12 = float value to display in string form after the first string N/A
MessageDialogDouble 58 $a0 = address of null-terminated string that is an information-type message to user $f12 = double value to display in string form after the first string N/A
MessageDialogString 59 $a0 = address of null-terminated string that is an information-type message to user $a1 = address of null-terminated string to display after the first string N/A

注释:服务编号为30及以上的服务不包含在SPIM服务8中。它遵循UNIX ‘fgets’的语义。对于指定长度n,字符串的长度不能超过n-1。如果长度小于n-1,则在末尾添加换行符。在任何情况下,都会用空字符填充。如果n等于1,则忽略输入并将空字符放置在缓冲区地址上。如果n大于1,则忽略输入并且不会将任何内容写入缓冲区。

服务11 - 打印低字节所对应的ASCII字符。

服务13 - MARS实现了三个标志值:0表示只读,1表示可写且可创建,9表示可写且可创建和追加。它忽略模式。如果操作失败,则返回的文件描述符将为负数。底层文件I/O实现使用java.io.FileInputStream.read()读取和java.io.FileOutputStream.write()写入。MARS内部维护文件描述符,并从3开始分配它们。文件描述符0、1、2始终用于:从标准输入读取、向标准输出写入和向标准错误写入(自版本4.3起新增)。

服务13、14、15 - 在MARS 3.7中,结果寄存器已更改为与SPIM兼容。之前在《计算机组织与设计》的附录B中错误地印成了$a0,现在是$a1。

服务17 - 如果MIPS程序在MARS图形界面(GUI)的控制下运行,则忽略$a1中的退出代码。服务30 - 系统时间来自java.util.Date.getTime(),表示自1970年1月1日以来的毫秒数。

服务31、33 - 通过声卡模拟MIDI输出。详情见下文。

服务40至44使用Java底层的伪随机数生成器,由java.util.Random类提供。每个流(由$a0的内容标识)都由不同的Random对象模拟。没有默认的种子值,因此如果需要重复的随机序列,请使用Set Seed服务(40)。

常用指令

str: .asciiz “The numbers are:\n”

# 这里使用了宏,%i 为存储当前行数的寄存器,%j 为存储当前列数的寄存器 # 把 (%i 8 + %j) 4 存入 %ans 寄存器中

.macro getindex(%ans, %i, %j)
sll %ans, %i, 3 #%ans = %i 8
add %ans, %ans, %j #%ans = %ans + %j
sll %ans, %ans, 2 # %ans = %ans
4 .end_macro

在汇编程序中,还有一种和C语言中 #define 类似的宏定义,一般用于常量的定义上,那就是 .eqv.eqv 用法如下:

.eqv EQV_NAME string

li $t0, 0 # $t0=0

la $a0, str 地址

beq $t0, $s0, loop_in_end # $t0 == $s0 的时候跳出循环

bne 不相等时

array: .space 40 # 存储这些数需要用到数组,数组需要使用 10 * 4 = 40 字节 # 一个 int 整数需要占用 4 个字节,需要存储 10 个 int 整数 # 因此,array[0] 的地址为 0x00,array[1] 的地址为 0x04 # array[2] 的地址为 0x08,以此类推。

sll $t1, $t0, 2 # $t1 = $t0 << 2,即 $t1 = $t0 * 4

sw $v0, array($t1) # 把输入的数v0存入地址为 array + $t1 的内存中

lw $a0, array($t1) # 把内存中地址为 array + $t1 的数取出到 $a0 中

addi $t0, $t0, 1 # $t0 = $t0 + 1

add $t0, $t0, $a0 # $t0 = $t0 + $a0

jal f #跳转到函数f,并且将$ra变为下一句地址

jr $ra 跳转到寄存器$ra

从 CO 到 OS:MIPS 相关知识补充

本节摘取自BUAA-OS2025课程官方文档,仅作自用参考,如有侵权和违反相关条例,联系作者将立即删除

回顾

在上学期的计组课程中,我们实现了一个可以与外设交互,响应并处理中断、异常的 MIPS CPU。

你是否好奇,CPU 是如何运行操作系统的?计组知识又是如何和 OS 实验衔接起来的?

下面,我们将介绍操作系统实验使用的 CPU 与我们计组的课设 CPU 的不同之处,并对 MIPS 汇编相关知识进行补充。

操作系统实验使用 MIPS32 4Kc CPU, 这是一款由 MIPS® Technologies 公司开发的使用 MIPS32 指令集的商业处理器核。

而计组中实现的 CPU 使用 MIPS-C 指令集,MIPS-C 指令集是 MIPS32 指令集的精简版本,也就是相当于计组实现的 CPU 指令集和功能是操作系统实验中使用的一个子集。

具体谈及操作系统实验中涉及,而计组 CPU 不完整的功能,主要是有两个部分:访存流程、CP0协处理器相关。

访存流程

在计组中,我们的 CPU 不存在虚拟地址机制,访存指令中的所有地址均是物理地址。物理地址被直接发送到 DM、IM 中,直接获取数据。这简化了 MIPS32 的访存流程,让大家可以更多的关注 CPU 内部计算与控制逻辑。

而在完整的 MIPS32 访存流程中,汇编指令 不直接和物理内存打交道

访存指令中的地址,被称作虚拟地址,在执行访存操作的时候,虚拟地址会先被送入 MMU 进行地址翻译权限检查,最终拿到物理地址。

对于 MMU 检查合法的访存操作,通过 MMU 拿到物理地址之后,相应访存操作才会进一步被实际在物理地址上执行。

所有的软件(包括 MIPS 汇编、C 语言编写的软件等)访存的地址都是虚拟地址。

MIPS32 的 MMU 支持基于 TLB页式地址翻译,而操作系统实验使用了 MIPS32 的这个地址翻译功能。这一部分在计组的 CPU 中并没有涉及。

这将牵扯出我们OS中的两个知识点:MIPS32 具体访存流程、TLB 在 MIPS32 地址翻译中扮演的角色。具体内容将在我们 Lab2 内存管理中学到,在 pre 中我们仅介绍到此。

目前,只需要大家牢记,在 MIPS32 指令集中,我们的访存指令不再直接操作物理地址,而是使用虚拟地址以及地址翻译机制,间接管理物理内存。

CP0 协处理器相关

计组课程中 CPU 的协处理器 CP0 基本只负责中断与异常的相关处理。

而 MIPS32 指令集的 CP0 更为复杂,也具有更多功能,整体上 CP0 用于对整个处理器的状态进行维护和控制,包括但不限于对于中断及异常信息的记录、特权管理、地址翻译控制等。

关于控制地址翻译的功能,将在 Lab2 的实验中具体涉及,我们先简单看一下前两个 CP0 的功能。

中断异常相关

对于中断及异常的处理,MIPS32 比 MIPS-C 的要复杂一些,而我们实验中使用的部分主要涉及 CP0 的一些寄存器: Cause、EPC、BadVAddr 。

其中 Cause 与 EPC 寄存器在 MIPS32 与 MIPS-C 中均存在,其定义也是相似的,而 BadVAddr 则不存在于 MIPS-C 中。

Cause 寄存器:上图是 MIPS32 中定义的 Cause 寄存器,其中保存着 CPU 中哪一些中断或者异常发生了。 15-8 位指示了哪一些中断等待处理,其中 15-10 位来自硬件,9-8 位可以由软件写入。当中断请求发生时,Cause 寄存器对应位置 1。 6-2 位则记录发生的异常号,供软件进行区别。还需要特别注意的是 31位 BD,当这一位为1时,表明发生异常的指令位于延迟槽,软件在处理其异常时需要特别特殊处理。

EPC 寄存器: EPC 寄存器存放异常发生的指令对应 PC 地址,软件在完成异常处理之后,可以根据此 PC 返回。

BadVAddr 寄存器: 在我们前文的访存流程中有提到,若访存操作合法,具体的操作才会被执行。不合法的访存操作则会触发异常,当访存相关的异常被触发时,这个寄存器将会被用来记录触发异常的访存地址供软件进行处理。

计组课程着重于实现一个简略的硬件 CP0 ,并不关注软件实现的部分,而操作系统课程更多关注配合硬件的软件部分(即操作系统本身)。

特权管理相关

在 MIPS-C 中,我们的处理器只有一个特权模式,对于所有指令,均一视同仁的在这个特权模式下,所有操作均被允许执行。而 MIPS32 中,则定义了多种特权模式,我们主要使用的两种状态被叫做用户态内核态

在内核态下,CPU 可以执行其架构允许的任何操作;可以执行任何指令、启动任何 I/O 操作、访问任何内存区域等等。在其他特权模式下, CPU 允许执行的操作收到硬件限制限制。通常,某些特殊指令是不被允许的(具体而言,通常是影响计算机控制或者完成 I/O 操作的指令),某些内存区域也无法访问,等等。

内核态即计组实验中所提到的特权态,用户态就是非特权态。

操作系统借助 CPU 的这套硬件特权控制机制,实现对并发运行的不同用户程序的隔离,以及对计算机整体安全的保护。

试想若 CPU 不存在这样一种特权控制机制,那么任意用户程序都可视为具有与操作系统同等的地位。这种情况下,用户程序可以完全脱离操作系统的管理与控制,这是不安全的。

这些内容会在我们 Lab3 进程与异常等实验中具体涉及到。在 pre 中,我们只需要先建立起基本的预备概念。

与处理器状态相关的寄存器,主要是 Status 寄存器,它控制处理器整体工作模式。在计组的 CP0 中,也存在一个 Status 寄存器,不过在当时,我们只使用了其对中断使能控制的功能。

Status 寄存器: 上图是 MIPS32 中定义的 Status 寄存器,其控制整个处理器状态。其完整定义比较复杂,我们只用关注其中部分位即可。Status 寄存器的第 0 位,是全局中断使能位。只有这一位配置为 1 ,处理器才可以响应中断。第 15-8 为中断使能位,分别控制 8 个中断输入的使能。第 4 位 UM 与 第 1 位 EXL 则用于控制处理器的特权模式。当且仅当 UM 为 1 且 EXL 为 0 时,处理器运行在用户态,其它状态下,处理器均运行于内核态。

计组中,我们 CPU 只能一次运行一个我们编写的 MIPS 汇编程序。而在操作系统中,我们可以通过操作系统配合硬件时钟中断给不同进程分配 CPU 时间,使得 CPU 在执行不同进程间切换,实现多个进程的并发执行。

对于 MIPS 体系结构来说实时钟中断产生时,就会触发中断,将 PC 指向异常处理程序入口。异常处理程序首先保存现场,此后根据 Cause 寄存器中的情况进行异常分发,转到内核的不同异常处理子程序。同学们将在 Lab3 中,看到其具体实现。

MIPS Calling Convention

本章内容精简自MIPS Calling Conventions Summary

通用寄存器

首先,我们回忆一下计组中学过的 MIPS 的 32 个通用寄存器。

寄存器编号 助记符 用途
0 zero 值总是为 0
1 at (汇编暂存寄存器)一般由汇编器作为临时寄存器使用。
2-3 v0-v1 用于存放表达式的值或函数的整形、指针类型返回值。
4-7 a0-a3 用于函数传参。其值在函数调用的过程中不会被保存。若函数参数较多,多出来的参数会采用栈进行传递。
8-15 t0-t7 用于存放表达式的值的临时寄存器; 其值在函数调用的过程中不会被保存。
16-23 s0-s7 保存寄存器; 这些寄存器中的值在经过函数调用后不会被改变。
24-25 t8-t9 用于存放表达式的值的临时寄存器; 其值在函数调用的过程中不会被保存。
26-27 k0-k1 仅在内核态下使用。
28 gp 全局指针和内容指针。
29 sp 栈指针。
30 fp或s8 保存寄存器(同 s0-s7)。也可用作帧指针。
31 ra 函数返回地址。

其中加粗的部分,是我们在函数调用中需要保留其值的寄存器。

栈帧

每次调用一个函数时,都会为该函数创建一个唯一的栈帧。

栈帧的结构很重要。它在调用者和被调用者之间形成了一个契约,它定义了参数在调用者和被调用者之间如何传递,一个函数调用的返回值如何从被调用者传递给调用者,并定义了如何共享寄存器。

通常,函数的栈帧可能包含以下几个部分:

  • 用于存储传递给此调用的函数参数的空间。
  • 存储已保存寄存器值的位置(s0 到 s7)。
  • 一个存储子程序返回地址的地方(ra)。
  • 一个用于本地数据存储的地方。

首先,我们先来看栈帧的黄色部分,即参数部分。当前函数 A 调用当前函数的子函数 B 时,会将 B 的参数压入当前函数 A 的栈帧中的参数部分,以实现对函数调用时的传参。此外,还需注意,由于 a0-a3 寄存器也将被用作传参,所以参数部分中的淡黄色部分(arg 0 至 arg 3)只需要保留栈帧空间,并不填入具体参数值。至于参数个数超过的 4 个的子函数,当前函数 A 就需要将多出来的第 4 至第 n-1 个参数压入栈中(arg 4 至 arg n-1),实现对子函数 B 的调用。

随后,我们再来看栈帧的浅绿部分,即保留寄存器部分,前面提到我们会在当前函数被调用后,在当前函数最开始,复制当前函数要使用的任何规定需要保存的寄存器(s0 到 s7)的值压入栈帧的保留寄存器部分。此后,由于我们已经在栈帧中保存了当前函数被调用时寄存器的最初样貌,在函数执行中,函数可以随意读写已保存寄存器。当函数执行完毕,返回前,我们会复原栈中保留寄存器部分到寄存器中,使得函数的调用者(父函数),可以认为在整个调用过程中,保存的寄存器没有发生变化。

接着,我们来看栈帧的深绿部分,即返回地址部分。返回地址部分用于存储 ra 的值。该值在执行当前函数开始时被复制到栈中,并在当前函数返回之前被复制回 ra 寄存器中。

此外,我们还需要注意栈帧的灰色部分,即填充部分,其作用在于确保栈帧的总大小总是 8 的倍数。在这里插入它,以确保其上方的局部数据存储部分首地址是双字对齐的。这是为了与 MIPS 64 位体系结构的双字读取指令兼容。

最后,我们看栈帧的蓝色部分,即局部数据存储部分。它用来存储函数的局部变量。函数必须为此区域的所有局部变量保留足够的存储空间,包括需要在函数调用前后保留的任何临时寄存器(t0 到 t9)值的空间。

Leaf & Nonleaf Subroutine

并不是每个函数都需要其栈帧中描述的每个部分。一般的规则是,如果程序不需要其中的一个部分,那么它可以从其栈帧中省略该部分。为了明确这一点,我们将区分 3 个不同类别的函数:

简单叶函数(Simple Leaf)

不调用任何其他子例程,不使用栈上的任何内存空间(因为其不需要内存来保存局部变量或寄存器的值)。这样的函数不需要栈帧,也不需要更改 sp。

有局部数据的叶函数(Leaf with Data)

需要栈空间的叶函数(但不需要、不调用任何其他子例程),其栈帧可用于本地变量或寄存器的值的保存。这样的函数被调用后应当压栈(栈帧大小应该是 8 的倍数)。但是,ra 并不用保存在栈帧中。

非叶函数(Nonleaf)

函数内调用了其他函数。一个非叶函数的栈帧包含了上一节所述的大部分结构。

(具体例子参阅MIPS Calling Conventions Summary P8)

MIPS Directives & Macro

.byte.half.word.ascii.asciiz

这类在计组中常见的 directives,相信同学们都很熟悉,分别对应着不同数据的类型。

.align

.align 的作用是“使下面的数据进行地址对齐”,下面这段代码使得下面大小为一个字的变量 var 按 4 字节进行对齐(参数 xx 代表以 2x2x 字节对齐)

1
2
.align 2 # align to 4-byte boundary (2^2)
var: .word 0

该指令允许程序员通过指定一个零对齐,来覆盖.half.word等的自动对齐特性。

1
2
3
.half 3
.align 0 # 关闭自动对齐
.word 100 # 使得 word 紧贴着 half

.globl.extern

我们在计组中的写的汇编程序往往只有一个文件。但在操作系统课中,存在跨文件的情况,例如跨文件调用函数。

  • .globl:将符号定义为具有对其他模块可见的全局符号
  • .extern:要对另一个模块中的全局符号的引用(即外部符号),需要注意的是所有对标签引用都会自动被认为是在引用全局符号,所以我们在对另一模块中的全局标签引用时,没有必要添加 .extern(但对另一模块中的全局变量引用时,需要添加.extern

此后在链接时,链接器要将各个目标文件的内容“合为一体”,通过上述方式标记的符号就可以被跨文件引用。

例如,在未来课程设计的 asm.h 头文件中,我们定义了如下宏:

1
2
3
4
#define FEXPORT(symbol)         \   # 使symbol代指的函数对其他模块可见
.globl symbol; \ # 使标签对链接器可见,使得其它文件中可以调用我们使用宏定义声明的函数。
.type symbol, @function; \
symbol:

在未来内核实验的 genex.S 汇编文件中,我们通过该宏声明了一个全局函数 ret_from_exception(你可以忽略函数中的内容)

1
2
FEXPORT(ret_from_exception)
/* Do Something */

此时我们可以在未来内核实验的另一文件 env_asm.S 中,直接调用该函数

1
2
3
4
5
.text
LEAF(env_pop_tf)
/* Do Something */
j ret_from_exception # 直接调用该函数,默认ret_from_exception为全局标签,无需.extern
END(env_pop_tf)

.set

设置汇编器的工作方式。默认情况下,汇编器会尝试通过重新排列指令,来填充分支指令和存取指令造成的空闲时间。了解即可。

  • .set noreorder.set reorder:告知汇编器是否重新对指令进行顺序进行排序。reorder 模式下汇编器会自动调度指令至延迟槽,noreorder 模式下需要手动填充延迟槽。
  • .set at.set noat:at 模式下,1 号寄存器($at)为汇编器保留用于实现扩展指令;noat 模式下,汇编器不会使用 1 号寄存器。

LEAFNESTEDEND 三个宏

在操作系统实验中,我们将常常会遇到三个和函数有关的宏。是我们人为定义的

LEAF

1
2
3
4
5
6
7
#define LEAF(symbol)                        \
.globl symbol; \
.align 2; \
.type symbol,@function; \
.ent symbol; \
symbol: \
.frame sp,0,ra
  • 第一行是对 LEAF 宏的定义,后面括号中的 symbol 类似于函数的参数,在宏定义 中的作用类似,编译时在宏中会将 symbol 替换为实际传入的文本,也即我们的函数名。
  • 第二行中,.globl 的作用是“使标签对链接器可见”,这样即使在其它文件中也可 以引用到 symbol 标签,从而使得其它文件中可以调用我们使用宏定义声明的函数
  • 第三行中,.align 2 的作用是“使下面的数据进行地址对齐”,这一行语句使得下面的 symbol 标签按 4 Byte 进行对齐,从而使得我们可以使用 jal 指令跳转到这个函数(末尾拼接两位 0)。
  • 第四行中,.type 的作用是设置 symbol 标签的类别,在这里我们设置了 symbol 标签为函数标签。
  • 第五行中,.ent 的作用是标记每个函数的开头,需要与 .end 配对使用。这些标记使得可以在 Debug 时查看调用链

LEAF宏在使用时, 我们可以这样用,例如:

1
2
3
4
5
LEAF(msyscall)
// Just use 'syscall' instruction and return.
syscall
jr ra
END(msyscall)

NESTED

1
2
3
4
5
6
7
#define NESTED(symbol, framesize, rpc)      \
.globl symbol; \
.align 2; \
.type symbol,@function; \
.ent symbol,0; \
symbol: \
.frame sp, framesize, rpc

通过对比,我们可以发现 LEAF 宏和 NESTED 宏的区别就在于 LEAF 宏定义的函数在被调用时没有分配栈帧的空间记录自己的“运行状态”,NESTED 宏在被调用时分配了栈帧的空间用于记录自己的“运行状态”

NESTED 宏在使用时, 我们可以这样用,例如:

1
2
3
4
5
6
7
8
9
10
NESTED(handle_int, TF_SIZE, zero)
mfc0 t0, CP0_CAUSE
mfc0 t2, CP0_STATUS
and t0, t2
andi t1, t0, STATUS_IM7
bnez t1, timer_irq
timer_irq:
li a0, 0
j schedule
END(handle_int)

END

1
2
3
#define END(function)                       \
.end function; \
.size function,.-function

第一行是对 END 宏的定义,与上面 LEAFNESTED 类似。

第二行的 .end 是为了与先前 LEAFNESTED 声明中的 .ent 配对,标记了 symbol 函数的结束。

第三行的 .size 是标记了 function 符号占用的存储空间大小,将 function 符号占用的空间大小设置为 .-function. 代表了当前地址,当前位置的地址减去 function 标签处的地址即可计算出符号占用的空间大小。