记一次FreeRTOS错误配置导致无法进入临界区

Posted w-smile

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次FreeRTOS错误配置导致无法进入临界区相关的知识,希望对你有一定的参考价值。

  最近项目用到FreeRTOS,在实际调试中发现我自己的一段代码本来好用的(在无RTOS的情况下),但是当我在带RTOS的情况下把代码放到一个单独的任务中运行时我发现本来好用的代码莫名其妙的出现问题,有一定的概率会失败,考虑到应该是内核发生了调度导致代码中时序比较严格的地方被打断因此会出现时好时不好的现象,因此我对时序严格的地方调用了taskENTER_CRITICAL();和taskEXIT_CRITICAL();进行任务切换保护和中断但是结果还是一样,由此一来这个问题困扰了好久,我就开始怀疑freeRTOS源码的问题,仔细看了源码中关于进入临界和退出临界的函数。

进入临界区的宏函数是:taskENTER_CRITICAL();而又一层宏是portENTER_CRITICAL()最后才是函数vPortEnterCritical()。

void vPortEnterCritical( void )
{
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;

    /* This is not the interrupt safe version of the enter critical function so
    assert() if it is being called from an interrupt context.  Only API
    functions that end in "FromISR" can be used in an interrupt.  Only assert if
    the critical nesting count is 1 to protect against recursive calls if the
    assert function also uses a critical section. */
    if( uxCriticalNesting == 1 )
    {
        configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
    }
}

注意函数中不仅仅将临界区深度加1同时还调用了宏函数:portDISABLE_INTERRUPTS();这个宏的实体函数是vPortRaiseBASEPRI()

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

    __asm
    {
        /* Set BASEPRI to the max syscall priority to effect a critical
        section. */
        msr basepri, ulNewBASEPRI
        dsb
        isb
    }
}

这里参考M3权威指南中关于异常掩蔽寄存器,PRIMASK, FAULTMASK 以及 BASEPRI的功能描述。

PRIMASK 用于除能在 NMI 和硬 fault 之外的所有异常,它有效地把当前优先级改为 0(可
编程优先级中的最高优先级)。该寄存器可以通过 MRS 和 MSR 以下例方式访问:

关中断
MOV R0, #1
MSR PRIMASK, R0

开中断
MOV R0, #0
MSR PRIMASK,  R0

FAULTMASK则更加绝对他可以关闭除NMI以外的所有中断,使用其专用的指令访问方式及功能如下:

FAULTMASK=1,关异常 
CPSID F ;

FAULTMASK=0,开异常
CPSIE F ;

最后也就是本次将说明关于FreeRTOS系统中临界区用到的BASEPRI特殊功能寄存器,这个寄存器在内核指南中用了一个“细腻”的描述,意思这个寄存器可以更加细分的控制中断屏蔽的问题。这个寄存器的作用就是如果你向其中写入 A则中断优先级数大于等于A的所有中断将被除能。同时如果你给其中写0则表示不屏蔽任何优先级的中断。回到上面说的FreeRTOS的临界区问题,在进入临界区函数内调用了portFORCE_INLINE 函数这个函数内部是用汇编写的但是内容比较浅显易懂其实就是将configMAX_SYSCALL_INTERRUPT_PRIORITY 写入BASEPRI因此这将屏蔽优先级不高于configMAX_SYSCALL_INTERRUPT_PRIORITY (中断优先级数大于等于configMAX_SYSCALL_INTERRUPT_PRIORITY )的中断,本次我遇到的问题比较LOW啊,我在配置文件中如下配置

#define configMAX_SYSCALL_INTERRUPT_PRIORITY     5

再实际中我将中断配置为中断优先级组4也就是全部用于抢占优先级共可以分16(0~~~15)级优先级,我使用到的中断我配置为5,但是在使用时发现,调用进入临界函数后调度器虽然不会再进行调度但是中断还是会执行,这我就奇怪了但都是自己粗心了。

在M3内核可以支持256级中断,且BASEPRI寄存器最多可以到9位,具体几位和芯片设计的优先级表示位数相关,因此这就是此次问题的原因了STM32F1只使用了8位优先级中的高四位,低位未使用,因此实际效果如下

技术分享图片

当我给BASEPRI寄存器中写入configMAX_SYSCALL_INTERRUPT_PRIORITY = 5  时实际写入的效果是 0000 0101因此此时对于STM32芯片来说,实际写入完成后的寄存器实际值是0x00因为对于第四位未实现写入和读取都会是0,所以会导致出现FreeRTOS临界区无法屏蔽中断的问题。同样对于如果不使用优先级组4时同理,将抢占优先级和子优先级合并后和BASEPRI寄存器中的之比较如果大于则会被屏蔽。这里注意不管使用多少位都是MSB对齐的,脊髓看内核指南的一段话:通过让优先级以 MSB 对齐,可以简化程序的跨器件移植。比如,如果一个程序早先在支持 4 位优先级的器件上运行,在移植到只支持 3 位优先级的器件后,其功能不受影响。但若是对齐到 LSB,则会使 MSB 丢失,导致数值大于 7 的低优先级一下子升高了,甚至会反转小于等于 7 的高优先级。如,8 号优先级因为损失了 MSB,现在反而变成 0 号了!这一点实际点也就是如图所示的意义:

技术分享图片

完事了。。。细节真的很重要啊!!!

 


以上是关于记一次FreeRTOS错误配置导致无法进入临界区的主要内容,如果未能解决你的问题,请参考以下文章

记一次操作:主机上Redis未配置profile导致的命令无效

记一次Oracle session数过多引起进程数超过processes限制最终导致客户端无法连接的问题

FreeRtos 进入bootloader 串口无法接收数据,如何解决???????

FreeRTOS 临界段和开关中断

记一次错误的系统参数设置导致无法保持登录状态

FreeRTOS临界区