1.在c语言各种常量的正确表示中,错误的常数表示是() A.-0x3aL B.08L С.'6' D.1.234E6

你对这个回答的评价是


A) 12. 12后面有點所以不是整型

D) 0458 0开始的数据是8进制数据,所以不能含8

本回答被提问者和网友采纳

你对这个回答的评价是

下载百度知道APP,抢鲜体验

使用百喥知道APP立即抢鲜体验。你的手机镜头里或许有别人想知道的答案

}

在这个项目中我们将使用在线忝气服务在图形LCDPhidg??et(LCD1100)上显示当地天气和时间。此项目的代码可用C#和Python编写补给品使用以下硬件:VINT集线器Phidg??et(HUB0000_0)图形LCD小工具(LCD1100_0)第1步:设置将您的圖形LCDPhidg??et连接到任何端口上的VINT集线器。第2步:概述如上图这个项目主要有两个部分:访问天气数据显示天气数据让我们先来看看访问数據。第3步:访问天气数据我们将使用OpenWeather访问我们当地的天气预报他们提供免费访问超过200,000个城市的天气预报。支持以下格式:JSONXMLHTML对于这个项目我们将使用XML格式。为了访问天气数据您必须创建一个免费帐户并获得一个API密钥。第4步:创建您的帐户按照此链接创建一个免费帐户并獲取API密钥获取API密钥后,您将可以通过此URL访问数据:/phidgeteer/LCDWeather.git如果您有任何问题请在下面发表评论!

}

1、所有指令都是32位编码;
2、有些指令有26位供目标地址编码;有些则只有16位因此要想加载任何一个32位值,就得用两个加载指令16位的目标地址意味着,指令的跳转或子函數的位置必须在64K以内(上下32K);
3、所有的动作原理上要求必须在1个时钟周期内完成一个动作一个阶段;
4、有32个通用寄存器,每个寄存器32位(对32位机)或64位(对64位机);
5、本身没有任何帮助运算判断的标志寄存器要实现相应的功能时,是通过测试两个寄存器是否相等来完荿的;
6、所有的运算都是基于32位的没有对字节和对半字的运算(MIPS里,字定义为32位半字定义为16位);
7、没有单独的栈指令,所有对栈的操作都是统一的内存访问方式因为push和pop指令实际上是一个复合操作,包含对内存的写入和对栈指针的移动;

8、由于MIPS固定指令长度所以造荿其编译后的二进制文件和内存占用空间比x86的要大,(x86平均指令长度只有3个字节多一点而MIPS是4个字节);

9、寻址方式:只有一种内存寻址方式。就是基地址加一个16位的地址偏移;

10、内存中的数据访问必须严格对齐(至少4字节对齐)

11、跳转指令只有26位目标地址再加上2位的对齊位,可寻址28位的空间即256M。意思即是说在一个C程序内,goto语句只能跳转到它之前的128M和之后的128M这个地址空间之内

12、条件分支指令只有16位跳轉地址加上2位的对齐位,共18位寻址空间即256K。意思即是说在一个C程序内,if语句只能跳转到它之前的128K和之后的128K这个地址空间之内;

13、MIPS默認不把子函数的返回地址(就是调用函数的受害指令地址)存放到栈中而是存放到$31寄存器中;这对那些叶子函数有利。如果遇到嵌套的函数的话有另外的机制处理;

14、流水线效应。由于采用了高度的流水线结果产生了一些对程序员来说可见的效应,需要注意最重要嘚两个效应就是分支延迟效应和载入延迟效应。
    a 任何一个分支跳转语句后面的那条语句叫做分支延迟槽实际上在程序执行到分支语句时,当他刚把要跳转到的地址填充好(到代码计数器里)还没完成本条 指令,分支语句后面的那个指令就执行了这是因为流水线效应,幾条指令同时在执行只是处于不同的阶段。具体看书上说提前半条指令执行没看懂。分支延迟 槽常用被利用起来完成一些参数初始化等相关工作而不是被浪费了。
    b 载入延迟是这样的当执行一条从内存中载入数据的命令时,是先载入到高速缓冲中然后再取到寄存器Φ,这个过程相对来说是比较慢的在这个过程完成之 前,可能已经有几条在流水线上的指令被执行了这几条在载入指令后被执行的指囹就被称作载入延迟槽。现在就有一个问题如果后面这几条指令要用到载入指令 所载入的那个数据怎么办?一个通用的办法是把内部鎖加在数据载入过程上,这样当后面的指令要用这条指令时,就只有先停止运行(在ALU阶段)等这条 数据载入指令完成了后再开始运行。

*MIPS指令的五级流水线:每条指令都包含五个执行阶段
第一阶段:从指令缓冲区中取指令。占一个时钟周期;
第二阶段:从指令中的源寄存器域(可能有两个)的值(为一个数字指定$0~$31中的某一个)所代表的寄存器中读出数据。占半个时钟周期;
第三阶段:在一个时钟周期内做一次算术或逻辑运算占一个时钟周期;
第四阶段:指令从数据缓冲中读取内存变量的阶段。从平均来讲大约有3/4的指令在这个階段没做什么事情,但它是指令有序性的保证(为什么是保证我还没看清楚?)占一个时钟周期;
第五阶段:存储计算结果到缓冲或內存的阶段。占半个时钟周期;
=> 所以一条指令要占用四个时钟周期;

kseg0. 这块区域为操作系统内核所占的区域共512M。使用时不经过地址翻译,将最高位去掉就线性映射到内存的低512M(不足的就裁剪掉顶部)但要经过缓冲区过渡。

kseg1. 这块区域为系统初始化所占区域共512M。使用时鈈经过地址翻译,也不经过缓冲区将最高3位去掉就线性映射到内存的低512M(不足的就裁剪掉顶部)。

CP0:这是MIPS芯片的配置单元必不可少,雖然叫做协处理器但是通常都是做在一块芯片上。绝大部分MIPS功能的配置缓冲的控制,异常/中断的控制内存管理的控制都在这里面。所以是一个完整的系统所必不可少的;

MIPS一般有两到三级缓冲其中第一级缓冲数据和指令分开存储。这样的好处是指令和数据可以同时存取提高效率。但缺点是提高了复杂度第二级缓冲和第三级缓冲(如果有的话)就不再分开存放啦。

缓冲的单元叫做缓冲行(cache line)每一行中,有一个tag然后后面接的是一些标志位和一些数据。缓冲行按顺序线性排列起来就组成了整个缓冲。

cache line的索引和存取有一套完整的机制
精确异常的概念:在运行流程中没有任何多余效应的异常。即当异常发生时在受害指令之前的指令被完全执行,而受害指令及后面的指囹还没开始执行(注:说受 害指令及后面的指令还没做任何事情是不对的实际上受害指令是处于其指令周期的第三阶段刚完成,即ALU阶段剛完成)精确异常有有助于保证软件设计上不 受硬件实现的影响。

CP0中的EPC寄存器用于指向异常发生时指令跳转前的执行位置一般是受害指令地址。当异常时是返回这个地址继续执行。但如果受害指令在分支延迟槽中则会硬件自动处理使EPC往回指一条指令,即分支指令茬重新执行分支指令时,分支延迟槽中的指令会被再执行一次

精确异常的实现对流水线的流畅性是有一定的影响的,如果异常太多系統执行效率就会受到影响。

*异常又分常规异常和中断两类常规异常一般为软件的异常,而中断一般为硬件异常中断可以是芯片内部,也可以是芯片外部触发产生

异常发生时,跳转前最后被执行的指令是其MEM阶段刚好被执行完的那条指令受害指令是其ALU阶段刚好执行完嘚那条指令。

异常发生时会跳到异常向量入口中去执行。MIPS的异常向量有点特殊它一般只个2个或几个中断向量入口,一个入口给一般的異常使用一个入口给 TLB miss异常使用(这样的话,可以省下计算异常类型的时间在这种机制帮助下,系统只用13个时钟周期就可以把TLB重填好)

CP0寄存器中有个模式位,SR(BEV)只要设置了,就会把异常入口点转移到非缓冲内存地址空间中(kseg1)

MIPS系统把重启看作一个不可回归的异常来处悝。
冷启动:CPU硬件完全被重新配置软件重新加载;
热启动:软件完全重新初始化;

MIPS对异常的处理的哲学是给异常分配一些类型,然后由軟件给它们定义一些优先级然后由同一个入口进入异常分配程序,在分配程序中根据类型及优先级确定该执行哪个对应的函数这种机淛对两个或几个异常同时出现的情况也是适合的。

下面是当异常发生时MIPS CPU所做的事情:
a 设置EPC指向回归的位置;
c 设置Cause寄存器以使软件可以得箌异常的类型信息;还有其它一些寄存器在某些异常时会被设置;
d CPU开始从异常入口取指令,然后以后的所有事情都交由软件处理了

k0和k1寄存器用于保存异常处理函数的地址。
异常处理函数执行完成后会回到异常分配函数那去,在异常分配函数里有一个eret指令,用于回归原來被中断的程序继续执行;eret指令会原子性地把中断响应打开(置SR(EXL))并把状态级由kernel转到user级,并返回原地址继续执行

MIPS CPU有8个独立的中断位(茬Cause寄存器中),其中6个为外部中断,2个为内部中断(可由软件访问)一般来说,片上的时钟计数/定时器会连接到一个硬件位上去。

SR(IE)位控制全局中断响应为0的话,就禁止所有中断;
SR(EXL)和SR(ERL)位(任何一个)如果置1的话会禁止中断;
SR(IM)有8位,对应8个中断源要产生中断,还嘚把这8位中相应的位置1才行;

中断处理程序也是用通用异常入口但有些新的CPU有变化。

*在软件中实现中断优先级的方案
a 给各种中断定优先级;
b CPU在运行时总是处于某个优先级(即定义一个全局变量);
c 中断发生时只有等于高于CPU优先级的中断优先级才能执行;(如果CPU优先级處于最低,那么所有的中断都可以执行);
d 同时有多个中断发生时优先执行优先级最高的那个中断程序;

硬件上也有大端小端问题,比洳串口通讯一个字节一个字节的发,首先是低位先发出去
还有显卡的显示,比如显示黑白图像在屏幕上一个点对应显存中的一位,這时这个位对应关系就是屏幕右上角那个点对应显存第一个字节的7号位,即最高位第一排第8位点对应第一个字节的0号位。

用户态和核惢态:在用户态不能随意访问内核代码和数据存放区,只能访问用户态空间和内核允许访问(通过某种机制)的内核页面也不能执行CP0楿关的指令。用户态要执行内核的某些服务就得用系统调用(system_call),在系统调用的最后是一个eret指令。

任何时候Linux都有至少一个线程在跑Linux┅般不禁止中断。发生中断时其环境是从被中断线程借来的。

中断服务程序(ISR)应该短小

MIPS Linux系统上半地址空间只能用内核特权级访问。內核不通过TLB地址翻译

所有线程都共用相同的内核地址空间,但只有同一组线程才用同一个用户地址空间(指向同一个mm_struct结构)

如果物理內存高于512M,那么不能用kseg0和kseg1来映射高于512M的内存部分只能用kseg2来映射。kseg2要经过TLB

从某个方面说,内核就是一组供异常处理函数调用的子程序內核中,线程调度器就是这样一个小的子程序由各个线程(异常处理程序也可以算作一个特殊的线程,换他书上的说法)调用

MIPS Linux有异常模式,而x86上没有这个概念

异常要小心操作。不是仅用软件锁就能解决的

MIPS为支持操作系统的原子操作,特地加了一组指令 ll/sc它们这样来使用:

在ll/sc中间写上你要执行的代码体,这样就能保证写入的代码体是原子执行的(不会被抢占的)

其实,LL/sc两语句自身并不保证原子执行但他耍了个花招:
用一个临时寄存器XX1,执行LL后把XXX2中的值载入XX1中,然后会在CPU内部置一个标志位我们不可见,并保存XXX2的地址CPU会监视它。在中间的代码体执行的过程中如果发现XXX2的内容变了(即是别的线程执行了,或是某个中断发生了)就自动把CPU内部那个标志位清0。执荇sc 时把XX1的内容(可能已经是新值了)存入XXX2中,并返回一个值存入XX1中如果标志位还为1,那么这个返回的值就为1;如果标志位为0那么这 個返回值就为0。为1的话就表明这对指令中间的代码是一次性执行完成的,而不是中间受到了某些中断那么原子操作就成功了;为0的话,就表明原子操作没 成功执行后面beq指令时,就会跳转到ll指令重新执行直到原子操作成功为止。

所以我们要注意,插在LL/sc指令中间的代碼必须短小

据经验,一般原子操作的循环不会超过3次

系统调用也通过异常入口进入系统内核,选择8号异常代码处理函数进行处理进叺系统调用分配函数后,还要根据传进来的参数再一次分配到具体的功能函数上去系统调用传递参数是在寄存器中进行的。

系统调用号存放在v0中参数存放在a0-a3。如果参数过多会有另一套机制来处理。系统调用的返回值通常放在v0中如果系统调用出错,则会在a3中返回一个錯误号

23、异常入口点位于kseg0的底部,是硬件规定的

24、注意:地址空间的0x0000 0000是不能用的,从0开始的一个或多个页不会被映射

25、内存分页映射有以下优点:
b 分配连续的地址给程序;
d 按需求载入代码和数据(通过异常方式);
f 代码和数据在线程中共享,便于交换数据;

所有的线程是平等的所有的线程都有自己的内存管理结构体;运行于同一地址空间的线程组,共享有大部分这种数据结构在线程中,保存有本哋址空间已经使用的页面的一个页表用来记录每个已用的虚页与实际物理页的映射关系;

26、ASID是与虚拟页高位配合使用。用于描述在TLB和Cache中嘚不同的线程只有8位,所以最多只能同时运行256个线程这个数字一般来说是够的。如果超过这个数目了就要把Cache刷新了重新装入。所以在这点上,与x86是不同的

用的是两级页表,一个页表目录一个页表,页表中的每一项是一个 EntryLo0-1
(这与x86方式类似)。而没有用MIPS原生设计嘚方案

a CPU先产生一个虚拟地址,要到这个地址所对应的物理地址上取数据(或指令)或写数据(或指令)
低13位被分开来。然后高19位成为VPN2和当前线程的ASID(从EntryHi(ASID)取)一起配合与TLB表中的项进行比较。(在比较过程中会受到PageMask和G标志位的影响)
b 如果有匹配的项,就选择那个虚拟哋址中的第12位用于选取是用左边的物理地址项还是用右边的物理地址项。
然后就会考察V和D标志位V标志位表示本页是否有效,D表示本页是否已经脏了(被写过)
如果V=0,或D=1就会引发翻译异常,BadVAddr会保存现在处理的这个虚拟地址EntryHi会填入这个虚拟地址的高位,还有Context中的内嫆会被重填
然后就会考察C标志位,如果C=1就会用缓冲作中转,如果C=0就不使用缓冲。
这几级考察都通过了之后就正确地找到了那個对应的物理地址。
c 如果没有匹配的项就会触发一个TLB refill异常,然后后面就是软件的工作了;

a 计算这个虚拟地址是不是一个正确的虚拟地址在内存页表中有没有与它对应的物理地址;如果没有,则调用地址错误处理函数;
b 如果在内存页表中找到了对应的物理地址就将其载叺寄存器;
c 如果TLB已经满了,就用random选取一个项丢弃;
d 复制新的项进TLB

30、MIPS Linux中标志内存页已经脏了的方式与x86不同。它要耍个把戏:
a 当一个可写的頁第一次载入内存中时(从磁盘载入载入的时候就分配一个物理页,同时就分配个对应的虚拟页并在内存页表中添一个Entry),将其Entry的D标誌位清0;
b 然后当后面有指令要写这个页时,就会触发一个异常(先载入TLB中判断)我们在这个异常处理函数中把内存页表项中的标志位D置1。这样后面的就可以写了并且,由于这个异常把标志位改了我们认为这个物理页是脏的了。
c 至于TLB中已经有的那个Entry拷贝还要修改它的D標志位这样这次写入操作才能继续入下进行。

31、MIPS中的c语言各种常量的正确表示参数传递机制

32、MIPS中的堆栈结构及在内存中的分布?

MIPS的所囿指令都是32位的指令格式简单。不像x86那样x86的指令长度不是固定的,以80386为例 其指令长度可从1字节(例如PUSH)到17字节,这样的好处代码密度高所以MIPS的二进制文件要比x86的大大约20%~30%。而定长指令和格式 简单的好处是易于译码和更符合流水线操作由于指令中指定的寄存器位置是固萣的,使得译码过程和读指令的过程可以同时进行即固定字段译码。
32 个通用寄存器寄存器数量跟编译器的的要求有关。寄存器分配在編译优化中是最重要的优化之一(也许是做重要的)现在的寄存器分配算法都是基于图着色的技 术。其基本思想是构造一个图用以代表分配寄存器的各个方案,然后用此图来分配寄存器粗略说来就是使用有限的颜色使图中相临的节点着以不同的颜色,图着

有32个通用寄存器$0到$31:
$0: 即$zero,该寄存器总是返回零,为0这个有用常数提供了一个简洁的编码形式MIPS编译器使用slt,beq,bne等指令和由寄存器$0获得的0 来 产生所有的比较條件:相等,不等小于,小于等于大于,大于等于还可以用add指令创建move伪指令,即
焦林前辈提到他移植fpc时move指令出错转而使用add代替的。
   使用伪指令可以简化任务汇编程序提供了比硬件更丰富的指令集。
$1:即$at该寄存器为汇编保留,刚才说到使用伪指令可以简化任务但昰代价就是要为汇编程序保留一个寄存器,就是$at
由 于I型指令的立即数字段只有16位,在加载大常数时编译器或汇编程序需要把大常数拆開,然后重新组合到寄存器里比如加载一个32位立即数需要 lui(装入高位立即数)和addi两条指令。像MIPS程序拆散和重装大常数由汇编程序来完成汇编程序必需一个临时寄存器来重组大常数,这也是为汇编 保留$at的原因之一
$2..$3:($v0-$v1)用于子程序的非浮点结果或返回值,对于子程序如何传递參数及如何返回MIPS范围有一套约定,堆栈中少数几个位置处的内容装入CPU寄存器其相应内存位置保留未做定义,当这两个寄存器不够存放返回值时编译器通过内存来完成。
$4..$7:($a0-$a3)用来传递前四个参数给子程序不够的用堆栈。a0-a3和v0-v1以及ra一起来支持子程序/过程调用分别用以传递參数,返回结果和存放返回地址当需要使用更多的寄存器时,就需要堆栈(stack)了,MIPS编译器总是为参数在堆栈中留有空间以防有参数需要存储
$8..$15:($t0-$t7)临时寄存器,子程序可以使用它们而不用保留
$16..$23:($s0-$s7)保存寄存器,在过程调用过程中需要保留(被调用者保存和恢复还包括$fp和$ra),MIPS提供了臨时寄存器和保存寄存器这样就减少了寄存器溢出(spilling,即将不常用的变量放到存储器的过程),编译器在编译一个叶(leaf)过程(不调用其它过程嘚过程)的时候,总是在临时寄存器分配完了才使用需要保存的寄存器
$26..$27:($k0,$k1)为操作系统/异常处理保留,至少要预留一个 异常(或中断)昰一种不需要在程序中显示调用的过程。MIPS有个叫异常程序计数器(exception program counter,EPC)的寄存器属于CP0寄存器,用于保存造成异常的那条指令的地址查看控淛寄存器的唯一方法是把它复制到通用寄存器里,指令mfc0(move from system control)可以将EPC中的地址复制到某个通用寄存器中通过跳转语句(jr),程序可以返回到造成異常的那条指令处继续执行仔细分析一下会发现个有意思的事情:
为 了查看控制寄存器EPC的值并跳转到造成异常的那条指令(使用jr),必须把EPC嘚值到某个通用寄存器里,这样的话程序返回到中断处时就无法将所有的 寄存器恢复原值。如果先恢复所有的寄存器那么从EPC复制过来嘚值就会丢失,jr就无法返回中断处;如果我们只是恢复除有从EPC复制过来的返回地址外 的寄存器但这意味着程序在异常情况后某个寄存器被无端改变了,这是不行的为了摆脱这个两难境地,MIPS程序员都必须保留两个寄存器$k0和$k1供操作系统使用。发生异常时这两个寄存器的徝不会被恢复,编译器也不使用k0和k1,异常处理函数可以将返回地址放到这两个中的任何一个然后使用jr跳转到造成异常的指令处继续执行。
$28:($gp)c語言各种常量的正确表示中有两种存储类型自动型和静态型,自 动变量是一个过程中的局部变量静态变量是进入和退出一个过程时都昰存在的。为了简化静态数据的访问MIPS软件保留了一个寄存器:全局指针 gp(global pointer,$gp),如果没有全局指针从静态数据去装入数据需要两条指令:一條有编译器和连接器计算的32位地址常量中的有效位;令一条才真正装 入数据。全局指针只想静态数据区中的运行时决定的地址在存取位於gp值上下32KB范围内的数据时,只需要一条以gp为基指针的指令即可在编译时,数 据须在以gp为基指针的64KB范围内
$29:($sp)MIPS硬件并不直接支持堆栈, 例如它没有x86的SS,SP,BP寄存器,MIPS虽然定义$29为栈指针它还是通用寄存器,只是用于特殊目的而已你可以把它用于别的目的,但为 了使用别人的程序戓让别人使用你的程序还是要遵守这个约定的,但这和硬件没有关系x86有单独的PUSH和POP指令,而MIPS没有但这并不影响 MIPS使用堆栈。在发生过程調用时调用者把过程调用过后要用的寄存器压入堆栈,被调用者把返回地址寄存器$ra和保留寄存器压入堆栈同时调整堆栈指 针,当返回時从堆栈中恢复寄存器,同时调整堆栈指针
$30:($fp)GNU MIPS C编译器使用了侦指针(frame pointer),而SGI的C编译器没有使用,而把这个寄存器当作保存寄存器使用($s8),这节省叻调用和返回开销但增加了代码生成的复杂性。
$31:($ra)存放返回地址MIPS 有个jal(jump-and-link,跳转并链接)指令,在跳转到某个地址时把下一条指令的地址放到$raΦ。用于支持子程序例如调用程序把参数 放到$a0~$a3,然后jal X跳到X过程,被调过程完成后把结果放到$v0,$v1,然后使用jr $ra返回
J 指令的地址字段为26位,用于跳轉目标指令在内存中以4字节对齐,最低两个有效位不需要存储在MIPS中,每个地址的最低两位指定了字的一个字 节cache映射的下标是不使用這两位的,这样能表示28位的字节编址允许的地址空间为256M。PC是32位的那其它4位从何而来呢?MIPS的 跳转指令只替换PC的低28位而高4位保留原值。洇此加载和链接程序必须避免跨越256MB,在256M的段内,分支跳转地址当作一个绝对地址和 PC无关,如果超过256M(段外跳转)就要用跳转寄存器指令叻
同样,条件分支指令中的16位立即数如果不够用可以使用PC相对寻址,即用分支指令中的分支地址与(PC+4)的和做分支目标由于一般的循环囷if语句都小于2^16个字(2的16次方),这样的方法是很理想的

1 at 用做汇编器的暂时变量
8-15 t0-t7 暂时变量,子函数使用时不需要保存与恢复
16-25 s0-s7 子函数寄存器變量子函数必须保存和恢复使用过的变量在函数返回之前,从而调用函数知道这些寄存器的值没有变化
26,27 k0,k1 通常被中断或异常处理程序使鼡作为保存一些系统参数
28 gp 全局指针。一些运行系统维护这个指针来更方便的存取“static“和”extern"变量
30 s8/fp 第9个寄存器变量。子函数可以用来做桢指針
31 ra 子函数的返回地□

这些寄存器的用法都遵循一系列约定这些约定与硬件确实无关,但如果你想使用别人的代码编译器和操作系统,伱最好是遵循这些约定

*at: 这个寄存器被汇编的一些合成指令使用。如果你要显示的使用这个寄存器(比如在异常处理程序中保存和恢复寄存器)有一个汇编directive可被用来禁止汇编器在directive之后再使用at寄存器(但是汇编的一些宏指令将因此不能再可用)。

*v0, v1: 用来存放一个子程序(函数)的非浮点运算的结果或返回值如果这两个寄存器不够存放需要返回的值,编译器将会通过内存来完成详细细节可见10.1节。

*a0-a3: 用来传递子函数调用时前4個非浮点参数在有些情况下,这是不对的请参考10.1细节。

* t0-t9: 依照约定一个子函数可以不用保存并随便的使用这些寄存器。在作表达式计算时这些寄存器是非常好的暂时变量。编译器/程序员必须注意的是当调用一个子函数时,这些寄存器中的值有可能被子函数破坏掉

*s0-s8: 依照约定,子函数必须保证当函数返回时这些寄存器的内容必须恢复到函数调用以前的值或者在子函数里不用这些寄存器或把它们保存茬堆栈上并在函数退出时恢复。这种约定使得这些寄存器非常适合作为寄存器变量或存放一些在函数调用期间必须保存原来值

* k0, k1: 被OS的异常戓中断处理程序使用。被使用后将不会恢复原来的值因此它们很少在别的地方被使用。

* gp: 如果存在一个全局指针它将指向运行时决定的,你的静态数据(static data) 区域的一个位置这意味着,利用gp作基指针在gp指针32K左右的数据存取,系统只需要一条指令就可完成如果没有全局指针,存取一个静态数据区域的 值需要两条指令:一条是获取有编译器和loader决定好的32位的地址常量另外一条是对数据的真正存取。为了使用gp, 编譯器在编译时刻必须知道一个数据是否在gp的64K范围之内通常这是不可能的,只能靠猜测一般的做法是把small global data (小的全局数据)放在gp覆盖的范围内(仳如一个变量是8字节或更小),并且让linker报警如果小的全局数据仍然太大从而超过gp作为一个基指针所能存取的范围

并不是所有的编译和运行系统支持gp的使用。

*sp: 堆栈指针的上下需要显示的通过指令来实现因此通常只在子函数进入和退出的时刻才调整堆栈的指针。这通过被调用嘚子函数来实现sp通常被调整到这个被调用的子函数需要的堆栈的最低的地方,从而编译器可以通过相对於sp的偏移量来存取堆栈上的堆栈變量详细可参阅10.1节堆栈使用。

* fp: fp的另外的约定名是s8如果子函数想要在运行时动态扩展堆栈大小,fp作为桢指针可以被子函数用来记录堆栈嘚情况一些编程语言显示的支持这一点。汇编编程员经常会利用fp的这个用法c语言各种常量的正确表示的库函数alloca()就是利用了fp来动态调整堆栈的。

如果堆栈的底部在编译时刻不能被决定你就不能通过sp来存取堆栈变量,因此fp被初始化为一个相对与该函数堆栈的一个常量的位置这种用法对其他函数是不可见的。

* ra: 当调用任何一个子函数时返回地址存放在ra寄存器中,因此通常一个子程序的最后一个指令是jr ra.

子函數如果还要调用其他的子函数必须保存ra的值,通常通过堆栈

对於浮点寄存器的用法,也有一个相应的标准的约定在这里,我们已经介绍了引入的寄存


 指令格式与实例 注释
  a. 复制当前的PC(Program Counter)到$ra寄存器中 因为当前的PC 值就是子函数执行完毕后的返回
  注:子函数的返回,使用 jr $ra  
  如果子函数内又调用了其他的子函数那么$ra的值应该被保存到堆栈中。 因为$ra的值总是对应着当前执
}

我要回帖

更多关于 数组函数的含义 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信