RT-Thread / rt-thread

RT-Thread is an open source IoT Real-Time Operating System (RTOS).
https://www.rt-thread.io
Apache License 2.0
10.54k stars 5.03k forks source link

Cortex-M4使能并使用FPU,线程切换时程序跑飞 #1196

Closed sukifeng closed 6 years ago

sukifeng commented 6 years ago

开发环境:MDK5.22

如果PendSV触发后,在PendSV中断标志被清除到执行中断关闭语句这非常短的时间内,被其他中断打断,在该中断中释放信号量并进行任务调度,此时PendSV的中断标志再次置位,也就是说PendSV中断服务程序会执行2次,在第二次执行时变量rt_thread_switch_interrupt_flag为0,所以程序直接跳转到pendsv_exit。

pendsv_exit
    ; restore interrupt
    MSR     PRIMASK, r2

    IF      {FPU} != "SoftVFP"
    ORR     lr, lr, #0x10           ; lr |=  (1 << 4), clean FPCA.
    CMP     r3,  #0                 ; if(flag_r3 != 0)
    BICNE   lr, lr, #0x10           ; lr &= ~(1 << 4), set FPCA.
    ENDIF

    ORR     lr, lr, #0x04
    BX      lr
    ENDP

由于此时寄存器R3的值不确定,PendSV退出时CONTROL.FPCA的值也就不确定,进而导致CPU出栈异常,例如压栈时压入了浮点寄存器,但出栈时未弹出,后续程序的堆栈错乱,PC值非法,进入Hardfault.

BernardXiong commented 6 years ago

@aozima 查下这个问题,是否可能在一些极端情况下会出现这样的问题。

aozima commented 6 years ago

楼主的分析感觉有可能,需要仔细分析并验证。 如果问题确认,暂时把 更新FPCA 移动到 pendsv_exit之前应该就可以了。 楼主可以先这样修改测试一下!

    IF      {FPU} != "SoftVFP"
    ORR     lr, lr, #0x10           ; lr |=  (1 << 4), clean FPCA.
    CMP     r3,  #0                 ; if(flag_r3 != 0)
    BICNE   lr, lr, #0x10           ; lr &= ~(1 << 4), set FPCA.
    ENDIF

pendsv_exit
    ; restore interrupt
    MSR     PRIMASK, r2

    ORR     lr, lr, #0x04
    BX      lr
    ENDP
aozima commented 6 years ago

同时可以把pendsv_exit按如下写两份,然后在后面下断点。 如果发生楼主所描述的场景,会有一定概率进入后面的pendsv_exit,这样就能佐证楼主的猜测。

    IF      {FPU} != "SoftVFP"
    ORR     lr, lr, #0x10           ; lr |=  (1 << 4), clean FPCA.
    CMP     r3,  #0                 ; if(flag_r3 != 0)
    BICNE   lr, lr, #0x10           ; lr &= ~(1 << 4), set FPCA.
    ENDIF

;pendsv_exit
    ; restore interrupt
    MSR     PRIMASK, r2

    ORR     lr, lr, #0x04
    BX      lr

pendsv_exit
    ; restore interrupt 在这里下断点
    MSR     PRIMASK, r2

    ORR     lr, lr, #0x04
    BX      lr
    ENDP
sukifeng commented 6 years ago

@aozima 我目前就是按照下面这样修改的,和你说的一样. 测试1天了,还没有出现问题.

    IF      {FPU} != "SoftVFP"
    ORR     lr, lr, #0x10           ; lr |=  (1 << 4), clean FPCA.
    CMP     r3,  #0                 ; if(flag_r3 != 0)
    BICNE   lr, lr, #0x10           ; lr &= ~(1 << 4), set FPCA.
    ENDIF

pendsv_exit
    ; restore interrupt
    MSR     PRIMASK, r2

    ORR     lr, lr, #0x04
    BX      lr
    ENDP

你说的验证方法我也试了,确实会进入pendsv_exit. 我昨天也验证了确实存在进入PendSV_handler后变量rt_thread_switch_interrupt_flag为0的情况. 从理论上分析也确实存在PendSV被打断的可能,只是时间窗口非常短,不容易碰到, 而我的应用恰恰中断非常多而且频繁。

liangyongxiang commented 6 years ago

在PendSV_Handler前面加延时,在shell里输入字符(会进入串口中断),就比较容易进入pendsv_exit了。

PendSV_Handler   PROC
    EXPORT PendSV_Handler

    MOV     R0, #0xF000
__nop
    NOP
    SUBS    R0, R0, #1
    CMP     R0, #0
    BGT     __nop

    ; disable interrupt to protect context switch
    MRS     r2, PRIMASK
    CPSID   I
aozima commented 6 years ago

看来这个问题确认了,楼上的方法加大了时间窗口,提高了问题复现的概率。