本节目标
在本节之前,FreeRTOS 还没有支持多优先级,只支持两个任务互相切换,从本章开始,任务中我们开始加入优先级的功能。在 FreeRTOS 中,数字优先级越小,逻辑优先级也越小,这与隔壁的 RT-Thread 和 μC/OS 刚好相反。
怎样实现支持多优先级
就绪列表 pxReadyTasksLists[ configMAX_PRIORITIES ] 是一个数组,数组里面存的是就绪任务的TCB(准确来说是 TCB 里面的 xStateListItem 列表项),数组的下标对应任务的优先级,优先级越低对应的数组下标越小。空闲任务的优先级最低,对应的是下标为 0 的列表。
图就绪列表中有两个任务就绪 演示的是就绪列表中有两个任务就绪,优先级分别为 1 和 2,其中空闲任务没有画出来,空闲任务自系统启动后会一直就绪,因为系统至少得保证有一个任务可以运行。
任务在创建的时候,会根据任务的优先级将任务插入到就绪列表不同的位置。相同优先级的任务插入到就绪列表里面的同一条列表中,这就是我们下一节要讲解的支持时间片。
pxCurrenTCB 是一个全局的 TCB 指针,用于指向优先级最高的就绪任务的 TCB,即当前正在运行的 TCB。那么我们要想让任务支持优先级,即只要解决在任务切换(taskYIELD)的时候,让 pxCurrenTCB 指向最高优先级的就绪任务的 TCB 就可以,前面的章节我们是手动地让 pxCurrenTCB在任务 1、任务 2 和空闲任务中轮转,现在我们要改成 pxCurrenTCB 在任务切换的时候指向最高优先级的就绪任务的 TCB 即可,那问题的关键就是:如果找到最高优先级的就绪任务的 TCB。
FreeRTOS 提供了两套方法,一套是通用的,一套是根据特定的处理器优化过的,接下来我们了解下这两个方法。
查找最高优先级的就绪任务相关代码
寻找最高优先级的就绪任务相关代码在 task.c 中定义
/* ①查找最高优先级的就绪任务:通用方法 */
#if ( configUSE_PORT_OPTIMISED_TASK_SELECTION == 0 )
/* ②uxTopReadyPriority 存的是就绪任务的最高优先级 */
#define taskRECORD_READY_PRIORITY( uxPriority ) \
{ \
if( ( uxPriority ) > uxTopReadyPriority ) \
{ \
uxTopReadyPriority = ( uxPriority ); \
} \
} /* taskRECORD_READY_PRIORITY */
/*-----------------------------------------------------------*/
#define taskSELECT_HIGHEST_PRIORITY_TASK()/*③*/ \
{ \
UBaseType_t uxTopPriority = uxTopReadyPriority;/*④*/ \
\
/* ⑤寻找包含就绪任务的最高优先级的队列 */ \
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \
{ \
--uxTopPriority; \
} \
\
/* ⑥获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */ \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
/* ⑦更新uxTopReadyPriority */ \
uxTopReadyPriority = uxTopPriority; \
} /* taskSELECT_HIGHEST_PRIORITY_TASK */
/*-----------------------------------------------------------*/
/* 这两个宏定义只有在选择优化方法时才用,这里定义为空 */
#define taskRESET_READY_PRIORITY( uxPriority )
#define portRESET_READY_PRIORITY( uxPriority, uxTopReadyPriority )
/* ⑧查找最高优先级的就绪任务:根据处理器架构优化后的方法 */
#else /* configUSE_PORT_OPTIMISED_TASK_SELECTION */
#define taskRECORD_READY_PRIORITY( uxPriority ) portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )//⑨
/*-----------------------------------------------------------*/
#define taskSELECT_HIGHEST_PRIORITY_TASK() /*⑩ */ \
{ \
UBaseType_t uxTopPriority; \
\
/* (11)寻找最高优先级 */ \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */ \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); /*(12)*/ \
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
/*-----------------------------------------------------------*/
#if 0
#define taskRESET_READY_PRIORITY( uxPriority )/*注意*/ \
{ \
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) \
{ \
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
} \
}
#else
#define taskRESET_READY_PRIORITY( uxPriority )/*(13)*/ \
{ \
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
}
#endif
#endif /* configUSE_PORT_OPTIMISED_TASK_SELECTION */
①查找最高优先级的就绪任务有两种方法,具体由 configUSE_PORT_OPTIMISED_TASK_SELECTION 这个宏控制,定义为 0 选择通用方法,定义为 1 选择根据处理器优化的方法,该宏默认在 portmacro.h 中定义为 1,即使用优化过的方法,但是通用方法我们也了解下。
通用方法
taskRECORD_READY_PRIORITY()
②taskRECORD_READY_PRIORITY() 用于更新 uxTopReadyPriority 的值。uxTopReadyPriority 是一个在 task.c 中定义的静态变量,用于表示创建的任务的最高优先级,默认初始化为 0,即空闲任务的优先级。
/* 空闲任务优先级宏定义,在 task.h 中定义 */
#define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U )
/* 定义 uxTopReadyPriority ,在 task.c 中定义 */
staticvolatile UBaseType_t uxTopReadyPriority = tskIDLE_PRIORITY;
taskSELECT_HIGHEST_PRIORITY_TASK()
③taskSELECT_HIGHEST_PRIORITY_TASK() 用于寻找优先级最高的就绪任务,实质就是更新 uxTopReadyPriority 和 pxCurrentTCB 的值。
④将 uxTopReadyPriority 的值暂存到局部变量 uxTopPriority,接下来需要用到。
⑤从最高优先级对应的就绪列表数组下标开始寻找当前列表下是否有任务存在,如果没有,则 uxTopPriority 减一操作,继续寻找下一个优先级对应的列表中是否有任务存在,如果有则跳出 while 循环,表示找到了最高优先级的就绪任务。之所以可以采用从最高优先级往下搜索,是因为任务的优先级与就绪列表的下标是一一对应的,优先级越高,对应的就绪列表数组的下标越大。
⑥获取优先级最高的就绪任务的 TCB,然后更新到 pxCurrentTCB。
⑦更新 uxTopPriority 的值到 uxTopReadyPriority。
优化方法
⑧优化的方法,这得益于 Cortex-M 内核有一个计算前导零的指令CLZ,所谓前导零就是计算一个变量(Cortex-M 内核单片机的变量为 32 位)从高位开始第一次出现 1 的位的前面的零的个数。比如:一个 32 位的变量 uxTopReadyPriority,其位 0、位 24 和位 25 均置 1,其余位为 0,具体见。那么使用前导零指令 __CLZ(uxTopReadyPriority)可以很快的计算出 uxTopReadyPriority 的前导零的个数为 6。
如果 uxTopReadyPriority 的每个位号对应的是任务的优先级,任务就绪时,则将对应的位置 1,反之则清零。那么图就表示优先级 0、优先级 24 和优先级 25 这三个任务就绪,其中优先级为 25 的任务优先级最高。利用前导零计算指令可以很快计算出就绪任务中的最高优先级为:
( 31UL - (uint32_t ) __clz( ( uxReadyPriorities ) ) ) = ( 31UL - ( uint32_t ) 6 )=25。
taskRECORD_READY_PRIORITY()
⑨taskRECORD_READY_PRIORITY() 用于根据传进来的形参(通常形参就是任务的优先级)将变量 uxTopReadyPriority 的某个位置 1。uxTopReadyPriority是一个在 task.c 中定义的静态变量,默认初始化为 0。与通用方法中用来表示创建的任务的最高优先级不一样,它在优化方法中担任的是一个优先级位图表的角色,即该变量的每个位对应任务的优先级,如果任务就绪,则将对应的位置 1,反之清零。根据这个原理,只需要计算出 uxTopReadyPriority 的前导零个数就算找到了就绪任务的最高优先级。与taskRECORD_READY_PRIORITY() 作用相反的是taskRESET_READY_PRIORITY()。
taskRECORD_READY_PRIORITY()taskRESET_READY_PRIORITY()(portmacro.h 中定义)
/* 根据优先级设置/清除优先级位图中相应的位 */
#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )
taskRESET_READY_PRIORITY()
(13)taskRESET_READY_PRIORITY() 用于根据传进来的形参(通常形参就是任务的优先级)将变量 uxTopReadyPriority 的某个位清零。
(注意):实际上根据优先级调用 taskRESET_READY_PRIORITY() 函数复位uxTopReadyPriorit 变量中对应的位时,要先确保就绪列表中对应该优先级下的链表没有任务才行。但是我们当前实现的阻塞延时方案还是通过扫描就绪列表里面的 TCB 的延时变量 xTicksToDelay 来实现的,还没有单独实现延时列表(任务延时列表将在下一个节了解),所以任务非就绪时暂时不能将任务从就绪列表移除,而是仅仅通过将任务优先级在变量 uxTopReadyPriority 中对应的位清零。在下一章我们实现任务延时列表之后,任务非就绪时,不仅会将任务优先级在变量 uxTopReadyPriority 中对应的位清零,还会降任务从就绪列表删除。
taskSELECT_HIGHEST_PRIORITY_TASK()
(10)taskSELECT_HIGHEST_PRIORITY_TASK() 用于寻找优先级最高的就绪任务,实质就是更新 uxTopReadyPriority 和pxCurrentTCB 的值。
(11)根据 uxTopReadyPriority 的值,找到最高优先级,然后更新到uxTopPriority 这个局部变量中。
portGET_HIGHEST_PRIORITY() 具体的宏实现在 portmacro.h 中定义。
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
(12)根据 uxTopPriority 的值,从就绪列表中找到就绪的最高优先级的任务的 TCB,然后将 TCB 更新到pxCurrentTCB。
修改代码,支持多优先级
接下来我们在上一节的代码上,继续迭代修改,从而实现多优先级。
修改任务控制块
在任务控制块中增加与优先级相关的成员
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /* 栈顶 */
ListItem_t xStateListItem; /* 任务的列表项 */
StackType_t *pxStack; /* 任务栈起始地址 */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* 任务名称,字符串形式 */
TickType_t xTicksToDelay; /* 用于延时 */
UBaseType_t uxPriority;
} tskTCB;
typedef tskTCB TCB_t;
修改 xTaskCreateStatic() 函数
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, /* 任务入口 */
const char * const pcName, /* 任务名称,字符串形式 */
const uint32_t ulStackDepth, /* 任务栈大小,单位为字 */
void * const pvParameters, /* 任务形参 */
UBaseType_t uxPriority, /* ①任务优先级,数值越大,优先级越高 */
StackType_t * const puxStackBuffer, /* 任务栈起始地址 */
TCB_t * const pxTaskBuffer) /* 任务控制块指针 */
{
TaskHandle_t xReturn; //任务句柄用于指向任务的TCB
TCB_t *pxNewTCB; //初始化xNewTCB为TCB结构体
//任务栈起始地址和任务控制块的参数传入NewTCB
if( (pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ))
{
pxNewTCB = (TCB_t * ) pxTaskBuffer;
pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer;
/* ②创建新的任务 */
prvInitialiseNewTask( pxTaskCode, /* 任务入口 */
pcName, /* 任务名称,字符串形式 */
ulStackDepth, /* 任务栈大小,单位为字 */
pvParameters, /* 任务形参 */
uxPriority,
&xReturn, /* 任务句柄 */
pxNewTCB);
/* ③将任务添加到就绪列表 */
prvAddNewTaskToReadyList( pxNewTCB );
}
else
{
xReturn = NULL;
}
/* 返回任务句柄,如果任务创建成功,此时xReturn应该指向任务控制块 */
return xReturn;
}
①增加优先级形参,数值越大,优先级越高。
prvInitialiseNewTask() 函数
②:修改 prvInitialiseNewTask() 函数,增加优先级形参和优先级初始化相关代码。
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, /* 任务入口 */
const char * const pcName, /* 任务名称,字符串形式 */
const uint32_t ulStackDepth, /* 任务栈大小,单位为字 */
void * const pvParameters, /* 任务形参 */
UBaseType_t uxPriority, /* 任务优先级,数值越大,优先级越高 */
TaskHandle_t * const pxCreatedTask, /* 任务句柄 */
TCB_t *pxNewTCB ) /* 任务控制块指针 */
{
StackType_t *pxTopOfStack;//定义任务栈顶数据类型
UBaseType_t x; //定义一个UBaseType_t数据辅助值x,辅助x用在任务的名字的长度
/* 获取栈顶地址 */
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
/* 向下做8字节对齐 */
pxTopOfStack = ( StackType_t * )( ( ( uint32_t ) pxTopOfStack ) &(~( ( uint32_t ) 0x0007 ) ) );
/* 将任务的名字存储在TCB中 */
for( x = ( UBaseType_t ) 0; x < (UBaseType_t) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
if( pcName[ x ] == 0x00 )
{
break;
}
}
/* 任务名字的长度不能超过configMAX_TASK_NAME_LEN */
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] ='\0';
/* 初始化TCB中的xStateListItem列表项 */
vListInitialiseItem( &(pxNewTCB->xStateListItem ));
/* 设置xStateListItem列表项的拥有者 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/* 初始化优先级 */
if ( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
pxNewTCB->uxPriority = uxPriority;
/* 初始化任务栈 */
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
/* 让任务句柄指向任务控制块 */
if( ( void * ) pxCreatedTask != NULL )
{
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
}
prvAddNewTaskToReadyList() 函数
③新增将任务添加到就绪列表的函数prvAddNewTaskToReadyList(),该函数在 task.c 中实现。
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
/* 进入临界段 */
taskENTER_CRITICAL();
{
/* ①全局任务计时器加一操作 */
uxCurrentNumberOfTasks++;
/* ②如果pxCurrentTCB为空,则将pxCurrentTCB指向新创建的任务 */
if( pxCurrentTCB == NULL )
{
pxCurrentTCB = pxNewTCB;
/* ③如果是第一次创建任务,则需要初始化任务相关的列表 */
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
{
/* 初始化任务相关的列表 */
prvInitialiseTaskLists();
}
}
else /* ④如果pxCurrentTCB不为空,则根据任务的优先级将pxCurrentTCB指向最高优先级任务的TCB */
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB;
}
}
uxTaskNumber++;
/* ⑤将任务添加到就绪列表 */
prvAddTaskToReadyList( pxNewTCB );
}
/* 退出临界段 */
taskEXIT_CRITICAL();
}
①全局任务计时器 uxCurrentNumberOfTasks 加一操作。uxCurrentNumberOfTasks 是一个在 task.c 中定义的静态变量,默认初始化为 0。
②如果 pxCurrentTCB 为空,则将 pxCurrentTCB 指向新创建的任务。pxCurrentTCB 是一个在 task.c 定义的全局指针,用于指向当前正在运行或者即将要运行的任务的任务控制块,默认初始化为 NULL。
③如果是第一次创建任务,则需要调用函数 prvInitialiseTaskLists() 初始化任务相关的列表,目前只有就绪列表需要初始化,该函数在task.c 中定义。
/* 初始化任务相关的列表 */
void prvInitialiseTaskLists( void )
{
UBaseType_t uxPriority;
for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++)
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
}
④如果 pxCurrentTCB 不为空,表示当前已经有任务存在,则根据任务的优先级将pxCurrentTCB指向最高优先级任务的TCB。在创建任务时,始终让pxCurrentTCB指向最高优先级任务的 TCB。
⑤将任务添加到就绪列表。prvAddTaskToReadyList() 是一个带参宏,在 task.c 中定义。
#define prvAddTaskToReadyList( pxTCB ) \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); /*①*/ \
vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), /*②*/&( ( pxTCB )->xStateListItem ) ); \
①根据优先级将优先级位图表 uxTopReadyPriority 中对应的位置位。
②根据优先级将任务插入到就绪列表 pxReadyTasksLists[]。
修改 vTaskStartScheduler() 函数
void vTaskStartScheduler( void )
{
/*======================================创建空闲任务start==============================================*/
TCB_t *pxIdleTaskTCBBuffer = NULL; /* 用于指向空闲任务控制块 */
StackType_t *pxIdleTaskStackBuffer = NULL; /* 用于空闲任务栈起始地址 */
uint32_t ulIdleTaskStackSize;
/* 获取空闲任务的内存:任务栈和任务TCB */
vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer,
&pxIdleTaskStackBuffer,
&ulIdleTaskStackSize );
xIdleTaskHandle = xTaskCreateStatic( (TaskFunction_t)prvIdleTask, /* 任务入口 */
(char *)"IDLE", /* 任务名称,字符串形式 */
(uint32_t)ulIdleTaskStackSize , /* 任务栈大小,单位为字 */
(void *) NULL, /* 任务形参 */
(UBaseType_t) tskIDLE_PRIORITY, /* 任务优先级,数值越大,优先级越高 */
(StackType_t *)pxIdleTaskStackBuffer, /* 任务栈起始地址 */
(TCB_t *)pxIdleTaskTCBBuffer );
// /* ②将任务添加到就绪列表 */
// vListInsertEnd( &( pxReadyTasksLists[0] ), &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem ) );
///*======================================创建空闲任务end================================================*/
//
// /* ③手动指定第一个运行的任务 */
// pxCurrentTCB = &Task1TCB;
/* 初始化系统时基计数器 */
xTickCount = ( TickType_t ) 0U;
/* 启动调度器 */
if( xPortStartScheduler() != pdFALSE )
{
/* 调度器启动成功,则不会返回,即不会来到这里 */
}
}
①创建空闲任务时,优先级配置为tskIDLE_PRIORITY,该宏在task.h中定义,默认为 0,表示空闲任务的优先级为最低。
②刚刚我们已经修改了创建任务函数 xTaskCreateStatic(),在创建任务时,就已经将任务添加到了就绪列表,这里将注释掉。
③在刚刚修改的创建任务函数 xTaskCreateStatic() 中,增加了将任务添加到就绪列表的函数 prvAddNewTaskToReadyList(),这里将注释掉。
修改 vTaskDelay() 函数
vTaskDelay() 函数修改内容是添加了将任务从就绪列表移除的操作。
void vTaskDelay( const TickType_t xTicksToDelay )
{
TCB_t *pxTCB = NULL;
/* 获取当前任务的TCB */
pxTCB = pxCurrentTCB;
/* 设置延时时间 */
pxTCB->xTicksToDelay = xTicksToDelay;
/* 将任务从就绪列表移除 */
//uxListRemove( &( pxTCB->xStateListItem ) );
taskRESET_READY_PRIORITY( pxTCB->uxPriority );//注意
/* 任务切换 */
taskYIELD();
}
(注意):将任务从就绪列表移除本应该完成两个操作:1 个是将任务从就绪列表移除,由函数 uxListRemove() 来实现;另一个是根据优先级将优先级位图表uxTopReadyPriority 中对应的位清零,由函数 taskRESET_READY_PRIORITY() 来实现。但是鉴于我们目前的时基更新函数 xTaskIncrementTick 还是需要通过扫描就绪列表的任务来判断任务的延时时间是否到期,所以不能将任务从就绪列表移除。当我们在接下来的“任务延时列表的实现”章节中,会专门添加一个延时列表,到时延时的时候除了根据优先级将优先级位图表 uxTopReadyPriority 中对应的位清零外,还需要将任务从就绪列表移除。
修改 vTaskSwitchContext() 函数
在新的任务切换函数 vTaskSwitchContext() 中,不再是手动的让 pxCurrentTCB 指针在任务 1、任务 2 和空闲任务中切换,而是直接调用函数 taskSELECT_HIGHEST_PRIORITY_TASK() 寻找到优先级最高的就绪任务的 TCB,然后更新到 pxCurrentTCB。
/* 任务切换,即寻找优先级最高的就绪任务 */
void vTaskSwitchContext( void )
{
/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */
taskSELECT_HIGHEST_PRIORITY_TASK();
}
#else
void vTaskSwitchContext( void )
{
/* 如果当前线程是空闲线程,那么就去尝试执行线程1或者线程2,
看看他们的延时时间是否结束,如果线程的延时时间均没有到期,
那就返回继续执行空闲线程 */
if( pxCurrentTCB == &IdleTaskTCB )
{
if(Task1TCB.xTicksToDelay == 0)
{
pxCurrentTCB =&Task1TCB;
}
else if(Task2TCB.xTicksToDelay == 0)
{
pxCurrentTCB =&Task2TCB;
}
else
{
return; /* 线程延时均没有到期则返回,继续执行空闲线程 */
}
}
else
{
/*如果当前线程是线程1或者线程2的话,检查下另外一个线程,如果另外的线程不在延时中,就切换到该线程
否则,判断下当前线程是否应该进入延时状态,如果是的话,就切换到空闲线程。否则就不进行任何切换 */
if(pxCurrentTCB == &Task1TCB)
{
if(Task2TCB.xTicksToDelay == 0)
{
pxCurrentTCB =&Task2TCB;
}
else if(pxCurrentTCB->xTicksToDelay != 0)
{
pxCurrentTCB = &IdleTaskTCB;
}
else
{
return; /* 返回,不进行切换,因为两个线程都处于延时中 */
}
}
else if(pxCurrentTCB == &Task2TCB)
{
if(Task1TCB.xTicksToDelay == 0)
{
pxCurrentTCB =&Task1TCB;
}
else if(pxCurrentTCB->xTicksToDelay != 0)
{
pxCurrentTCB = &IdleTaskTCB;
}
else
{
return; /* 返回,不进行切换,因为两个线程都处于延时中 */
}
}
}
}
#endif
修改 xTaskIncrementTick() 函数
修改 xTaskIncrementTick() 函数,即在原来的基础上增加:当任务延时时间到,将任务就绪的代码。
void xTaskIncrementTick( void )
{
TCB_t *pxTCB = NULL;
BaseType_t i = 0;
/* 更新系统时基计数器xTickCount,xTickCount是一个在port.c中定义的全局变量 */
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
/* 扫描就绪列表中所有线程的xTicksToDelay,如果不为0,则减1 */
for(i=0; i<configMAX_PRIORITIES; i++)
{
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &pxReadyTasksLists[i] ) );
if(pxTCB->xTicksToDelay > 0)
{
pxTCB->xTicksToDelay --;
/* ①延时时间到,将任务就绪 */
if( pxTCB->xTicksToDelay ==0 )
{
taskRECORD_READY_PRIORITY( pxTCB->uxPriority );
}
}
}
/* 任务切换 */
portYIELD();
}
①(增加)延时时间到,将任务就绪。即根据优先级将优先级位图表uxTopReadyPriority 中对应的位置位。在刚刚修改的上下文切换函数 vTaskSwitchContext()中,就是通过优先级位图表 uxTopReadyPriority 来寻找就绪任务的最高优先级的。
main 函数
本节 main 函数与上一节基本一致,修改不大。
int main(void)
{
/* 硬件初始化 */
/* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */
/* 创建任务 */
Task1_Handle = xTaskCreateStatic( ( TaskFunction_t )Task1_Entry,
(char *)"Task1",
(uint32_t)TASK1_STACK_SIZE,
(void *)NULL,
(UBaseType_t) 1, //①
(StackType_t *)Task1Stack,
(TCB_t *)&Task1TCB);/
// /* ②将任务添加到就绪列表 */
// vListInsertEnd( &( pxReadyTasksLists[1]), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );
Task2_Handle = xTaskCreateStatic( ( TaskFunction_t )Task2_Entry,
(char *)"Task2",
(uint32_t)TASK2_STACK_SIZE,
(void *)NULL,
(UBaseType_t) 2,//③
(StackType_t *)Task2Stack,
(TCB_t *)&Task2TCB);
// /* ④将任务添加到就绪列表 */
// vListInsertEnd( &( pxReadyTasksLists[2]), &( ((TCB_t *)(&Task2TCB))->xStateListItem ) );
/* 启动调度器,开始多任务调度,启动成功则不返回 */
vTaskStartScheduler();
for(;;)
{
/*啥事不干*/
}
}
①和③设置任务的优先级,数字优先级越高,逻辑优先级越高。
②和④这部分代码删除,因为在任务创建函数 xTaskCreateStatic()中,已经调用函数 prvAddNewTaskToReadyList() 将任务插入到了就绪列表。
实验现象
进入软件调试,全速运行程序,从逻辑分析仪中可以看到两个任务的波形是完全同步,就好像CPU 在同时干两件事情。
参考资料:《FreeRTOS 内核实现与应用开发实战—基于RT1052》