mips 需要手动添加亚音频后接收延迟延迟槽吗

MIPS流水线以及分支延迟和加载延迟概念(一) | Imagination中文技术社区MIPSdelayslot 延迟槽是学习中的难点,本文介绍了 对理解很有帮助 SCM 单片机开发 238万源代码下载-
&文件名称: MIPSdelayslot
& & & & &&]
&&所属分类:
&&开发工具: TEXT
&&文件大小: 2 KB
&&上传时间:
&&下载次数: 16
&&提 供 者:
&详细说明:MIPS延迟槽是学习中的难点,本文介绍了延迟槽的,对理解很有帮助-MIPS Delay Slot is a learning difficulty, this article describes the delay slots, and very helpful for understanding
文件列表(点击判断是否您需要的文件,如果是垃圾请在下面评价投诉):
&&MIPSdelayslot.txt
&[]:纯粹是垃圾
&相关搜索:
&输入关键字,在本站238万海量源码库中尽情搜索:
&[] - mips run 中文版 ,对mpis构建感兴趣的可以看看哦
&[] - 讲述mips下c编程的基础,有需要的可以看下
&[] - mips架构UCOS-ii移植例程,不同于ucos-ii网站上下载例程,新增加2个例程文件,对新手十分重要
&[] - mips-lite Simulator
流水线模拟器实现
&[] - mips汇编语言程序设计指南,详细介绍MIPS汇编语言,通俗易懂,实用性强
&[] - mips 模拟器. 里面有 ppt 说明 讲解。
&[] - mips命令集详解,the mips32 instruction set and instruction bit encodings2347人阅读
MIPS(26)
原文网址:http://www.kernelchina.org/node/262
  在MIPS体系结构中,最多支持4个协处理器(Co-Processor)。其中,协处理器CP0是体系结构中必须实现的。它起到控制CPU的作用。MMU、异常处理、乘除法等功能,都依赖于协处理器CP0来实现。它是MIPS的精髓之一,也是打开MIPS特权级模式的大门。
  MIPS的CP0包含32个寄存器。关于它们的资料可以参照MIPS官方的资料MIPS32(R) Architecture For Programmers Volume III: The MIPS32(R) Privileged Resource Architecture的Chap7和Chap8。本文中,仅讨论常见的一些寄存器。
  Register 0: Index,作为MMU的索引用。将来讨论MMU和TLB时会详解之。
  Register 2, EntryLo0,访问TLB Entry偶数页中的地址低32Bit用。同上,在MMU和TLB的相关章节中详解。
  Register 3, EntryLo1,访问TLB Entry奇数页中的地址低32Bit用。
  Register 4, Context,用以加速TLB Miss异常的处理。
  Register 5, PageMask,用以在MMU中分配可变大小的内存页。
  Register 8, BadVAddr,在系统捕获到TLB Miss或Address Error这两种Exception时,发生错误的虚拟地址会储存在该寄存器中。对于引发Exception的Bug的定位来说,这个寄存器非常重要。
  Register 9, Count,这个寄存器是R4000以后的MIPS系统引入的。它是一个计数器,计数频率是系统主频的1/2。BCM,RMI XLR系列以及Octeon的Cavium处理器均支持该寄存器。对于操作系统来说,可以通过读取该寄存器的值来获取tick的时基。在系统性能测试中,利用该寄存器也可以实现打点计数。
  Register 10,EntryHi,这个寄存器同EntryLo0/1一样,用于MMU中。以后会详述。
  Register 11,Compare,配合Count使用。当Compare和Count的值相等的时候,会触发一个硬件中断(Hardware Interrupt),并且总是使用Cause寄存器的IP7位。
  Register 12,Status,用于处理器状态的控制。
  Register 13,Cause,这个寄存器体现了处理器异常发生的原因。
  Register 14,EPC,这个寄存器存放异常发生时,系统正在执行的指令的地址。
  Register 15,PRID,这个寄存器是只读的,标识处理器的版本信息。向其中写入无意义。
  Register 18/19,WatchLo/WatchHi,这对寄存器用于设置硬件数据断点(Hardware Data Breakpoint)。该断点一旦设定,当CPU存取这个地址时,系统就会发生一个异常。这个功能广泛应用于调试定位内存写坏的错误。
  Register 28/29,TagLo和TagHi,用于高速缓存(Cache)管理。
  下面,我们来详解CP0中常用的几个寄存器,它们是:BadVAddr,Count/Compare,Status/Cause,EPC,WatchLo/WatchHi。
BadVAddr: 错误的虚拟地址。实际上,这个寄存器仅限于出现TLB Miss和ADE (Address Error)两种异常的时候,才能用到。发生错误的虚拟地址会放在这个寄存器里。
一般地,在设定TLB时,通常将0地址附近的一块,设定为无映射区域。这样,一旦编程时不慎访问了空指针(0地址),或是空指针加上一定的偏移量,那么,系统就会抛出一个TLB Miss Exception。在这种情况下,发生错误的地址会被记录在BadVAddr寄存器中。一般地,这个地址是一个非常接近于0的地址。往往地,通过BadVAddr在寄存器中的值,和相关数据结构的分析,就可以找出对应的语句。
另外,对于ADE异常,异常地址也会被保存在BadVAddr中。一般地,操作系统会自行接管这个地址,分两次读取/写入这个地址处的数据,而不会发生Core Dump的情况。但是,如果这个地址既属于非对齐地址,又属于TLB Miss,那么,系统还是会抛出一个Core Dump的。正常地,操作系统应当正确处理这个异常,如果在Exception Handler中发现地址在TLB中未映射,还是应当抛出ADE异常,而不是TLB Miss。
Count/Compare: 这两个寄存器是CP0中的一对欢喜冤家。Count是一个计数器,每两个系统时钟周期,Count会增加1,而当它的值和Compare相等时,会发生一个硬件中断(Hardware Interrupt)。这个特性经常用来为操作系统提供一个可靠的tick时脉。
Status: 这个寄存器标识了处理器的状态。其中,中断控制的8个IM(Interrupt Mask)位和设定处理器大小端的RE(Reverse Endianess)位。8个IM位,分别可以控制8个硬件中断源。它们将在讲述‘硬件中断’时详解。RE位很有趣,设定这个Bit可以让CPU在大端(Big Endian)和小端(Little Endian)之间切换。默认情况下,MIPS处理器是大端的,和网络序相同。但是,为了能在MIPS上运行类似Windows NT的服务器操作系统,设定这个Bit可以令处理器工作在Little
Endian模式下。
Cause: 在处理器异常发生时,这个寄存器标识出了异常的原因。其中,最重要的是从Bit2到Bit6,5个Bit的Excetion Code位。它们标识出了引起异常的原因。具体数值代表的异常类型,如下所示:
0: Interrupt,中断;
1: TLB Modified,试图修改TLB中映射为只读的内存地址;
2: TLB Miss Load,试图读取一个没有在TLB中映射到物理地址的虚拟地址;
3: TLB Miss Store,试图向一个没有在TLB中映射到物理地址的虚拟地址存入数据;
4: Address Error Load,试图从一个非对齐的地址读取信息;
5: Address Error Store,试图向一个非对齐的地址写入信息;
6: Instruction Bus Error,一般是指令Cache出错;
7: Data Bus Error,一般是数据Cache出错;
8: Syscall,由syscall指令产生。操作系统下,通用的由用户态进入内核态的方法。可以类比IA32的“调用门”理解;
9: Break Point,由break指令产生。最常见的bp指令,是由编译器产生的,在除法运算时插入一个break point指令,以达到在除0时抛出错误信息的目的。因此,如果在定位问题时发现了一个Break Point异常,且它的异常分代码为07,应当考虑是出现了除0的情形;
10: RI,保留指令。在CPU执行到一条没有定义的指令时,进入此异常;
11: Co-processor Unavilible,协处理器不可用。这个异常是由于试图对不存在的协处理器进行操作引起的。特别的,在没有浮点协处理器的处理器上执行这条命令,会导致这个异常。随之,操作系统会调用模拟浮点的lib库,来实现软件的浮点运算;
12: Overflow,算术溢出。只有带符号的运算会引起这个异常;
13: Trap,这个异常来源于trap指令。和syscall指令类似地,trap指令也会引起一个异常,但trap指令可以附带一些条件,这样可以用于调试程序用。
14: VCEI,指令高速缓存中的虚地址一致性错误。(没明白怎么回事,还有待高手补充)
15: Float Point Exception,浮点异常;
16: Co-processor 2 Exception,协处理器2的异常;
17~22,留作将来的扩展;
23: Watch,内存断点异常。当设定了WatchLo/WatchHi两个寄存器时起作用。当load/store的虚拟地址和WatchLo/WatchHi中匹配时,会引发这样一个异常;/* 这个地方经典著作《See MIPS Run》犯了一个错误,将虚拟地址写成了物理地址 */
24~30,留作将来的扩展;
EPC: 这个寄存器的作用很简单,就是保存异常发生时的指令地址。从这个地方可以找到异常发生的指令,再结合BadVAddr, sp, ra等寄存器,就可以推导出异常时的程序调用关系,从而定位问题的根因。一旦异常发生时EPC的内容丢失,那么对异常的定位将是一件非常困难的事情。
WatchLo/WatchHi: 这一对寄存器可以用来设定“内存硬件断点”,也就是对指定点的内存进行监测。当访问的内存地址和这两个寄存器中地址一致时,会发生一个异常。为了适应64Bit的一些扩展功能,某些MIPS处理器又对这两个寄存器的功能做了一些修改,与MIPS体系结构的定义已经有了差别,如RMI的多核处理器等。
对于协处理器CP0的访问,需要使用特别的指令。这些指令属于“特权级指令”,只有在内核态(Kernel Mode)下才能执行。如果在用户态下,会引起一个异常(Exception)。
对CP0的主要操作有以下的指令:
mfc0 rt, rd 将CP0中的rd寄存器内容传输到rt通用寄存器;
mtc0 rt, rd 将rt通用寄存器中内容传输到CP0中寄存器rd;
mfhi/mflo rt 将CP0的hi/lo寄存器内容传输到rt通用寄存器中;
mthi/mtlo rt 将rt通用寄存器内容传输到CP0的hi/lo寄存器中;
当MIPS体系结构演进到MIPS IV的64位架构后,新增了两条指令dmfc0和dmtc0,向CP0的寄存器中读/写一个64bit的数据。
前面提到,MIPS体系结构是一个无互锁,高度流水的五级pipeline架构,这就意味着,前一条指令如果尚未执行完,后一条指令有可能已经进入了取指令/译码阶段。这样,就有可能发生所谓的CP0冒险(CP0 Hazard)现象。简单地说,就是mfc和mtc指令的执行速度是比较慢的,因此,开始执行完下一条指令时,有可能CP0寄存器的值尚未最后传输到指定的目标通用寄存器中。此时,如果读取该通用寄存器,有可能并未得到正确的值。这就是所谓的CP0冒险现象。
为了避免CP0冒险,我们在编程时需要在CP0操作指令的后面加上一条与前一条指令的目的通用寄存器无关的指令,也就是所谓的延迟槽(delay slot)。如果对性能不敏感,可以考虑用一条nop空操作指令填充延迟槽。
例如,经常在异常处理例程(exception handler)中出现的内容:
mfc0 k0, $cause
nop /* mfc0指令执行速度慢,在延迟槽中加一个空操作 */
mov t0, k0 /* 将cause寄存器内容放到t0,进行下一步操作 */
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:460671次
积分:5579
积分:5579
排名:第3541名
原创:146篇
转载:252篇
(9)(14)(15)(12)(7)(22)(7)(6)(3)(1)(5)(3)(9)(14)(3)(11)(15)(2)(1)(4)(2)(1)(3)(4)(2)(1)(3)(6)(4)(4)(3)(9)(16)(1)(5)(3)(2)(1)(10)(18)(22)(7)(23)(15)(8)(35)(19)(7)leishangwen
阅读(2922)
将陆续上传本人写的新书《》,今天是第34篇,我尽量每周四篇
感兴趣的朋友可以在亚马逊、当当、京东等查找。
另外,开展晒书评送书活动,在、京东、当当三大图书网站上,发表《自己动手写CPU》书评的前十名读者,均可获赠《步步惊芯——软核内部》一书,大家踊跃参与吧!活动时间:至
& & & 本章将为OpenMIPS处理器添加转移指令,转移指令包括跳转、分支两种,区别在于前者是绝对转移,后者是相对转移,但实现方法是相似的。转移指令涉及延迟槽,所以首先在8.1节介绍了延迟槽的概念,接着在8.2节对MIPS32指令集架构中定义的所有转移指令的格式、作用、用法进行了说明。在8.3节介绍了OpenMIPS实现转移指令的思路,以及对数据流图、系统结构的修改。8.4节通过修改代码实现了转移指令,最后通过两个测试程序,验证转移指令是否实现正确。
8.1 延迟槽
& & & 在实现转移指令之前,先介绍一下延迟槽的概念。在第5章已经介绍了流水线中存在的三种相关:数据相关、结构相关、控制相关。其中控制相关是指流水线中的转移指令或者其他需要改写PC的指令造成的相关。这些指令改写了PC的值,所以导致后面已经进入流水线的几条指令无效,比如:如果转移指令在流水线的执行阶段进行转移条件判断,在发生转移时,会导致当前处于取指、译码阶段的指令无效,需要重新取指。如图8-1所示。
& & & 也就是在流水线执行阶段进行转移判断,并且转移发生,那么会有2条无效指令,导致浪费了两个时钟周期。为了减少损失,规定转移指令后面的指令位置为“延迟槽”,延迟槽中的指令被称为“延迟指令”(也可称之为“延迟槽指令”)。延迟指令总是被执行,与转移发生与否没有关系。引入延迟槽后的指令执行顺序如图8-2所示。OpenMIPS处理器就计划使用延迟槽技术。
& & & 但是,即使引入延迟槽,在转移发生时仍然会导致已经进入取指阶段的指令无效,也就是仍浪费一个时钟周期,要解决这个问题,可以在译码阶段进行转移判断,这样就可以避免浪费时钟周期。OpenMIPS处理器就设计为在译码阶段进行转移判断。
8.2 转移指令说明
& & & MIPS32指令集架构中定义的转移指令共有14条,可分为如下两类。
&跳转指令: jr 、jalr 、j 、jal&分支指令:b、bal、beq、bgez、bgezal、bgtz、blez、bltz、bltzal、bne
& & & 其中,跳转指令是绝对转移,分支指令是相对转移。本节分别介绍这两类指令。
& & & 1、跳转指令
& & & 跳转指令的格式如图8-3所示。
& & & 从图8-3可知,j、jal指令可以通过指令码进行判断,jr、jalr指令的指令码为SPECIAL,还需要依据功能码进一步判断。
&当指令中的指令码为SPECIAL,功能码为6'b001000时,表示是jr指令
& & & 指令用法为:jr rs
& & & 指令作用为:pc &- rs,将地址为rs的通用寄存器的值赋给寄存器PC,作为新的指令地址。
&当指令中的指令码为SPECIAL,功能码为6'b001001时,表示是jalr指令
& & & 指令用法为:jalr rs 或者jalr rd, rs
& & & 指令作用为:rd &- return_address, pc &- rs,将地址为rs的通用寄存器的值赋给寄存器PC,作为新的指令地址,同时将跳转指令后面第2条指令的地址作为返回地址保存到地址为rd的通用寄存器,如果没有在指令中指明rd,那么默认将返回地址保存到寄存器$31。
&当指令中的指令码为6'b000010时,表示是j指令
& & & 指令用法为:j target
& & & 指令作用为:pc &- (pc+4)[31,28] || target || ‘00’,转移到新的指令地址,其中新指令地址的低28位是指令中的target(也就是图8-3中的instr_index)左移两位的值,新指令地址的高4位是跳转指令后面延迟槽指令的地址高4位。
&当指令中的指令码为6'b000011时,表示是jal指令
& & & 指令用法为:jal target
& & & 指令作用为:pc &- (pc+4)[31,28] || target || ‘00’,转移到新的指令地址,新指令地址与指令j相同,不再解释。但是,指令jal还要将跳转指令后面第2条指令的地址作为返回地址保存到寄存器$31。
& & & j、jal、jr、jalr指令在转移之前都要先执行延迟槽指令。
& & & 2、分支指令
& & & 分支指令的格式如图8-4所示。
& & & 从图8-4可知,前5条指令beq、b、bgtz、blez、bne可以直接依据指令中的指令码进行判断,确定是哪一条指令,而后5条指令bltz、bltzal、bgez、bgezal、bal的指令码都是REGIMM,这是一个宏定义,值为6'b000001,需要根据指令中16-20bit的值进一步判断,从而确定是哪一条指令。
& & & 从图8-4还可知,所有分支指令的0-15bit存储的都是offset,如果发生转移,那么将offset左移2位,并符号扩展至32位,然后与延迟槽指令的地址相加,加法的结果就是转移目的地址,从该地址取指令。
转移目标地址 = (signed_extend)( offset || ‘00’ ) + (pc+4)
&当指令中的指令码为6'b000100时,表示是beq指令
& & & 指令用法为:beq rs, rt, offset
& & & 指令作用为:if rs = rt then branch,将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值进行比较,如果相等,那么发生转移。
&当指令中的指令码为6'b000100,且16-25bit为0时,表示是b指令
& & & 指令用法为:b offset
& & & 指令作用为:无条件转移,从图8-4可知,b指令可以认为是beq指令的特殊情况,当beq指令的rs、rt都等于0时,即为b指令,所以在OpenMIPS实现的时候不需要特意实现b指令,只需要实现beq指令即可。
&当指令中的指令码为6'b000111时,表示是bgtz指令
& & & 指令用法为:bgtz rs, offset
& & & 指令作用为:if rs & 0 then branch,如果地址为rs的通用寄存器的值大于零,那么发生转移。
&当指令中的指令码为6'b000110时,表示是blez指令
& & & 指令用法为:blez rs, offset
& & & 指令作用为:if rs ≤ 0 then branch,如果地址为rs的通用寄存器的值小于等于零,那么发生转移。
&当指令中的指令码为6'b000101时,表示是bne指令
& & & 指令用法为:bne rs, rt, offset
& & & 指令作用为:if rs ≠ rt then branch,如果地址为rs的通用寄存器的值不等于地址为rt的通用寄存器的值,那么发生转移。
&当指令中的指令码为REGIMM,且16-20bit为5'b00000时,表示是bltz指令
& & & 指令用法为:bltz rs, offset
& & & 指令作用为:if rs & 0 then branch,如果地址为rs的通用寄存器的值小于0,那么发生转移。
&当指令中的指令码为REGIMM,且16-20bit为5'b10000时,表示是bltzal指令
& & & 指令用法为:bltzal rs, offset
& & & 指令作用为:if rs & 0 then branch,如果地址为rs的通用寄存器的值小于0,那么发生转移,并且将转移指令后面第2条指令的地址作为返回地址,保存到通用寄存器$31。
&当指令中的指令码为REGIMM,且16-20bit为5'b00001时,表示是bgez指令
& & & 指令用法为:bgez rs, offset
& & & 指令作用为:if rs ≥ 0 then branch,如果地址为rs的通用寄存器的值大于等于0,那么发生转移。
&当指令中的指令码为REGIMM,且16-20bit为5'b10001时,表示是bgezal指令
& & & 指令用法为:bgezal rs, offset
& & & 指令作用为:if rs ≥ 0 then branch,如果地址为rs的通用寄存器的值大于等于0,那么发生转移,并且将转移指令后面第2条指令的地址作为返回地址,保存到通用寄存器$31。
&当指令中的指令码为REGIMM,且21-25bit为0,16-20bit为5'b10001时,表示是bal指令
& & & 指令用法为:bal offset
& & & 指令作用为:无条件转移,并且将转移指令后面第2条指令的地址作为返回地址,保存到通用寄存器$31。从图8-4的指令格式可知,bal指令是bgezal指令的特殊情况,当bgezal指令的rs为0时,就是bal指令,所以在OpenMIPS实现时,不用特意考虑bal指令,只要实现bgezal指令即可。
& & & 综上,b、bal指令不用单独实现,需要OpenMIPS实现的分支指令只有8条。
& & & 所有的分支指令在转移到目标地址前都要先执行延迟槽中的指令。
阅读排行榜MIPS指令特点: 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的指令在这个阶段没做什么事情,但它是指令有序性的保证(为什么是保证,我还没看清楚?)。占一个时钟周期; 第五阶段:存储计算结果到缓冲或内存的阶段。占半个时钟周期; =& 所以一条指令要占用四个时钟周期;
15、MIPS的虚拟地址内存映射空间: a& 0x ~ 0x7fff ffff 用户级空间,2GB,要经MMU(TLB)地址翻译。kuseg。可以控制要不要经过缓冲。
b&0x ~ 0x9fff ffff kseg0. 这块区域为操作系统内核所占的区域,共512M。使用时,不经过地址翻译,将最高位去掉就线性映射到内存的低512M(不足的就裁剪掉顶部)。但要经过缓冲区过渡。
c&0xa000&0000 ~ 0xbfff ffff kseg1. 这块区域为系统初始化所占区域,共512M。使用时,不经过地址翻译,也不经过缓冲区。将最高3位去掉就线性映射到内存的低512M(不足的就裁剪掉顶部)。
d&0xc000&0000 ~ 0xffff ffff kseg2. 这块区域也为内核级区域。要经过地址翻译。可以控制要不要经过缓冲。
16、MIPS的协处理器 CP0:这是MIPS芯片的配置单元。必不可少,虽然叫做协处理器,但是通常都是做在一块芯片上。绝大部分MIPS功能的配置,缓冲的控制,异常/中断的控制,内存管理的控制都在这里面。所以是一个完整的系统所必不可少的;
17、 MIPS的高速缓冲 MIPS一般有两到三级缓冲,其中第一级缓冲数据和指令分开存储。这样的好处是指令和数据可以同时存取,提高效率。但缺点是提高了复杂度。第二级缓冲和第三级缓冲(如果有的话)就不再分开存放啦。
缓冲的单元叫做缓冲行(cache line)。每一行中,有一个tag,然后后面接的是一些标志位和一些数据。缓冲行按顺序线性排列起来,就组成了整个缓冲。
cache line的索引和存取有一套完整的机制。 18、MIPS的异常机制精确异常的概念:在运行流程中没有任何多余效应的异常。即当异常发生时,在受害指令之前的指令被完全执行,而受害指令及后面的指令还没开始执行(注:说受 害指令及后面的指令还没做任何事情是不对的,实际上受害指令是处于其指令周期的第三阶段刚完成,即ALU阶段刚完成)。精确异常有有助于保证软件设计上不 受硬件实现的影响。
CP0中的EPC寄存器用于指向异常发生时指令跳转前的执行位置,一般是受害指令地址。当异常时,是返回这个地址继续执行。但如果受害指令在分支延迟槽中,则会硬件自动处理使EPC往回指一条指令,即分支指令。在重新执行分支指令时,分支延迟槽中的指令会被再执行一次。
精确异常的实现对流水线的流畅性是有一定的影响的,如果异常太多,系统执行效率就会受到影响。
*异常又分常规异常和中断两类。常规异常一般为软件的异常,而中断一般为硬件异常,中断可以是芯片内部,也可以是芯片外部触发产生。
异常发生时,跳转前最后被执行的指令是其MEM阶段刚好被执行完的那条指令。受害指令是其ALU阶段刚好执行完的那条指令。
异常发生时,会跳到异常向量入口中去执行。MIPS的异常向量有点特殊,它一般只个2个或几个中断向量入口,一个入口给一般的异常使用,一个入口给 TLB miss异常使用(这样的话,可以省下计算异常类型的时间。在这种机制帮助下,系统只用13个时钟周期就可以把TLB重填好)。
CP0寄存器中有个模式位,SR(BEV),只要设置了,就会把异常入口点转移到非缓冲内存地址空间中(kseg1)。
MIPS系统把重启看作一个不可回归的异常来处理。 冷启动:CPU硬件完全被重新配置,软件重新加载; 热启动:软件完全重新初始化;
MIPS对异常的处理的哲学是给异常分配一些类型,然后由软件给它们定义一些优先级,然后由同一个入口进入异常分配程序,在分配程序中根据类型及优先级确定该执行哪个对应的函数。这种机制对两个或几个异常同时出现的情况也是适合的。
下面是当异常发生时MIPS CPU所做的事情: a 设置EPC指向回归的位置; b 设置SR(EXL)强迫CPU进入kernel态,并禁止所有中断响应。 c 设置Cause寄存器,以使软件可以得到异常的类型信息;还有其它一些寄存器在某些异常时会被设置; d CPU开始从异常入口取指令,然后以后的所有事情都交由软件处理了。
k0和k1寄存器用于保存异常处理函数的地址。 异常处理函数执行完成后,会回到异常分配函数那去,在异常分配函数里,有一个eret指令,用于回归原来被中断的程序继续执行;eret指令会原子性地把中断响应打开(置SR(EXL)),并把状态级由kernel转到user级,并返回原地址继续执行。
19、中断 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 同时有多个中断发生时,优先执行优先级最高的那个中断程序;
20、大小端问题 硬件上也有大端小端问题,比如串口通讯,一个字节一个字节的发,首先是低位先发出去。 还有显卡的显示,比如显示黑白图像,在屏幕上一个点对应显存中的一位,这时,这个位对应关系就是屏幕右上角那个点对应显存第一个字节的7号位,即最高位。第一排第8位点对应第一个字节的0号位。
21、MIPS上的Linux运行情况
用户态和核心态:在用户态,不能随意访问内核代码和数据存放区,只能访问用户态空间和内核允许访问(通过某种机制)的内核页面。也不能执行CP0相关的指令。用户态要执行内核的某些服务,就得用系统调用(system_call),在系统调用的最后,是一个eret指令。
任何时候Linux都有至少一个线程在跑,Linux一般不禁止中断。发生中断时,其环境是从被中断线程借来的。
中断服务程序(ISR)应该短小。
MIPS Linux系统上半地址空间只能用内核特权级访问。内核不通过TLB地址翻译。
所有线程都共用相同的内核地址空间,但只有同一组线程才用同一个用户地址空间(指向同一个mm_struct结构)。
如果物理内存高于512M,那么不能用kseg0和kseg1来映射高于512M的内存部分。只能用kseg2来映射。kseg2要经过TLB。
从某个方面说,内核就是一组供异常处理函数调用的子程序。内核中,线程调度器就是这样一个小的子程序。由各个线程(异常处理程序也可以算作一个特殊的线程,换他书上的说法)调用。
MIPS Linux有异常模式,而x86上没有这个概念。
异常要小心操作。不是仅用软件锁就能解决的。
21、原子操作 MIPS为支持操作系统的原子操作,特地加了一组指令 ll/sc。它们这样来使用:
先写一句 atomic_block: LL XX1, XXX2 &. sc XX1, XXX2 beq XX1, zero, automic_block &.
在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次。
22、系统调用 syscall 系统调用也通过异常入口进入系统内核,选择8号异常代码处理函数进行处理,进入系统调用分配函数后,还要根据传进来的参数再一次分配到具体的功能函数上去。系统调用传递参数是在寄存器中进行的。
系统调用号存放在v0中,参数存放在a0-a3。如果参数过多,会有另一套机制来处理。系统调用的返回值通常放在v0中。如果系统调用出错,则会在a3中返回一个错误号。
23、异常入口点位于kseg0的底部,是硬件规定的。
24、注意:地址空间的0x是不能用的,从0开始的一个或多个页不会被映射。
25、内存分页映射有以下优点: a 隐藏和保护数据; b 分配连续的地址给程序; c 扩展地址空间; d 按需求载入代码和数据(通过异常方式); e 便于重定位; f 代码和数据在线程中共享,便于交换数据;
所有的线程是平等的,所有的线程都有自己的内存管理结构体;运行于同一地址空间的线程组,共享有大部分这种数据结构。在线程中,保存有本地址空间已经使用的页面的一个页表,用来记录每个已用的虚页与实际物理页的映射关系;
26、ASID是与虚拟页高位配合使用。用于描述在TLB和Cache中的不同的线程,只有8位,所以最多只能同时运行256个线程。这个数字一般来说是够的。如果超过这个数目了,就要把Cache刷新了重新装入。所以,在这点上,与x86是不同的。
27、MIPS Linux的内存驻留页表结构 用的是两级页表,一个页表目录,一个页表,页表中的每一项是一个 EntryLo0-1。 (这与x86方式类似)。而没有用MIPS原生设计的方案。
28、TLB的refill过程-硬件部分 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异常,然后后面就是软件的工作了;
29、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 个通用寄存器,寄存器数量跟编译器的的要求有关。寄存器分配在编译优化中是最重要的优化之一(也许是做重要的)。现在的寄存器分配算法都是基于图着色的技 术。其基本思想是构造一个图,用以代表分配寄存器的各个方案,然后用此图来分配寄存器。粗略说来就是使用有限的颜色使图中相临的节点着以不同的颜色,图着 色问题是个图大小的指数函数,有些启发式算法产生近乎线形时间运行的分配。全局分配中如果有16个通用寄存器用于整型变量,同时另有额外的寄存器用于浮点 数,那么图着色会很好的工作。在寄存器数教少时候图着色并不能很好的工作。 && 问: 既然不能少于16个,那为什么不用64个呢? 答: 使用64个或更多寄存器不但需要更大的指令空间来对寄存器编码,还会增加上下文切换的负担。除了那些很大不能感非常复杂的函数,32个寄存器就已足够保存 经常使用的数据。使用更多的寄存器并不必要,同时计算机设计有个原则叫&越小越快&,但是也不是说使用31个寄存器会比32个性能更好,32个通用寄存器 是流行的做法。指令格式 所有MIPS指令长度相同,都是32位,但为了让指令的格式刚好合适,于是设计者做了一个折衷:所有指令定长,但是不同的指令有不同的格式。MIPS指令有三种格式:R格式,I格式,J格式。每种格式都由若干字段(filed)组成,图示如下: I型指令 && && 6 && 5 &&& 5 &&& 16    ------|-----|-----|------------------| && | op | rs | rt&& | 立即数操作 | && &&& ------|-----|-----|------------------| 加载/存储字节,半字,字,双字 条件分支,跳转,跳转并链接寄存器 R型指令 && && 6 && 5 &&& 5 &&& 5 &&& 5 &&& 6    ------|-----|-----|-----|-----|--------| && |op | rs&& | rt&& | rd |shamt|funct |    ------|-----|-----|-----|-----|---------| 寄存器-寄存器ALU操作 读写专用寄存器 J型指令 && && 6 && && && && 26 && ------|------------------------------| && |op&& |  跳转地址 && && && | && &&& ------|------------------------------| 跳转,跳转并链接 陷阱和从异常中返回
各字段含义:op:指令基本操作,称为操作码。
rs:第一个源操作数寄存器。
rt:第二个源操作数寄存器。
rd:存放操作结果的目的操作数。
shamt:位移量
funct:函数,这个字段选择op操作的某个特定变体。&& 所有指令都按照着三种类型之一来编码,通用字段在每种格式中的位置都是相同的。 &&& 这种定长和简单格式的指令编码很规则,很容易看出其机器码,例如: add $t0,$s0,$s1 &&& 表示$t0=$s0+$s1,即16号寄存器(s0)的内容和17号寄存器(s1)的内容相加,结果放到8号寄存器(t0)。 &&& 指令各字段的十进制表示为    ------|-----|-----|-----|-----|------| && |&& 0 | 16 | 17 |&& 8 |&& 0 |&& 32 |    ------|-----|-----|-----|-----|------| op=0和funct=32表示这是加法,16=$s0表示第一个源操作数(rs)在16号寄存器里,17=$s1表示第二个源操作数(rt)在17号寄存器里,8=$t0表示目的操作数(rd)在8号寄存器里。 把各字段写成二进制,为 ------|-----|-----|-----|-----|------| && |00||| ------|-----|-----|-----|-----|------| 这就是上述指令的机器码(machine code),可以看出是很有规则性的。
通用寄存器(GPR) 有32个通用寄存器,$0到$31: $0: 即$zero,该寄存器总是返回零,为0这个有用常数提供了一个简洁的编码形式。MIPS编译器使用slt,beq,bne等指令和由寄存器$0获得的0 来 产生所有的比较条件:相等,不等,小于,小于等于,大于,大于等于。还可以用add指令创建move伪指令,即 move $t0,$t1 实际为 add $t0,$0,$t1 焦林前辈提到他移植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)过程(不调用其它过程的过程)的时候,总是在临时寄存器分配完了才使用需要保存的寄存器。$24..$25:($t8-$t9)同($t0-$t7)$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返回。在调用时需要保存的寄存器为$a0~$a3,$s0~$s7,$gp,$sp,$fp,$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次方),这样的方法是很理想的。
0 zero 永远返回值为0
1 at 用做汇编器的暂时变量
2-3 v0, v1 子函数调用返回结果
4-7 a0-a3 子函数调用的参数
8-15 t0-t7 暂时变量,子函数使用时不需要保存与恢复
24-25 t8-t9
16-25 s0-s7
子函数寄存器变量。子函数必须保存和恢复使用过的变量在函数返回之前,从而调用函数知道这些寄存器的值没有变化。
26,27 k0,k1 通常被中断或异常处理程序使用作为保存一些系统参数
28 gp 全局指针。一些运行系统维护这个指针来更方便的存取&static&和&extern"变量。
29 sp 堆栈指针
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的值,通常通过堆栈。 对於浮点寄存器的用法,也有一个相应的标准的约定。在这里,我们已经介绍了引入的寄存
指令实例:
1. load/store & la $t0, val_1 复制val_1表示的地址到t0寄存器中&&   注: val_1是个Label  lw $t2, ($t0) t0寄存器中的值作为地址,把这个地址起始的Word 复制到t2 中  lw $t2, 4($t0) t0寄存器中的值作为地址, 把这个地址再加上偏移量4后 所起始的Word 复制到t2 中  sw $t2, ($t0) 把t2寄存器中值(1 Word),存储到t0的值所指向的RAM中  sw $t2, -12($t0) 把t2寄存器中值(1 Word),存储到t0的值再减去偏移量12, 所指向的RAM 中 2. 算数运算指令   算数运算指令的所有操作数都是寄存器,不能直接使用RAM地址或间接寻址。   操作数的大小都为 Word (4-Byte)   指令格式与实例 注释   move $t5, $t1&&&&&& // $t5 = $t1;   add $t0, $t1,&&&&&& // $t2 $t0 = $t1 + $t2; 带符号数相加   sub $t0, $t1,& & && // $t2 $t0 = $t1 - $t2; 带符号数相减   addi $t0, $t1, 5&&& // $t0 = $t1 + 5;   addu $t0, $t1, $t2& // $t0 = $t1 + $t2; 无符号数相加   subu $t0, $t1, $t2& // $t0 = $t1 - $t2; 无符号数相减   mult $t3, $t4&&&&&& // $t3 * $t4, 把64-Bits 的积,存储到Lo,Hi中。即: (Hi, Lo) = $t3 * $t4;   div $t5, $t6&&&&&&& // Lo = $t5 / $t6 (Lo为商的整数部分); Hi = $t5 mod $t6 (Hi为余数)   mfhi $t0&&&&&&&&&&& // $t0 = Hi   mflo $t1 & & & & && // $t1 = Lo 3. 分支跳转指令  分支指令格式与实例 注释   b target 无条件的分支跳转,将跳转到target 标签处   beq $t0, $t1, target&&&&&& // 如果 $t0 == $t1, 则跳转到target 标签处   blt $t0, $t1, target&&&&&& // 如果 $t0 & $t1,& 则跳转到target 标签处   ble $t0, $t1, target&&&&&& // 如果 $t0 &=$t1,& 则跳转到target 标签处   bgt $t0, $t1, target&&&&&& // 如果 $t0 & $t1,& 则跳转到target 标签处   bge $t0, $t1, target&&&&&& // 如果 $t0 &= $t1, 则跳转到target 标签处   bne $t0, $t1, target&&&&&& // 如果 $t0 != $t1, 则跳转到target 标签处 4. 跳转指令  指令格式与实例 注释   j target&&&&&&&&& // 无条件的跳转, 将跳转到target 标签处   jr $t3&&&&&&&&&&& // 跳转到t3寄存器所指向的地址处(Jump Register) 5. 子函数调用指令  指令格式与实例 注释   jal sub_routine_label 执行步骤:   a. 复制当前的PC(Program Counter)到$ra寄存器中。 因为当前的PC 值就是子函数执行完毕后的返回 &&&&&& 地址。   b. 程序跳转到子程序标签sub_routine_label处。     注:子函数的返回,使用 jr $ra     如果子函数内又调用了其他的子函数,那么$ra的值应该被保存到堆栈中。 因为$ra的值总是对应着当前执 &&& 行的子函数的返回地址。
阅读(...) 评论()}

我要回帖

更多关于 阴界之门 手动延迟 的文章

更多推荐

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

点击添加站长微信