在《刘火良FreeRTOS内核实现与应用学习之6------多优先级》的基础上:关键是添加了全局变量:xNextTaskUnblockTime ,与延时列表(xDelayedTaskList1、xDelayedTaskList2)来高效率的实现延时。
以前需要在扫描就绪列表中所有任务的xTicksToDelay值,进行任务就绪与否的操作;现在修改之后只需根据xNextTaskUnblockTime 值判断延时列表中的任务是否就绪,进行相应的操作;
重要数据结构及数据
0. 全局变量 xNextTaskUnblockTime
位置:在task.c文件中定义;静态变量;
static volatile TickType_t xNextTaskUnblockTime = ( TickType_t ) 0U;
1. 全局变量 任务延时列表
位置:在task.c文件中定义;静态变量;
static List_t xDelayedTaskList1; //xTickCount没有溢出时,使用一个列表
static List_t xDelayedTaskList2; //xTickCount 溢出时,使用另外一个列表
static List_t * volatile pxDelayedTaskList; //xTickCount没有溢出时,使用一个列表
static List_t * volatile pxOverflowDelayedTaskList; //xTickCount 溢出时,使用另外一个列表
a. 列表中的每一个节点代表了正在延时的任务;
b. 且节点按照延时时间大小做升序排列;
c. 当时基中断(SysTick中断)来临时,就用系统时基计数器的值xTickCount 与xNextTaskUnblockTime 进行比较:相等则任务就绪;否则,更新系统时基计数器xTickCount,任务切换;
有关任务操作重要函数
1. 任务延时列表初始化
/* 初始化任务相关的列表 */
void prvInitialiseTaskLists( void )
{
UBaseType_t uxPriority;
/* 初始化就绪列表 */
for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
vListInitialise( &xDelayedTaskList1 );
vListInitialise( &xDelayedTaskList2 );pxDelayedTaskList = &xDelayedTaskList1;
pxOverflowDelayedTaskList = &xDelayedTaskList2;}
2. 修改延时函数
void vTaskDelay( const TickType_t xTicksToDelay )
{
TCB_t *pxTCB = NULL;
/* 获取当前任务的TCB */
pxTCB = pxCurrentTCB;
/* 设置延时时间 */
//pxTCB->xTicksToDelay = xTicksToDelay;
/* 将任务插入到延时列表 */
prvAddCurrentTaskToDelayedList( xTicksToDelay );/* 任务切换 */
taskYIELD();
}
实现将任务插入延时列表;
在 prvAddCurrentTaskToDelayedList函数中实现
a. 把当前任务从就绪列表中移除;
/* 将任务从就绪列表中移除 */
if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* 将任务在优先级位图中对应的位清除 */
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
}
b. 计算xNextTaskUnblockTime 值:
/* 计算延时到期时,系统时基计数器xTickCount的值是多少 */
xTimeToWake= xConstTickCount + xTicksToWait;
c. 把延时任务插入延时列表(xDelayedTaskList1 或者 xDelayedTaskList2)中
i. 由于《节点按照延时时间大小做升序排列》,在插入前需要设置节点的xItemValue值:用于帮助节点做顺序排列;
/* 将延时到期的值设置为节点的排序值 */
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
ii. 根据刚才计算的 xTimeToWake是否溢出,决定把延时任务是放到延时列表(非溢出xDelayedTaskList1 或者 溢出xDelayedTaskList2)中;
/* 溢出 */
if( xTimeToWake < xConstTickCount )
{
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else /* 没有溢出 */
{
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
/* 更新下一个任务解锁时刻变量xNextTaskUnblockTime的值 */
if( xTimeToWake < xNextTaskUnblockTime )
{
xNextTaskUnblockTime = xTimeToWake;
}
}
iii. 更新xNextTaskUnblockTime值:
/* 更新下一个任务解锁时刻变量xNextTaskUnblockTime的值 */
if( xTimeToWake < xNextTaskUnblockTime )
{
xNextTaskUnblockTime = xTimeToWake;
}
重要函数 :prvAddCurrentTaskToDelayedList
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait )
{
TickType_t xTimeToWake;
/* 获取系统时基计数器xTickCount的值 */
const TickType_t xConstTickCount = xTickCount;
/* 将任务从就绪列表中移除 */
if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* 将任务在优先级位图中对应的位清除 */
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
}
/* 计算延时到期时,系统时基计数器xTickCount的值是多少 */
xTimeToWake = xConstTickCount + xTicksToWait;
/* 将延时到期的值设置为节点的排序值 */
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
/* 溢出 */
if( xTimeToWake < xConstTickCount )
{
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else /* 没有溢出 */
{
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
/* 更新下一个任务解锁时刻变量xNextTaskUnblockTime的值 */
if( xTimeToWake < xNextTaskUnblockTime )
{
xNextTaskUnblockTime = xTimeToWake;
}
}
}
3. 修改xTaskIncrementTick函数
实现如下功能:
a. 更新xTickCount 值;为何不直接加一?
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
b. 如果 xConstTickCount溢出,则切换延时列表;
/* 如果xConstTickCount溢出,则切换延时列表 */
if( xConstTickCount == ( TickType_t ) 0U )
{
taskSWITCH_DELAYED_LISTS();
}
重要宏代码 taskSWITCH_DELAYED_LISTS
/*
* 当系统时基计数器溢出的时候,延时列表pxDelayedTaskList 和
* pxOverflowDelayedTaskList要互相切换
*/
#define taskSWITCH_DELAYED_LISTS()\
{\
List_t *pxTemp;\
pxTemp = pxDelayedTaskList;\
pxDelayedTaskList = pxOverflowDelayedTaskList;\
pxOverflowDelayedTaskList = pxTemp;\
xNumOfOverflows++;\
prvResetNextTaskUnblockTime();\
}
当前的延时列表指针切换到溢出延时列表中;
c. 最近的延时任务延时到期:
直接通过比较xConstTickCount与xNextTaskUnblockTime,大于则进去看谁到期了,无需遍历所有的;
/* 最近的延时任务延时到期 */
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ;; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
/* 延时列表为空,设置xNextTaskUnblockTime为可能的最大值 */
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
else /* 延时列表不为空 */
{
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
/* 直到将延时列表中所有延时到期的任务移除才跳出for循环 */
if( xConstTickCount < xItemValue )
{
xNextTaskUnblockTime = xItemValue;
break;
}
/* 将任务从延时列表移除,消除等待状态 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
/* 将解除等待的任务添加到就绪列表 */
prvAddTaskToReadyList( pxTCB );
}
}
}/* xConstTickCount >= xNextTaskUnblockTime */
代码框架为死循环<用于部分遍历延时到期任务>,设置条件跳出;
i. 根据列表中的节点计数器判断,延时列表是否为空,空则跳出;
ii. 编列列表中的任务节点的xItemValue(节点此值是升序的),若xConstTickCount < xItemValue: 表示此任务延时未到,跳出死循环;
若xConstTickCount > xItemValue: 表示此任务延时到,进行如下操作:
- 从当前延时任务从延时列表中移除;
- 将当前延时任务加到就绪列表中;
d. 任务切换;
xTaskIncrementTick函数由时基函数调用:
时基函数SysTick中断服务函数:xPortSysTickHandler
位置:在port.c文件中定义;
/*
*************************************************************************
* SysTick中断服务函数
*************************************************************************
*/
void xPortSysTickHandler( void )
{
/* 关中断 */
vPortRaiseBASEPRI();
/* 更新系统时基 */
xTaskIncrementTick();
/* 开中断 */
vPortClearBASEPRIFromISR();
}