首页 > 其他分享 >FreeRTOS简单内核实现6 优先级

FreeRTOS简单内核实现6 优先级

时间:2024-06-17 20:12:14浏览次数:28  
标签:优先级 FreeRTOS uxPriority void 链表 任务 内核 就绪

0、思考与回答

0.1、思考一

如何实现 RTOS 内核支持多优先级?

因为不支持优先级,所以所有的任务都插入了一个名为 pxReadyTasksLists 的就绪链表中,相当于所有任务的优先级都是一致的,那如果我们创建一个就绪链表数组,数组下标代表优先级,优先级为 x 的任务就插入到 pxReadyTasksLists[x] 中,这样通过一个就绪链表数组就实现了将不同优先级的任务放在不同的就绪链表中,方便在进行任务调度时支持任务优先级

1、就绪链表

1.1、创建

将原来的就绪链表修改为就绪链表数组

/* task.c */
// 就绪链表数组
List_t pxReadyTasksLists[configMAX_PRIORITIES];

configMAX_PRIORITIES 是一个表示 RTOS 内核支持的最大优先级的宏定义,值得提醒的是目前 RTOS 支持的最大优先级数量为 32 个(这与后面使用到的记录优先级的位图有关,具体内容会在后面遇到优先级位图时做介绍)

/* FreeRTOSConfig.h */
// 设置 RTOS 支持的最大优先级
#define configMAX_PRIORITIES                    5

1.2、初始化

修改就绪链表初始化函数,即遍历整个就绪链表数组然后依次对每个就绪链表进行初始化,具体如下所示

/* task.c */
// 就绪链表始化函数
void prvInitialiseTaskLists(void)
{
	UBaseType_t uxPriority;
	// 初始化就绪任务链表
	for(uxPriority = (UBaseType_t)0U;
	    uxPriority < (UBaseType_t)configMAX_PRIORITIES; uxPriority++)
	{
		vListInitialise(&(pxReadyTasksLists[uxPriority]));
	}
}

1.3、添加任务

1.3.1、prvAddNewTaskToReadyList( )

已完成的内核中添加任务到就绪链表是对每个任务手动调用 vListInsertEnd() 函数实现的,现在创建一个函数用于在任务创建后自动将其添加到就绪链表中,具体函数流程如下所示

  1. 当前系统中任务数量加一
  2. 如果第一次创建任务,就初始化任务相关的链表(就绪链表数组等)
  3. 如果不是第一次创建任务,就根据任务的优先级将 pxCurrentTCB 指向最高优先级任务的 TCB

注意:if(pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority) 判断 pxCurrentTCB 指向最高优先级任务的 TCB 时取了 = 号,也就意味着,如果系统中创建了两个相同优先级的任务,那启动调度器后第一个执行的任务将是最后创建的那个任务

任务控制块的 uxPriority 参数将在 "2.1、TCB" 小节中添加

/* task.c */
// 全局任务计数器
static volatile UBaseType_t uxCurrentNumberOfTasks = (UBaseType_t)0U;

// 添加任务到就绪链表中
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;
			}
		}
		
		// 将任务添加到就绪列表
		prvAddTaskToReadyList(pxNewTCB);
	}
	// 退出临界段
	taskEXIT_CRITICAL();
}

1.3.2、prvAddTaskToReadyList( )

是怎么把任务添加到就绪链表的?

首先将要添加任务的优先级记录在优先级位图中,然后通过 vListInsertEnd() 函数将任务插入到对应优先级的就绪链表中,具体如下所示

/* task.c */
// 32位的优先级位图,默认全 0 ,记录了所有存在的优先级
static volatile UBaseType_t uxTopReadyPriority = 0;

// 根据任务优先级置位优先级位图
#define taskRECORD_READY_PRIORITY(uxPriority)	portRECORD_READY_PRIORITY(uxPriority, uxTopReadyPriority)

// 根据任务优先级添加任务到对应的就绪链表
#define prvAddTaskToReadyList(pxTCB) \
	taskRECORD_READY_PRIORITY((pxTCB)->uxPriority); \
	vListInsertEnd(&(pxReadyTasksLists[(pxTCB)->uxPriority]), \
	               &((pxTCB)->xStateListItem)); \

什么是优先级位图?

优先级位图本质是一个 32 位的数,如果有对应的优先级任务,就将优先级位图这个变量的对应位标记为 1 (比如当前任务的优先级为 2 ,则将优先级位图的从右向左第二位置一)

为什么要使用优先级位图记录任务优先级?

方便快速找到当前系统中存在的最高优先级,通过计算优先级位图的前导零个数,然后让 31 减去前导零个数就可以很快找到最高优先级

/* protMacro.h */
#define portRECORD_READY_PRIORITY(uxPriority, uxReadyPriorities) (uxReadyPriorities) |= (1UL << (uxPriority))

1.4、寻找最高优先级任务

RTOS 支持任务优先级后,任务的调度策略就可以修改为始终让系统中处于就绪态的最高优先级的任务得到执行,因此我们需要寻找最高优先级任务,寻找到之后将 pxCurrentTCB 指向该任务,然后任务调度切换任务时就会切换到该最高优先级的任务

/* task.c */
// 找到就绪列表最高优先级的任务并更新到 pxCurrentTCB
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
	UBaseType_t uxTopPriority; \
	/* 寻找最高优先级 */ \
	portGET_HIGHEST_PRIORITY(uxTopPriority, uxTopReadyPriority); \
	/* 获取优先级最高的就绪任务的 TCB,然后更新到 pxCurrentTCB */ \
	listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, \
	                            &(pxReadyTasksLists[uxTopPriority])); \
}

获取系统中存在的最高优先级任务的原理正如 "1.3.2、prvAddTaskToReadyList( )" 小节中 ”为什么要使用优先级位图记录任务优先级?“ 问题所述内容

/* protMacro.h */
#define portGET_HIGHEST_PRIORITY(uxTopPriority, uxReadyPriorities) uxTopPriority = (31UL - (uint32_t) __clz((uxReadyPriorities)))

2、修改内核程序

2.1、TCB

在任务控制块中增加任务优先级参数

/* task.h */
typedef struct tskTaskControlBlock
{
	// 省略之前的结构体成员定义
    UBaseType_t           uxPriority;                           // 优先级
}tskTCB;

2.2、xTaskCreateStatic( )

修改静态创建任务函数,在参数列表中增加任务优先级参数,然后将创建好的任务直接自动添加到就绪链表中,不再需要额外手动将任务插入

/* task.c */
// 静态创建任务函数
#if (configSUPPORT_STATIC_ALLOCATION == 1)
TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode,
                            const char* const pcName,
                            const uint32_t ulStackDepth,
                            void* const pvParameters,         
                            UBaseType_t uxPriority,           // 优先级
                            StackType_t* const puxTaskBuffer,
                            TCB_t* const pxTaskBuffer)
{
	// 省略未改变的代码
	......
		// 真正的创建任务函数
		prvInitialiseNewTask(pxTaskCode,
							 pcName,
							 ulStackDepth,
							 pvParameters,
							 uxPriority,                      // 优先级
							 &xReturn,
							 pxNewTCB);
	
		// 创建完任务自动将任务添加到就绪链表
		prvAddNewTaskToReadyList(pxNewTCB);
	// 省略未改变的代码
	......
}
#endif

/* task.h */
// 函数声明
TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode,
                            const char* const pcName,
                            const uint32_t ulStackDepth,
                            void* const pvParameters,
                            UBaseType_t uxPriority,           // 优先级
                            StackType_t* const puxTaskBuffer,
                            TCB_t* const pxTaskBuffer);

2.3、prvInitialiseNewTask( )

由于增加了优先级参数,因此需要在真正的创建任务函数中增加对任务优先级初始化的部分,具体如下所示

/* task.c */
// 真正的创建任务函数																 
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)
{
	// 省略未改变的代码
	......
	// 初始化优先级
	if(uxPriority >= (UBaseType_t)configMAX_PRIORITIES)
	{
		uxPriority = (UBaseType_t)configMAX_PRIORITIES - (UBaseType_t)1U;
	}
	pxNewTCB->uxPriority = uxPriority;
	
	if((void*)pxCreatedTask != NULL)
	{
	    *pxCreatedTask = (TaskHandle_t)pxNewTCB;
	}
}

2.4、vTaskStartScheduler( )

由于在启动任务调度器函数中创建了空闲任务,因此还需要在创建空闲任务的参数列表中增加优先级参数,为了不抢占任何其他任务的运行,空闲任务的优先级应该保持为最低优先级,使用 taskIDLE_PRIORITY 宏定义表示,具体如下所示

/* task.c */
// 启动任务调度器
void vTaskStartScheduler(void)
{
    // 创建空闲任务
	TaskHandle_t xIdleTaskHandle = xTaskCreateStatic((TaskFunction_t)prvIdleTask,
                                      (char *)"IDLE",
                                      (uint32_t)confgiMINIMAL_STACK_SIZE,
                                      (void *)NULL,
                                      (UBaseType_t)taskIDLE_PRIORITY,
                                      (StackType_t *)IdleTasKStack,
                                      (TCB_t *)&IdleTaskTCB);
	
	if(xPortStartScheduler() != pdFALSE){}
}
/* task.h */
// 空闲任务优先级最低
#define taskIDLE_PRIORITY              ((UBaseType_t) 0U)

2.5、vTaskDelay( )

当一个任务调用阻塞延时函数时,可以将其优先级从优先级位图上清除掉,这样在寻找最高优先级任务时就不会找到阻塞状态的该任务

值得提醒的是,这种做法虽然可以简单的达到让进入阻塞状态的任务暂时脱离调度的效果,但是由于其仍然存在就绪链表中,并不是真正的从就绪链表中移除,因此在遍历就绪链表对就绪态的任务操作时会产生额外的操作

/* task.c */
// 阻塞延时函数
void vTaskDelay(const TickType_t xTicksToDelay)
{
	// 省略未修改程序
    ......
    // 将任务从优先级位图上清除,这样调度时就不会找到该任务
    taskRESET_READY_PRIORITY(pxTCB->uxPriority);
    // 主动产生任务调度,让出 MCU 
    taskYIELD();
}
/* task.c */
// 根据任务优先级清除优先级位图
#define taskRESET_READY_PRIORITY(uxPriority) \
{ \
	portRESET_READY_PRIORITY((uxPriority), (uxTopReadyPriority)); \
}

与置位优先级位图原理刚好相反,清除优先级位图则是根据任务的优先级将优先级位图上对应的位清零,具体如下所示

/* protMacro.h */
#define portRESET_READY_PRIORITY(uxPriority, uxReadyPriorities) (uxReadyPriorities) &= ~(1UL << (uxPriority))

2.6、vTaskSwitchContext( )

任务调度函数中始终选择系统中就绪状态的最高优先级任务

/* task.c */
// 任务调度函数
void vTaskSwitchContext(void)
{
	taskSELECT_HIGHEST_PRIORITY_TASK();
}

2.7、xTaskIncrementTick( )

更新任务阻塞延时参数,如果任务延时时间到期,将其任务优先级在优先级位图上恢复,然后产生任务调度切换任务

/* task.c */
// 更新任务延时参数
void xTaskIncrementTick(void)
{
	TCB_t *pxTCB = NULL;
	uint8_t i =0;
	uint8_t xSwitchRequired = pdFALSE;
	
	// 更新 xTickCount 系统时基计数器
	const TickType_t xConstTickCount = xTickCount + 1;
	xTickCount = xConstTickCount;
	
	// 扫描就绪列表中所有任务,如果延时时间不为 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--;
		}
		// 延时时间到,将任务就绪
		else 
		{
			taskRECORD_READY_PRIORITY(pxTCB->uxPriority);
			xSwitchRequired = pdTRUE;
		}
	}
	// 如果就绪链表中有任务从阻塞状态恢复就产生任务调度
	if(xSwitchRequired == pdTRUE){
		// 产生任务调度
		portYIELD();
	}
}

3、实验

3.1、测试

测试程序与 FreeRTOS简单内核实现5 阻塞延时 几乎一致,但是已经不需要我们手动将任务插入就绪链表中了,不过创建任务时需要额外指定任务的优先级参数,另外我们添加一个模拟任务一直运行的延时函数,具体如下所示

/* main.c */
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
/* USER CODE END Includes */

/* USER CODE BEGIN PV */
// 软件延时
void delay(uint32_t count)
{
	for(;count!=0;count--);
}

TaskHandle_t Task1_Handle;
#define TASK1_STACK_SIZE                    128
StackType_t Task1Stack[TASK1_STACK_SIZE];
TCB_t Task1TCB;
UBaseType_t Task1Priority = 1;

TaskHandle_t Task2_Handle;
#define TASK2_STACK_SIZE                    128
StackType_t Task2Stack[TASK2_STACK_SIZE];
TCB_t Task2TCB;
UBaseType_t Task2Priority = 2;

// 任务 1 入口函数
void Task1_Entry(void *parg)
{
	for(;;)
	{
		HAL_GPIO_TogglePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin);
		vTaskDelay(100);
	}
}
// 任务 2 入口函数
void Task2_Entry(void *parg)
{
	for(;;)
	{
		HAL_GPIO_TogglePin(ORANGE_LED_GPIO_Port, ORANGE_LED_Pin);
		// 模拟高优先级任务一直运行
		delay(10000000);
	}
}
/* USER CODE END PV */

/* USER CODE BEGIN 2 */
// 创建任务 1 和 2
Task1_Handle = xTaskCreateStatic((TaskFunction_t)Task1_Entry,
								 (char *)"Task1",
								 (uint32_t)TASK1_STACK_SIZE,
								 (void *)NULL,
								 (UBaseType_t)Task1Priority,
								 (StackType_t *)Task1Stack,
								 (TCB_t *)&Task1TCB);
														
Task2_Handle = xTaskCreateStatic((TaskFunction_t)Task2_Entry,
								 (char *)"Task2",
								 (uint32_t)TASK2_STACK_SIZE,
								 (void *) NULL,
								 (UBaseType_t)Task2Priority,
								 (StackType_t *)Task2Stack,
								 (TCB_t *)&Task2TCB );
// 启动任务调度器,永不返回
vTaskStartScheduler();
/* USER CODE END 2 */

使用逻辑分析仪捕获 GREEN_LED 和 ORANGE_LED 两个引脚的电平变化,具体如下图所示

可以发现因为我们使用软件延时模拟高优先级 Task2 任务一直运行,导致低优先级的 Task1 任务完全没时间运行,也即 Task1 被饿死了

将 Task1 的优先级修改为 3 ,重新编译烧录程序再次观察两个引脚的电平变化,具体如下图所示

可以发现 Task1 不再被饿死,通过上述测试可以证明目前的 RTOS 已经支持多任务优先级

3.2、待改进

当前 RTOS 简单内核已实现的功能有

  1. 静态方式创建任务
  2. 手动切换任务
  3. 临界段保护
  4. 任务阻塞延时
  5. 支持任务优先级

当前 RTOS 简单内核存在的缺点有

  1. 缺少阻塞链表
  2. 不支持时间片轮询

标签:优先级,FreeRTOS,uxPriority,void,链表,任务,内核,就绪
From: https://www.cnblogs.com/lc-guo/p/18250550

相关文章

  • 从Linux内核设计者的角度看 - 设备驱动的架构设计
    Linux中的设备驱动概念中的设备和驱动指的是啥?  直接说设备驱动其实是比较抽象的,举个例子就特别明了了,比如我们要控制1个led的亮灭,那么led就是设备,控制led运行的软件就是该设备的驱动。也就是说,这里的设备就是现实中的一个电子设备,设备驱动就是控制这个电子设备运行的软件程序......
  • FreeRTOS简单内核实现5 阻塞延时
    0、思考与回答0.1、思考一为什么FreeRTOS简单内核实现3任务管理文章中实现的RTOS内核不能看起来并行运行呢?Task1延时100ms之后执行taskYIELD()切换到Task2,Task2延时500ms之后执行taskYIELD()再次切换Task1,在延时期间两个任务均占用MCU,所以只能一个任务执行......
  • Linux 内核定时器实验
    Linux内核定时器实验内核时间管理简介Linux内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序、对于我们驱动编写者来说最常用的定时器。硬件定时器提供时钟源,时钟源的频率可以设置,设置好以后就周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频......
  • FreeRTOS简单内核实现4 临界段
    @目录0、思考与回答0.1、思考一0.2、思考二0.3、思考三1、关中断1.1、带返回值1.2、不带返回值2、开中断3、临界段4、应用0、思考与回答0.1、思考一为什么需要临界段?有时候我们需要部分代码一旦这开始执行,则不允许任何中断打断,这段代码称为临界段0.2、思考二如何实现临界段......
  • 内核参数kernel.shmall和kernel.shmmax
    在Linux系统中,内核参数kernel.shmall和kernel.shmmax与共享内存(SystemV共享内存)有关,它们分别定义了系统可以分配的共享内存段的最大页数和单个共享内存段的最大字节数。以下是一些关于这些参数的推荐设置:kernel.shmall:这个参数控制可以使用的共享内存的总页数。Linux共享内存......
  • 泰山派学习10--内核驱动模块
    1、编写内核驱动模块hello.c2、编写makefile3、编译makemodule4、拷贝到开发板adbpush./hello.ko/home/zbl/drv5、修改文件执行权限chmod777hello.ko6、加载内核驱动sudoinsmodhello.ko7、查看下加载是否成功lsmod8、卸载内核驱动sudormmodhello.ko /**......
  • FreeRTOS简单内核实现3 任务管理
    0、思考与回答0.1、思考一对于Cotex-M4内核的MCU在发生异常/中断时,哪些寄存器会自动入栈,哪些需要手动入栈?会自动入栈的寄存器如下R0-R3:通用寄存器R12:通用寄存器LR(LinkRegister):链接寄存器,保存返回地址PC(ProgramCounter):程序计数器,保存当前执行指令的地址xPSR(Pro......
  • gdb catchsyscall的内核支持
    intro通常使用gdb调试器,希望知道某个系统调用的发生时机,直接在该系统调用打断点即可。这里有一个假设就是这里使用的glibc库的实现,但是go生成的可执行文件就是一个单独的、静态链接文件,在go生成文件中,gdb的时候并没有可以打断点监测系统调用的方法。我想在go中大概率有对特定系......
  • FreeRTOS简单内核实现2 双向链表
    FreeRTOSKernelV10.3.1FreeRTOS的list.c/list.h文件中有3个数据结构、2个初始化函数、2个插入函数、1个移除函数和一些宏函数,链表是FreeRTOS中的重要数据结构,下述“1、数据结构”和“2、操作链表”两个小节内容主要对其原理进行讲解1、数据结构1.1、xLIST_IT......
  • FreeRTOS 简单内核实现1 前言
    0、写在前面为深入理解RTOS内核工作机制,笔者制作了名为“FreeRTOS内核简单实现”的项目专栏,目标为自己动手从0到1编程一个简单的RTOS内核,从而实现任务并行工作的效果,主要实现了以下功能静态创建任务临界段保护支持任务多优先级任务阻塞延时时间片轮询注意:本......