FreeRTOS:时间管理

news2024/9/21 4:34:55

目录

  • 前言
  • 一、FreeRTOS 延时函数
    • 1.1函数vTaskDelay()
    • 1.2 函数prvAddCurrentTaskToDelayedList()
    • 1.3 函数vTaskDelayUntil()
  • 二、FreeRTOS 系统时钟节拍

前言

在使用FreeRTOS 的过程中我们通常会在一个任务函数中使用延时函数对这个任务延时,当执行延时函数的时候就会进行任务切换,并且此任务就会进入阻塞态,直到延时完成,任务重新进入就绪态。延时函数属于FreeRTOS 的时间管理,接下来我们就来学习一些FreeRTOS 的这个时间管理过程,看看在调用延时函数以后究竟发生了什么?任务是如何进入阻塞态的,在延时完成以后任务又是如何从阻塞态恢复到就绪态的?

一、FreeRTOS 延时函数

1.1函数vTaskDelay()

学习过UCOSIII 的朋友应该知道,在UCOSIII 中延时函数OSTimeDly()可以设置为三种模式:相对模式、周期模式和绝对模式。在FreeRTOS 中延时函数也有相对模式和绝对模式,不过在FreeRTOS 中不同的模式用的函数不同,其中函数vTaskDelay()是相对模式(相对延时函数),函数vTaskDelayUntil()是绝对模式(绝对延时函数)。函数vTaskDelay()在文件tasks.c 中有定义,要使用此函数的话宏INCLUDE_vTaskDelay 必须为1,函数代码如下:

#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 )                               /*(1)*/
    {
        configASSERT( uxSchedulerSuspended == 0 );
        vTaskSuspendAll();                                                /*(2)*/
        {
            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 );      /*(3)*/
        }
        xAlreadyYielded = xTaskResumeAll();                                /*(4)*/
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

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

#endif /* INCLUDE_vTaskDelay */

(1)、延时时间由参数xTicksToDelay 来确定,为要延时的时间节拍数,延时时间肯定要大于0。否则的话相当于直接调用函数portYIELD()进行任务切换。
(2)、调用函数vTaskSuspendAll()挂起任务调度器。
(3) 、调用函数prvAddCurrentTaskToDelayedList() 将要延时的任务添加到延时列表pxDelayedTaskList 或者pxOverflowDelayedTaskList() 中。后面会具体分析函数prvAddCurrentTaskToDelayedList()。
(4)、调用函数xTaskResumeAll()恢复任务调度器。
(5)、如果函数xTaskResumeAll()没有进行任务调度的话那么在这里就得进行任务调度。
(6)、调用函数portYIELD_WITHIN_API()进行一次任务调度。

1.2 函数prvAddCurrentTaskToDelayedList()

函数prvAddCurrentTaskToDelayedList()用于将当前任务添加到等待列表中,函数在文件tasks.c 中有定义,缩减后的函数如下:

static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount;  //1

	#if( INCLUDE_xTaskAbortDelay == 1 )
	{
		/* About to enter a delayed list, so ensure the ucDelayAborted flag is
		reset to pdFALSE so it can be detected as having been set to pdTRUE
		when the task leaves the Blocked state. */
		pxCurrentTCB->ucDelayAborted = pdFALSE;
	}
	#endif

	/* Remove the task from the ready list before adding it to the blocked list
	as the same list item is used for both lists. */
	//将当前任务的状态列表项从就绪列表中移除,
	//为什么这么肯定就是就绪列表呢? 因为调用vTaskDelay函数一定是当前正在运行的任务(也就是任务自身),
	//不可能说A任务调用vTaskDelay让B任务延时,这是不存在的。
	//既然是当前正在运行的任务调用vTaskDelay,那么说明他一定在就绪列表中。
	if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )  //2
	{
		/* The current task must be in a ready list, so there is no need to
		check, and the port reset macro can be called directly. */
		//当前优先级下没有就绪任务就复位优先级
		portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );//3
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	#if ( INCLUDE_vTaskSuspend == 1 )
	{
		if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )//4
		{
			/* Add the task to the suspended task list instead of a delayed task
			list to ensure it is not woken by a timing event.  It will block
			indefinitely. */
			//如果延时portMAX_DELAY个时钟节拍就添加到挂起列表中,也就是挂起任务,
			//但是调用vTaskDelay函数即使传递参数portMAX_DELAY也不能挂起任务 因为xCanBlockIndefinitely固定传递的是pdFALSE
			vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );//5
		}
		else
		{
			/* Calculate the time at which the task should be woken if the event
			does not occur.  This may overflow but this doesn't matter, the
			kernel will manage it correctly. */
			xTimeToWake = xConstTickCount + xTicksToWait;//计算出唤醒时间点  //6

			/* The list item will be inserted in wake time order. */
			//将唤醒时间点的值写入到当前任务控制块的状态列表项中,后续会把状态列表项挂载到延时列表
			listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );   //7

			if( xTimeToWake < xConstTickCount )//如果下一个时间点小于当前时间点    //8
			{
				/* Wake time has overflowed.  Place this item in the overflow
				list. */
				//这种说明"唤醒点"会在xTickCount溢出之后到来  溢出就挂载到pxOverflowDelayedTaskList指针指向的延时列表上
				vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );   //9
			}
			else
			{
				/* The wake time has not overflowed, so the current block list
				is used. */
				//这种情况说名"唤醒点"会在xTickCount溢出之前到来  
				//"唤醒点"在xTickCount溢出之前到来就挂载到pxDelayedTaskList指针指向的延时列表上 
				vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );   //10

				/* If the task entering the blocked state was placed at the
				head of the list of blocked tasks then xNextTaskUnblockTime
				needs to be updated too. */
				
				//xNextTaskUnblockTime保存了最近一个任务的解锁时间
				//如果新添加到阻塞列表中的"唤醒点"小于最近一个任务解锁时刻
				//那么就更新xNextTaskUnblockTime  
				if( xTimeToWake < xNextTaskUnblockTime )   //11
				{
					//更新xNextTaskUnblockTime
					xNextTaskUnblockTime = xTimeToWake;   //12
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
	}
	#else /* INCLUDE_vTaskSuspend */
	{
		/* Calculate the time at which the task should be woken if the event
		does not occur.  This may overflow but this doesn't matter, the kernel
		will manage it correctly. */
		xTimeToWake = xConstTickCount + xTicksToWait;

		/* The list item will be inserted in wake time order. */
		listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

		if( xTimeToWake < xConstTickCount )
		{
			/* Wake time has overflowed.  Place this item in the overflow list. */
			vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
		}
		else
		{
			/* The wake time has not overflowed, so the current block list is used. */
			vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

			/* If the task entering the blocked state was placed at the head of the
			list of blocked tasks then xNextTaskUnblockTime needs to be updated
			too. */
			if( xTimeToWake < xNextTaskUnblockTime )
			{
				xNextTaskUnblockTime = xTimeToWake;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}

		/* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */
		( void ) xCanBlockIndefinitely;
	}
	#endif /* INCLUDE_vTaskSuspend */
}

(1)、读取进入函数prvAddCurrentTaskToDelayedList()的时间点并保存在xConstTickCount 中,后面计算任务唤醒时间点的时候要用到。xTickCount 是时钟节拍计数器,每个滴答定时器中断xTickCount 都会加一。
(2)、要将当前正在运行的任务添加到延时列表中,肯定要先将当前任务从就绪列表中移除。
(3)、将当前任务从就绪列表中移除以后还要取消任务在uxTopReadyPriority 中的就绪标记。也就是将uxTopReadyPriority 中对应的bit 清零。
(4) 、延时时间为最大值portMAX_DELAY , 并且xCanBlockIndefinitely 不为pdFALSE(xCanBlockIndefinitely 不为pdFALSE 的话表示允许阻塞任务)的话直接将当前任务添加到挂起列表中,任务就不用添加到延时列表中。
(5)、将当前任务添加到挂起列表xSuspendedTaskList 的末尾。
(6)、计算任务唤醒时间点,也就是(1)中获取到的进入函数prvAddCurrentTaskToDelayedList()的时间值xConstTickCount 加上延时时间值xTicksToWait。
(7)、将计算到的任务唤醒时间点值xTimeToWake 写入到任务列表中壮态列表项的相应字段中。
(8)、计算得到的任务唤醒时间点小于xConstTickCount,说明发生了溢出。全局变量xTickCount 是TickType_t 类型的,这是个32 位的数据类型,因此在用xTickCount 计算任务唤醒时间点xTimeToWake 的时候的肯定会出现溢出的现象。FreeRTOS 针对此现象专门做了处理,在FreeROTS 中定义了两个延时列表xDelayedTaskList1 和xDelayedTaskList2,并且也定义了两个指针pxDelayedTaskList 和pxOverflowDelayedTaskList 来访问这两个列表,在初始化列表函数prvInitialiseTaskLists() 中指针pxDelayedTaskList 指向了列表xDelayedTaskList1 , 指针pxOverflowDelayedTaskList 指向了列表xDelayedTaskList2。这样发生溢出的话就将任务添加到pxOverflowDelayedTaskList 所指向的列表中,如果没有溢出的话就添加到pxDelayedTaskList 所指向的列表中。
(9)、如果发生了溢出的话就将当前任务添加到pxOverflowDelayedTaskList 所指向的列表中。
(10)、如果没有发生溢出的话就将当前任务添加到pxDelayedTaskList 所指向的列表中。
(11)、xNextTaskUnblockTime 是个全局变量,保存着距离下一个要取消阻塞的任务最小时间点值。 当xTimeToWake 小于xNextTaskUnblockTime 的话说明有个更小的时间点来了。
(12)、更新xNextTaskUnblockTime 为xTimeToWake。

1.3 函数vTaskDelayUntil()

函数vTaskDelayUntil()会阻塞任务,阻塞时间是一个绝对时间,那些需要按照一定的频率运行的任务可以使用函数vTaskDelayUntil()。此函数再文件tasks.c 中有如下定义:


#if ( INCLUDE_vTaskDelayUntil == 1 )

void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
{
TickType_t xTimeToWake;
BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;

    configASSERT( pxPreviousWakeTime );
    configASSERT( ( xTimeIncrement > 0U ) );
    configASSERT( uxSchedulerSuspended == 0 );

    vTaskSuspendAll();                                                                  /*(1)*/
    {
        /* Minor optimisation.  The tick count cannot change in this
        block. */
        const TickType_t xConstTickCount = xTickCount;                                    /*(2)*/

        /* Generate the tick time at which the task wants to wake. */
        xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;                               /*(3)*/

        if( xConstTickCount < *pxPreviousWakeTime )                                       /*(4)*/
        {
            /* The tick count has overflowed since this function was
            lasted called.  In this case the only time we should ever
            actually delay is if the wake time has also overflowed,
            and the wake time is greater than the tick time.  When this
            is the case it is as if neither time had overflowed. */
            if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )/*(5)*/
            {
                xShouldDelay = pdTRUE;                                                      /*(6)*/
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            /* The tick time has not overflowed.  In this case we will
            delay if either the wake time has overflowed, and/or the
            tick time is less than the wake time. */
            if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )/*(7)*/
            {
                xShouldDelay = pdTRUE;                                                     /*(8)*/
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }

        /* Update the wake time ready for the next call. */
        *pxPreviousWakeTime = xTimeToWake;                                             /*(9)*/

        if( xShouldDelay != pdFALSE )                                                  /*(10)*/
        {
            traceTASK_DELAY_UNTIL( xTimeToWake );

            /* prvAddCurrentTaskToDelayedList() needs the block time, not
            the time to wake, so subtract the current tick count. */
            prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );  /*(11)*/
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    xAlreadyYielded = xTaskResumeAll();                                               /*(12)*/

    /* 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_vTaskDelayUntil */

参数:
pxPreviousWakeTime: 上一次任务延时结束被唤醒的时间点,任务中第一次调用函数vTaskDelayUntil 的话需要将pxPreviousWakeTime 初始化进入任务的while()循环体的时间点值。在以后的运行中函数vTaskDelayUntil()会自动更新pxPreviousWakeTime。xTimeIncrement:任务需要延时的时间节拍数(相对于pxPreviousWakeTime 本次延时的节拍数)。
(1)、挂起任务调度器。
(2)、记录进入函数vTaskDelayUntil()的时间点值,并保存在xConstTickCount 中。
(3)、根据延时时间xTimeIncrement 来计算任务下一次要唤醒的时间点, 并保存在xTimeToWake 中。可以看出这个延时时间是相对于pxPreviousWakeTime 的,也就是上一次任务被唤醒的时间点。pxPreviousWakeTime、xTimeToWake、xTimeIncrement 和xConstTickCount 的关系如下图所示。
在这里插入图片描述
其中 (1)为任务主体,也就是任务真正要做的工作, (2)是任务函数中调用vTaskDelayUntil()对任务进行延时,(3)为其他任务在运行。任务的延时时间是xTimeIncrement,这个延时时间是相对于pxPreviousWakeTime 的,可以看出任务总的执行时间一定要小于任务的延时时间xTimeIncrement!也就是说如果使用vTaskDelayUntil()的话任务相当于任务的执行周期永远都是xTimeIncrement,而任务一定要在这个时间内执行完成。这样就保证了任务永远按照一定的频率运行了,这个延时值就是绝对延时时间,因此函数vTaskDelayUntil()也叫做绝对延时函数。
(4)、根据上图可以看出,理论上xConstTickCount 要大于pxPreviousWakeTime 的,但是也有一种情况会导致xConstTickCount 小于pxPreviousWakeTime,那就是xConstTickCount 溢出了!
(5)、既然xConstTickCount 都溢出了,那么计算得到的任务唤醒时间点肯定也是要溢出的,并且xTimeToWake 肯定也是要大于xConstTickCount 的。这种情况如下图所示:
在这里插入图片描述
(6)、如果满足(5)条件的话就将pdTRUE 赋值给xShouldDelay,标记允许延时。
(7)、还有其他两种情况,一:只有xTimeToWake 溢出,二:都没有溢出。只有xTimeToWake溢出的话如下图所示:
在这里插入图片描述
都不溢出的话就如(3))图所示,这两种情况都允许进行延时。
(8)、将pdTRUE 赋值给xShouldDelay,标记允许延时。
(9)、更新pxPreviousWakeTime 的值,更新为xTimeToWake,为本函数的下一次执行做准备。
(10)、经过前面的判断,允许进行任务延时。
(11)、调用函数prvAddCurrentTaskToDelayedList()进行延时。函数的第一个参数是设置任务的阻塞时间,前面我们已经计算出了任务下一次唤醒时间点了,那么任务还需要阻塞的时间就是下一次唤醒时间点xTimeToWake 减去当前的时间xConstTickCount。而在函数vTaskDelay()中只是简单的将这参数设置为xTicksToDelay。
(12)、调用函数xTaskResumeAll()恢复任务调度器。函数vTaskDelayUntil()的使用方法如下:

void TestTask( void * pvParameters )
{
    TickType_t	PreviousWakeTime;
    //延时 50ms,但是函数 vTaskDelayUntil()的参数需要设置的是延时的节拍数,不能直接
    //设置延时时间,因此使用函数 pdMS_TO_TICKS 将时间转换为节拍数。
    
    const TickType_t TimeIncrement = pdMS_TO_TICKS( 50 );
    PreviousWakeTime = xTaskGetTickCount();	//获取当前的系统节拍值for( ;; )
    {
        /******************************************************************/
        /*************************任务主体*********************************/
        /******************************************************************/
 
        //调用函数vTaskDelayUntil 进行延时
        vTaskDelayUntil( &PreviousWakeTime, TimeIncrement);
    }
}

其实使用函数vTaskDelayUntil()延时的任务也不一定就能周期性的运行,使用函数vTaskDelayUntil()只能保证你按照一定的周期取消阻塞,进入就绪态。如果有更高优先级或者中断的话你还是得等待其他的高优先级任务或者中断服务函数运行完成才能轮到你。这个绝对延时只是相对于vTaskDelay()这个简单的延时函数而言的。

二、FreeRTOS 系统时钟节拍

不管是什么系统,运行都需要有个系统时钟节拍,前面已经提到多次了,xTickCount 就是FreeRTOS 的系统时钟节拍计数器。每个滴答定时器中断中xTickCount 就会加一,xTickCount 的具体操作过程是在函数xTaskIncrementTick()中进行的,此函数在文件tasks.c 中有定义,如下:

BaseType_t xTaskIncrementTick( void )
{
    TCB_t * pxTCB; TickType_t xItemValue;
    BaseType_t xSwitchRequired = pdFALSE;
 
    //每个时钟节拍中断(滴答定时器中断)调用一次本函数,增加时钟节拍计数器 xTickCount 的
    //值,并且检查是否有任务需要取消阻塞。
    traceTASK_INCREMENT_TICK( xTickCount );
    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )         //1
    {
        const TickType_t xConstTickCount = xTickCount + 1;        //2
        //增加系统节拍计数器 xTickCount 的值,当为 0,也就是溢出的话就交换延时和溢出列
        //表指针值。
        xTickCount = xConstTickCount;
        if( xConstTickCount == ( TickType_t ) 0U )                //3
        {
            taskSWITCH_DELAYED_LISTS();                          //4
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
 
        //判断是否有任务延时时间到了,任务都会根据唤醒时间点值按照顺序(由小到大的升
        //序排列)添加到延时列表中,这就意味这如果延时列表中第一个列表项对应的任务的
        //延时时间都没有到的话后面的任务就不用看了,肯定也没有到。
        if( xConstTickCount >= xNextTaskUnblockTime )               //5
        {
            for( ;; )
            {
                if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )     //6
                {
                    //延时列表为空,设置 xNextTaskUnblockTime 为最大值。
                    xNextTaskUnblockTime = portMAX_DELAY;                  //7
                    break;
                }
                else
                {
                    //延时列表不为空,获取延时列表的第一个列表项的值,根据判断这个值
                    //判断任务延时时间是否到了, 如果到了的话就将任务移除延时列表。
                    pxTCB = ( TCB_t * )\	                        //8
                    listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); xItemValue =\	         //9
                    listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
 
                    if( xConstTickCount < xItemValue )	               //10
                    {
 
                    }
                    else
                    {
 
                    }
 
                    //任务延时时间还没到,但是 xItemValue 保存着下一个即将解除
                    //阻塞态的任务对应的解除时间点,所以需要用 xItemValue 来更新
                    //变量xNextTaskUnblockTime
                    xNextTaskUnblockTime = xItemValue;	                   //11
                    break;
 
                    mtCOVERAGE_TEST_MARKER();
 
                    //将任务从延时列表中移除
                    ( void ) uxListRemove( &( pxTCB->xStateListItem ) );	       //12
 
                    //任务是否还在等待其他事件?如信号量、队列等,如果是的话就将这些
                    //任务从相应的事件列表中移除。相当于等待事件超时退出!
                    if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) !\ = NULL )       //13                  
                    {
 
                    }
                    else
                    {
 
                    }
 
                    ( void ) uxListRemove( &( pxTCB->xEventListItem ) );	 //14
                    mtCOVERAGE_TEST_MARKER();
 
 
                    //将任务添加到就绪列表中
                    prvAddTaskToReadyList( pxTCB );	             //15
 
                    #if (	configUSE_PREEMPTION == 1 )
                    {
                        //使用抢占式内核,判断解除阻塞的任务优先级是否高于当前正在
                        //运行的任务优先级,如果是的话就需要进行一次任务切换!
                        if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )	             //16  
                        {
 
                        }
                        else
                        {
 
                        }
                    }
 
                xSwitchRequired = pdTRUE;
                mtCOVERAGE_TEST_MARKER();
                #endif /* configUSE_PREEMPTION */
            }
        }
    }
 
    //如果使能了时间片的话还需要处理同优先级下任务之间的调度
    #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )              //17
    {
        if( listCURRENT_LIST_LENGTH( &( \
        pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
        {
 
        }
        else
        {
 
        }
    }
 
    xSwitchRequired = pdTRUE;
    mtCOVERAGE_TEST_MARKER();
    #endif
 
    //使用时钟节拍钩子函数
    #if ( configUSE_TICK_HOOK == 1 )
    {
        if( uxPendedTicks == ( UBaseType_t ) 0U )
        {
 
        }
        else
        {
 
        }
    }    
    vApplicationTickHook();	                   //18
    mtCOVERAGE_TEST_MARKER();
    #endif /* configUSE_TICK_HOOK */
}
 else//任务调度器挂起期间                //19
	{
		++uxPendedTicks;//任务调度器挂起期间 uxPendedTicks变量++ 记录时钟节拍个数       //20

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

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

	return xSwitchRequired;//返回是否进行任务切换的标志             //22
}

(1)、判断任务调度器是否被挂起。
(2)、将时钟节拍计数器xTickCount 加一,并将结果保存在xConstTickCount 中,下一行程序会将xConstTickCount 赋值给xTickCount,相当于给xTickCount 加一。
(3)、xConstTickCount 为0,说明发生了溢出!
(4)、如果发生了溢出的话使用函数taskSWITCH_DELAYED_LISTS 将延时列表指针pxDelayedTaskList 和溢出列表指针pxOverflowDelayedTaskList 所指向的列表进行交换,函数taskSWITCH_DELAYED_LISTS()本质上是个宏,在文件tasks.c 中有定义,将这两个指针所指向的列表交换以后还需要更新xNextTaskUnblockTime 的值。
(5)、变量xNextTaskUnblockTime 保存着下一个要解除阻塞的任务的时间点值,如果xConstTickCount 大于xNextTaskUnblockTime 的话就说明有任务需要解除阻塞了。
(6)、判断延时列表是否为空。
(7)、如果延时列表为空的话就将xNextTaskUnblockTime 设置为portMAX_DELAY。
(8)、延时列表不为空,获取延时列表第一个列表项对应的任务控制块。
(9)、获取(8)中获取到的任务控制块中的壮态列表项值。
(10)、任务控制块中的壮态列表项值保存了任务的唤醒时间点,如果这个唤醒时间点值大于当前的系统时钟(时钟节拍计数器值),说明任务的延时时间还未到。
(11)、任务延时时间还未到,而且xItemValue 已经保存了下一个要唤醒的任务的唤醒时间点,所以需要用xItemValue 来更新xNextTaskUnblockTime。
(12)、任务延时时间到了,所以将任务先从延时列表中移除。
(13)、检查任务是否还等待某个事件,比如等待信号量、队列等。如果还在等待的话就任务从相应的事件列表中移除。因为超时时间到了!
(14)、将任务从相应的事件列表中移除。
(15)、任务延时时间到了,并且任务已经从延时列表或者事件列表中已经移除。所以这里需要将任务添加到就绪列表中。
(16)、延时时间到的任务优先级高于正在运行的任务优先级,所以需要进行任务切换了,标记xSwitchRequired 为pdTRUE,表示需要进行任务切换。
(17)、如果使能了时间片调度的话,还要处理跟时间片调度有关的工作,具体过程参考9.6小节。
(18)、如果使能了时间片钩子函数的话就执行时间片钩子函数vApplicationTickHook(),函数的具体内容由用户自行编写。
(19)、如果调用函数vTaskSuspendAll()挂起了任务调度器的话在每个滴答定时器中断就不不会更新xTickCount 了。取而代之的是用uxPendedTicks 来记录调度器挂起过程中的时钟节拍数。这样在调用函数xTaskResumeAll()恢复任务调度器的时候就会调用uxPendedTicks 次函数xTaskIncrementTick(),这样xTickCount 就会恢复,并且那些应该取消阻塞的任务都会取消阻塞。函数xTaskResumeAll()中相应的处理代码如下:

BaseType_t xTaskResumeAll( void )
{
    TCB_t *pxTCB = NULL;
    BaseType_t xAlreadyYielded = pdFALSE; configASSERT( uxSchedulerSuspended );
 
    taskENTER_CRITICAL();
 
    /************************************************************************/
    /****************************省略部分代码********************************/
    /************************************************************************/
 
    UBaseType_t uxPendedCounts = uxPendedTicks; if( uxPendedCounts > ( UBaseType_t ) 0U )
    {
        //do-while()循环体,循环次数为 uxPendedTicks do
        {
            if( xTaskIncrementTick() != pdFALSE )	//调用函数xTaskIncrementTick
            {
 
            }
            else
            {
                xYieldPending = pdTRUE;	//标记需要进行任务调度。
                mtCOVERAGE_TEST_MARKER();
            }
            --uxPendedCounts;
        } while( uxPendedCounts > ( UBaseType_t ) 0U );
        uxPendedTicks = 0;	//循环执行完毕,uxPendedTicks 清零
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
 
    /************************************************************************/
    /****************************省略部分代码********************************/
    /************************************************************************/
 
    taskEXIT_CRITICAL();
    return xAlreadyYielded;
}

(20)、uxPendedTicks 是个全局变量,在文件tasks.c 中有定义,任务调度器挂起以后此变量用来记录时钟节拍数。
(21)、有时候调用其他的API 函数会使用变量xYieldPending 来标记是否需要进行上下文切换,后面具体遇到具体分析。
(22)、返回xSwitchRequired 的值,xSwitchRequired 保存了是否进行任务切换的信息,如果为pdTRUE 的话就需要进行任务切换,pdFALSE 的话就不需要进行任务切换。函数xPortSysTickHandler()中调用xTaskIncrementTick()的时候就会判断返回值,并且根据返回值决定是否进行任务切换。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/564901.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C/C++】内存管理

【C/C】内存管理 目录 【C/C】内存管理C/C内存分布C语言和C中动态内存管理方式new/delete操作内置类型new和delete操作自定义类型 operator new与operator delete函数operator new与operator delete的类专属重载new和delete的实现原理定位new表达式(placement-new)重要的知识ma…

Docker(1)

一)什么是虚拟化&#xff1f;容器化&#xff1f; 1)物理机:实际的服务器或者计算机&#xff0c;相对于虚拟机而言的对实体计算机的称呼。物理 机提供给虚拟机以硬件环境&#xff0c;有时也称为“寄主”或“宿主&#xff1b; 2)虚拟化:是指通过虚拟化技术将一台计算机虚拟为多台…

【C++】——string类的介绍及模拟实现

文章目录 1. 前言2. string类的常用接口2.1 string类对象的常见构造2.2 string类对象的容量操作2.3 string类对象的访问及遍历操作2.4 string类对象的修改操作2.5 string类非成员函数2.6 string四种迭代器类型2.7 string类的insert和erase函数 3. 浅拷贝和深拷贝4. string类模拟…

评奖系统设计

系列文章 任务40 评奖系统设计 文章目录 系列文章一、实践目的与要求1、目的2、要求 二、课题任务三、总体设计1.存储结构及数据类型定义2.程序结构3.所实现的功能函数4、程序流程图 四、小组成员及分工五、 测试执行完毕程序展示成功&#xff01;学生投票&#xff0c;举例第一…

URP的多Pass和Features用法

回到目录 大家好&#xff0c;我是阿赵。这里用一个传统的描边例子来说明一下&#xff0c;URP下怎么使用多Pass和Features。 一、传统多Pass描边 最常用的制作描边方法&#xff0c;就是写多一个Cull Front的Pass&#xff0c;然后通过法线方向扩展顶点&#xff0c;模拟描边的效…

机试打卡 -05 接雨水(动态规划栈)

我的思路&#xff1a;依次计算每一列能接收的雨水量。 关键点&#xff1a;如何计算得到每一列所能接收到的雨水量&#xff1f; 某一列能够接收到的雨水量&#xff0c;取决于其左右两侧最高的柱子。仅有当左右两侧的柱子均高于该列的高度&#xff0c;该列才可收到雨水&#x…

Java 17 VS Java 8: 新旧对决,这些Java 17新特性你不容错过

&#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; Java是一门非常流行的编程语言&#xff0c;由于其跨平台性、可移植性以及强大的面向对象特性而备受青睐。Java最初由Sun Microsystems公司于1995年推出&#xff0c;随着时间的推…

[极客大挑战 2019]HardSQL1

拿到题目是一个登录界面 提交万能密码后拿到回显信息&#xff0c;说明页面存在过滤 burp抓包爆破后发现&#xff0c;所有736都是被过滤字符 联合注入和时间盲注被过滤&#xff0c;因为页面存在报错信息&#xff0c;所以尝试报错注入 因为空格也被过滤&#xff0c;所以我们使用括…

HOMER docker版本配置优化

概述 HOMER是一款100%开源的针对SIP/VOIP/RTC的抓包工具和监控工具。 HOMER是一款强大的、运营商级、可扩展的数据包和事件捕获系统&#xff0c;是基于HEP/EEP协议的VoIP/RTC监控应用程序&#xff0c;并可以使用即时搜索、处理和存储大量的信令、RTC事件、日志和统计信息。 …

机器学习-01概论

人们在生活中可能已经注意到了这样一种现象&#xff1a;我们能够轻松地通过相貌区分出日本人、韩国人和泰国人&#xff0c;但是面对英国人、俄罗斯人和德国人时&#xff0c;我们却很难辨认他们的面孔。造成这种现象的原因一方面是因为日韩泰都是我国的邻国&#xff0c;我们观察…

信号处理与分析-确定性信号的分析

目录 一、引言 二、确定性信号的定义 三、确定性信号的分类 四、确定性信号的分析方法 4.1 傅里叶变换 4.2 离散傅里叶变换 4.3 离散余弦变换 4.4 小波变换 五、确定性信号的处理方法 六、结论 一、引言 信号分析与处理是现代通信技术和信息处理技术的重要组成部分。…

Redis安装及其配置文件修改

一、redis 安装 点击即可下载 https://download.redis.io/releases/ 将下载后的包通过xftp上传到服务器 解压&#xff0c;我这边是解压到/usr/local目录下 -- 创建路径 mkdir /usr/local/redis -- 解压 tar -zxvf redis-4.0.0.tar.gz -C /usr/local/redis 为防止编译失败&am…

MyBatis-Plus精讲和使用注意事项

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

【国产虚拟仪器】基于Zynq的雷达10Gbps高速PCIE数据采集卡方案(一)总体设计

2.1 引言 本课题是来源于雷达辐射源识别项目&#xff0c;需要对雷达辐射源中频信号进行采集传输 和存储。本章基于项目需求&#xff0c;介绍采集卡的总体设计方案。采集卡设计包括硬件设计 和软件设计。首先对采集卡的性能和指标进行分析&#xff0c;接着提出硬件的总体设计…

详解知识蒸馏原理和代码

目录 知识蒸馏原理概念技巧举例说明KL 散度及损失 KD训练代码导入包网络架构teacher网络student网络 teacher网络训练定义基本函数训练主函数 student网络训练&#xff08;重点&#xff09;理论部分定义kd的loss定义基本函数训练主函数 绘制结果teacher网络的暗知识softmax_t推…

使用dockerfile自定义Tomcat镜像

一&#xff1a;创建目录 mkdir /root/tomcat chmod 777 /root/ chmod 777 /root/tomcat 或者chmod -R 777 /root 这里的无效选项是因为我想递归修改root目录及root目录文件以下的权限 chmod :-R 递归修改指定目录下所有子目录和文件的权限 二&#xff1a;将jdk和apache压…

RPG游戏自动打怪之朝向判断

RPG游戏辅助想要做到自动打怪 获得到最近怪物信息以后 还需要面向怪物 否则背对怪物等等情况是没有办法攻击以及释放技能的 游戏设计的时候朝向是有很多种情况的 第一种 2D&#xff0c;2.5D老游戏&#xff0c;例如传奇 他的朝向一般是极为固定的4朝向或则8朝向 也就是不…

数组题目总结 -- 花式遍历

目录 一. 反转字符串中的单词思路和代码&#xff1a;I. 博主的做法II. 东哥的做法III. 其他做法1IV. 其他做法2 二. 旋转图像思路和代码&#xff1a;I. 博主的做法II. 东哥的做法 三. 旋转图像&#xff08;逆时针旋转90&#xff09;思路和代码&#xff1a;I. 博主和东哥的做法 …

SpringBoot2-基础入门(一)

SpringBoot2-基础入门&#xff08;一&#xff09; 文章目录 SpringBoot2-基础入门&#xff08;一&#xff09;1. 为什么学习SpringBoot1.1 SpringBoot的优点1.2 SpringBoot的缺点1.3 SpringBoot开发环境 2. 第一个SpringBoot程序2.1 添加依赖2.2 编写主程序类 -- 固定写法2.3 编…

SpringCloud(25):熔断降级实现

熔断降级会在调用链路中某个资源出现不稳定状态时&#xff08;例如调用超时或异常比例升高&#xff09;&#xff0c;对这个资源的调用进行限制&#xff0c;让请求快速失败&#xff0c;避免影响到其它的资源而导致级联错误。当资源被降级后&#xff0c;在接下来的降级时间窗口之…