Uboot17之start.S-ARM流水线

时间:2018.3.28  作者:Tom   工作:HWE 说明:如需转载,请注明出处。

1.pc指针到底在哪里?

上面一段是我们uboot.dis反汇编文件,我们继续分析上部分的中断异常调用。它是怎样调用函数的呢?

我们可以看出ldr pc, _undefined_instruction这句代码反编译后看到地址为c3e00014。调用函数是pc指向函数,即:

ldr    pc, [pc, #20]

也就是pc = pc + 20;这里的20是立即数,是十进制,需要转换成16进制为0x14。那此时pc指向的是0xc3e00028。此时我们发现这个地址并不是_undefined_instruction,而是:

c3e00028:    e59ff014     ldr    pc, [pc, #20]    ; c3e00044 <_irq>

而调用行的结果再后面是c3e00030:

这就涉及到我们的ARM流水线,此时我们的流水线是三级,因此pc的位置并不是c3e00014,而是c3e0001c。所以算出来的结果就是c3e00014+0x14+0x8=c3e00030。

下面我们来看下什么是ARM流水线,了解ARM流水线之前我们必须看下ARM的37个寄存器。

2.ARM的37个寄存器

ARM共有37个寄存器,都是32位长度37个寄存器中30个为"通用"型,1个固定用作PC,一个固定用作CPSR,5个固定用作5种异常模式下的SPSR。

ARM处理器共有7种不同的处理器模式,在每一种处理器模式中有一组相应的寄存器组。任意时刻(也就是任意的处理器模式下),可见的寄存器包括15个通用寄存器(R0~R14)、一个或两个状态寄存器及程序计数器(PC)。在所有的寄存器中,有些是各模式共用的同一个物理寄存器;有一些寄存器是各模式自己拥有的独立的物理寄存器。

通用寄存器

通用寄存器可以分为下面3类:未备份寄存器(The unbanked registers),包括R0~R7。备份寄存器(The banked registers),包括R8~R14。程序计数器PC,即R15。

未备份寄存器

未备份寄存器包括R0~R7。对于每一个未备份寄存器来说,在所有的处理器模式下指的都是同一个物理寄存器。在异常中断造成处理器模式切换时,由于不同的处理器模式使用相同的物理寄存器,可能造成寄存器中数据被破坏。未备份寄存器没有被系统用于特别的用途,任何可采用通用寄存器的应用场合都可以使用未备份寄存器。

备份寄存器

对于备份寄存器R8~R12来说,每个寄存器对应两个不同的物理寄存器。例如,当使用快速中断模式下的寄存器时,寄存器R8和寄存器R9分别记作R8_fiq、R9_fiq;当使用用户模式下的寄存器时,寄存器R8和寄存器R9分别记作R8_usr、R9_usr等。在这两种情况下使用的是不同的物理寄存器。系统没有将这几个寄存器用于任何的特殊用途,但是当中断处理非常简单,仅仅使用R8~R14寄存器时,FIQ处理程序可以不必执行保存和恢复中断现场的指令,从而可以使中断处理过程非常迅速。对于备份寄存器R13和R14来说,每个寄存器对应6个不同的物理寄存器,其中的一个是用户模式和系统模式共用的;另外的5个对应于其他5种处理器模式。采用记号R13_<mode>来区分各个物理寄存器:

其中,<mode>可以是下面几种模式之一:usr、svc、abt、und、irq及fiq。

寄存器R13在ARM中常用作栈指针。在ARM指令集中,这只是一种习惯的用法,并没有任何指令强制性的使用R13作为栈指针,用户也可以使用其他的寄存器作为栈指针;而在Thumb指令集中,有一些指令强制性地使用R13作为栈指针。

每一种异常模式拥有自己的物理的R13。应用程序初始化该R13,使其指向该异常模式专用的栈地址。当进入异常模式时,可以将需要使用的寄存器保存在R13所指的栈中;当退出异常处理程序时,将保存在R13所指的栈中的寄存器值弹出。这样就使异常处理程序不会破坏被其中断程序的运行现场。

寄存器R14又被称为连接寄存器(Link Register,LR),在ARM体系中具有下面两种特殊的作用:

(1)保存子程序返回地址。使用BL或BLX时,跳转指令自动把返回地址放入r14中;子程序通过把r14复制到PC来实现返回,通常用下列指令之一:

MOV PC, LR

BX LR

通常子程序这样写,保证了子程序中还可以调用子程序。

stmfd sp!, {lr}

……

ldmfd sp!, {pc}

(2)当异常发生时,异常模式的r14用来保存异常返回地址,将r14如栈可以处理嵌套中断。

程序计数器R15

程序计数器R15又被记作PC。它虽然可以作为一般的通用寄存器使用,但是有一些指令在使用R15时有一些特殊限制。当违反了这些限制时,该指令执行的结果将是不可预料的。

由于ARM采用了流水线机制,当正确读取了PC的值时,该值为当前指令地址值加8个字节。也就是说,对于ARM指令集来说,PC指向当前指令的下两条指令的地址。

由于ARM指令是字对齐的,PC值的第0位和第1位总为0。需要注意的是,当使用指令STR/STM保存R15时,保存的可能是当前指令地址值加8字节,也可能保存的是当前指令地址加12字节。到底是哪种方式,取决于芯片具体设计方式。无论如何,在同一芯片中,要么采用当前指令地址加8,要么采用当前指令地址加12,不能有些指令采用当前指令地址加8,另一些指令采用当前指令地址加12。因此对于用户来说,尽量避免使用STR/STM指令来保存R15的值。当不可避免这种使用方式时,可以先通过一些代码来确定所用的芯片使用的是哪种实现方式。

对于ARM版本4以及更高的版本,程序必须保证写入R15寄存器的地址值的bits[1:0]为0b00;否则将会产生不可预知的结果。

对于Thumb指令集来说,指令是半字对齐的。处理器将忽略bit[0],即写入R15的地址值首先与0XFFFFFFFC做与操作,再写入R15中。

还有—些指令对于R15的用法有一些特殊的要求。比如,指令BX利用bit[0]来确定是ARM指令,还是Thumb指令。这种读取PC值和写入PC值的不对称的操作需要特别注意。

程序状态寄存器

CPSR(当前程序状态寄存器)可以在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。每一种处理器模式下都有一个专用的物理状态寄存器,称为SPSR(备份程序状态寄存器)。当特定的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断程序退出时,可以用SPSR中保存的值来恢复CPSR。

由于用户模式和系统模式不是异常中断模式,所以它们没有SPSR。当在用户模式或系统模式中访问SPSR,将会产生不可预知的结果。CPSR的格式如下所示。SPSR格式与CPSR格式相同。

CPSR中各个bit位表明了CPU的某些状态信息,这些信息非常重要,和后面学到的汇编指令息息相关(譬如BLE指令中的E就和CPSR中的Z标志位有关)CPSR中的I、F位和开中断、关中断有关。CPSR中的mode位(bit4~bit0共5位)决定了CPU的工作模式,在uboot代码中会使用汇编进行设置。

https://blog.csdn.net/SmalOSnail/article/details/53048784

3.为什么ARM7中的pc指针为pc = pc + 8 ?

众所周知,AMR7,是三级流水线,ARM9是五级流水线。其细节见图:

从上图,其实很容易看出,第一条指令:

add r0, r1,#5

执行的时候,此时PC已经指向第三条指令:

cmp r2,#3

的地址了,所以,是PC=PC+8.

R15(PC)总是指向"正在取指"的指令,而不是指向"正在执行"的指令或正在"译码"的指令。一般来说,人们习惯性约定将"正在执行的指令作为参考点",称之为当前第一条指令,因此PC总是指向第三条指令。当ARM状态时,每条指令为4字节长,所以PC始终指向该指令地址加8字节的地址,即:PC值=当前程序执行位置+8;

4.为什么ARM9中的pc指针也是pc = pc + 8 ?

ARM7的三条流水线,PC=PC+8,很好理解,但是AMR9中,是五级流水线,为何还是PC=PC+8,
而丌是
PC
=PC+(5-1)*4
=PC + 16,
呢?

下面对每一个指令周期,CPU 做了哪些事情,分别详细迕行阐述:
在看下面具体解释之前,有一句话要牢记,那就是:PC 不是指向你正在运行的指令,而是PC 始终指向你要取的指令的地址。认识清楚了返个前提,后面的举例讲解,就容易懂了

指令周期 Cycle1
(1)取指
PC 总是指向将要读取的指令的地址(即我们常说的,指向下一条指令的地址),而当前 PC=4,所以去取物理地址为 4 对对应的指令"ldr pc, [pc, #20] ", 其 对 应 二 进 制 代 码 为e59ff014。此处取指完成后,自动更新 PC 的值,即 PC=PC+4(单个指令占 4 字节,所以加 4)=4+4=8
指令周期 Cycle2
(1)译指:翻译指令 e59ff014
(2)同时再去取指
PC 总是指向将要读取的指令的地址(即我们常说的,指向下一条指令的地址),而当前 PC=8,所以去物理地址为 8 所对应的指令"ldr pc, [pc, #20]" 其对应二进制代码为 e59ff014此处取指完完后,自动更新 PC 的值,即 PC=PC+4=8+4=12=0xc
指令周期 Cycle3
(1)执行(指令):执行"e59ff014",即"ldr pc, [pc, #20]"所对表达的含义,即 PC
= PC + 20
= 12 + 20
= 32
= 0x20
此处,叧是计算出待会要赋值给 PC 的值是 0x20,这个 0x20还只是放在执行单元中内部的缓冲中。
(2)译指:翻译 e59ff014
(3)取指
此步骤由亍是和上面(1)中的执行同步做的,所以,未受到影响,继续取指,而取指的那一时刻,PC 为上一 Cycle 更新后的值,即 PC=0xc,所以是去取物理地址为 0xc 所对应的指令" ldr pc, [pc, #20]" ,对应二进制为 e59ff014。其实,分析到返里,大家就可以看出:在 Cycle3 的时候,PC 的值,刚好已经在 Cycle1 和 Cycle2,分别加了 4,所以 Cycle3 的时候,PC=PC+8,而同样道理,对与任何一条指令的,都是在 Cycle3,指令的 Execute执行阶段,如果用到 PC 的值,那么 PC 那一时刻,就是 PC=PC+8。所以,此处虽然是五级流水线,但是却不是是 PC=PC+16,而是 PC=PC+8。进一步地,我们发现,其实 PC=PC+N 的 N,是和指令的执行阶段所处亍于流水线的深度有关,即此处指令的执行 Execute 阶段,是五级流水线中的第三个,而返个第三阶段的 Execute和指令的第一个阶段的 Fetch 取指,相差的值是 3 -1 =2,即两个 CPU 的 Cycle,而每个Cycle 都会导致 PC=+PC+4,所以,指令到了 Execute 阶段,才会发现,此时 PC 已经变成 PC=PC+8 了。

回过头来反观 ARM7 的三级流水线,也是同样的道理,指令的 Execute 执行阶段,是处于指令的第三个阶段,同理,在指令计算数据的时候,如果用到 PC,就会发现此时 PC=PC+8。同理,假如 ARM9 的五级流水线,把指令的 Execute 执行阶段,设计在了第四个阶段,那么就是 PC=PC+(第 4 阶段-1)*4 个字节 = PC= PC+12 了。

【不同阶段的 PC 值的关系】
对应地,在 ARM7 的三级流水线(取指,译指,执行)和 ARM9 的五级流水线(取指,译
指,执行,存储,写回)中,可以这么说:
PC, 总是指向当前正在被取指的指令的地址,
PC-4,总是指向当前正在被译指的指令的地址,
PC-8,总是指向当前的那条指令,即我们一般说的,正在被执行的指令的地址。

【总结】
ARM7 的三级流水线,PC=PC+8,
ARM9 的五级流水线,也是 PC=PC+8,
根本的原因是,两者的流水线设计中,指令的 Execute 执行阶段,都是处于流水线的第三级。
所以使得 PC=PC+8。
类似地,可以推导出:
假设,Execute 阶段处于流水线中的第 E 阶段,每条指令是 T 个字节,那么
PC

= PC + N*T
= PC + (E - 1) * T
此处 ARM7 和 ARM9:
Execute 阶段都是第 3 阶段

-> E=3

每条指令是 4 个字节

-> T=4


所以:
PC
=PC + N* T
=PC + (3 -1 ) * 4
= PC + 8

【关于直接改变 PC 的值,会导致流水线清空的解释】
把 PC 的值直接赋值为 0x20。而 PC 值更改,直接导致流水线的清空,即导致下一个 cycle中的,对应的流水线中的其他几个步骤,包括接下来的同一个 Cycle 中的取指的工作被取消。在 PC 跳转到 0x20 的位置之后,流水线重新计算,重新一步步地按照流水线的逻辑,去一点点执行。当然要保证当前指令的执行完成,即执行之后,还有两个 cycle,分别做的Memory 和 Write,会继续执行完成。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值