freertos内核原理Day7(支持时间片)

FreeRtos内核实现其它章节:

                     1、链表

                     2、任务定义及切换

                     3、临界段保护

                     4、空闲任务与阻塞延时

                     5、支持多优先级

                     6、实现任务延时列表

目录

7.支持时间片

7.1 实现时间片

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中实现)

7.1.5 修改systick中断函数

7.2仿真


这是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的任务,具体如下:

为了方便在逻辑分析仪中地分辨出任务 1 和任务 2 使用的时间片大小 ,这两个任务中使用普通延时函数而不用阻塞延时函数,任务 3 的阻塞时间设置为 1 tick。任务 1 和任务 2 的任务主体所用的延时方式不是用的阻塞延时,这就意味着优先级为2的任务在创建时就会一直被就绪,优先级低于 2 的任务就会被饿死, 得不到执行,比如空闲任务,具体代码如下:
/* 软件延时 */
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 实现时间片

要实现时间片,就要使同一优先级下的任务都享有同样tick时间片。上面我们在main函数中创建了三个任务其中任务1和任务2的优先级是相同的都为2,任务3的优先级是3(比任务1/2高),现在让程序运行,则第一个执行的肯定是任务3(因为优先级最高),由于任务3采用阻塞方式延时,当任务3阻塞延时后,下一个运行的任务就是优先级为2的任务了,此时我们假定运行的是任务1,任务1会运行到直到下一刻的sysTick中断,之后程序又会运行到任务3,任务3再次进入阻塞时,要保证这次进入调度器后,运行的是任务2(也就是要保证相同优先级的任务是按顺序调用的,比如优先级为2的任务中第一次被调用的是任务1,那么第二次调用优先级为2的任务时,就必须要调用任务2了,如此循环,再下次调用优先级为2的任务时就又到任务1了
故现在要加一个循环调用相同优先级的任务的函数,下面将介绍这个函数listGET_OWNER_OF_NEXT_ENTRY函数,具体如下所示:
//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内核实现与应用

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值