Freertos应用与源码分析:临界区

目录

一、概述

二、应用

三、源码分析

1、进入临界区

2、退出临界区

3、中断临界区

(1)应用

(2)进入中断临界区

(3)退出中断临界区

四、注意事项

一、概述

        当一个任务在使用某个资源的过程中,即还没有完全结束对资源的访问时,便被切出运行态,使得资源处于非一致,不完整的状态。如果这个时候有另一个任务或者中断来访问这个资源,则会导致数据损坏或是其它相似的错误。如下

  • 任务 A 运行,并往 LCD 写字符串”Hello world”。
  • 任务 A 被任务 B 抢占,但此时字符串才输出到”Hello w”。
  • 任务 B 往 LCD 写”Abort, Retry, Fail?”,然后进入阻塞态。
  • 任务 A 从被抢占处继续执行,完成剩余的字符输出——“orld”。 
  • 现在 LCD 显示的是被破坏了的字符串”Hello wAbort, Retry, Fail?orld”。

        因为在程序运行时,是将语句拆分若干条指令,比如a++操作,执行读->加->写,CPU每一行一条指令,就会判断一下有没有中断或异常触发。
        如下,当任务A_Task调用add_Task时候,执行到“读”操作,此时被B_Task抢占执行,B_Task执行完读->加->写操作后,此时a=1,那么A_Task再次去执行的时候,执行完读->加->写后,那么a到底等于1还是等于2?此时就会出现问题。那么如何避免?其中一个方法,我们加入临界区。 

uint8_t a=0;
void add_Task(void)
{
        a++;
}
void A_Task(void *p)
{
    while(1)
    {
        add_Task();  
    }
}
void B_Task(void *p)
{
    while(1)
    {
        add_Task(); 
    }
}

二、应用

        相关函数

/*进入临界区*/
void taskENTER_CRITICAL( void );
/*退出临界区*/
void taskEXIT_CRITICAL( void );

        当A_Task执行调用add_Task函数,进入临界区保护后,a++就被保护起来了,可以理解为关中断。如果所使用的 FreeRTOS 移植使用了configMAX_SYSCALL_INTERRUPT_PRIORITY 内核配置常量,那么将会禁用优先级低于或等于configMAX_SYSCALL_INTERRUPT_PRIORITY宏定义设置的优先级的中断,并启用所有高于此优先级的中断。抢占式上下文切换仅在中断内发生, 在中断被禁用时不会发生。

uint8_t a=0;
void add_Task(void)
{
    //进入临界区
    taskENTER_CRITICAL();
    //临界资源
    a++;
    //退出临界区
    taskEXIT_CRITICAL();
}
void A_Task(void *p)
{
    while(1)
    {
        add_Task();  
    }
}
void B_Task(void *p)
{
    while(1)
    {
        add_Task(); 
    }
}

三、源码分析

1、进入临界区

/*进入临界区*/
void taskENTER_CRITICAL( void );

        经过层层调用,最后来到vPortEnterCritical函数。如下,portDISABLE_INTERRUPTS()函数作用表示将中断优先级设置为configMAX_SYSCALL_INTERRUPT_PRIORITY宏定义所设置的值(后续会讲)。uxCriticalNesting之所以执行++,是因为临界区可以嵌套使用。

void vPortEnterCritical( void )
{   
    /*关闭中断*/
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;
    /*可以理解为:设置中断优先级,大于可屏蔽中断优先级,这样就不会被抢占*/
    if( uxCriticalNesting == 1 )
    {
        configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
    }
}

        如下,portDISABLE_INTERRUPTS()经过调用后,来到了vPortRaiseBASEPRI函数。将basepri强制设置成configMAX_SYSCALL_INTERRUPT_PRIORITY参数。

        basepri寄存器的作用:禁止优先级低于某个特定等级的中断,只需要将所需的屏蔽优先级写入basepri寄存器即可。通俗一点讲,禁止优先级低于等于basepri的中断。

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
    /*configMAX_SYSCALL_INTERRUPT_PRIORITY中断优先级*/
    uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
    __asm
    {
        /*将ulNewBASEPRI写进basepri*/
        msr basepri, ulNewBASEPRI
        /*确保指令完全执行*/
        dsb
        isb

    }
}

        那么我们就可以理解,当我们定义了configMAX_SYSCALL_INTERRUPT_PRIORITY宏定义之后。进入临界区,就是将禁止优先级低于等于configMAX_SYSCALL_INTERRUPT_PRIORITY宏定义的中断触发。

2、退出临界区

/*退出临界区*/
void taskEXIT_CRITICAL( void );

        如下,经过层层调用,最后来到vPortExitCritical函数。portENABLE_INTERRUPTS()函数作用表示将basepri设置为0。因为临界区可以嵌套使用,执行uxCriticalNesting--,当xCriticalNesting等于0的时候表示完全退出临界区。

/*退出临界区*/
void vPortExitCritical( void )
{
    /*检查作用*/
    configASSERT( uxCriticalNesting );
    /*确保嵌套结束*/
    uxCriticalNesting--;
    /*退出中断,将中断优先级设置为0,这样相当于退出中断*/
    if( uxCriticalNesting == 0 )
    {
        /*将basepri置0*/
        portENABLE_INTERRUPTS();
    }
}

        如下,portENABLE_INTERRUPTS()经过调用后,来到了vPortSetBASEPRI函数。说明此时将要完全退出临界区,故将将basepri强制设置成0。

#define portENABLE_INTERRUPTS()                   vPortSetBASEPRI( 0 )
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
    __asm
    {
        msr basepri, ulBASEPRI
    }
}

那么我们就可以理解,退出临界区就是将basepri设置为0即可。

3、中断临界区

(1)应用
UBaseType_t taskENTER_CRITICAL_FROM_ISR( void );
void taskEXIT_CRITICAL_FROM_ISR( UBaseType_t uxSavedInterruptStatus );

        中断临界区函数使用和临界区函数使用有一点区别,但是核心一样,都是将其basepri寄存器设置为configMAX_SYSCALL_INTERRUPT_PRIORITY宏定义数值。只不过在设置之前需要保存当前中断掩码。

//应用方法
void vDemoISR( void )
{	/*定义保存中断掩码变量*/
    UBaseType_t uxSavedInterruptStatus;
    /*保存返回值(中断掩码)*/
    uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
    //资源
    /*写入中断掩码,用于退出临界区*/
    taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
}
(2)进入中断临界区

        经过taskENTER_CRITICAL_FROM_ISR()的层层调用,来到了ulPortRaiseBASEPRI函数。先保存旧的中断掩码等级,随后在设置新的BASEPRI等级。

static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
    /*设置新的ulNewBASEPRI等级,与之前步骤相同*/
    uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

    __asm
    {
        /*保存当前basepri等级*/
        mrs ulReturn, basepri
        /*设置新的basepri*/
        msr basepri, ulNewBASEPRI
        /*确保指令完全执行*/
        dsb
        isb
    }
    /*返回原始中断掩码*/
    return ulReturn;
}
(3)退出中断临界区

        经过taskEXIT_CRITICAL_FROM_ISR(参数)的层层调用,来到了vPortSetBASEPRI函数。ulBASEPRI传入的原始中断掩码参数

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
    __asm
    {	/*将原始中断掩码写入basepri寄存器*/
        msr basepri, ulBASEPRI
    }
}

四、注意事项

1、临界区必须简短,因为长时间关闭中断会极大影响系统性能。

2、不得从临界区调用Freertos api函数。

3、中断服务程序(ISR)中想要使用临界区要使用专门的函数。

4、不能在临界区出现阻塞等任务操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值