mips知识点
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 | .align 2 # align to 4-byte boundary (2^2) |
该指令允许程序员通过指定一个零对齐,来覆盖.half
、.word
等的自动对齐特性。
1 | .half 3 |
.globl
、.extern
我们在计组中的写的汇编程序往往只有一个文件。但在操作系统课中,存在跨文件的情况,例如跨文件调用函数。
.globl
:将符号定义为具有对其他模块可见的全局符号.extern
:要对另一个模块中的全局符号的引用(即外部符号),需要注意的是所有对标签引用都会自动被认为是在引用全局符号,所以我们在对另一模块中的全局标签引用时,没有必要添加.extern
(但对另一模块中的全局变量引用时,需要添加.extern
)
此后在链接时,链接器要将各个目标文件的内容“合为一体”,通过上述方式标记的符号就可以被跨文件引用。
例如,在未来课程设计的 asm.h
头文件中,我们定义了如下宏:
1 | #define FEXPORT(symbol) \ # 使symbol代指的函数对其他模块可见 |
在未来内核实验的 genex.S
汇编文件中,我们通过该宏声明了一个全局函数 ret_from_exception
(你可以忽略函数中的内容)
1 | FEXPORT(ret_from_exception) |
此时我们可以在未来内核实验的另一文件 env_asm.S
中,直接调用该函数
1 | .text |
.set
设置汇编器的工作方式。默认情况下,汇编器会尝试通过重新排列指令,来填充分支指令和存取指令造成的空闲时间。了解即可。
.set noreorder
和.set reorder
:告知汇编器是否重新对指令进行顺序进行排序。reorder 模式下汇编器会自动调度指令至延迟槽,noreorder 模式下需要手动填充延迟槽。.set at
和.set noat
:at 模式下,1 号寄存器($at)为汇编器保留用于实现扩展指令;noat 模式下,汇编器不会使用 1 号寄存器。
LEAF
、NESTED
、END
三个宏
在操作系统实验中,我们将常常会遇到三个和函数有关的宏。是我们人为定义的
LEAF
1 | #define LEAF(symbol) \ |
- 第一行是对
LEAF
宏的定义,后面括号中的symbol
类似于函数的参数,在宏定义 中的作用类似,编译时在宏中会将symbol
替换为实际传入的文本,也即我们的函数名。 - 第二行中,
.globl
的作用是“使标签对链接器可见”,这样即使在其它文件中也可 以引用到symbol
标签,从而使得其它文件中可以调用我们使用宏定义声明的函数。 - 第三行中,
.align 2
的作用是“使下面的数据进行地址对齐”,这一行语句使得下面的symbol
标签按 4 Byte 进行对齐,从而使得我们可以使用jal
指令跳转到这个函数(末尾拼接两位 0)。 - 第四行中,
.type
的作用是设置symbol
标签的类别,在这里我们设置了symbol
标签为函数标签。 - 第五行中,
.ent
的作用是标记每个函数的开头,需要与.end
配对使用。这些标记使得可以在 Debug 时查看调用链
LEAF
宏在使用时, 我们可以这样用,例如:
1 | LEAF(msyscall) |
NESTED
1 | #define NESTED(symbol, framesize, rpc) \ |
通过对比,我们可以发现 LEAF
宏和 NESTED
宏的区别就在于 LEAF
宏定义的函数在被调用时没有分配栈帧的空间记录自己的“运行状态”,NESTED
宏在被调用时分配了栈帧的空间用于记录自己的“运行状态”。
NESTED
宏在使用时, 我们可以这样用,例如:
1 | NESTED(handle_int, TF_SIZE, zero) |
END
1 | #define END(function) \ |
第一行是对 END
宏的定义,与上面 LEAF
与 NESTED
类似。
第二行的 .end
是为了与先前 LEAF
或 NESTED
声明中的 .ent
配对,标记了 symbol
函数的结束。
第三行的 .size
是标记了 function
符号占用的存储空间大小,将 function
符号占用的空间大小设置为 .-function
,.
代表了当前地址,当前位置的地址减去 function
标签处的地址即可计算出符号占用的空间大小。