FreeRTOS专题六:支持多优先级

在 FreeRTOS 中,数字优先级越小,逻辑优先级也越小,这与RT-Thread 和 μC/OS刚好相反。

就绪列表 pxReadyTasksLists[ configMAX_PRIORITIES ]是一个数组,数组里面存的是就绪任务的 TCB(准确来说是 TCB 里面的 xStateListItem 节点),数组的下标对应任务的优先级,优先级越低对应的数组下标越小。空闲任务的优先级最低,对应的是下标为 0 的链表。空闲任务自系统启动后会一直就绪,因为系统至少得保证有一个任务可以运行。任务在创建的时候,会根据任务的优先级将任务插入到就绪列表不同的位置,相同优先级的任务插入到就绪列表里面的同一条链表中。

查找最高优先级就绪任务

pxCurrenTCB 是一个全局的 TCB 指针,用于指向优先级最高的就绪任务的 TCB,即当前正在运行的 TCB。那么我们要想让任务支持优先级,即只要解决在任务切换(taskYIELD)的时候,让 pxCurrenTCB 指向最高优先级的就绪任务的 TCB 就可以。那问题的关键就是:如果找到最高优先级的就绪任务的 TCB。FreeRTOS 提供了两套方法,一套是通用的,一套是根据特定的处理器优化过的,接下来我们重点讲解下这两个方法。

1 通用的方法:

首先是这个宏taskSELECT_HIGHEST_PRIORITY_TASK(),用于更新最高优先级变量uxTopReadyPriority

/* uxTopReadyPriority 存的是就绪任务的最高优先级 */
	// taskRECORD_READY_PRIORITY这个宏的作用,就是要更新一下最高优先级变量uxTopReadyPriority
	// 传入参数为uxPriority,实际上,应该这样调用taskRECORD_READY_PRIORITY( TCB->uxPriority )
	#define taskRECORD_READY_PRIORITY( uxPriority )														\
	{																									\
		if( ( uxPriority ) > uxTopReadyPriority )														\
		{																								\
			uxTopReadyPriority = ( uxPriority );														\
		}																								\
	} /* taskRECORD_READY_PRIORITY */

接下来这个宏,taskSELECT_HIGHEST_PRIORITY_TASK(),用于选择最高优先级的任务(从最高优先级开始查找非空列表),并传入到pxCurrentTCB控制块,最后更新uxTopReadyPriority变量。

// 选择最高优先级任务
	#define taskSELECT_HIGHEST_PRIORITY_TASK()															\
	{																									\
		/* 先获得当前的最高优先级,然后从最高优先级往下开始查找非空列表	*/			\
		UBaseType_t uxTopPriority = uxTopReadyPriority;														\
																										\
		/* 寻找包含就绪任务的最高优先级的队列 */                                                          \
		while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )							\
		{																								\
			--uxTopPriority;																			\
		}																								\
																										\
		/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */							            \
		/* 把查找到的当前最高优先级任务传给pxCurrentTCB控制块,再更新一下uxTopReadyPriority */		\
		listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );			\
		/* 更新uxTopReadyPriority */                                                                    \
		uxTopReadyPriority = uxTopPriority;																\
	} /* taskSELECT_HIGHEST_PRIORITY_TASK */

以上是通用方法。可以看到,这里,我们的任务优先级没有限制,设置为多大都可以(任务优先级为unsigned long类型)。

通用方式的优点:

  1. 所有平台的移植文件都可以配置为 0,因为这是通用方式。
  2. 纯 C 编写,比专用方式效率低。
  3. 可用的优先级数量不限制。

2 优化方法(用内核中的自带指令来操作):

优化方法的特点:

  1. 部分平台支持。
  2. 这些平台架构有专用的汇编指令,比如 CLZ(Count Leading Zeros)指令,通过这些指令可以加速算法执行速度。
  3. 比通用方式高效。
  4. 有最大优先级数限制,通常限制为 32 个(因为这个变量为32位,所以最大32个任务优先级)。

接下来看一下优化方法的宏定义:

这个宏,根据传入的任务优先级,把uxTopReadyPriority变量的对应位号置1,其他位号不改变

// 这个宏,根据传入的任务优先级,把uxTopReadyPriority变量的对应位号置1,其他位号不改变
	#define taskRECORD_READY_PRIORITY( uxPriority )	portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )
	// 这个宏的展开式如下,很好理解
	/* #define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities )    \
         ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) ) */

同理,还可以把uxTopReadyPriority对应的位号清除,其他位不变

#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities )     \
    ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )

我们要记住这两个宏:

  1. RECORD_READY就是把uxTopReadyPriority对应的位号置1;
  2. RESET_READY就是把uxTopReadyPriority对应的位号清0;

然后继续看优化方法中的其他宏定义:

// 选择最高优先级任务,并传入到pxCurrentTCB
	#define taskSELECT_HIGHEST_PRIORITY_TASK()														    \
	{																								    \
	UBaseType_t uxTopPriority;																		    \
																									    \
		/* 寻找最高优先级 */								                            \
		portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );								    \
		/* 上面这个宏portGET_HIGHEST_PRIORITY,展开如下													\
		#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) 				\
				uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )		\
			这就是利用CM3内核指令的快速算法							*/						\
		/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */                                       \
		listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );		    \
	} /* taskSELECT_HIGHEST_PRIORITY_TASK() */

下面这个宏,用于清除掉传入参数的优先级(传入的位清0)

#define taskRESET_READY_PRIORITY( uxPriority )											            \
    {																							        \
            portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) );					        \
    }

修改代码,支持多优先级

1 修改任务控制块,增加与优先级相关的成员:

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;

2 修改创建任务的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 )         /* 任务控制块 */
{
	TCB_t *pxNewTCB;
	TaskHandle_t xReturn;

	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;
	}

	return xReturn;
}

3 修改初始化一个新任务的prvInitialiseNewTask()函数  

截取的部分代码(增加了初始化优先级):

/* 任务名字的长度不能超过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;
	}

4 新增把任务添加到就绪列表的prvAddNewTaskToReadyList()函数,这个函数会自动的把任务加载到就绪列表,在创建任务的函数内调用,就不需要我们再手动的把任务添加到列表内了。

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();
}

之前,我们是先创建任务,再手动将任务插入到就绪列表,需要两个语句。

现在就不需要了,在创建任务时,会根据优先级自动插入到就绪列表。

5 修改开始任务调度器的函数 vTaskStartScheduler()

截取的部分代码,新增了空闲任务的优先级(定义为0),然后删除了添加任务到就绪列表的语句(因为新的创建任务函数,会自动的根据优先级插入到就绪列表),删除了手动指定第一个运行任务的语句,因为现在回自动获取最高优先级的任务去执行。

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;
    
    /* 启动调度器 */
    if( xPortStartScheduler() != pdFALSE )
    {
        /* 调度器启动成功,则不会返回,即不会来到这里 */
    }

6 修改vTaskDelay()函数

根据优先级将优先级位图表 uxTopReadyPriority 中对应的位清零,由函数 taskRESET_READY_PRIORITY()来实现。这样,在扫描最高优先级时,就可以避免扫描到这个函数。

void vTaskDelay( const TickType_t xTicksToDelay )
{
    TCB_t *pxTCB = NULL;
    
    /* 获取当前任务的TCB */
    pxTCB = pxCurrentTCB;
    
    /* 设置延时时间 */
    pxTCB->xTicksToDelay = xTicksToDelay;
    
    /* 把优先级对应的位清零 */
    /* 这样的话,任务切换就会切到下一个任务去执行
		实际上,这里修改的是uxTopReadyPriority变量的相应位清0,而在任务控制块内,还保留着任务优先级
		这样的话,每执行一个程序,都会把优先级清0,我们系统的运行状态就是
		从最高优先级开始,把所有的任务都执行(然后所有任务的优先级都为0了),此时开始执行空闲函数
		在时基函数内,还是不断地递减。一旦某个任务控制块的延时时间到了,就恢复该任务优先级,然后任务切换
		从而能执行到这个任务,而任务一旦执行,就又会在延时函数内清零,这样可以实现多个任务的调度 */
		// 所以,延时函数是必不可少的
    taskRESET_READY_PRIORITY( pxTCB->uxPriority );
    
    /* 任务切换 */
    taskYIELD();
}

7 修改任务切换的函数  vTaskSwitchContext()函数

不再实行手动切换,而是寻找最高优先级的任务去执行。

/* 任务切换,即寻找优先级最高的就绪任务 */
void vTaskSwitchContext( void )
{
	/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */
    taskSELECT_HIGHEST_PRIORITY_TASK();
}

8 修改更新系统时基的函数(任务函数执行时,通过延时函数会清0优先级转去执行其他任务函数,而一旦这个任务的延时时间到了,又会重新开启这个任务):

void xTaskIncrementTick( void )
{
    TCB_t *pxTCB = NULL;
    BaseType_t i = 0;
    
    const TickType_t xConstTickCount = xTickCount + 1;
    xTickCount = xConstTickCount;

    
    /* 扫描就绪列表中所有线程的remaining_tick,如果不为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();
}

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值