GCC在ISR中生成无用的代码

时间:2014-05-29 21:15:10

标签: c gcc interrupt avr

我为atmega328编写了一个非常简单的中断服务程序(ISR),并使用avrgcc(使用-Os)使用AVR studio进行编译。

ISR (TIMER0_OVF_vect) { 
    txofcnt++;  //count overflows and store in uint16_t 
}

如果你注意到生成的程序集(如下),它使用r24,r25来获取增加易失性uint16_t txofcnt的作业,但它也是push-write-pop r1,r28,r29而没有读取它们。它还有一个额外的r0推/弹,而不会在它们之间使用它。

我没理解为什么r1被推动,清理然后最终成功。 但是为什么gcc觉得需要将EIMSK和GPIOR0加载到寄存器中然后不使用它们。如果你能告诉我GPIOR0的用途是什么,那么数据表说它存在但没有描述。

00000258 <__vector_16>:

ISR (TIMER0_OVF_vect) {
 258:   1f 92           push    r1
 25a:   0f 92           push    r0
 25c:   00 90 5f 00     lds r0, 0x005F
 260:   0f 92           push    r0
 262:   11 24           eor r1, r1
 264:   8f 93           push    r24
 266:   9f 93           push    r25
 268:   cf 93           push    r28
 26a:   df 93           push    r29
 26c:   cd b7           in  r28, 0x3d   ; 61 reads register EIMSK
 26e:   de b7           in  r29, 0x3e   ; 62 reads register GPIOR0
    txofcnt++;  
 270:   80 91 0a 01     lds r24, 0x010A
 274:   90 91 0b 01     lds r25, 0x010B
 278:   01 96           adiw    r24, 0x01   ; 1
 27a:   90 93 0b 01     sts 0x010B, r25
 27e:   80 93 0a 01     sts 0x010A, r24
}
 282:   df 91           pop r29
 284:   cf 91           pop r28
 286:   9f 91           pop r25
 288:   8f 91           pop r24
 28a:   0f 90           pop r0
 28c:   00 92 5f 00     sts 0x005F, r0
 290:   0f 90           pop r0
 292:   1f 90           pop r1
 294:   18 95           reti

2 个答案:

答案 0 :(得分:3)

请记住,ISR 中断当前在CPU上执行的任何代码。他们必须小心保存当前CPU状态(序言)并在退出(结尾)时恢复它,以便被中断的代码可以正常继续。

在AVR上,有几个寄存器不需要在正常的功能序言/结尾中保存/恢复,但确实需要由ISR保存:

  • r0 - 临时寄存器。普通函数可以随意执行任何操作,但ISR必须保留它。
  • r1 - 零寄存器,但可以通过正常功能中的指令写入。同样,必须由ISR保存。

这解释了为什么保存r0和r1。 GPIOR0显然只是一个函数可能正在使用的另一个通用寄存器,因此它也需要保存。如果优化器可以判断某些寄存器实际上没有被ISR修改,那么可能会优化其中一些,但优化器可能不那么聪明,或者您可能需要尝试更高的优化级别。

更多信息位于http://gcc.gnu.org/wiki/avr-gcc

答案 1 :(得分:3)

有一些关于GCC在https://gcc.gnu.org/wiki/avr-gcc

的AVR注册用法的文档

与您的问题相关的一些段落:

  

固定注册

     

固定寄存器是GCC不会分配的寄存器   注册分配器。寄存器R0和R1是固定的并且是隐式使用的   打印出汇编程序指令时:

     
      
  • R0

         

    用作临时寄存器,使用后无需恢复。它必须在中断服务程序中保存和恢复   (ISR)序幕和结语。在内联汇编程序中,您可以使用   __tmp_reg__用于临时寄存器。

  •   
  • R1

         

    始终包含零。在insn期间,内容可能被破坏,例如通过使用R0 / R1作为隐式的MUL指令   输出寄存器。如果insn销毁R1,那么insn必须将R1恢复为   之后为零。该寄存器必须保存在ISR序言中并且必须保存   然后设置为零,因为R1可能包含非零值。   ISR结局恢复了价值。在内联汇编程序中,您可以使用   零寄存器__zero_reg__

         

    ...

  •   
     

使用呼叫的注册

     

呼叫使用或呼叫破坏的通用寄存器(GPR)是   可能被函数调用破坏(破坏)的寄存器。

     
      
  • R18-R27,R30,R31

         

    这些GPR被称为破坏。普通功能可以在不恢复内容的情况下使用它们。中断服务程序(ISR)必须   保存并恢复他们使用的每个寄存器。

         

    ...

  •   
     

呼叫保存的注册

     
      
  • R2-R17,R28,R29

         

    其余的GPR被呼叫保存,即使用这种寄存器的功能必须恢复其原始内容。即使寄存器用于传递函数参数,这也适用。

  •   

以下是我对编译器为什么在ISR序言/结语中执行一些显然不必要的寄存器保存/恢复的猜测:

    保存/恢复
  • r0r1,因为编译器生成或调用的代码将在上面列出关于它们的假设。由于它们没有被GCC的注册分配器跟踪,序言必须确保它们被保存(并且在r1的情况下被初始化为0)。

  • r28r29用于保存堆栈指针(0x3d / SPL0x3e / SPH)。我猜测(并且我想强调这是猜测)编译器编写者认为中断处理程序交换堆栈可能很常见,这可以确保ISR可以恢复正在使用的堆栈。中断发生了。编译器可以假设这些寄存器不会被被调用的函数改变,因为它们是“调用保存”寄存器。

另外,你应该注意到显然是“额外”的推动&amp; pop r0用于将SREG状态寄存器保存在堆栈中。即使在r0push指令之间没有使用pop,请记住r0寄存器是暂存寄存器,寄存器分配器不会跟踪它,所以编译器不会认为r0在加载SREG后不会发生变化。

作为旁注,0x3d0x3e的读取是SPLSPH堆栈指针寄存器,而不是EIMSK和{{1寄存器。有关使用GPIOR0 / IN指令而不是加载或存储指令时寄存器寻址方式有何不同的详细信息,请参见reference manual here中的寄存器汇总表的注释4。 / p>

关于OUT的奖励积分:

  

8.5.1通用I / O寄存器

     

ATmega48A / PA / 88A / PA / 168A / PA / 328 / P包含三个通用目的   I / O寄存器。这些寄存器可用于存储任何寄存器   信息,它们对于存储全局特别有用   变量和状态标志。通用I / O寄存器   地址范围0x00 - 0x1F可以使用SBI直接进行位访问,   CBI,SBIS和SBIC指令。