FreeRtos的时间管理

vTaskDelay函数

入参:const TickType_t xTicksToDelay

        延时时间,类型为const TickType_t,含义是xTicksToDelay个滴答时钟

返回值:void,空

#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();
			{
				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 */

#if ( INCLUDE_vTaskDelay == 1 )

... ...

#endif /* INCLUDE_vTaskDelay */

首尾是条件编译选项,在移植过程汇总,将文件FreeRTOSConfig.h中的宏INCLUDE_vTaskDelay定义为1,如下:

/***************************************************************************************************************/
/*                                FreeRTOS可选函数配置选项                                                      */
/***************************************************************************************************************/
#define INCLUDE_xTaskGetSchedulerState          1                       
#define INCLUDE_vTaskPrioritySet		        1
#define INCLUDE_uxTaskPriorityGet		        1
#define INCLUDE_vTaskDelete				        1
#define INCLUDE_vTaskCleanUpResources	        1
#define INCLUDE_vTaskSuspend			        1
#define INCLUDE_vTaskDelayUntil			        1
#define INCLUDE_vTaskDelay				        1
#define INCLUDE_eTaskGetState			        1
#define INCLUDE_xTimerPendFunctionCall	        1

表示启用了这个vTaskDelay函数。

一般在使用的时候,比如延时个1s时间,就将参数写1000(vTaskDelay(1000)),当然,这得是你在设定好了芯片的滴答时钟频率之后,每一个滴答中断时间为1ms,与系统时钟有关。比如你的芯片主频是72Mhz,在设置分频的时候,如何将中断时间设定为1ms这里就不讨论了。

首先定义一个局部变量:

      BaseType_t xAlreadyYielded = pdFALSE;  

这个变量通过名字就推断出来,是来标志任务是否已经进行切换了的,既然是任务的切换标志,起始值当然是没有切换,因为刚进vTaskDelay肯定是没有任务切换的,所以起始值是pdFALSE。要明确一点,vTaskDelay这个函数,不仅仅传统意义上的延时,它还有一个重要的作用是在任务中调用它以后,使得调度器将任务切换到另外一个就绪了的任务去,让另外的那个任务去运行。有这样一个标记,意味着vTaskDelay这个函数中,有不同的时刻让任务进行切换,因为如果只有一个方式让任务切换,就没有必要去定义这样一个标志任务切换的变量,因为方式是唯一的,为啥还要知道标志呢对不对。

那么是哪些方式让任务切换呢?

其实就两个,一个是当延时时间为0的时候,表示不延时,立马去置位pendSV异常的位,也就是切换任务;

另外一个就比较复杂,得从挂起(vTaskSuspendAll();)和解挂(xAlreadyYielded = xTaskResumeAll();)操作说起。

挂起调度器是为了在添加延时任务到延时列表中时,不会产生任务切换的行为。但是挂起调度器这段时间内,可能有一些阻塞的任务(无论是因为时间还是等待事件的原因的阻塞),会在这期间不再阻塞,但是根据状态迁移的原则,这个时候阻塞的任务是不会转为就绪态的,因为调度器被挂起了,只能从阻塞状态迁移到挂起状态。

那么当解挂(调用xTaskResumeAll()的开始阶段就解开了)的时候,就立马去把这些本应是就绪了的任务从挂起列表中删除,并添加到对应的就绪列表中,并根据这些就绪列表中的任务的优先级来置位一个全局变量(xYieldPending),之后会根据这个全局变量的值来对xTaskResumeAll中的xAlreadyYielded局部变量进行赋值,注意,这里的这个标志xAlreadyYielded是xTaskResumeAll函数里面的,而不是vTaskDelay里面的。但是由于xTaskResumeAll需要提供一个返回值,给vTaskDelay这个调用了它的函数,而这个接收xTaskResumeAll返回值的变量就是vTaskDelay中的xAlreadyYielded。也就是说xTaskResumeAll会在返回的时候告知调用它的对象,在xTaskResumeAll里面是否进行了任务的切换操作。其实也就是说如果在xTaskResumeAll中进行了任务的切换,那么全局变量xYieldPending就是pdTRUE,如果没有切换,那么就是pdFALSE。那么在vTaskDelay中就根据xTaskResumeAll告知的切换与否来判断要不要再vTaskDelay自己函数体内执行任务的切换,因为无论如何vTaskDelay在返回的时候,必然会让任务切换一次。

这里提一点,当调用了vTaskSuspendAll函数的时候,调度器是被挂起了的,调度器其实是用来进行任务切换的,它被挂起了,就是任务不能切换了,不能切换其实也就是挂起了任务,所以说调度器被挂起,也就是所有任务被挂起了。我们想象一下,在调度器被挂起期间,是一段时间,这个时间内,肯定有延时的任务到时间了的可能,这是一种情况,这些任务分类的话,就是TCB里面的statelist里的任务,他们阻塞是因为时间的延时;另外有一种情况,一些任务在等待某个队列里的消息,或者信号量或者通知等等,这些任务的阻塞不是因为延时,而是因为等待队列里的消息,而被阻塞了;我们假设这两种阻塞的任务在调用vTaskSuspendAll函数进行挂起调度器之前被阻塞着,而在调用vTaskSuspendAll后和xTaskResumeAll前达到了恢复阻塞的条件,也就是延时时间到了,或者消息队列里有消息了,这两种阻塞的任务应该是从阻塞态转为就绪态的,操作就是从阻塞的列表(对于延时的任务是延时列表,对于等待消息的任务是消息队列结构体中的出队列表)中删除,然后加入到就绪列表。但是由于调度器的挂起,两种情况都不允许这些操作。具体的操作是什么呢?请耐心看下面的描述。

首先对延时的阻塞任务来说,在调度器挂起期间,逻辑层面上来说,是没问题的,然而在代码层面上,在这个挂起调度器的时间内,延时阻塞任务用来判断时间点的全局变量根本就没有被修改,始终停留在挂起调度器时的值,也就是逻辑上时间是到了,但是代码的判断上,时间就像没有到一样,这是因为在修改xTickCount的函数xTaskIncrementTick中,对uxSchedulerSuspended这个调度器挂起嵌套全局变量进行了判断,调度器挂起的时候,它是大于0的,当systick在挂起调度器期间有了一次异常产生,进入到了xTaskIncrementTick中,想去修改xTickCount值的时候,由于uxSchedulerSuspended不为0,导致修改xTickCount的分支无法进入,而是去增加了另外一个在挂起调度器期间,记录过了多少个systick的全局变量uxPendedTicks,这个变量在恢复调度器函数xTaskResumeAll中起到了恢复xTickCount值的作用。

我们知道延时任务队列的列表项中,也就是任务控制块(TCB)中有按升序排列着这些被描述的任务,任务的TCB记录着每个任务的结束延时时间,每当进入一次systick的时候,之所以调用xTaskIncrementTick函数,不单单只是为了更新xTickCount这个描述时钟节拍的全局变量,还要在函数体中,利用这个时钟节拍xTickCount变量对每一个延时列表中的任务延时结束时间进行对比,如果时间值比xTickCount值小,就说明这些任务延时时间到了,那肯定是要从延时列表中删除然后加入到就绪列表中的,这也是延时任务的阻塞与等待事件的任务的阻塞的区别。延时任务不是在到时间点的那一刻立马就按逻辑去执行,而是在调度器解挂以后,利用uxPendedTicks这个变量来调用xTaskIncrementTick,从而恢复xTickCount的正常值,uxPendedTicks记录了几次时钟节拍,那么xTaskIncrementTick就要去执行几次,在每一次的执行中,都查看了延时列表中是否有到时的任务,并让此任务进入就绪态,等待被调度器进行切换。

而等待事件的任务则不同,它不是靠时间点来判断状态迁移的。延时阻塞的任务加入到延时列表,等待事件阻塞的任务加入到队列结构体中描述的等待列表中。创建队列,队列接收消息,任务在等待队列中有消息的时候被阻塞了,我们知道此时调度器是被挂起的,那么调用挂起调度器的任务如果没有发送消息的能力,而其他有能力发送消息的任务又因为调度器无法工作而不能发消息,此时只有一种可能性就是中让中断来发送消息了,因为中断的执行不受调度器的控制,此时某中断给消息队列发送了消息,使用的是xQueueSend这个发送消息的函数,而实际上做事的是xQueueGenericSend这个函数,在这个函数内会在判断了等待接收消息列表中确实有等待消息的任务后,将这个任务从那个等待接收消息列表xTasksWaitingToReceive中删除,因为此时消息队列中已经通过判断确定了有消息了,当然就不能再继续待在阻塞状态的列表中了,xTaskRemoveFromEventList就是删除操作的函数,而xTaskRemoveFromEventList函数中,就要去判断了,此时这个阻塞的任务,是在调度器被挂起的状态下解除阻塞呢,还是在没有挂起调度器的情况下解除的阻塞呢,没有挂起调度器,将直接加入到就绪列表中,但是如果挂起了调度器,就不能直接加入到就绪列表,而是应该加入到挂起列表了。最后当调度器解挂的时候,xTaskResumeAll这个函数将在函数体内对挂起列表进行遍历,此时挂起的任务都是那些本该就绪的任务,从状态迁移图也可以看得出结论,只要是挂起的任务,只能往就绪的状态迁移,因此挂起列表里的任务,在解挂以后都应该加入到就绪列表中:

之后就可以根据优先级的顺序,又调度器再去对任务进行切换了。

很明显,当任务是延时阻塞的时候是等待调度器从挂起态恢复以后,再通过调用相应次数的xTaskIncrementTick函数,将任务从延时任务列表中删除再加入就绪列表中的。而事件阻塞的任务则是在往消息队列中发送消息,或者从消息队列接收消息的函数中,去通过判断全局变量uxSchedulerSuspended是否为0了,来相应的把发送消息阻塞队列或者接收消息阻塞队列加入到挂起列表中(uxSchedulerSuspended为1时)或者添加到就绪任务列表中(uxSchedulerSuspended为0时)。

以上就说清楚了如果在vTaskDelay挂起调度器的那段时间,又任务从阻塞变为不再阻塞应该怎么做。弄清楚了这个,是为了知道在解挂的时候,xTaskResumeAll返回的那个是否已经切换了的标志是什么,那么现在你能回答吗?不能回答我来回答。xTaskResumeAll如果返回的标志是任务切换了,那就代表xTaskResumeAll里面进行了任务切换,这是句废话,如果在xTaskResumeAll里面切换了,那么在vTaskDelay里面就不用切换。

xTaskResumeAll切换没有呢?我们上面分析了一大堆,再来分析一下,就是阻塞的一个任务在挂起调度器的时候应该恢复,那么在调用xTaskResumeAll的时候,先把uxSchedulerSuspended自减变为0,接下来就是恢复xTickCount的值,并用这个值在xTaskIncrementTick函数内,判断这个被阻塞并应该恢复的任务应该迁移到任务就绪态了。这没有完,任务是有优先级的,如果这个被加入到就绪列标中的任务的优先级比当前任务的优先级高,那么就在xTaskResumeAll内进行任务的切换,不要在等到vTaskDelay里面去切了,优先级不比当前任务的优先级高,那么再去vTaskDelay里面,等到vTaskDelay什么事都做完了,再去从当前这个任务切换到就绪列表中任务,这就是抢占优先级的作用。

以上,说清楚了这个函数的第一句定义那个标志临时变量的原因,实际上已经说的差不多了。

接下来的挂起调度器,在挂起调度器以后,将当前这个任务加入到延时队列中,当然加入之前这个任务肯定是在就绪任务列表的,肯定是要从这个列表中删除的。这写具体操作,大家就自己去看prvAddCurrentTaskToDelayedList函数的源码吧。加入到延时列表中就可以解挂调度器了。解挂调度器函数返回一个是否已经切换了任务的返回值,最后在vTaskDelay中通过if语句的判断来决定是否再进行任务的切换。

完。

  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值