精华内容
下载资源
问答
  • 寄存器

    2020-04-22 22:49:17
    1.通用寄存器 一个典型的CPU由运算器,控制器和寄存器几部分...AX,BX,CX,DX这四个寄存器一般用来存放一般性数据,称为通用寄存器。 由于上一代CPU都是8位,出于兼容性考虑,将通用寄存器分为两个8位寄存器。如AX...

    1.通用寄存器

    一个典型的CPU由运算器,控制器和寄存器几部分构成,这些器件靠内部总线连接。程序员可以通过修改寄存器的数据,来实现对CPU的控制。不同CPU的寄存器的结构,数量都不相同。8086CPU所有的寄存器都是16位的,可以存放两个字节。AX,BX,CX,DX这四个寄存器一般用来存放一般性数据,称为通用寄存器
    由于上一代CPU都是8位,出于兼容性考虑,将通用寄存器分为两个8位寄存器。如AX由AH和AL组成,例如:
    寄存器AX中的数据为3412H,[AH]=34H,[AL]=12H
    在数据运算和传输的时候,操作对象位数应该一致。

    2.物理地址的产生

    由于8086CPU的地址总线有20位,而内部总线只有16位,8086CPU有一套独特的计算物理地址的方法,可以通过两次16位数据的传输加权算出20位的地理地址:
    物理地址=段地址*16+偏移地址
    乘以16是因为地址都是16进制计算,相当于让段地址左移一位,然后在这个基础上偏移。

    3.数据的基本知识

    3.1字的存储

    低位字节存在低地址,高位字节存在高地址,起始地址为N的字单元简称N地址单元。

    3.2mov,sub,add指令的用法

    三者用法基本一致,具体情况如图:
    在这里插入图片描述

    4.段寄存器

    段的划分来源于CPU,在编程时可将一段连续地址视为段。受16位寻址能力影响,段的最大长度位64KB(216B)。也由于段地址要乘一个16,所以段首地址必为16的倍数。这里要理解一下,记住这些话都是基于16进制而言,不要用十进制的思维去看待,比如2200H就是16的整数倍。

    • 代码寄存器CS,指针寄存器IP。CS:IP对应的数据会被认定为指令,计算机读取对应地址的数据,识别指令长度,然后IP自动加上指令长度,以这样的方式实现读取下一条指令。
    • 对CS:IP的修改只能用jmp指令,全修改:jmp 段地址:偏移地址;只修改IP:jmp 某个合法寄存器。
    • 访问数据的DS。在读取数据时默认以DS内的数据为段地址。
    • 栈SS:SP。栈遵守LIFO原则,也就是后进先出。基本操作为入栈PUSH,出栈POP。要注意,栈的所有操作对象都是,不是字节。当入栈时,先SP=SP-2,再放数据;出栈时,先出数据,再SP=SP+2.
      可以看出空栈的时候,SP其实是比栈空间的上界大一
    展开全文
  • MIPS指令集:寄存器

    千次阅读 2020-03-14 15:04:51
       不同的计算机架构中寄存器的种类和数量也不相同。MIPSmips中用到的寄存器按照功能分为有通用寄存器、协处理器0、浮点寄存器、乘法部件寄存器。通用寄存器共32个,是没有特殊限制,一般程序员可以使用的寄存器。...

       不同的计算机架构中寄存器的种类和数量也不相同。MIPSmips中用到的寄存器按照功能分为有通用寄存器、协处理器0、浮点寄存器、乘法部件寄存器。通用寄存器共32个,是没有特殊限制,一般程序员可以使用的寄存器。协处理器0寄存器也叫控制寄存器,共32个,用来控制并管理CPU。浮点寄存器和乘法部件寄存器都是专用寄存器。浮点寄存器也叫协处理器1寄存器,共32个,用来存储和浮点计算相关的数据。乘法部件寄存器共2个,用来存储和乘除法相关的数据。
       寄存器的长度是由机器字长决定的,机器字长是指计算机进行一次整数运算所能处理的二 进制数据的位数。龙芯3A/3B系列计算机的字长为64位,所以寄存器的长度也是64位。也叫64位寄存器。

    一、 32个通用寄存器

       MIPS可供程序使用的通用寄存器(general-purpose register 简称GPR)共32个,编号从$0-$31。各个通用寄存器的别名和功能如表1所示。

    表1 n64通用寄存器的别名和功能
    在这里插入图片描述
    n64代表的是用于64位处理器上的MIPS ABI。在表1中列出了n64上的32个通用寄存器的别名和功能简介。


    1.1 MIPS ABI

       ABI(Application Binary Interface):应用程序二进制接口。描述了应用程序与操作系统之间的底层接口,包括目标文件格式、数据类型、数据对齐方式、函数调用约定(应用程序如何使用内存地址和使用寄存器的约定)等。MIPS历史上重要的3个ABI如下:

    • o32 “o”代表old 。传统的32位处理器的MIPS ABI约定。指针和long类型为32位。
    • n64 “n”代表new。新的用于64位处理器的MIPS ABI约定。新的ABI约定了指针和long类型都为64位。同时改变了寄存器和参数传递的规则。将更多的参数用寄存器传递(a0-a8)。
    • n32 “n”代表new。用于64位处理器上兼容32位程序。指针和long类型还是使用32位。

    1.2 寄存器别名

       为了便于记忆和阅读,每个寄存器都有一个别名。别名多以寄存器功能的英文首字母+数字或字母缩写表示。例如寄存器2的别名a0开头的a代表arguments。寄存器29的别名sp代表stack pointer 。在实际汇编语言编写过程中,我们也更推荐使用寄存器的别名方式,使用MIPS别名时需要包含头文件regdef.h(例如 #include <sys/regdef.h>),里面已经定义好了寄存器编号和别名的对应。下面这两句指令是完全相等的。
        daddiu $29,$29,32
        daddiu sp,sp,32

        在MIPS指令中使用任何寄存器编号时都要加上符号”$”。上述两条指令是完全相同的,都是实现$29+32,结果存入$29的功能。使用$29和sp是完全相同的。不过使用sp更让我们是在对堆栈指针的操作。


    1.3通用寄存器功能介绍
    • 用作伪指令的寄存器zero、at

       寄存器zero($0)是常量寄存器,即不管存入什么值,永远返回0。其对我们平时的汇编语言编写用处不大,但是对一些合成指令的作用还是很大,它为我们提供了一种更简洁的编码方式。比如MIPS中常常用到的一些伪指令:

       //伪指令     ->     //汇编指令

       move t0,t1        or t0,t1,zero

       简单解释一下 伪指令。伪指令就是为了方便软件编程,由编译器定义的命令,在编译时转换为CPU实际执行的机器指令 。也就是说伪指令并不是CPU最终可执行的机器指令,而是给汇编器看的指令。汇编器负责将伪指令翻译成正式的机器指令。有的地方称伪指令为合成指或宏指令。

       MIPS汇编中没有两个寄存器之间数据拷贝的指令。要实现这个功能可以通过or指令实现。上面的汇编指令or t0,t1,zero 意思是寄存器t1和寄存器zero进行或运算 t1 | zero,结果存入t0。寄存器zero里面值都为0。所以t1|zero的结果还是t1,然后存入t0。这就实现了一个寄存器t1到另一个寄存器t0的数据拷贝。但是这个写法还不是很直观,从“or”名字上很难看出这是个拷贝功能。所以汇编器对此实现了与此功能相同的伪指令move,要实现寄存器t1到t0的拷贝,只要写成move t0,t1即可。伪指令move t0,t1就是告诉汇编器拷贝寄存器t1的值到寄存器t0。汇编器帮助我们把这条指令翻译成CPU可识别和执行的机器指令or t0,t1,zero。

       通用寄存器at(assembly temporary),为汇编器所保留。基本可以认为通用寄存器at是伪指令的中间变量。之前提到过I型指令的立即数字段只有16位,所以在加载大常数(大于16位的数)时,编译器或汇编程序需要把大常数拆开,然后重新组合到寄存器里。比如加载一个32位立即数需要 lui(装入高位立即数)和addi两条指令。这个过程由汇编程序来完成。这时汇编器就需要一个临时寄存器at来重新组合大常数。

       既然寄存器at是为汇编器所保留的,那么我们在编写汇编程序时就要注意避免使用此寄存器。当然如果你确实想在自己的汇编程序中使用这个寄存器,可以通过伪指令.set noat来通知汇编器。但是这样一来汇编器中用at做中间变量的宏指令就不能再使用了,比如指令rol(循环左移)、指令rem(有符号整数除余)、指令jal(跳转)等。

    • 用作函数调用的寄存器v0、v1、a0-a7、ra
         所有的高级语言中都有子程序或者函数这个概念。子程序或者函数就是可以独立实现一个特定功能的程序块。在这里我更愿意使用函数。函数基本由返回值、函数名、参数、函数体组成。格式如下:

       返回值类型 函数名称(参数列表){函数体};

       在使用一个函数时需要关心的是参数、返回值和返回地址。MIPS n64约定函数调用时使用寄存器a0至寄存器a7来传递前8个参数,用寄存器v0存放子程序的返回值(整数或者指针),寄存器v1保留,用寄存器ra保存返回地址。

       比如我们编写一个有2个整型参数,一个整型返回值子程序的c语言代码如下:
          int ret = add(2,3); //c语言代码

       经过编译后对应的汇编指令如下:

       li a0,0x2
       li a1,0x3
       bal add
       nop
       sw v0,32(gp)

       第1行和第2行分别将两个整型参数2、3存入寄存器a0、a1。第3行的bal指令完成跳转到函数add。第4行的nop是空指令,意思是什么都不做,充当延迟槽作用。指令bal实现到函数add的跳转,在跳转到add之前,指令bal会负责保存返回地址(指令sw 所在位置)到寄存器ra($31)。这样在函数add返回可以通过jr ra完成。汇编器把变量ret的地址保存在gp+32的位置。那么就可以通过最后的sw指令把返回值v0写到ret。


    • 充当临时变量的寄存器t0-t3、t8、t9

       通用寄存器t0至t3和t8、t9共6个寄存器在子程序中充当临时变量的作用,这里”t”表示temporaries。临时变量就是在子程序中使用的变量。子程序结束后,这几个寄存器的值就无效。所以一个程序中不必保存而自由使用这6个寄存器。其中,t9($25)经常被汇编器用来保存子程序的地址,然后执行子程序跳转功能,类似于如下写法:

        jalr t9

    指令jalr t9意思是跳转到寄存器t9所存储的地址。
    说明:Linux/MIPS约定所有PIC调用都是用t9存储被调函数的地址。


    • 寄存器变量s0-s7

        s0-s7中的”s”代表saved。这8个寄存器是保存寄存器,如果程序中使用了这组里面的寄存器,在发生函数调用之前需要对该寄存器做保存。也称子程序寄存器变量。这里解释一下什么是寄存器变量。通常我们的程序是保存在内存或者外存上的,需要时才会加载到寄存器。如果一个变量在程序中频繁使用,例如循环变量,那么,系统就必须多次访问内存中的该单元,影响程序的执行效率。因此,C语言\C++语言还定义了一种变量,不是保存在内存上,而是直接存储在CPU中的寄存器中,这种变量称为寄存器变量。寄存器变量的定义格式为 “register 类型标识符 变量名”。例如我们使用c语言定义如下两个寄存器变量:

        register int a=2;
        register int b=3;

    上面两行语句经过gcc编译后,对应的汇编指令如下:

        li s1,2
        li s0,3

        上面的2条汇编指令li是加载立即数(常数)到寄存器s1和s0。使用时就无需去内存加载。s0-s7在使用上和t0-t9恰恰相反,s0-s7在子程序的执行过程中,需要将它们存储在堆栈里,并在子程序结束前恢复。从而在调用函数看来这些寄存器的值没有变化。


    • 系统保留寄存器k0、k1

       k0和k1是为系统所保留,专门保留给系统发生中断时程序使用的寄存器。这里的”k”代表keep。中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。

       MIPS架构的系统在这个过程中就是通过k0,k1引用一段可以保存其他寄存器的内存空间来实现正在运行程序的环境保存。关键汇编程序如下:

       mfc0 k1,CP0_CAUSE
       andi k1,k1,0x7c
       ld k0,exception_handlers(k1)
       jr k0

       上述指令中,首先通过指令mfc0把控制寄存器CP0_CAUSE值拷贝到通用寄存器k1。CP0_CAUSE保存了这次中断发生的原因。用andi指令把k1的值加上0x7c,然后用指令ld加载具体一个中断处理程序的入口地址exception_handlers(k1)。exception_handlers()记录了很多中断处理程序,根据k1的值找到当前系统需要执行哪个中断处理程序。然后使用指令jr跳转到这个中断处理程序。exception_handlers(k1)里将完成被中断的程序环境保存。

    备注:这段指令引自内核代码genex.S


    • 寻址寄存器gp

       通用寄存器gp可以看做是为汇编器保留。gp代表global pointer,用于快速的存取static和extern类型的变量。
       MIPS指令集中,加载指令都是I-型指令,也就是用16位来存储地址偏移量。所以要对一个常数或者变量地址的加载最少需要2条指令才能完成。比如要加载32位的变量地址就先需要一条指令加载变量地址的高16位到临时寄存器(通常是通用寄存器at),然后是一条以该变量地址的低16位为偏移量的加载指令。例如:

       lui at,%hi(addr)
       lw v0,%lo(addr)(at)

       上面两条指令中lui at,%hi(addr)就代表了取变量addr的高16位,赋值给at。%lo(addr)就是取变量addr的低16位做偏移量,at为基址。%lo(addr)(at)相当于at+%lo(addr)。指令lw 就完成了addr地址加载到寄存器v0。

       c语言程序中包含了很多static和extern类型的变量。对这些类型变量的加载和存储需要至少2条指令来完成确实开销很大。对此编译器利用寄存器gp来做了优化,可以使用1条指令完成对这类变量的加载和存储。做法编译器会统一把static和extern类型变量放在一个64KB大小的内存区域。然后让寄存器gp指向这块内存区域的中间位置,接下来的变量寻址都以gp为基址,加上特定偏移量就可定位到某个变量所在地址并完成加载或者存储。例如:

       lw v0,offset(gp)

    gp+offset就可以定位到一个static或extern类型变量的地址,然后通过指令lw加载到寄存器v0。

    注意:这里的变量不包括函数内部定义的局部变量。函数内部的局部变量是通过寄存器或堆栈实现,不需要gp。


    • 函数栈和寄存器sp、fp

       在计算机领域,栈(stack)是允许在同一端进行插入和删除操作的动态存储空间。它按照先进后出的原则存储数据,先进入的数据被压在栈底,最后的数据在栈顶。函数栈就是在程序运行时动态分配,用来保存一个函数调用时需要维护的信息。这些信息包括函数的返回地址和参数,临时变量、栈位置。一个典型的函数栈如图1所示:

    图1:MIPS函数栈示例
    在这里插入图片描述

       MIPS架构中,栈是向下增长的,也就是栈底在高地址,栈顶在低地址。在图3-2中,GPR[sp]代表通用寄存器sp($29)指向栈顶,又称sp为栈指针(stack pointer)。每次函数开始的时候sp都会向下移动n字节,sp预先指定了一块存储区域。这n个字节就是此函数的栈空间。这里要求n要求必须是16的倍数。典型的分配函数栈指令:

       daddiu sp,sp,-48

       指令daddiu是带常数的加法指令,上面指令相当于c语言表达式sp = sp-48。相当于把sp指针向下移动48字节,也就是申请一个48字节的栈。当函数返回时就通过sp指针向上移动48字节恢复栈,指令如下:

       daddiu sp,sp,48

       通常通过sp栈指针分配空间后,我们就可以根据需要操作函数信息的保存和恢复。一般都会先把通用寄存器ra和fp入栈,如果有gp的操作,那么也会把gp入栈。当此函数要调用其他函数时,那么此函数内部的局部变量、寄存器变量、参数等也要做入栈保存。

       栈空间的分配是以进程为单位的。进程是系统进行资源分配和调度的基本单位。系统会在进程启动时指定一个固定大小的栈空间,用于该进程的函数参数和局部变量的存储。sp的初始值就指向了这个固定大小栈的栈底。该进程中的每一次函数调用,都会通过sp指针的移动来为函数在此空间划分出一块空间用作函数栈,sp指向栈顶。函数栈又被称作栈帧(stack frame),用通用寄存器fp($30)指向当前函数栈的栈底。fp又被称作帧指针(frame pointer)。一个进程典型的栈空间如图3-3所示。

       每次调用一个函数,都要为该次调用的函数实例分配栈空间,用来局部变量的分配和释放传递函数参数,存储返回值信息,保存寄存器、以供恢复调用前处理机状态。mips下栈空间采用的是向下增长的方式(上面为高地址,下面为低地址)。SP(stack pointer) 就是当前函数的栈指针,它指向的是栈底的位置。
       进入一个函数时需要将当前栈指针向下移动 n 字节,这个大小为n字节的存储空间就是此函数的栈的存储区域,然后在函数返回时再将栈指针加上这个偏移量恢复栈现场。例如下面函数部分指令:
    //通过objdump工具 获取到的main函数

    0000000120000b38 :
    120000b38: 67bdffd0 daddiu sp,sp,-48 //开辟48字节空间的栈
    120000b3c: ebbe00bf gssq ra,s8,32(sp) //ra,s8寄存器值入栈
    120000b40: ffbc0018 sd gp,24(sp) // gp寄存器值入栈

    120000ba0: 67bd0030 daddiu sp,sp,48 //恢复栈现场
    120000ba4: 03e00008 jr ra //函数返回
    120000ba8: 00000000 nop

       从上面的例子来看,我们似乎只用一个sp寄存器就管理了整个函数栈,完全不需要fp(s8)寄存器。那么fp什么时候会用到呢?
       首先我们要知道每个进程都有自己的栈。所有函数都在这同一个栈上分配空间来存储和本函数相关的信息。每个函数所使用的那部分空间就叫栈针(stack frame)。sp和fp 就限定了每个函数的栈边界,如图2所示。

    图2:MIPS进程栈示例
    在这里插入图片描述

       在图2中,所有的函数栈都是在当前进程的栈空间中按照向下增长的方式分配出来的一段空间。有了fp和sp就界定了每个函数栈的边界。每个函数栈内部的fp都指向了调用者函数的栈顶。fp的好处就在于当我们需要回溯函数调用关系或者动态堆栈管理时,通过当前函数里的sp和fp,就可以得到上一个函数的sp和fp,以此类推直到第一个函数。


    二、 整数乘法寄存器HI、LO

       MIPS中和整数乘除法相关的寄存器有两个:HI寄存器和LO寄存器。在64位处理器上,对于两个32位做乘法运算后,可以产生64位结果。可以临时将结果的低32位放在LO寄存器,高32位放在HI寄存器。例如下面的指令:

       mult t0,t1
       mflo a4
       mfhi a5

       上面的指令”mult t0,t1”实现的是寄存器t0和寄存器t1的乘法操作,结果的低32位存放在寄存器LO,结果的高32位存放在寄存器HI。mult中的mul代表乘法,t代表临时存储(temply)。HI和LO是特殊的寄存器,不允许程序直接使用,里面的结果要通过指令mflo和mfhi获取。“mflo a4”就是拷贝寄存器LO的值到通用寄存器a4,“mfhi a5”就是拷贝寄存器HI的值到通用寄存器a5。
       HI和LO寄存器也用于除法运算结果的临时保存。除法运算结果商临时存放在寄存器LO,余数存放在寄存器HI。使用实例如下:

       div t0,t1
       mflo a4
       mfhi a5

       上面指令“div t0,t1”实现的是寄存器t0和t1的除法运算,运算结果的商存放在寄存器LO,余数存放在寄存器HI。类似于LO = t0/t1,HI = t0%t1。在HI和LO内的数据还是要通过指令mflo和指令mfhi拷贝到通用寄存器才可以使用。


    三、 协处理器CP0

       在MIPS体系结构中,可支持多个协处理器(Co-Processor)。其中,协处理器0(简称CP0)是体系结构中必须实现的。CP0就是系统控制处理器,它起到控制CPU的作用,比如CPU配置、高速缓存控制、异常中断控制、存储单元控制、定时器、错误检测等。CP0包含32个寄存器,本节中只介绍有助于我们调试程序的几个关键寄存器。

    注意:和CP0相关的寄存器和指令在用户态是没有权限使用的。用户态指的是非特权状态,在此状态下,执行的代码被硬件限定而不能进行某些操作。与此相对的是内核态(特权状态),在此状态下程序可以进行任何操作。通常我们的应用程序都是工作在用户态。


    3.1 EPC寄存器

       异常程序计数器(Exception Program Counter 简称EPC)。EPC是一个 64 位可读写寄存器,其存储了异常处理完成后继续开始执行的指令的 地址。在MIPS体系架构中,中断、自陷、系统调用、程序错误等事件都称为异常。回到我们第一章程序崩溃时捕获的例子:

    potentially unexpected fatal signal 8.
    CPU: 1 PID: 10132 Comm: exception Not tainted 3.10.84-22.fc21.loongson.10.mips64el #1

    Hardware name: /Loongson-3A5-780E-1w-V1.1-demo, BIOS Loongson-PMON-V3.3-\x9c\x9f\xffffffe4\xffffffb8
    task: 980000017d3e6d00 ti: 9800000174afc000 task.ti: 9800000174afc000
    $ 0 : 0000000000000000 0000000000000001 0000000000000000 000000000000000a
    $ 4 : 0000000000000001 000000ffff8303e8 000000ffff8303f8 0000000000000000
    $ 8 : 000000ffee7f3820 000000ffee81fbe8 000000ffff8303e0 0080000000000000
    $12 : 000000ffee631140 0000000000000003 00000000f63d4e2e 000000ffee841e28
    $16 : 000000ffee7f1cc8 0000000120000b90 0000000000000000 0000000000000000
    $20 : 0000000126d8ad60 0000000126c96170 0000000000000000 0000000120158008
    $24 : 0000000000000000 0000000120000ad0
    $28 : 0000000120019010 000000ffff830260 000000ffff830260 0000000120000b78
    Hi : 0000000000000000
    Lo : 0000000000000000
    epc : 0000000120000b04 0x120000b04
    Not tainted
    ra : 0000000120000b78 0x120000b78
    Status: e400ccf3 KX SX UX USER EXL IE
    Cause : 10000034
    PrId : 0014630d (ICT Loongson-3)

       这里面“PID: 10132”表明此程序运行的进程号为10132。从$0至$28行分别记录了发生异常时32个通用寄存器的值。Hi和Lo记录了发生异常时的寄存器HI和LO的数据结果。“unexpected fatal signal 8”可知引起此次程序异常的信号值为8,对应的信号名可以通过/usr/include/asm/signal.h里查找到8即为 SIGFPE(浮点数异常)。这里EPC的值为0x120000b04,通过objdump反汇编程序后,找到0x120000b04附近的指令如下:

    0000000120000ad0 <test>:
    120000ad0: 67bdffd0 daddiu sp,sp,-48
    120000ad4: ebbe00bf gssq ra,s8,32(sp)
    120000ad8: ffbc0018 sd gp,24(sp)

    120000afc: 8fc20004 lw v0,4(s8)
    120000b00: 0062001a div zero,v1,v0
    120000b04: 004001f4 teq v0,zero,0x7
    120000b08: 00001810 mfhi v1
    120000b0c: 00001012 mflo v0
       这就可以看出异常发生在test函数内。发生异常的是一条除0指令“div zero,v1,v0”。注意异常指令执行后,EPC已经指向下一条指令。此时你就可以去程序代码中找原因去了。上述异常我使用的C语言实例如下:
    void test(){
    int a = 10,b = 0;
    int c = a/b; //非法除0操作
    }


    3.2 无效地址寄存器BadVaddr

       BadVAddr 寄存器是一个64位只读寄存器,这个寄存器保存引发异常的地址。如果程序中发生了非法或无效地址访问、地址没有正确对齐时,该寄存器都会被设置。比如我故意编写了一段地址错误的语句:
        int* ep = NULL;
        int c = *ep; //无效地址访问

    包含这段语句的程序编译运行后会报错如下:

    potentially unexpected fatal signal 11.
    CPU: 0 PID: 13610 Comm: exception Not tainted 3.10.84-22.fc21.loongson.10.mips64el #1

    epc : 0000000120000ac4 0x120000ac4
    Not tainted
    ra : 0000000120000b0c 0x120000b0c
    Status: e400ccf3 KX SX UX USER EXL IE
    Cause : 10000008
    BadVA : 0000000000000000
    PrId : 0014630d (ICT Loongson-3)

    这里“unexpected fatal signal 11”指明了异常信号为SIGSEGV。“BadVA : 0000000000000000”异常地址为0。也就是NULL指针,出错位置通过epc可以看出在0x120000ac4


    四、 浮点寄存器

        浮点寄存器也称协处理器1(Co-Processor 1 简称CP1)。MIPS 拥有32个浮点寄存器,记为f0f0-f31。每个浮点寄存器为64位。这32个浮点寄存器在使用约定(ABI)上和通用寄存器一样,也有自己一套说明。如表2所示:

    表2:n64 浮点寄存器 ABI

    浮点寄存器编号 功能简介
    f0,f0,f2 用作函数返回值
    f12f12-f19 用作传递参数
    f24f24-f31 寄存器变量,发生函数调用时要保存
    f1f1、f3-f11f11、f20-$23 用作临时变量

       在表2中,已经对n64的浮点寄存器使用约定做了简单介绍。功能的分配基本上和通用寄存器相同。比如一个函数返回值为整数或指针时,使用通用寄存器的v0,返回值为浮点类型时,就使用浮点寄存器f0。函数调用时的参数是整型就用通用寄存器a0-a7传递,如果是浮点类型就用f12f12-f19传递。对于函数返回地址、栈指针等还是使用通用寄存器的ra、sp。

       比如我们要实现两个浮点数的加法运算,用c语言实现就是:

       float c = fa+fb;

       同样的功能,对应的汇编指令如下:

       add.s f0,f0,f0,$f1

       指令add.s实现的是对两个浮点寄存器的加法操作。上面的“add.s f0,f0,f0,f1f1”意思是浮点寄存器f0和f1f1的加法运算,结果存入f0。相当于f0=f0=f0+$f1。由于浮点运算的性能和功耗会低于整数,平时使用又不是很多,所以浮点寄存器的使用不做展开介绍,阅读mips汇编代码时能认出浮点寄存器就可以了。












    展开全文
  • 什么是寄存器

    2020-08-31 16:06:45
    寄存器就是 ATP,可以随时拿来用,性能高,但数量有限; 内存就是葡萄糖,性能一般,但是存量可以比较多; 外存(比如硬盘)就是脂肪,容量可以非常大,性能很差,要先转化为葡萄糖(存进内存),然后转化为

    专业的解释就看百度百科吧,这里是形象的比喻:
    以下内容转自两名知乎用户:

    寄存器就是你的口袋。身上只有那么几个,只装最常用或者马上要用的东西。
    内存就是你的背包。有时候拿点什么放到口袋里,有时候从口袋里拿出点东西放在背包里。
    辅存就是你家里的抽屉。可以放很多东西,但存取不方便。

    如果把被储存的东西比作能量:

    1. 寄存器就是 ATP,可以随时拿来用,性能高,但数量有限;
    2. 内存就是葡萄糖,性能一般,但是存量可以比较多;
    3. 外存(比如硬盘)就是脂肪,容量可以非常大,性能很差,要先转化为葡萄糖(存进内存),然后转化为 ATP(放到寄存器)才能直接利用(存取)。

    原文链接
    [https://www.zhihu.com/question/20539463/answer/724173258]

    展开全文
  • 一般情况下,内核线程中的简单局部变量都在寄存器存储器中。 局部存储器 对于每个线程,局部存储器也是私有的,如果寄存器被消耗完,数据将被存储在局部存储其中。如果每个线程使用了过多的寄存器,或声明了大型...

    寄存器

    GPU片上高速缓存器,基本单元时寄存器文件,每个寄存器文件大小为32bit。

    计算能力1.0/1.1版本硬件中,每个SM中寄存器文件数量为8192;而在1.2/1.3硬件中,每个SM中寄存器文件数量为16384.

    一般情况下,内核线程中的简单局部变量都在寄存器存储器中。

    局部存储器

    对于每个线程,局部存储器也是私有的,如果寄存器被消耗完,数据将被存储在局部存储其中。如果每个线程使用了过多的寄存器,或声明了大型结构体或数组,或者编译器无法确定数组的大小,线程的私有数据就有可能会被分配到local memory中。一个线程的输入和中间变量将被保存在寄存器或者局部存储器中。局部存储器中的数据被数保存在显存中,因此对local memory的访问速度很慢。

    如下,mt会被存入local memory中

    __global__ void localmemDemo(float* A)
    {
      unsigned int mt[3];
    }
    

    如果在定义线程私有数组的同时,对其就进行了初始化,那么如果数组尺寸不大,这个数组仍然有可能被分到寄存器中。

    __global__ void localmemDemo(float* A)
    {
      unsigned int mt[3] = {1, 2, 3};
    }
    

    在编译时,输出ptx(parallel thread execution)汇编代码(在编译时加上-ptx或者-keep选项),就能观察变量在编译的第一阶段是否被分配到了local memory中,如果一个变量在ptx中以.local助记符声明,可使用ld.local和st.local助记符访问,这个变量就被存放在local memory中。不过,即使初次编译的变量不位于local memory中,在编译的第二阶段仍然有可能根据目标硬件中存储器的大小将变量存放在local memory中。这时,可以通过加上–ptxas-options=-v编译选项用来观察lmem的使用情况。

    如果数组较小,并且一定要分配在寄存器中,用以下方法:

    __global__ void localmemDemo(float* A)
    {
      unsigned int mt0, mt1, mt2;
    }
    
    展开全文
  • 库函数开发与寄存器开发

    千次阅读 2018-09-22 14:47:05
    在以前 8 位机时代的程序开发中,一般直接配置芯片的寄存器,控制芯片的工作方式,如中断,定时器等。配置的时候,常常要查阅寄存器表,看用到哪些配置位,为了配置某功能,该置 1还是置 0。这些都是很琐碎的、机械...
  • 如果开启了超线程,一个物理核可以分成n个逻辑核(一般是2),n为超线程的数量。(1)路(多个CPU)“路”都是指服务器CPU的数量,也就是服务器主板上CPU插槽的数量。通常有单路、两路、四路和多路。单路指服务器支持1个...
  • CISC SISC 指令系统 复杂、庞大 简单、精简 指令数目 一般大约200条 一般小于100条 指令字长 不固定 定长 可访存指令 不加限制 只有Load / Store指令 ...通用寄存器数量 ...寄存器数量多,指令数目少。 定常...
  • 但是寄存器数量有限,所以寄存器一般是根据需求进行分配的。Java中程序员不能直接控制(但是,C和C++允许程序员向编译器建议寄存器的分配方式)。 2)堆栈。位于RAM -- Random- Access Memory(随机访问存储器)中。...
  • jvm003-内存管理

    2012-07-30 10:46:55
    03,寄存器一般和本地机器的寄存器数量相当,大概模拟出以下几种。PC程序计数器、optop操作数栈顶指针、frame当前执行环境指针、vars指向当前执行环境中第一个局部变量的指针,所有寄存器均为32位。pc用于记录程序的...
  • 2021-02-10

    2021-02-10 19:33:08
    c语言中的变量(静态变量,自动变量,寄存器变量);...寄存器变量:在硬件寄存器中存储,所有被register修饰的自动变量,寄存器数量有限,效率比内存还要高 作用域:静态变量>自动变量=寄存器变量 ...
  • java存储数据

    2017-06-01 13:18:42
    java 数据存储区,主要以下五种: 一、寄存器 寄存器是集中在CPU指令的缓存区,是CPU直接作为数据交换,存取速度最快,一般用于复杂运算操作,不建议存放大数据,因寄存器数量有限,只存放要求非常快的数据,JAVA...
  • 精简指令系统(RISC :Reduced Instruction-Set Computer) 特点 指令数量少:只包括两条指令 LOAD(从存储器中读数)和STORE(把数据写入存储器) 指令寻址方式少 ...CPU中通用寄存器数量多:一般32个以上...
  • MCP2515 (2)

    千次阅读 2017-02-21 16:41:38
    接收屏蔽寄存器一般有相同的数量匹配接收滤波寄存器 规定接收滤波寄存器标识符每一位的值是否需要进行匹配比如芯片设置有 6个接收滤波寄存器和 6个接收屏蔽寄存器: 从总线上接收 CAN帧,然后依次将收到的 CAN帧...
  • 王爽汇编学习(二)

    2020-09-03 09:08:09
    寄存器数量不够的时候,一般使用栈用来暂时存储数据。 ​ 一般栈顶是低地址,栈底是高地址,所以push的时候,sp存储的地址会减少,pop的时候,sp存储的地址会增加。可以把栈想象成一个桶,桶底是高地址,接触...
  • 数据存在哪里Java

    2010-03-28 19:01:00
    由于寄存器数量有限,所以编译器会根据本身需求适当的分配寄存器来使用。 内存上的数据区域:Stack:堆栈位于一般的RAM中。处理器器经由其指针(stack pointer)提供直接支持。当程序分配一块新的内存时,statck...
  • 寄存器位于处理器内部,是CPU可以直接控制并进行存取的地方,但数量与容量都有限,所以一般用于高频数据的缓存。该区主要由操作系统直接管理,用户的语言代码没有直接控制权。 栈(stack) 栈区的存取效率仅次于...
  • 成,接收滤波寄存器设置了标识符每位的值,接收屏蔽寄存器一般有相同的数量匹配接收滤波寄存器,规定接 收滤波寄存器标识符每一位的值是否需要进行匹配,比如芯片设置有6个接收滤波寄存器和6个接收屏蔽寄 存器,从总线上...
  • 小伙伴们知道,尽管寄存器和存储器均用于存储信息,但是鉴于CPU内的寄存器数量少,存取速度快,它主要用于临时存放参加运算的操作数和中间结果;而存储器一般在CPU外(但单片机CPU例外,其内部一般均含有一定容量的...
  • 小伙伴们知道,尽管寄存器和存储器均用于存储信息,但是鉴于CPU内的寄存器数量少,存取速度快,它主要用于临时存放参加运算的操作数和中间结果;而存储器一般在CPU外(但单片机CPU例外,其内部一般均含有一定容量的...
  • 1、GP接口简介 GP接口是ZYNQ-7000系列器件中用于实现PS与PL端进行数据通信的数据接口,GP接口传输数据速率一般较慢,通常用作控制信息的传输,在利用GP接口的时候,PS端的... 寄存器数量为4-512个,寄存器位宽为32...
  • java内存

    2011-10-14 10:04:42
    一般Java在内存分配时会涉及到以下区域 (1) 寄存器(Registers)。这是速度最快的存储场所,因为寄存器其他所有...不过,寄存器数量十分有限,所以寄存器是根据需要由编译器适当地分配。作为一个程序员,我们对此没有
  • 特点: 它数量有限,你不能直接控制, 在程序中感觉不到寄存器的存在. 2.堆栈 位于RAM中, 这里的速度仅次于寄存器, 一般用来存放对象引用. 特点: 当程序创建时, 系统必须知道堆栈中每一项确切的生命周期, 以便...
  • java内存分配

    2011-10-14 10:56:20
    一般Java在内存分配时会涉及到...不过,寄存器数量十分有限,所以寄存器是根据需要由编译器适当地分配。作为一个程序员,我们对此没有直接的控制权,也没办法在程序里头感觉到寄存器的任何存在迹象。 (2) Stac
  • 二、一切都是对象

    2018-06-26 21:59:00
    但是寄存器数量是机器有限的,所以一般是根据需要来进行分配的。我们不能够直接控制寄存器,也不能在程序中感觉到寄存器的存在的任何迹象(另一方面,C和C++允许向编译器建议寄存器的分配方式)。  堆栈 位于通用...
  • 原因:完成相同功能所需的指令数量一般会比寄存器架构多,因为出栈、入栈操作本身就产生了相当多的指令数量。 栈实现在内存之中,频繁的栈访问也就意味着频繁的内存访问,不如处理器的寄存器快。 基于...
  • java对象的存储

    2013-02-18 20:15:47
    寄存器:最快,cpu内部,数量小,由编译器分配 堆栈:ram区域,通过堆栈指针处理,指针向下创建新内存,向上移释放内存,速度仅存寄存器一般引用在此,对象不在 堆:ram区域,java对象 静态存储:ram固定区域,...
  • 然而,寄存器数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹(2)堆栈。驻留于常规 RAM(随机访问存储器)区域,但可通过它的“堆栈...
  • 一般是用于参数数量比较少的情况,例如中断的参数传递就是通过eax寄存器来传递,告诉操作系统需要执行什么类型的中断。 2、当参数数量寄存器多,这是参数通常存在内存的块和表中,并将块的地址通过寄存器来传递。 ...

空空如也

空空如也

1 2 3 4 5 6
收藏数 109
精华内容 43
关键字:

寄存器数量一般