转载自 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)任务管理之任务延时的更多相关文章

  1. (41)freeRTOS之任务管理

    1. 简介: 在 FreeRTOS 中没有线程和进程的区别,只有一个被翻译成任务的程序,相当于进程的概念,拥有独立的栈空间. 对于实时性,可以分为 软实时.硬实时:桌面电脑的输入处理可以看做是软实时, ...

  2. mariadb 10.2.3支持延时复制

    在mysql 5.6开始就支持延时复制,这在一些需要维护大量非标准化系统或者运维技术水平较低的公司和开发人员众多的项目组这是一个非常有价值的特性,可以说误操作的概率跟一个城市车祸概率的水平差不多了,我 ...

  3. FreeRTOS --(11)任务管理之系统节拍

    转载自 https://blog.csdn.net/zhoutaopower/article/details/107146764 前面有了创建任务.启动调度器.任务控制,接下来便开始分析一个 Tick ...

  4. 从单片机到操作系统⑦——深入了解FreeRTOS的延时机制

    >没研究过操作系统的源码都不算学过操作系统 # FreeRTOS 时间管理 时间管理包括两个方面:系统节拍以及任务延时管理. ## 系统节拍: 在前面的文章也讲得很多,想要系统正常运行,那么时钟 ...

  5. [FreeRTOS]FreeRTOS使用

    转自:https://blog.csdn.net/zhzht19861011/article/details/49819109 FreeRTOS系列第1篇---为什么选择FreeRTOS? FreeR ...

  6. STM32普通定时器实现延时函数

    /* SystemFrequency / 1000 1ms中断一次 * SystemFrequency / 100000 10us中断一次 * SystemFrequency / 1000000 1u ...

  7. Redis学习笔记之延时队列

    目录 一.业务场景 二.Redis延时队列 一.业务场景 所谓延时队列就是延时的消息队列,下面说一下一些业务场景比较好理解 1.1 实践场景 订单支付失败,每隔一段时间提醒用户 用户并发量的情况,可以 ...

  8. mongodb添加延时节点

    1.      简介 延时节点是主节点过去某个时间点的“数据快照”,通常用来做数据备份,如果主节点有误操作而删除了数据,可以通过延时节点来恢复数据.例如,当前时间是10:00,并且延时节点设置1个小时 ...

  9. 《linux设备驱动开发详解》笔记——10中断与时钟

    10.1 中断与定时器 中断一般有如下类型: 内部中断和外部中断:内部中断来自CPU,例如软件中断指令.溢出.除0错误等:外部中断有外部设备触发 可屏蔽中断和不可屏蔽中断 向量中断和非向量中断,ARM ...

随机推荐

  1. BFC优化?

    块格式化上下文, 特性: 使 BFC 内部浮动元素不会到处乱跑: 和浮动元素产生边界.

  2. zookeeper 是如何保证事务的顺序一致性的?

    zookeeper 采用了全局递增的事务 Id 来标识,所有的 proposal(提议)都在被 提出的时候加上了 zxid,zxid 实际上是一个 64 位的数字,高 32 位是 epoch(时 期; ...

  3. Kafka 分区数可以增加或减少吗?为什么?

    我们可以使用 bin/kafka-topics.sh 命令对 Kafka 增加 Kafka 的分区数据,但是 Kafka 不支持减少分区数. Kafka 分区数据不支持减少是由很多原因的,比如减少的分 ...

  4. Redis的安装与启动(一)

    Redis是c语言开发的. 安装redis需要c语言的编译环境.如果没有gcc需要在线安装.yum install gcc-c++ 安装步骤: 第一步:redis的源码包上传到linux系统.--源码 ...

  5. 什么是 Busy spin?我们为什么要使用它?

    Busy spin 是一种在不释放 CPU 的基础上等待事件的技术.它经常用于避免丢 失 CPU 缓存中的数据(如果线程先暂停,之后在其他 CPU 上运行就会丢失). 所以,如果你的工作要求低延迟,并 ...

  6. mysql 的INNODB引擎和MYISAM引擎的区别、索引相关

    两个引擎都是使用B+tree 数据结构作为索引 不同点: 1.INNODB的主键必须要有,同时也是聚集索引,INNODB的数据文件本身就是索引文件:而MYISAM则是存储了数据的地址 2.INNODB ...

  7. Windows 10 安装 wordpress

    如何在Windows上安装wordpress. 安装前准备: 安装并配置好Apache httpd及PHP,见<Windows10安装PHP7+Apache 2.4>. 安装好MySQL, ...

  8. 如何利用WebSocket实现网页版聊天室

    花了将近一周的时间终于完成了利用WebSocket完成网页版聊天室这个小demo,期间还走过了一段"看似弯曲"的道路,但是我想其实也不算是弯路吧,因为你走过的路必将留下你的足迹.这 ...

  9. python-转换函数使用

    输入一个整数和进制,转换成十进制输出 输入格式: 在一行输入整数和进制 输出格式: 在一行十进制输出结果 输入样例: 在这里给出一组输入.例如: 45,8 输出样例: 在这里给出相应的输出.例如: 3 ...

  10. 将word文件转为excel文件

    有些word文件里的数据是有顺序或者规律,想转成表格的形式,下面就以我要转的word为例. 我的word文件是这样的 1.word转txt(文本文件) 文件--->另存为--->路径--- ...