第七章 FreeRTOS的临界区与任务调度器

目录

一.临界区

1.1 临界段函数介绍

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

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

1.4 函数的特点

1.5 源码的介绍

1.5.1 taskENTER_CRITICAL()

1.5.2 taskEXIT_CRITICAL()

1.5.3 taskENTER_CRITICAL_FROM_ISR()

1.5.4 taskEXIT_CRITICAL_FROM_ISR()

二.任务调度器

2.1 挂起和恢复

2.2 任务调度函数介绍

2.3 函数的源码认识

2.3.1 vTaskSuspendAll()

2.3.2 xTaskResumeAll()


一.临界区

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

适用的场合,例如

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

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

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

注意:

① 可以打断当前程序运行的情况:

     a.中断;b.任务调度(高优先级的任务可以抢占低优先级的任务)。

② 如果不想被打断,则关闭中断

      这样做有两个好处:

        1.  关闭了中断,中断将不再打断正在运行的程序;

        2.  PendSV属于最低优先级的中断,在PendSV中进行任务切换和调度,FreeRTOS关闭中断,5~15优先级的中断将不起作用,则不能进行任务切换。

        但是优先级为0~4的中断可以正常打断正在运行的程序

1.1 临界段函数介绍

 FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。

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

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

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

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

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

1.4 函数的特点

1. 成对使用

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

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

临界区是直接屏蔽了中断,系统任务调度靠中断,ISR也靠中断

1.5 源码的介绍

1.5.1 taskENTER_CRITICAL()

进入临界段函数 taskENTER_CRITICAL()最底层为以下函数
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.5.2 taskEXIT_CRITICAL()

退出临界段函数taskEXIT_CRITICAL()最低层函数为以下函数.
void vPortExitCritical( void )
{
    configASSERT( uxCriticalNesting );
    uxCriticalNesting--;
 
    if( uxCriticalNesting == 0 )    //将变量uxCriticalNesting递减,直至变量为0时开启中断。
    {
                                    //uxCriticalNesting 不能为0,如果为0,减1后则变成负值。
        portENABLE_INTERRUPTS();    
    }
}

1.5.3 taskENTER_CRITICAL_FROM_ISR()

进入临界段函数taskENTER_CRITICAL_FROM_ISR()最底层为以下函数,返回无符号32位。
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;
}

1.5.4 taskEXIT_CRITICAL_FROM_ISR()

退出临界段函数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* */
    }
}

二.任务调度器

2.1 挂起和恢复

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

2.2 任务调度函数介绍

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

 使用格式示例

首先挂起任务调度器,执行内容不允许被其他内容打断,中断可以正常打断执行的内容,
防止任务与任务之间的资源抢夺,恢复任务调度器。
vTaskSuspendAll() ;
{
        … …	/* 内容 */
}
xTaskResumeAll()	;

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

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

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

2.3 函数的源码认识

2.3.1 vTaskSuspendAll()

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! -
	http://goo.gl/wu4acr */
	++uxSchedulerSuspended;
}
任务调度器挂起函数只是将变量uxSchedulerSuspended自加,根本上任务调度器是进行任务的切换.

任务切换是PendSV中断进行的在嘀嗒定时器中断服务函数SysTick_Handler()中触发PendSV中断。  变量uxSchedulerSuspended默认为0——pdFALSE。

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

void SysTick_Handler(void)
{	
    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
    {
        xPortSysTickHandler();	
    }
}

--------------------------------------------------------------------------------

/* 
    xPortSysTickHandler()函数中,
    只要xTaskIncrementTick()函数返回值不为pdFALSE ,则触发PendSV中断。
*/
void xPortSysTickHandler( void )
{
	vPortRaiseBASEPRI();
	{
		/* Increment the RTOS tick. */
        由于变量uxSchedulerSuspended已经自加,
        xTaskIncrementTick函数则不能进入第一个if,
        则最终返回变量xSwitchRequired的值为pdFALSE。
		if( xTaskIncrementTick() != pdFALSE )
		{
            由以上函数知则触发不了PendSV中断,任务调度器被挂起。
			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
		}
	}
	vPortClearBASEPRIFromISR();
}

2.3.2 xTaskResumeAll()

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

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


{
		--uxSchedulerSuspended;

		if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
		{
			if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
			{

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

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


/* Move any readied tasks from the pending list into the
appropriate ready list. */
while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
{
    判断等待就绪列表中是否有任务,如果有任务,
    则将列表中任务全部移除,添加到就绪列表。
    pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
    ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
    ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
    prvAddTaskToReadyList( pxTCB );

    /* If the moved task has a priority higher than 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();    //更新下一次阻塞的时间。 
}

{
	UBaseType_t uxPendedCounts = uxPendedTicks;
    
    /* 
        恢复滴答定时器,在任务调度器被挂起时丢失的节拍数
        (只有任务调度器未被挂起时,节拍数才会自加)。
        任务被挂起时自加的变量xPendedTicks,恢复后将变量的值赋值给变量xPendedCounts。
    */
	if( uxPendedCounts > ( UBaseType_t ) 0U )
	{
    /*
        如果xPendedCounts大于0,则调用函数xTaskIncrementTick(),
        将丢失的节拍数进行加回。
        通过函数xTaskIncrementTick()的返回值判断是否需要任务切换,
        每补齐一次,则xPendedCounts自减。
	*/
    	do
		{
			if( xTaskIncrementTick() != pdFALSE )
			{
			    xYieldPending = pdTRUE;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
				--uxPendedCounts;
		} while( uxPendedCounts > ( UBaseType_t ) 0U );
		uxPendedTicks = 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();
				}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值