FreeRtos内核实现其它章节:
目录
7.1.1 listGET_OWNER_OF_NEXT_ENTRY相同优先级任务流转函数,具体做了以下操作:(list.h中实现)
7.1.2 修改后的taskSELECT_HIGHEST_PRIORITY_TASK寻找最高优先级函数做了以下操作:
7.1.3 修改taskRESET_READY_PRIORITY函数(task.c中)
7.1.4 修改xTaskIncrementTick函数(task.c中实现)
这是freertos内核实现的最后一节, 如果理解了前面的内容,这节内容理解起来会非常容易,这节内容也比较少;
这里先说明时间片的概念:一个优先级的根节点下,可以挂载多个属于这个优先级的任务节点,且它们都享有相同的CPU运行时间。前面几节可以知道systick被配置为了1ms中断一次,systick中断触发的周期就是一个tick(tick就是Rtos中的任务运行时长的最小时间单位)。
7.支持时间片
首先在main函数中先创建3个任务:
int main(void)
{
/* 硬件初始化 */
/* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */
/* 创建任务 */
Task1_Handle = xTaskCreateStatic( (TaskFunction_t)Task1_Entry, /* 任务入口 */
(char *)"Task1", /* 任务名称,字符串形式 */
(uint32_t)TASK1_STACK_SIZE , /* 任务栈大小,单位为字 */
(void *) NULL, /* 任务形参 */
(UBaseType_t) 2, /* 任务优先级,数值越大,优先级越高 */
(StackType_t *)Task1Stack, /* 任务栈起始地址 */
(TCB_t *)&Task1TCB ); /* 任务控制块 */
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 ); /* 任务控制块 */
Task3_Handle = xTaskCreateStatic( (TaskFunction_t)Task3_Entry, /* 任务入口 */
(char *)"Task3", /* 任务名称,字符串形式 */
(uint32_t)TASK3_STACK_SIZE , /* 任务栈大小,单位为字 */
(void *) NULL, /* 任务形参 */
(UBaseType_t) 3, /* 任务优先级,数值越大,优先级越高 */
(StackType_t *)Task3Stack, /* 任务栈起始地址 */
(TCB_t *)&Task3TCB ); /* 任务控制块 */
portDISABLE_INTERRUPTS();
/* 启动调度器,开始多任务调度,启动成功则不返回 */
vTaskStartScheduler();
for(;;)
{
/* 系统启动成功不会到达这里 */
}
}
可以看到在创建的任务中task1 和task2都是优先级为2的任务,task3为优先级为3的任务,具体如下:
/* 软件延时 */
void delay (uint32_t count)
{
for(; count!=0; count--);
}
/* 任务1 */
void Task1_Entry( void *p_arg )
{
for( ;; )
{
flag1 = 1;
// vTaskDelay( 1 );
delay (100);
flag1 = 0;
delay (100);
// vTaskDelay( 1 );
}
}
/* 任务2 */
void Task2_Entry( void *p_arg )
{
for( ;; )
{
flag2 = 1;
// vTaskDelay( 1 );
delay (100);
flag2 = 0;
delay (100);
// vTaskDelay( 1 );
}
}
/* 任务3 */
void Task3_Entry( void *p_arg )
{
for( ;; )
{
flag3 = 1;
vTaskDelay( 1 );
flag3 = 0;
vTaskDelay( 1 );
}
}
7.1 实现时间片
//list.h中实现
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \
List_t * const pxConstList = ( pxList ); \
/* 节点索引指向链表第一个节点调整节点索引指针,指向下一个节点,
如果当前链表有N个节点,当第N次调用该函数时,pxInedex则指向第N个节点 */\
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
/* 当前链表为空 */ \
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
{ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
} \
/* 获取节点的OWNER,即TCB */ \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}
7.1.1 listGET_OWNER_OF_NEXT_ENTRY相同优先级任务流转函数,具体做了以下操作:(list.h中实现)
1、 将当前根节点的索引指针,往下移动一个节点位置;(这是最重要的操作,是循环调用相同优先级的关键,这样就能循环的调用);
2、若当前索引指针,指向的是根节点中的mini节点,那么将其指向链表中的第一个任务节点;(为了让索引指针在执行第一步操作时,指向的一定是一个任务);
3、传入的pxTCB形参一般是pxcurrent指针,所以现在当前任务块指针,指向了下一个任务的任务控制块;
现在我们完成了同优先级任务流转的函数实现,这个函数要加在合适的位置才管用,这里我们加在寻找最高优先级任务的地(在找到就绪优先级列表的同时,也实现了循环调用同优先级任务的操作),具体如下:
//task.c中实现
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority; \
/* 寻找包含就绪任务的最高优先级的队列 */ \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */ \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
7.1.2 修改后的taskSELECT_HIGHEST_PRIORITY_TASK寻找最高优先级函数做了以下操作:
1、寻找当前最大优先级并已就绪的任务列表;
2、将寻找到的那个优先级的任务列表,调用同优先级流转函数,以此保证同优先级的任务能享用到相同时常的时间片段;
7.1.3 修改taskRESET_READY_PRIORITY函数(task.c中)
相较于之前几节,这里优化了taskRESET_READY_PRIORITY清除优先级列表相应位函数,具体如下:
//task.c中定义
#define taskRESET_READY_PRIORITY( uxPriority ) \
{ \
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) \
{ \
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
} \
}
可以看到,相对于前几节,这里加了个判断,如果当前就绪列表下已经没有了任务,则不会调用重置其优先级的相应位函数,因为它所处的优先级位置已经是0了;(这样就防止了重复操作,节省了cpu资源)
7.1.4 修改xTaskIncrementTick函数(task.c中实现)
这个函数基本没变但为了和官方的实现方式一致,现在加入返回值,具体如下:
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
/* 如果xConstTickCount溢出,则切换延时列表 */
if( xConstTickCount == ( TickType_t ) 0U )
{
taskSWITCH_DELAYED_LISTS();
}
/* 最近的延时任务延时到期 */
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 );
#if ( configUSE_PREEMPTION == 1 )
{
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* configUSE_PREEMPTION */
}
}
}/* xConstTickCount >= xNextTaskUnblockTime */
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) )
> ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
return xSwitchRequired;
/* 任务切换 */
//portYIELD();
}
xTaskIncrementTick函数具体修改了如下内容:
1、新定义了一个局部变量xSwitchRequired(它为true则表示需要进行任务切换,为false则表示现在不需要进行任务切换);
2、configUSE_PREEMPTION宏默认为1(这个宏不用管它,因为我们要添加返回值就必须要加这段代码),这段代码的意思是若当前从延时列表中解除的任务,它的优先级大于当前任务,则表示要切换到这个优先级更高的任务(置xSwitchRequired 为ture);
3、configUSE_TIME_SLICING代表支持时间片默认为1,这里的代码是在将当前任务换为了当前最大优先级任务之后执行的,但当最近没有延时任务到期的时候(调用这个函数时已经有1个tick了),要判断一下当前优先级的列表是否下挂载有多个任务,若是则也要调用任务切换函数,置xSwitchRequired变量为true(因为相同优先级的任务都应该享有相同的时间片,所以这里要再次调用切换函数);
4、将任务切换操作portYIELD调换到了,systick的中断判断中;
7.1.5 修改systick中断函数
为了配合xTaskIncrementTick函数的返回值,需要修改systick中断,如下所示:
void xPortSysTickHandler( void )
{
/* 关中断 */
vPortRaiseBASEPRI();
{
//xTaskIncrementTick();
/* 更新系统时基 */
if( xTaskIncrementTick() != pdFALSE )
{
/* 任务切换,即触发PendSV */
//portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
taskYIELD();
}
}
/* 开中断 */
vPortClearBASEPRIFromISR();
}
可以看到必须要xTaskIncrementTick返回值为ture时,才能开始任务切换;(已配合返回值)
至此FreeRtos裁剪过的内核已经实现完毕(官方的源码也是在内核的基础上进行的拓展),相信现在实时操作系统各个概念及原理实现都已了然于心了,之后再去看应用开发的东西会上手的很快,基本不会有它为什么会这样做的疑问了。
7.2仿真
上面创建的三个任务,它们的标志位放到逻辑分析仪中图像如下:
可以看到任务1和任务2都享有相同的时间片段(不断切换0和1状态的是任务1/2正在执行的情况);
程序是由野火提供的例程:(这节程序在野火提供的程序上做了一些小改动)
链接:https://pan.baidu.com/s/1kBRUTlfbUKN0Ij7ZUUr-wg?pwd=1234
提取码:1234
本人为初学菜鸟,文章如有错误地方,感谢指正!!
参考:野火freertos内核实现与应用