八、FreeRTOS临界段代码保护及任务调度器挂起与恢复

 

一、临界段代码保护简介

临界段:临界段代码也叫临界区,指那些必须完整运行,不能被打断的代码段

什么场合需要用到临界段?

1.外设:需要严格按照时序初始化的外设:IIC、SPI等

2.系统:系统自身需求

3.用户:自身需求

除了中断还有什么可以打断当前程序运行?

任务调度(pendsv):如果处于低优先级,则可能会出现被抢占情况

二、临界段代码保护函数介绍

FreeRTOS在进入临界段代码时需要关闭中断,直到处理完临界段代码才能再打开

函数描述

taskENTER_CRITICAL()

任务级进入临界段

taskEXIT_CRITICAL()

任务级退出临界段

taskENTER_CRITICAL_FROM_ISR()

中断级进入临界段

taskEXIT_CRITICAL_FROM_ISR()

中断级退出临界段

任务级临界区调用格式示例:                 中断级临界区调用格式示例:

特点:

1、成对使用

2、支持嵌套

3、尽量保持临界段耗时短

void vPortEnterCritical( void )
{
    portDISABLE_INTERRUPTS();//中断关闭
    uxCriticalNesting++;
//临界区嵌套计数器,初始化为0,进入一次++进行计数,在退出临界段函数中--,减到0才会开启中断,也就是进入n次临界段的次数=退出n次临界段次数,

    /* 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 );
    }

 taskENTER_CRITICAL()中包含的portDISABLE_INTERRUPTS()(关闭中断)函数原理为将最高为5的优先级设入basepri寄存器里(中断屏蔽寄存器),这样关闭优先级小于5的所有中断

void vPortExitCritical( void )
{
    configASSERT( uxCriticalNesting );//段凝功能:提示作用,保证成对出现
    uxCriticalNesting--;

    if( uxCriticalNesting == 0 )
    {
        portENABLE_INTERRUPTS();
    }
}

 

 static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
    {
        uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

        __asm
        {
            /* Set BASEPRI to the max syscall priority to effect a critical
             * section. */
/* *INDENT-OFF* */
            mrs ulReturn, basepri//读取中断屏蔽寄存器
            msr basepri, ulNewBASEPRI//关闭中断
            dsb
            isb
/* *INDENT-ON* */
        }

        return ulReturn;//返回值进入出口参数的中断屏蔽寄存器,保证进临界区和退出临界区的中断状态是一致的
    }

三、任务调度器的挂起与恢复

挂起任务调度器, 调用此函数不需要关闭中断

函数描述

vTaskSuspendAll()

挂起任务调度器

xTaskResumeAll()

恢复任务调度器

使用格式实例:

 

1、与临界区不一样的是,挂起任务调度器,未关闭中断;

2、它仅仅是防止了任务之间的资源争夺,中断照样可以直接响应;

3、挂起调度器的方式,适用于临界区位于任务与任务之间;既不用去延时中断,又可以做到临界区的安全

#if ( INCLUDE_vTaskDelay == 1 )

    void vTaskDelay( const TickType_t xTicksToDelay )
    {
        BaseType_t xAlreadyYielded = pdFALSE;

        /* A delay time of zero just forces a reschedule. */
        if( xTicksToDelay > ( TickType_t ) 0U )
        {
            configASSERT( uxSchedulerSuspended == 0 );
            vTaskSuspendAll();//任务挂起,函数内部通过pendsv ++SchedulerSuspended(初始0)进行任务切换,pendsv在滴答定时器中断服务函数里面,只要xTaskIncrementTick返回值不为pdFALSE(初始值为pdFALSE,只要++就不会返回pdFALSE)
            {
                traceTASK_DELAY();

                /* A task that is removed from the event list while the
                 * scheduler is suspended will not get placed in the ready
                 * list or removed from the blocked list until the scheduler
                 * is resumed.
                 *
                 * This task cannot be in an event list as it is the currently
                 * executing task. */
                prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
            }
            xAlreadyYielded = xTaskResumeAll();//是否进行了任务切换
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        /* Force a reschedule if xTaskResumeAll has not already done so, we may
         * have put ourselves to sleep. */
        if( xAlreadyYielded == pdFALSE )
        {
            portYIELD_WITHIN_API();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

#endif /* INCLUDE_vTaskDelay */
void xPortSysTickHandler( void )
{
 
    vPortRaiseBASEPRI();
    {
        if( xTaskIncrementTick() != pdFALSE )
        {
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;//pendsv中断触发不了,任务切换不了,调度器被挂起
        }
    }

    vPortClearBASEPRIFromISR();
BaseType_t xTaskResumeAll( void )
{
    TCB_t * pxTCB = NULL;
    BaseType_t xAlreadyYielded = pdFALSE;

    configASSERT( uxSchedulerSuspended );

    
    taskENTER_CRITICAL();
    {
        --uxSchedulerSuspended;//挂起时函数++,恢复时--

        if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )//=0时调度器恢复
        {
            if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )//判断任务数量是否>0
            {
                while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )//判断等待就绪列表是否有任务,如有,则全部移除,添加到就绪列表
                {
                    pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ); 
                    listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
                    portMEMORY_BARRIER();
                    listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
                    prvAddTaskToReadyList( pxTCB );

                    /* If the moved task has a priority higher than or equal to
                     * the current task then a yield must be performed. */
                    if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )//判断恢复任务优先级是否大于等于正在执行任务,如大于则进行一次任务切换
                    {
                        xYieldPending = pdTRUE;
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }

                if( pxTCB != NULL )
                {
               
                    prvResetNextTaskUnblockTime();//更行下一次阻塞时间
                }

                /* If any ticks occurred while the scheduler was suspended then
                 * they should be processed now.  This ensures the tick count does
                 * not  slip, and that any delayed tasks are resumed at the correct
                 * time. */
                 /* 恢复滴答定时器在任务调度器被挂起的时候所丢失的节拍数 */
                 /* FreeRTOS中的心跳节拍都是通过xTickCount来实现,但只有在调度器没被挂起才会进入函数,所以FreeRTOS在挂起时有一个新的函数pendTicks来计数被挂起的时间有,用于恢复 */
                {
                    TickType_t xPendedCounts = xPendedTicks;//加回丢失的节拍数

                    if( xPendedCounts > ( TickType_t ) 0U )
                    {
                        do
                        {
                            if( xTaskIncrementTick() != pdFALSE )//补齐丢失的节拍数
                            {
                                xYieldPending = pdTRUE;//判断是否进行任务切换
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }

                            --xPendedCounts;//补齐一次进行--
                        } while( xPendedCounts > ( TickType_t ) 0U );//补齐时退出

                        xPendedTicks = 0;//清零
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }

                if( xYieldPending != pdFALSE )//不等于(优先级大于)则进行任务切换
                {
                    #if ( configUSE_PREEMPTION != 0 )//判断是否使用了抢占式调度器
                        {
                            xAlreadyYielded = pdTRUE;
                        }
                    #endif
                    taskYIELD_IF_USING_PREEMPTION();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    taskEXIT_CRITICAL();

    return xAlreadyYielded;

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值