新闻中心

EEPW首页>模拟技术>设计应用> Xtensa处理器窗寄存器函数调用机制与应用

Xtensa处理器窗寄存器函数调用机制与应用

作者: 时间:2012-10-10 来源:网络 收藏

本文引用地址://m.amcfsurvey.com/article/185680.htm

5. Windows下溢(underflow)问题

  当子返回时,RETW或者RETW.N指令执行,此时也仅此时将进行上溢检查。如果当Windowbase所在位置的前3个window pane(4 registers组)的WindowStart比特都为零,则意味着返回后的父发生过 WindowOverflow,父的窗口曾经被压入栈,要先行通过相应的underflow弹出。

  如果不是全为零,则应该不为零的点和正常window返回的点对应,要正常返回,如果不同,则说明发生了不正常的调用,a0被破坏掉,要产生非法指令错误。关于这个方面的具体硬件原语,读者可以参考的ISA手册,这里不再赘述。

Alloca异常问题

  C语言中函数中经常会发生从堆栈中分配临时空间的情况,在正常的不发生窗溢出的时候没有任何问题。但是,如果该函数的下级函数的嵌套调用曾导致过寄存器溢出,由于该函数的堆栈Frame底部存有溢出的basic area的寄存器,如果简单的偏移堆栈指针来分配临时空间,则这些保存过basic寄存器会被完全破坏掉。为有效解决这一问题,架构引入一个特殊的Alloca异常来管理basic area寄存器的搬移和临时空间的分配。

  当函数内部进行局部stack的内存分配时,编译器会生成一个MOVSP at,as指令,异常的检测通过这一指令来完成,该指令有如下原语:

  if WindowStartWindowBase-0011WindowBase-0001 = 03 then

  Exception (AllocaCause)

  elseAR[t] ← AR[s]

  endif

  类似于underflow,如果当前寄存器窗口前3个register pane的占用状态全为0(全部为自由使用状态),则说明其上一级函数一定发生过窗口溢出,当前函数栈下方一定保存有溢出的寄存器,简单的修改SP指针不再安全,需要触发Alloca异常来进行正确处理。需要说明的是,发生alloc异常的时候,过去的寄存器窗口调用已经循环一周,且发生溢出,溢出的充分必要条件必然是当前寄存器窗口的前3个register pane占用状态全为0(WindowStartWindowBase-0011WindowBase-0001 = 000),其次当前函数不可能是调用树的叶子节点,当前函数的前半部分曾经进入过,且过去进入的路径上发生过溢出,否则就没有产生异常的必要。alloca异常是为解决sp覆盖而引入的硬件机制。

  这里解释了alloc异常产生的基本原理,那么,什么样的代码会产生MOVsp指令,从而可能触发alloc异常呢? 有如下几种情况:

  调用alloc函数,如

  void foo(int array_size) {

  char * bar = alloca(array_size);

  …

  使用变长数组(GNU C 扩展语言),如

  void foo(int array_size) {

  char bar[array_size];

  …

  使用嵌套函数定义(GNU C 扩展语言),如

  void afunction(void) {

  …

  int anotherfunction(void) {

  }

  使用特别长的局部数组,如

  void foo(void) {

  int an_array[8192]; // 32,768 bytes

  int another_array[100]; // 400 bytes

  …

  精确的size限制是32,760,包含16~48字节的Frame开销。

  当然,这里列出的不是全部的可能情况,仅仅列出几个常见用例,读者不能认为自己的代码没有以上情况,编译器就不会产生movsp指令,从而不会产生alloc异常。

  由于alloc是一种不容易避免的正常的异常,应用软件需要积极的处理。处理的思路有两种,其一是用异常或者中断栈作为临时储存来搬移,这里要介绍另外一种比较巧妙的方法,如下述代码:

  rsr a2,PS

  rsr a4,WINDOWBASE

  extui a3,a2,XCHAL_PS_OWB_SHIFT,XCHAL_PS_OWB_BITS

  xor a3,a3,a4

  slli a3,a3,XCHAL_PS_OWB_SHIFT

  xor a2,a2,a3

  wsr a2,PS

  l32i a4,a1,A4_SAVE

  l32i a3,a1,A3_SAVE

  l32i a2,a1,A2_SAVE

  addi a1,a1,USER_EXCEPTION_FRAME_SIZE

  rsync

  // Now branch on the call increment

  // We could branch earlier rotw instructions prior to the handler

  // which would avoid executing two rotw instructions in the underflow

  // eight case. However,the underflow 8 handler is right at a

  // cache-line,so that would likely involve an extra cache miss better

  // to just take the single cycle penalty here.

  rotw -1

  // what was a0 (that had the return address) is now a4

  _bbci.l a4,31,_WindowUnderflow4

  rotw -1

  // what was a0 (that had the return address) is now a8

  _bbci.l a8,30,_WindowUnderflow8

  j _setup_WindowUnderflow12

  这段代码的巧妙之处在于首先通过将当前的Windowbase保存到PS的OWB域中,然后通过反向旋转窗,根据a0的高端2bit表示的调用类型,跳转到相应的下溢exception的位置进行出栈恢复save area的寄存器,当sp指针正确偏移后,利用寄存器的引用触发overflow异常自动进行再次入栈,从而实现搬移。

Context Switch下的寄存器保存与恢复

  在windows窗口寄存器机制下,多任务RTOS的上下文环境切换问题变得非常有趣。 那么该如何保存这些通用寄存器呢,是不是所有的物理寄存器都要保存与恢复呢? 答案是否定的,除了当前窗口的逻辑寄存器要保存外,只需要保存当前任务进程里的live寄存器(置1的寄存器pane),而恢复则只需要恢复逻辑寄存器。

  对于任务软件来说,寄存器的实现机制是透明的,因此特别要说明的是WindowsStart和WindowsBase寄存器则完全不需要保存与恢复。参考如下进程切换的环境保护核心代码:

  .Lspill_loop:

  // Top of save loop.

  // Find the size of this call and branch to the appropriate save routine.

  beqz a2,.Ldone // if no start bit remaining,we're done

  bbsi.l a2,0,.Lspill4 // if next start bit is set,it's a call4

  bbsi.l a2,1,.Lspill8 // if 2nd next bit set,it's a call8

  bbsi.l a2,2,.Lspill12 // if 3rd next bit set,it's a call12

  j .Linvalid_window // else it's an invalid window!

  // SAVE A CALL4

  .Lspill4:

  addi a3,a9,-16 // a3 gets call[i+1]'s sp - 16

  s32i a4,a3,0 // store call[i]'s a0

  s32i a5,a3,4 // store call[i]'s a1

  s32i a6,a3,8 // store call[i]'s a2

  s32i a7,a3,12 // store call[i]'s a3

  srli a6,a2,1 // move and shift the start bits

  rotw 1 // rotate the window

  j .Lspill_loop

  // SAVE A CALL8

  .Lspill8:

  ……

  该代码的基本思路是通过处理WindowStart寄存器,解析各live窗口的相对偏移,基于调用栈布局规范进行入栈处理。 当任务切换到其他的进程后,这些live窗口可能会被破坏(相应的WindowStart比特清零),这没有关系, 当进程重新切换回来时,如果这些live窗口已经在切换过的进程恢复(WindowStart比特置1),则切换回来后无需出栈,程序可正常继续执行,如果没有恢复(相应的WindowStart比特继续为零),那么该进程就可以根据这个清零的状态位将原先入栈的live寄存器正确恢复。

本文小结

  Xtensa的寄存器窗口旋转函数调用是一种非常巧妙的实现机制,通过这一机制嵌入式软件可明显提高性能,并且其alloc异常,多任务上下文切换等等衍生和应用问题也可高效而经济的解决,其和TIE(Tensilica Instruction Extension),其他诸多可配置选项等等正充分说明了Xtensa架构经久不衰,广泛应用,焕发持久生命力的原因所在。

c语言相关文章:c语言教程



上一页 1 2 下一页

评论


相关推荐

技术专区

关闭