【06】FreeRTOS临界段代码保护及调度器挂起与恢复

目录

1.临界段代码保护简介

2.临界段代码保护函数介绍

2.1任务级临界区调用格式示例

2.2中断级临界区调用格式示例

2.3函数调用特点

2.4任务级进入和退出临界段函数

2.5中断级进入和退出临界段函数

3.任务调度器的挂起和恢复

3.1任务调度器挂起函数vTaskSuspendAll()

3.2任务调度器恢复函数xTaskResumeAll()

4.总结


1.临界段代码保护简介

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

适用的场合,例如

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

2,系统:系统自身需求(FreeRTOS源码很多地方用到了临界段代码保护,属于系统自身的需求);

3,用户:用户需求(用户想某些代码段不想被打断);

可以打断当前程序运行的情况:1、中断;2、任务调度(高优先级的任务可以抢占低优先级的任务)。

如果不想被打断,则关闭中断。这样做有两个好处,1是关闭了中断,中断将不再打断正在运行的程序;2是PendSV属于最低优先级的中断,在PendSV中进行任务切换和调度,FreeRTOS关闭中断,5~15优先级的中断将不起作用,则不能进行任务切换,但是优先级为0~4的中断可以正常打断正在运行的程序

2.临界段代码保护函数介绍

        FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断,流程图如下所示

【06】FreeRTOS临界段代码保护及调度器挂起与恢复_第1张图片

临界段代码保护函数
函数 描述
taskENTER_CRITICAL() 任务级进入临界段
taskEXIT_CRITICAL() 任务级退出临界段
taskENTER_CRITICAL_FROM_ISR() 中断级进入临界段(中断服务函数中调用
taskEXIT_CRITICAL_FROM_ISR() 中断级退出临界段(中断服务函数中调用

         进入或退出临界段,本质是关闭或开启中断。taskENTER_CRITICAL()taskEXIT_CRITICAL()与【05】FreeRTOS的中断管理此篇文章中提到的关闭和开启中断,本质也是调用【05】文章中提到的关闭中断函数portENABLE_INTERRUPTS()和开启中断函数portDISABLE_INTERRUPTS(),只不过做了嵌套功能。

2.1任务级临界区调用格式示例

taskENTER_CRITICAL() ;//关闭中断
{
        … …	/* 临界区 */
}
taskEXIT_CRITICAL()//开启中断

2.2中断级临界区调用格式示例

        taskENTER_CRITICAL_FROM_ISR()函数提前返回之前中断屏蔽寄存器的值,并关闭中断。taskEXIT_CRITICAL_FROM_ISR()函数退出临界区时,将之前保存的“值”赋值给中断屏蔽寄存器,保持原有的状态。

uint32_t  save_status;
save_status  = taskENTER_CRITICAL_FROM_ISR();
{
        … …	/* 临界区 */
}
taskEXIT_CRITICAL_FROM_ISR(save_status );	

2.3函数调用特点

1、成对使用

2、支持嵌套(使用两个进入临界段,则有两个退出临界段

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

2.4任务级进入和退出临界段函数

       进入临界段函数 taskENTER_CRITICAL()最底层为以下函数,portDISABLE_INTERRUPTS()关闭中断,变量uxCriticalNesting计算进入临界段的次数。

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

         退出临界段函数taskEXIT_CRITICAL()最低层函数为以下函数,将变量uxCriticalNesting递减,直至变量为0时开启中断。变量uxCriticalNesting不能为0,如果为0,减1后则变成负值。

void vPortExitCritical( void )
{
    configASSERT( uxCriticalNesting );
    uxCriticalNesting--;

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

        configASSERT()断言功能是进行调试的,报一些提示、报错,判断x是否等于0,如果等于0则打印错误。

#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )

2.5中断级进入和退出临界段函数

        进入临界段函数taskENTER_CRITICAL_FROM_ISR()最底层为以下函数,返回无符号32位。

mrs ulReturn, basepri:将中断屏蔽寄存器中的值读取出来;

msr basepri, ulNewBASEPRI:关闭中断;

    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;
    }

        退出临界段函数taskEXIT_CRITICAL_FROM_ISR()最低层函数为以下函数,将关闭中断时返回的值代入中断屏蔽寄存器中,保持进入临界段和退出临界段时中断状态一致。

    static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
    {
        __asm
        {
            /* Barrier instructions are not used as this function is only used to
             * lower the BASEPRI value. */
/* *INDENT-OFF* */
            msr basepri, ulBASEPRI
/* *INDENT-ON* */
        }
    }

3.任务调度器的挂起和恢复

        挂起,意思就是暂停,任务调度器停止,代表任务将不能切换。挂起任务调度器, 任务将不能进行调度、切换(中断依旧可以响应),调用此函数不需要关闭中断。

函数 描述
vTaskSuspendAll() 挂起任务调度器
xTaskResumeAll() 恢复任务调度器(具有返回值,判断是否需要进行任务切换

 使用格式示例(首先挂起任务调度器,执行内容不允许被其他内容打断,中断可以正常打断执行的内容,防止任务与任务之间的资源抢夺,恢复任务调度器):

vTaskSuspendAll() ;
{
        … …	/* 内容 */
}
xTaskResumeAll()	;

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

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

3、挂起调度器的方式,适用于临界区位于任务与任务之间(任务与任务之间不抢夺,属于任务与任务之间的临界区);既不用去延时中断,又可以做到临界区的安全(如果临界区代码量过大,执行时间较长,将导致延时中断响应,使用此方式可以避免延时中断

3.1任务调度器挂起函数vTaskSuspendAll()

        任务调度器挂起函数只是将变量uxSchedulerSuspended自加,根本上任务调度器是进行任务的切换,任务切换是PendSV中断进行的,在嘀嗒定时器中断服务函数SysTick_Handler()中触发PendSV中断。  变量uxSchedulerSuspended默认为0,pdFALSE。

void vTaskSuspendAll( void )
{
    /* A critical section is not required as the variable is of type
     * BaseType_t.  Please read Richard Barry's reply in the following link to a
     * post in the FreeRTOS support forum before reporting this as a bug! -
     * https://goo.gl/wu4acr */

    /* portSOFTWARE_BARRIER() is only implemented for emulated/simulated ports that
     * do not otherwise exhibit real time behaviour. */
    portSOFTWARE_BARRIER();

    /* The scheduler is suspended if uxSchedulerSuspended is non-zero.  An increment
     * is used to allow calls to vTaskSuspendAll() to nest. */
    ++uxSchedulerSuspended;

    /* Enforces ordering for ports and optimised compilers that may otherwise place
     * the above increment elsewhere. */
    portMEMORY_BARRIER();
}

嘀嗒定时器中断服务函数SysTick_Handler():

void SysTick_Handler(void)
{	
    HAL_IncTick();
	if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)					//OS开始跑了,才执行正常的调度处理
	{
		xPortSysTickHandler();
	}
}

         xPortSysTickHandler()函数中,只要xTaskIncrementTick()函数返回值不为pdFALSE ,则触发PendSV中断。

void xPortSysTickHandler( void )
{
    /* The SysTick runs at the lowest interrupt priority, so when this interrupt
     * executes all interrupts must be unmasked.  There is therefore no need to
     * save and then restore the interrupt mask value as its value is already
     * known - therefore the slightly faster vPortRaiseBASEPRI() function is used
     * in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
    vPortRaiseBASEPRI();
    {
        /* Increment the RTOS tick. */
        if( xTaskIncrementTick() != pdFALSE )
        {
            /* A context switch is required.  Context switching is performed in
             * the PendSV interrupt.  Pend the PendSV interrupt. */
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }
    }

    vPortClearBASEPRIFromISR();
}

        由于变量uxSchedulerSuspended已经自加,则不能进入第一个if,则最终返回变量xSwitchRequired的值为pdFALSE。由以上函数知则触发不了PendSV中断,任务调度器被挂起。

BaseType_t xTaskIncrementTick( void )
{
    TCB_t * pxTCB;
    TickType_t xItemValue;
    BaseType_t xSwitchRequired = pdFALSE;

    /* Called by the portable layer each time a tick interrupt occurs.
     * Increments the tick then checks to see if the new tick value will cause any
     * tasks to be unblocked. */
    traceTASK_INCREMENT_TICK( xTickCount );

    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
    {
        /* Minor optimisation.  The tick count cannot change in this
         * block. */
        const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;

        /* Increment the RTOS tick, switching the delayed and overflowed
         * delayed lists if it wraps to 0. */
        xTickCount = xConstTickCount;

        if( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */
        {
            taskSWITCH_DELAYED_LISTS();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        /* See if this tick has made a timeout expire.  Tasks are stored in
         * the  queue in the order of their wake time - meaning once one task
         * has been found whose block time has not expired there is no need to
         * look any further down the list. */
        if( xConstTickCount >= xNextTaskUnblockTime )
        {
            for( ; ; )
            {
                if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
                {
                    /* The delayed list is empty.  Set xNextTaskUnblockTime
                     * to the maximum possible value so it is extremely
                     * unlikely that the
                     * if( xTickCount >= xNextTaskUnblockTime ) test will pass
                     * next time through. */
                    xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
                    break;
                }
                else
                {
                    /* The delayed list is not empty, get the value of the
                     * item at the head of the delayed list.  This is the time
                     * at which the task at the head of the delayed list must
                     * be removed from the Blocked state. */
                    pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
                    xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );

                    if( xConstTickCount < xItemValue )
                    {
                        /* It is not time to unblock this item yet, but the
                         * item value is the time at which the task at the head
                         * of the blocked list must be removed from the Blocked
                         * state -  so record the item value in
                         * xNextTaskUnblockTime. */
                        xNextTaskUnblockTime = xItemValue;
                        break; /*lint !e9011 Code structure here is deemed easier to understand with multiple breaks. */
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }

                    /* It is time to remove the item from the Blocked state. */
                    listREMOVE_ITEM( &( pxTCB->xStateListItem ) );

                    /* Is the task waiting on an event also?  If so remove
                     * it from the event list. */
                    if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
                    {
                        listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }

                    /* Place the unblocked task into the appropriate ready
                     * list. */
                    prvAddTaskToReadyList( pxTCB );

                    /* A task being unblocked cannot cause an immediate
                     * context switch if preemption is turned off. */
                    #if ( configUSE_PREEMPTION == 1 )
                        {
                            /* Preemption is on, but a context switch should
                             * only be performed if the unblocked task has a
                             * priority that is equal to or higher than the
                             * currently executing task. */
                            if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
                            {
                                xSwitchRequired = pdTRUE;
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                    #endif /* configUSE_PREEMPTION */
                }
            }
        }

        /* Tasks of equal priority to the currently running task will share
         * processing time (time slice) if preemption is on, and the application
         * writer has not explicitly turned time slicing off. */
        #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
            {
                if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
                {
                    xSwitchRequired = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */

        #if ( configUSE_TICK_HOOK == 1 )
            {
                /* Guard against the tick hook being called when the pended tick
                 * count is being unwound (when the scheduler is being unlocked). */
                if( xPendedTicks == ( TickType_t ) 0 )
                {
                    vApplicationTickHook();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        #endif /* configUSE_TICK_HOOK */

        #if ( configUSE_PREEMPTION == 1 )
            {
                if( xYieldPending != pdFALSE )
                {
                    xSwitchRequired = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        #endif /* configUSE_PREEMPTION */
    }
    else
    {
        ++xPendedTicks;

        /* The tick hook gets called at regular intervals, even if the
         * scheduler is locked. */
        #if ( configUSE_TICK_HOOK == 1 )
            {
                vApplicationTickHook();
            }
        #endif
    }

    return xSwitchRequired;
}

3.2任务调度器恢复函数xTaskResumeAll()

        taskENTER_CRITICAL()函数进入临界区,关闭中断(避免执行后面程序时,被中断或任务切换所打断)。

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

    /* If uxSchedulerSuspended is zero then this function does not match a
     * previous call to vTaskSuspendAll(). */
    configASSERT( uxSchedulerSuspended );

    /* It is possible that an ISR caused a task to be removed from an event
     * list while the scheduler was suspended.  If this was the case then the
     * removed task will have been added to the xPendingReadyList.  Once the
     * scheduler has been resumed it is safe to move all the pending ready
     * tasks from this list into their appropriate ready list. */
    taskENTER_CRITICAL();
    {

将变量uxSchedulerSuspended自减,如果变量减至0,则表明任务调度器已经被恢复。 

        --uxSchedulerSuspended;
        if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
        {

         判断当前任务总量是否大于0,如果当前创建任务总数不大于0,则恢复任务调度器无意义

            if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
            {
                /* Move any readied tasks from the pending list into the
                 * appropriate ready list. */

        判断等待就绪列表中是否有任务,如果有任务则将列表中任务全部移除,添加到就绪列表。在中断级恢复任务中,当调度器被挂起时,恢复任务不是恢复到就绪列表,而是等待就绪列表中


                while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
                {
                    pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
                    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 )
                {
                    /* A task was unblocked while the scheduler was suspended,
                     * which may have prevented the next unblock time from being
                     * re-calculated, in which case re-calculate it now.  Mainly
                     * important for low power tickless implementations, where
                     * this can prevent an unnecessary exit from low power
                     * state. */
                    prvResetNextTaskUnblockTime();
                }

         恢复滴答定时器,在任务调度器被挂起时丢失的节拍数(只有任务调度器未被挂起时,节拍数才会自加)。任务被挂起时自加的变量xPendedTicks,恢复后将变量的值赋值给变量xPendedCounts

                /* 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. */
                {
                    TickType_t xPendedCounts = xPendedTicks; /* Non-volatile copy. */

        如果xPendedCounts大于0,则调用函数xTaskIncrementTick(),将丢失的节拍数进行加回。通过函数xTaskIncrementTick()的返回值判断是否需要任务切换,每补齐一次,则xPendedCounts自减。

                    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();
                    }
                }

         如果xYieldPending不等于pdFALSE则进行任务切换。宏configUSE_PREEMPTION(在FreeRTOSCofig.h中配置)判断是否是抢占式任务调度器,是则将xAlreadyYielded置1。函数taskYIELD_IF_USING_PREEMPTION()进行任务切换。

                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;
}

4.总结

【06】FreeRTOS临界段代码保护及调度器挂起与恢复_第2张图片

【06】FreeRTOS临界段代码保护及调度器挂起与恢复_第3张图片

【06】FreeRTOS临界段代码保护及调度器挂起与恢复_第4张图片 ​​​​​​​【06】FreeRTOS临界段代码保护及调度器挂起与恢复_第5张图片

 

 

 

 

你可能感兴趣的:(FreeRTOS,嵌入式,STM32,FreeRTOS,c#,c++)