FreeRTOS --(10)任务管理之任务延时
转载自 https://blog.csdn.net/zhoutaopower/article/details/107101723
在《FreeRTOS --(7)任务管理之入门篇》中讲过,如果有几个任务同时跑,但是又都不阻塞的话,那么最高优先级的任务将会占领整个 CPU,因为每次都会调度到它,一直处于 Ready 状态,所以呢,调度器每次都要选择优先级最高的任务来让它执行;所以,不管怎么样,任务做完自己该做的事情,就应该进入阻塞状态,等待下次该自己做任务的时候,在占领 CPU,这样既可以让 Idle 线程,在系统空闲的时候跑,也可以让让任务在合理的时间占领 CPU;
之前也说过,让任务进入阻塞状态的方式有两种:
1、让任务延时:因为任务是一个 While 1 的无限循环,所以执行完自己的事情后,可以调用接口进行延时,进入阻塞,让出 CPU;
2、让任务等待某个事件:当任务需要的资源满足不了的时候,可以让任务阻塞的等待所需的资源,这样也是合理的;
这章就是要讲让任务进入延时进入阻塞的方法以及相关的原理;
1、接口介绍
任务执行完自己的事情后,可以调用如下接口,让任务进入阻塞态,delay 一段时间:
1、vTaskDelay()
2、vTaskDelayUntil()
下面分别来介绍这两个函数的用法和官方的解释;
1.1、vTaskDelay
1.1.1、Usage
这个函数是相对延时函数,它的函数原型为:
/**
* task. h
* <pre>void vTaskDelay( const TickType_t xTicksToDelay );</pre>
*
* Delay a task for a given number of ticks. The actual time that the
* task remains blocked depends on the tick rate. The constant
* portTICK_PERIOD_MS can be used to calculate real time from the tick
* rate - with the resolution of one tick period.
*
* INCLUDE_vTaskDelay must be defined as 1 for this function to be available.
* See the configuration section for more information.
*
*
* vTaskDelay() specifies a time at which the task wishes to unblock relative to
* the time at which vTaskDelay() is called. For example, specifying a block
* period of 100 ticks will cause the task to unblock 100 ticks after
* vTaskDelay() is called. vTaskDelay() does not therefore provide a good method
* of controlling the frequency of a periodic task as the path taken through the
* code, as well as other task and interrupt activity, will effect the frequency
* at which vTaskDelay() gets called and therefore the time at which the task
* next executes. See vTaskDelayUntil() for an alternative API function designed
* to facilitate fixed frequency execution. It does this by specifying an
* absolute time (rather than a relative time) at which the calling task should
* unblock.
*
* @param xTicksToDelay The amount of time, in tick periods, that
* the calling task should block.
*
* Example usage:
void vTaskFunction( void * pvParameters )
{
// Block for 500ms.
const TickType_t xDelay = 500 / portTICK_PERIOD_MS;
for( ;; )
{
// Simply toggle the LED every 500ms, blocking between each toggle.
vToggleLED();
vTaskDelay( xDelay );
}
}
* \defgroup vTaskDelay vTaskDelay
* \ingroup TaskCtrl
*/
void vTaskDelay( const TickType_t xTicksToDelay ) PRIVILEGED_FUNCTION;
官方的注释写得非常非常仔细,甚至于用法都写进了注释;
任务调用这个函数,传入一个 xTicksToDelay ,代表相对现在,对任务进行延时;这个的这个 xTicksToDelay 的单位是 Tick,比如,你 1ms 一个 Tick 的话,那么设置 50,就是延时 50ms;如果 10ms 一个 Tick 的话,设置 50,就是延时 500ms;
实际情况是,将当前任务阻塞,到时间的时候,再把它加入到就绪队列,参与调度;
简单的 Demo 如下所示:
void vTaskFunction( void * pvParameters )
{
// Block for 250ms.
const TickType_t xDelay250ms = pdMS_TO_TICKS( 250 ); for( ;; )
{
// Simply toggle the LED every 250ms, blocking between each toggle.
vToggleLED(); vTaskDelay( xDelay250ms );
}
}
时序上比如:
1.1.2、Implement
知道了用法后,我们来看下 vTaskDelay 的实现,在 task.c 文件中:
#if ( INCLUDE_vTaskDelay == 1 ) void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE; /* 如果延时时间为0,则不会将当前任务加入延时列表 */
if( xTicksToDelay > ( TickType_t ) 0U )
{
vTaskSuspendAll();
{
/* 将当前任务从就绪列表中移除,并根据当前系统节拍计数器值计算唤醒时间,然后将任务加入延时列表 */
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
}
xAlreadyYielded = xTaskResumeAll();
} /* 强制执行一次上下文切换*/
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
} #endif /* INCLUDE_vTaskDelay */
vTaskDelay 的实现依赖于宏 INCLUDE_vTaskDelay ,必须定义这个宏,才能够使用这个函数;
先判空,如果入参,也就是需要延时的时间大于 0 才有效;
先调用 vTaskSuspendAll(); 来挂起调度器,暂时暂停调度;
然后调用 prvAddCurrentTaskToDelayedList 将当前的这个任务添加到 Delayed 链表;
这个函数稍后深入分析,这里先扩展分析一下几个链表:
/* Lists for ready and blocked tasks. --------------------
xDelayedTaskList1 and xDelayedTaskList2 could be move to function scople but
doing so breaks some kernel aware debuggers and debuggers that rely on removing
the static qualifier. */
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*< Prioritised ready tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList1; /*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2; /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList; /*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList; /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList; /*< Tasks that have been readied while the scheduler was suspended. They will be moved to the ready list when the scheduler is resumed. */
对于一个任务,有很多种状态,可能是 Running、Ready、Blocked、Suspend,那对于不一样的的状态,FreeRTOS 中将其挂接到不同的链表,进行管理;
单核情况下,同一时间,只有一个任务处于 Running 状态,所以用 pxCurrentTCB 便可以代表了 Running 状态;
Blocked 阻塞态的任务,阻塞在时间上的任务,被挂接到名为 xxxDelayedTaskListx 上:
PRIVILEGED_DATA static List_t xDelayedTaskList1; /*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2; /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList; /*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList; /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
用了两个链表,主要是为了处理时间回绕的场景(时间回绕,的含义为,用 U32 来记录运行时间,每次 SysTick 的时候增加1个计数,由于 SysTick 的周期我们是知道的,比如 1ms,所以我们就知道当前的时间,但是如果系统长时间运行,记录时间的 U32 势必会溢出,就导致了时间回绕);具体的处理方式我们细细来品;
最后调用 xTaskResumeAll 查看是否有需要调度的任务,有的话,强制触发一次调度;
下面来看看 prvAddCurrentTaskToDelayedList 的实现:
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount; #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. */
if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* 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 ); /*lint !e931 pxCurrentTCB cannot change as it is the calling task. pxCurrentTCB->uxPriority and uxTopReadyPriority cannot change as called with scheduler suspended or in a critical section. */
}
else
{
mtCOVERAGE_TEST_MARKER();
} #if ( INCLUDE_vTaskSuspend == 1 )
{
if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
{
/* 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. */
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
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; /* 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();
}
}
}
}
#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、首先,当前的这个任务,肯定是处于 Ready 链表,所以将它从 Ready 链表移除;
2、获取当前的绝对时间 xTickCount,也就是 SysTick 会累积增加的那个,然后将延时的时间加上这个基准时间,配置成为唤醒该任务的时间,并赋值给这个 Item 的 Value 字段,并将其挂接到 Delay 链表;
虽然代码逻辑如上所示,不过在链表挂接的时候,需要处理一些临界状态,比如,将当前任务从 Ready 链表中拿去的时候,需要判断当前 Ready 链表中,拿去这个任务后,是否已为空,如果是这样的话,就要清除记录优先级对应的 uxTopReadyPriority (Bitmap);
配置唤醒时间的时候,就要通过比对时间基准来判断 U32 的回绕,如果时间回绕,那么将其挂接到 pxOverflowDelayedTaskList 这个链表,否则挂接到 pxDelayedTaskList 链表;
唤醒的时间如果比最近的唤醒时间还早,那么需要更新唤醒时间到全局变量 xNextTaskUnblockTime 中,在 SysTick 来的时候进行判断比对唤醒时间;
总的来说,vTaskDelay 接口实现基于当前时间的一个增量延时,并 Block 了当前任务;
1.2、vTaskDelayUntil
我们再来看看这个 vTaskDelayUntil ;
1.2.1、Usage
这个函数是绝对延时函数,可以用来做周期性任务(必须最高优先级才可以),它的函数原型为:
/**
* task. h
* <pre>void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement );</pre>
*
* INCLUDE_vTaskDelayUntil must be defined as 1 for this function to be available.
* See the configuration section for more information.
*
* Delay a task until a specified time. This function can be used by periodic
* tasks to ensure a constant execution frequency.
*
* This function differs from vTaskDelay () in one important aspect: vTaskDelay () will
* cause a task to block for the specified number of ticks from the time vTaskDelay () is
* called. It is therefore difficult to use vTaskDelay () by itself to generate a fixed
* execution frequency as the time between a task starting to execute and that task
* calling vTaskDelay () may not be fixed [the task may take a different path though the
* code between calls, or may get interrupted or preempted a different number of times
* each time it executes].
*
* Whereas vTaskDelay () specifies a wake time relative to the time at which the function
* is called, vTaskDelayUntil () specifies the absolute (exact) time at which it wishes to
* unblock.
*
* The constant portTICK_PERIOD_MS can be used to calculate real time from the tick
* rate - with the resolution of one tick period.
*
* @param pxPreviousWakeTime Pointer to a variable that holds the time at which the
* task was last unblocked. The variable must be initialised with the current time
* prior to its first use (see the example below). Following this the variable is
* automatically updated within vTaskDelayUntil ().
*
* @param xTimeIncrement The cycle time period. The task will be unblocked at
* time *pxPreviousWakeTime + xTimeIncrement. Calling vTaskDelayUntil with the
* same xTimeIncrement parameter value will cause the task to execute with
* a fixed interface period.
*
* Example usage:
<pre>
// Perform an action every 10 ticks.
void vTaskFunction( void * pvParameters )
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 10;
// Initialise the xLastWakeTime variable with the current time.
xLastWakeTime = xTaskGetTickCount ();
for( ;; )
{
// Wait for the next cycle.
vTaskDelayUntil( &xLastWakeTime, xFrequency );
// Perform action here.
}
}
</pre>
* \defgroup vTaskDelayUntil vTaskDelayUntil
* \ingroup TaskCtrl
*/
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement ) PRIVILEGED_FUNCTION
两个入参:
pxPreviousWakeTime:初始化为当前的时间,后面便不用管了;
xTimeIncrement:任务的周期;
简单的 Demo 如下所示:
void vTaskB( void * pvParameters )
{
static portTickType xLastWakeTime;
const portTickType xFrequency = pdMS_TO_TICKS(500); // 使用当前时间初始化变量xLastWakeTime ,注意这和vTaskDelay()函数不同
xLastWakeTime = xTaskGetTickCount(); for( ;; )
{
/* 调用系统延时函数,周期性阻塞500ms */
vTaskDelayUntil( &xLastWakeTime,xFrequency ); // ...
// 这里为任务主体代码,周期性执行.注意这和vTaskDelay()函数也不同
// ... }
}
1.2.2、Implement
它的实现如下所示:
#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();
{
/* Minor optimisation. The tick count cannot change in this
block. */
const TickType_t xConstTickCount = xTickCount; /* Generate the tick time at which the task wants to wake. */
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement; if( xConstTickCount < *pxPreviousWakeTime )
{
/* 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 ) )
{
xShouldDelay = pdTRUE;
}
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 ) )
{
xShouldDelay = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
} /* Update the wake time ready for the next call. */
*pxPreviousWakeTime = xTimeToWake; if( xShouldDelay != pdFALSE )
{
traceTASK_DELAY_UNTIL( xTimeToWake ); /* prvAddCurrentTaskToDelayedList() needs the block time, not
the time to wake, so subtract the current tick count. */
prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
xAlreadyYielded = xTaskResumeAll(); /* 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 */
也是先挂起任务,最后恢复;
与 xTaskDelay 不同,它定义了几个变量:
*pxPreviousWakeTime 代表上一次解除阻塞执行任务的时间;
xConstTickCount 是当前的时间;
xTimeToWake 是下一次唤醒的时间;
首先还是判断 Tick 溢出的场景;
可以看到,在最后调用:
prvAddCurrentTaskToDelayedList
的时候,它每次都是动态的去调整 Delay 的时间:xTimeToWake - xConstTickCount,尽量做到任务执行开始的时间保持一致;
FreeRTOS --(10)任务管理之任务延时的更多相关文章
- (41)freeRTOS之任务管理
1. 简介: 在 FreeRTOS 中没有线程和进程的区别,只有一个被翻译成任务的程序,相当于进程的概念,拥有独立的栈空间. 对于实时性,可以分为 软实时.硬实时:桌面电脑的输入处理可以看做是软实时, ...
- mariadb 10.2.3支持延时复制
在mysql 5.6开始就支持延时复制,这在一些需要维护大量非标准化系统或者运维技术水平较低的公司和开发人员众多的项目组这是一个非常有价值的特性,可以说误操作的概率跟一个城市车祸概率的水平差不多了,我 ...
- FreeRTOS --(11)任务管理之系统节拍
转载自 https://blog.csdn.net/zhoutaopower/article/details/107146764 前面有了创建任务.启动调度器.任务控制,接下来便开始分析一个 Tick ...
- 从单片机到操作系统⑦——深入了解FreeRTOS的延时机制
>没研究过操作系统的源码都不算学过操作系统 # FreeRTOS 时间管理 时间管理包括两个方面:系统节拍以及任务延时管理. ## 系统节拍: 在前面的文章也讲得很多,想要系统正常运行,那么时钟 ...
- [FreeRTOS]FreeRTOS使用
转自:https://blog.csdn.net/zhzht19861011/article/details/49819109 FreeRTOS系列第1篇---为什么选择FreeRTOS? FreeR ...
- STM32普通定时器实现延时函数
/* SystemFrequency / 1000 1ms中断一次 * SystemFrequency / 100000 10us中断一次 * SystemFrequency / 1000000 1u ...
- Redis学习笔记之延时队列
目录 一.业务场景 二.Redis延时队列 一.业务场景 所谓延时队列就是延时的消息队列,下面说一下一些业务场景比较好理解 1.1 实践场景 订单支付失败,每隔一段时间提醒用户 用户并发量的情况,可以 ...
- mongodb添加延时节点
1. 简介 延时节点是主节点过去某个时间点的“数据快照”,通常用来做数据备份,如果主节点有误操作而删除了数据,可以通过延时节点来恢复数据.例如,当前时间是10:00,并且延时节点设置1个小时 ...
- 《linux设备驱动开发详解》笔记——10中断与时钟
10.1 中断与定时器 中断一般有如下类型: 内部中断和外部中断:内部中断来自CPU,例如软件中断指令.溢出.除0错误等:外部中断有外部设备触发 可屏蔽中断和不可屏蔽中断 向量中断和非向量中断,ARM ...
随机推荐
- 请解释Spring Bean的生命周期?
首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy: Spring上下文中的Bean生命周期也类似,如下: (1)实例化Bean: 对于BeanFac ...
- java-jsp-learnning
简介:JSP(全称Java Server Pages) 一种动态网页开发技术.它使用JSP标签在HTML网页中插入Java代码.标签通常以<%开头以%>结束. JSP是一种Java ser ...
- Python - 文档格式转换(CSV与JSON)
- Linux内核, 编译一个自己的内核
本文,我们将一步一步地介绍如何从源代码编译和安装一个Linux内核.需要注意的是本指导基于Ubuntu 20.04版本编译安装,其它发行版可能会有差异. 在前面文章中我们反复提到过Linux内核, ...
- IT架构和架构类型
What is IT Architecture & Types of Architectures | ITARCH.INFO What is IT Architecture & Typ ...
- react、react-router、redux 也许是最佳小实践1
小前言 这是一个小小的有关react的小例子,希望通过一个小例子,可以让新手更好的了解到react.react-router4.0.redux的集中使用方法. 这是基于create-react-app ...
- Python:爬取全国各省疫情数据并在地图显示
代码: import requests import pymysql import json from pyecharts import options as opts from pyecharts. ...
- 界面跳转+信息传递+AS中如何将ADV转移到其他盘中
今日所学:界面跳转 信息传递 遇到的问题: 昨天遇到不能新建java类,在网上百度了很多,大多原因是没有新建java类的模板,但是我有,换了一个新的新建的方式后,发现虽然能建立了,但在测试时还是不能页 ...
- 谈谈我认识的js原型
众所周知,JavaScript中是没有传统类的概念的,js通过原型链的方式实现继承.原型是js学习中的一大重点知识,在ES6出来之前,因为js不像php.java一样拥有类的写法,所以继承方式也就不像 ...
- 使用mockjs模拟后端返回的json数据;
前后端分离开发中最重要的一部就是前后端联调,很多时候后端进度是跟不上前端的,所以需要前端模拟一些数据进行调试,这样前端的进度就可以加快了.后端的小哥哥别打我: 使用mockjs可以很方便的模拟出想要的 ...