当前位置: 首页 > news >正文

刘火良FreeRTOS内核实现与应用学习之6——多优先级

        在FreeRTOS中,数字优先级越小,逻辑优先级也越小;在任务创建时,会根据任务的优先级将任务插入就绪列表不同的位置。

        List_t pxReadyTasksLists[ configMAX_PRIORITIES ] 就绪列表是一个数组,数组中存储的是就绪任务TCB(任务控制块通过自己的钩子<TCB中的xStateListItem节点>挂上了List_t)。

        创建任务时,a. 会根据任务的优先级将任务插入就绪列表数组的不同的位置;

       b.  相同优先级的任务插入就绪列表数组中的同一条链表中。

        要想任务支持优先级,即只要实现在任务切换(taskYIELD)时,让pxCurrentTCB指向最高优先级的就绪任务的TCB既可以。

重要数据

0.  全局TCB指针

/* 当前正在运行的任务的任务控制块指针,默认初始化为NULL */
TCB_t * volatile pxCurrentTCB = NULL;

要想让任务支持优先级,即只要实现任务切换(portYIELD)时,让pxCurrentTCB指向最高优先级的就绪任务的TCB即可。

1. 静态变量:uxTopReadyPriority

位置:在task.c文件中定义;静态变量;

static volatile UBaseType_t uxTopReadyPriority         = tskIDLE_PRIORITY;

表示创建的任务的最高优先级,默认初始化为0,即空闲任务的优先级。

数值越大,优先级越高;

2. 修改任务控制块:增加一变量

        增加了一个变量:UBaseType_t            uxPriority;

描述任务块的优先级。

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;

3. 全局任务数

static volatile UBaseType_t uxCurrentNumberOfTasks     = ( UBaseType_t ) 0U;

static UBaseType_t uxTaskNumber                     = ( UBaseType_t ) 0U;

此处有一个疑问:这个变量做什么用?

        查找当前最高优先级的就绪任务,给uxTopReadyPriority 赋值,给pxCurrentTCB赋值,有两种方法。

两种方法

        查找最高优先级的就绪任务有两种方法,具体由宏configUSE_PORT_OPTIMISED_TASK_SELECTION控制,定义为0选择通用方法,定义为1选择根据处理器优化的方法,该宏默认在portmacro.h文件中定义为1,即使用优化过的方法。

#ifndef configUSE_PORT_OPTIMISED_TASK_SELECTION
	#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
#endif

      该功能主要是通过四个宏来实现:

        taskRECORD_READY_PRIORITY( uxPriority )   :记录就绪最高优先级的就绪任务uxTopReadyPriority 赋值;

        taskSELECT_HIGHEST_PRIORITY_TASK():在任务列表中寻找就绪最高优先级的就绪任务,获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB;

        taskRESET_READY_PRIORITY( uxPriority )

        portRESET_READY_PRIORITY( uxPriority, uxTopReadyPriority )

通用方法

        在通用方法中实现了上面的宏1、宏2;宏3~4为空。

两个宏定义

taskRECORD_READY_PRIORITY( uxPriority )   

位置:在task.c文件中定义;

实现记录当前运行任务的优先级,直接得到优先级的序号,并更新到静态变量 uxTopReadyPriority 中;

	#define taskRECORD_READY_PRIORITY( uxPriority )														\
	{																									\
		if( ( uxPriority ) > uxTopReadyPriority )														\
		{																								\
			uxTopReadyPriority = ( uxPriority );														\
		}																								\
	} /* taskRECORD_READY_PRIORITY */
taskSELECT_HIGHEST_PRIORITY_TASK()

位置:在task.c文件中定义;

	#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 */

1)用于寻找优先级最高的就绪任务、更新静态变量 uxTopReadyPriority;调用宏:listLIST_IS_EMPTY( pxList )

/* 判断链表是否为空 */
#define listLIST_IS_EMPTY( pxList )	( ( BaseType_t ) ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) )

此处有一个疑问:链表中的变量计数器(uxNumberOfItems)用来做什么用?

/* 链表结构体定义 */
typedef struct xLIST
{
    UBaseType_t uxNumberOfItems;    /* 链表节点计数器 */
    ListItem_t *  pxIndex;            /* 链表节点索引指针 */
    MiniListItem_t xListEnd;        /* 链表最后一个节点 */
} List_t;
 

现在可以回答这个问题了,也就是这列链表中节点的个数,当任务的优先级一样的话,是插到就绪任务链表数组的同一位置的,所以同一优先级就绪任务需要计数:uxNumberOfItems
a. 将节点从链表中删除:

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove ) 函数会减1;

b. 将节点插入到链表的尾部 :void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem ) 函数中加1;

2)更新全局变量pxCurrentTCB;调用宏:listGET_OWNER_OF_NEXT_ENTRY(..)

/* 获取链表节点的OWNER,即TCB */
#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;											 \
}

/* 节点结构体定义 */
struct xLIST_ITEM
{
    TickType_t xItemValue;             /* 辅助值,用于帮助节点做顺序排列 */            
    struct xLIST_ITEM *  pxNext;       /* 指向链表下一个节点 */        
    struct xLIST_ITEM *  pxPrevious;   /* 指向链表前一个节点 */    
    void * pvOwner;                       /* 指向拥有该节点的内核对象,通常是TCB */
    void *  pvContainer;               /* 指向该节点所在的链表 */
};
typedef struct xLIST_ITEM ListItem_t;  /* 节点数据类型重定义 */

3)继续更新静态变量 uxTopReadyPriority;

优化方法

        Cortex-M 内核有一个计算前导零的指令:CLZ。用于计算一个变量从高位开始第一次出现1的位前面的0的个数。

        __clz( ( uxReadyPriorities ):

a. uxReadyPriorities 为32位,每一个位号对应一个任务的优先级,1就绪,反之为0;

b. 32个优先级的任务: ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )表示最高就绪任务的等级;

#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

两个宏定义

taskRECORD_READY_PRIORITY( uxPriority )

位置:在task.c文件中定义;

#define taskRECORD_READY_PRIORITY( uxPriority )	portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )

里头又涉及到两个宏:portRECORD_READY_PRIORITY   和 portRESET_READY_PRIORITY

位置:在portmacro.h文件中定义;

a. portRECORD_READY_PRIORITY 完成功能:任务就绪就把uxReadyPriorities相应的位置1;得到能够反应就绪的32位的变量uxReadyPriorities;

b.portRESET_READY_PRIORITY 功能相反;

#if configUSE_PORT_OPTIMISED_TASK_SELECTION == 1

	/* 根据优先级设置/清除优先级位图中相应的位 */
	#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
	#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )

	/*-----------------------------------------------------------*/

	#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

#endif /* taskRECORD_READY_PRIORITY */
taskSELECT_HIGHEST_PRIORITY_TASK()

位置:在portmacro.h文件中定义;

就是利用前面提到的计算前导零的指令:CLZ 实现,算出就绪最高优先级任务的的级别出来;

#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

从而实现宏:taskSELECT_HIGHEST_PRIORITY_TASK。

	#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() */

有关任务操作重要函数

1. 修改:TaskHandle_t xTaskCreateStatic(...)

1)内部增加了将任务添加到就绪列表的功能:prvAddNewTaskToReadyList;

2)在原本的prvInitialiseNewTask中增加了设置优先级的功能;

prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters,uxPriority, &xReturn, pxNewTCB);

原来的函数,初始化与任务相关的列表,如就绪列表;

static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )

是一个带参宏,位置:在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();
}

完成如下功能:

a. 初始化任务链表(若是第一次):prvInitialiseTaskLists;

b. 把任务加到就绪列表:prvAddTaskToReadyList;

 这个是通过前面的宏 (查找优先级)+ 列表插入函数实现(根据优先级将任务插入就绪列表pxReadyTasksLists[])

prvAddTaskToReadyList( pxNewTCB );

* 将任务添加到就绪列表 */                                    
#define prvAddTaskToReadyList( pxTCB )                                                                   \
    taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );                                                   \
    vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); 

完成
a. 任务控制块的钩子(过自己的钩子<TCB中的xStateListItem节点>挂上了List_t)。);

b. 任务控制块的容纳者是链表数组的某一个;

c.该链表拥有的任务(控制块)的数量加一;

2. vTaskStartScheduler()维持不变

完成如下功能:

1)创建空闲任务;

2)启动任务调度器xPortStartScheduler;

3.修改:void vTaskDelay( const TickType_t xTicksToDelay )

位置:在task.c文件中定义;

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

此处的修改注意,以前是从任务列表中移除:uxListRemove( &( pxTCB->xStateListItem ) );

现在是通过函数(宏):taskRESET_READY_PRIORITY( pxTCB->uxPriority ) ;实现复位变量uxReadyPriorities相对于的位;

然后是调用任务切换taskYIELD,触发中断:vTaskSwitchContext;

4. 修改:void vTaskSwitchContext( void )

任务切换,即寻找优先级最高的就绪任务;

利用宏自动寻找优先级最高的就绪任务的TCB,然后更新到全局变量任务控制块指针pxCurrentTCB中;

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

5. 修改:void xTaskIncrementTick( void )

位置:在task.c文件中定义;

由定时中断xPortSysTickHandler调用;完成如下功能:

a. 实现全局变量xTickCount 计数操作;

此处有一个疑问:xTickCount 的加1为何这么麻烦,通过xConstTickCount常量来实现啊

    const TickType_t xConstTickCount = xTickCount + 1;
    xTickCount = xConstTickCount;

可能的原因 这种写法可能是出于以下原因之一: 1. 调试方便:使用局部变量 xConstTickCount 可以方便地在调试时观察 xTickCount 自增后的值。 2. 代码风格:某些开发者可能倾向于通过局部变量来分离计算和赋值逻辑,以提高代码的可读性。 3. 历史遗留:这段代码可能是从更复杂的逻辑简化而来的,原本 xConstTickCount 可能有其他用途,但后来简化成了当前的形式。

b. 扫描所有的就绪列表的时间是否到了,到了记录:taskRECORD_READY_PRIORITY( pxTCB->uxPriority );

c. 扫描结束进行任务切换portYIELD,触发中断;

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 --;
		}
	}
    
    /* 任务切换 */
    portYIELD();
}

6. 任务切换函数 portYIELD() 与taskYIELD

        功能:设置 PendSV 的中断挂起位,产生上下文切换。

#define taskYIELD()                           portYIELD()

#define portYIELD()																\
{																				\
	/* 设置 PendSV 的中断挂起位,产生上下文切换 */								\
	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;								\
																				\
	/* Barriers are normally not required but do ensure the code is completely	\
	within the specified behaviour for the architecture. */						\
	__dsb( portSY_FULL_READ_WRITE );											\
	__isb( portSY_FULL_READ_WRITE );											\
}

主函数及其调试

重要文件

相关文章:

  • 蓝桥杯备考:模拟算法之排队接水
  • skynet.socket.limit 使用详解
  • 数据结构每日一题day5(顺序表)★★★★★
  • 为mariadb和mysql添加用户和修改密码的方法
  • 树莓派 —— 在树莓派4b板卡下编译FFmpeg源码,支持硬件编解码器(mmal或openMax硬编解码加速)
  • 清华大学第10讲:迈向未来的AI教学实验396页PPT 探索未来教育的无限可能|附PPT下载方法
  • 毕业设计:实现一个基于Python、Flask和OpenCV的人脸打卡Web系统(六)
  • 2025年最新自动化/控制保研夏令营预推免面试真题分享(东南大学苏州校区/华东理工/南航/天大)
  • 封装了一个支持多个分区的iOS自适应动态宽度layout
  • 探索MVC、MVP、MVVM和DDD架构在不同编程语言中的实现差异
  • 自然语言处理|人工智能如何革新作文批改:技术全解析
  • PyTorch 深度学习实战(27):扩散模型(Diffusion Models)与图像生成
  • LearnOpenGL(九)自定义转换类
  • 在 Ubuntu 上安装 Docker 的完整指南
  • 核心:一多开发项目搭建
  • 链表(1)
  • 浅谈Binder的个人理解
  • windows第十八章 菜单、工具栏、状态栏
  • 单元测试之Arrange-Act-Assert(简称AAA)
  • 【空间变换】欧拉角与四元数
  • 为何选择上海?两家外企提到营商环境、人才资源……
  • 前四个月人民币贷款增加10.06万亿元,4月末M2余额同比增长8%
  • 男子不满和睦家医院手术效果还遇到了“冒牌医生”?院方回应
  • 黄仕忠丨戏曲文献研究之回顾与展望
  • 共情场域与可持续发展——关于博物馆、美术馆运营的新思考
  • 支持企业增强战略敏捷更好发展,上海市领导密集走访外贸外资企业