【学习日记】【FreeRTOS】任务调度时如何考虑任务优先级——任务的自动切换

写在前面

本文开始为 RTOS 加入考虑任务优先级的自动调度算法,代码大部分参考野火。
本文主要是一篇学习笔记,加入了笔者自己对野火代码的梳理和理解。

一、基本思路

  • 首先我们要知道,在 RTOS 中,优先级越高、越需要被先执行的的任务的优先级的数字越大。比如优先级数字为 5 的任务就要比 优先级数字为 1 的任务先执行。
  • 在之前我们定义过就绪列表。所谓就绪列表,就是一个包含多条链表的数组,其中的每条链表又包含多个 TCB 作为列表的多个节点。现在,我们要把相同优先级的任务放入同一条链表中,而优先级越高的链表在就绪列表中下标越大。如图:
    在这里插入图片描述
  • 所以,我们需要把不在延时中的任务放进就绪列表中,然后按照优先级的大小进行切换执行;而如果任务进入了就绪状态,就将其从就绪列表中剔除。

二、代码实现

依据上面的思路,我们需要:

  • 一个记录就绪任务的最高优先级的变量
  • 一个设置就绪任务的最高优先级的变量的函数
  • 一个清除记录就绪任务的最高优先级的变量的函数
  • 一个让当前任务指针切换到最高优先级任务的函数
  • TCB 中要增加一个优先级的参数
  • 修改了 TCB 后,相关的任务创建函数需要修改
  • 如果任务进入延时,把它的就绪状态清除
  • 修改任务切换函数,使其找到优先级最高的任务执行
  • 修改计时函数,当有任务的延时结束,使其变回就绪状态

1. 记录就绪任务的最高优先级的变量

初始化为空闲任务的优先级,也就是最低的优先级 0:

#define tskIDLE_PRIORITY			       ( ( UBaseType_t ) 0U )static volatile UBaseType_t uxTopReadyPriority 		= tskIDLE_PRIORITY;

2. 设置、清除、选择就绪任务的最高优先级的变量的函数

① 通用方法

一种很朴素的想法是,使用上面定义的变量 uxTopReadyPriority 直接记录当前可执行的最高优先级。

  • 设置函数:
#define taskRECORD_READY_PRIORITY( uxPriority )														\{																									\if( ( uxPriority ) > uxTopReadyPriority )														\{																								\uxTopReadyPriority = ( uxPriority );														\}																								\} /* taskRECORD_READY_PRIORITY */
  • 选择优先级最高任务的函数:
#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 */

② 优化方法

下面这段话是野火教程的解释:

所谓优化方法,就是使用Cortex-M 内核一个计算前导零的指令
CLZ,所谓前导零就是计算一个变量(Cortex-M 内核单片机的变量为 32位)从高位开始第
一次出现 1 的位的前面的零的个数。比如:一个 32 位的变量 uxTopReadyPriority,其位 0、
位 24 和 位 25 均 置 1 , 其 余 位 为 0 , 具 体 见 。 那 么 使 用 前 导 零 指 令 __CLZ
(uxTopReadyPriority)可以很快的计算出 uxTopReadyPriority 的前导零的个数为 6。
在这里插入图片描述
如果 uxTopReadyPriority 的每个位号对应的是任务的优先级,任务就绪时,则将对应
的位置 1,反之则清零。那么图 10-2 就表示优先级 0、优先级 24 和优先级 25 这三个任务
就绪,其中优先级为 25的任务优先级最高。利用前导零计算指令可以很快计算出就绪任务
中的最高优先级为:( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) ) = ( 31UL - ( uint32_t )
6 ) = 25。

  • 设置函数
#define taskRECORD_READY_PRIORITY( uxPriority )	portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
  • 清除函数
#define taskRESET_READY_PRIORITY( uxPriority )											            \{																							        \portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) );					        \}#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )
  • 选择函数
#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() */

3. 修改 TCB 和相关的 TCB 创建函数

① 修改 TCB

增加 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;

② 修改静态任务创建函数:

调用任务初始化函数并将任务添加到就绪列表:

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;}/* 返回任务句柄,如果任务创建成功,此时xReturn应该指向任务控制块 */return xReturn;
}

③ 修改任务初始化函数

主要是初始化了任务的优先级:

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 )                       /* 任务控制块 */{StackType_t *pxTopOfStack;UBaseType_t x;	/* 获取栈顶地址 */pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );//pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );/* 向下做8字节对齐 */pxTopOfStack = ( StackType_t * ) ( ( ( uint32_t ) pxTopOfStack ) & ( ~( ( uint32_t ) 0x0007 ) ) );	/* 将任务的名字存储在TCB中 */for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ){pxNewTCB->pcTaskName[ x ] = pcName[ x ];if( pcName[ x ] == 0x00 ){break;}}/* 任务名字的长度不能超过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;}
}

④ 增加将新创建的任务添加到就绪列表的函数

  • 修改任务个数计数器
  • 调用任务插入就绪列表函数
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();
}

⑤ 修改将任务添加到就绪列表的函数

  • 修改优先级就绪变量
  • 将任务按照优先级大小插入对应的列表下标链表中:
/* 将任务添加到就绪列表 */                                    
#define prvAddTaskToReadyList( pxTCB )																   \taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );												   \vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \

3. 修改任务阻塞延时函数

  • 把任务从就绪列表中移除(因为我们现在还没有延时链表,所以先不做这个)
  • 将任务的优先级就绪变量清除
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();
}

4. 修改任务切换函数

  • 寻找优先级最高的就绪任务执行即可:
/* 任务切换,即寻找优先级最高的就绪任务 */
void vTaskSwitchContext( void )
{/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */taskSELECT_HIGHEST_PRIORITY_TASK();
}
  • taskSELECT_HIGHEST_PRIORITY_TASK() 在上文已经定义

5. 修改计时函数

当有任务的延时结束,使其变回就绪状态:

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

三、结果展示

/* 创建任务 */Task1_Handle = xTaskCreateStatic( (TaskFunction_t)Task1_Entry,   /* 任务入口 */(char *)"Task1",               /* 任务名称,字符串形式 */(uint32_t)TASK1_STACK_SIZE ,   /* 任务栈大小,单位为字 */(void *) NULL,                 /* 任务形参 */(UBaseType_t) 1,               /* 任务优先级,数值越大,优先级越高 */(StackType_t *)Task1Stack,     /* 任务栈起始地址 */(TCB_t *)&Task1TCB );          /* 任务控制块 *//* 将任务添加到就绪列表 */                                 //vListInsertEnd( &( pxReadyTasksLists[1] ), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );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 );          /* 任务控制块 */ /* 将任务添加到就绪列表 */                                 //vListInsertEnd( &( pxReadyTasksLists[2] ), &( ((TCB_t *)(&Task2TCB))->xStateListItem ) );/* 启动调度器,开始多任务调度,启动成功则不返回 */vTaskStartScheduler();    

创建两个任务,在两个任务中分别对两个标志变量进行 电平变换-延时-电平变换 的循环操作,结果如下:
在这里插入图片描述
可以看到,两个标注变量几乎同时进行电平切换,CPU 没有被延时阻塞。
在这里插入图片描述
而且任务 2 由于设置的优先级比任务 1 高,所以电平先切换为高,优先级切换的功能添加成功。

后记

如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/46641.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解析大规模开发:提升企业级开发效率与质量,加速创新

在数字化转型的大环境下&#xff0c;越来越多的企业依赖软件来驱动业务和创新。然而&#xff0c;随着开发规模日益庞大&#xff0c;如何更好地提升研发效能&#xff0c;从而塑造更强大的竞争力&#xff0c;已然成为众多企业亟待解决的共同难题。 作为国内领先的DevSecOps提供商…

运放和三极管构成的恒流源电路

这是一个由运放和三极管构成的恒流源电路&#xff0c;RL为负载电阻&#xff0c;R1为采样电阻。 流过三极管集电极的电流 下面分析下这个电路的工作原理。首先我们可以看到这个运放引入了负反馈&#xff0c;所以它工作在线性区的&#xff0c;就有VINVPVN。 所以流过采样电阻R1的…

C#程序变量统一管理例子 - 开源研究系列文章

今天讲讲关于C#应用程序中使用到的变量的统一管理的代码例子。 我们知道&#xff0c;在C#里使用变量&#xff0c;除了private私有变量外&#xff0c;程序中使用到的公共变量就需要进行统一的存放和管理。这里笔者使用到的公共变量管理库划分为&#xff1a;1)窗体&#xff1b;2)…

OpenCV-Python中的图像处理-GrabCut算法交互式前景提取

OpenCV-Python中的图像处理-GrabCut算法交互式前景提取 Python-OpenCV中的图像处理-GrabCut算法交互式前景提取 Python-OpenCV中的图像处理-GrabCut算法交互式前景提取 cv2.grabCut(img: Mat, mask: typing.Optional[Mat], rect, bgdModel, fgdModel, iterCount, mode…) img…

基于Java+SpringBoot制作一个智能用电小程序

在当今快节奏的生活中,高效利用能源变得越来越重要。制作一个智能用电小程序,旨在帮助您更智能地管理家庭电器的用电,从而提升能源利用效率,助您掌握用电情况,降低能耗成本,实现绿色低碳生活。 目录 一、小程序1.1 项目创建1.2 首页轮播图快捷导航iconfont图标引入

GSM/CDMA/VoLTE/VoIP通话

1.GSM(Global System for Mobile Communications) 本质是一种多址技术&#xff0c;将多个通话放入一段无线电频道的方法。特点是通过“时间划分”&#xff0c;称为时分多址。 2.CDMA(Code Division Multiple Access) 一种多址技术&#xff0c;将多个通话放入一段无线电频道的…

AIGC绘画:kaggle部署stable diffusion项目绘画

文章目录 kaggle介绍项目部署edit my copy链接显示 结果展示 kaggle介绍 Kaggle成立于2010年&#xff0c;是一个进行数据发掘和预测竞赛的在线平台。从公司的角度来讲&#xff0c;可以提供一些数据&#xff0c;进而提出一个实际需要解决的问题&#xff1b;从参赛者的角度来讲&…

async/await 编程理解

博客主要是参考 Asynchronous Programming in Rust &#xff0c;会结合简单的例子&#xff0c;对 async 和 await 做比较系统的理解&#xff0c;如何使用 async 和 await 是本节的重点。 async 和 await 主要用来写异步代码&#xff0c;async 声明的代码块实现了 Future 特性&a…

批量爬虫采集完成任务

批量爬虫采集是现代数据获取的重要手段&#xff0c;然而如何高效完成这项任务却是让许多程序员头疼的问题。本文将分享一些实际操作价值高的方法&#xff0c;帮助你提高批量爬虫采集的效率和专业度。 目标明确&#xff0c;任务合理划分&#xff1a; 在开始批量爬虫采集前&…

Linux:安全技术与防火墙

目录 一、安全技术 1.安全技术 2.防火墙的分类 3.防水墙 4.netfilter/iptables关系 二、防火墙 1、iptables四表五链 2、黑白名单 3.iptables命令 3.1查看filter表所有链 iptables -L ​编辑3.2用数字形式(fliter)表所有链 查看输出结果 iptables -nL 3.3 清空所有链…

SpringCloud Ribbon中的7种负载均衡策略

SpringCloud Ribbon中的7种负载均衡策略 Ribbon 介绍负载均衡设置7种负载均衡策略1.轮询策略2.权重策略3.随机策略4.最小连接数策略5.重试策略6.可用性敏感策略7.区域敏感策略 总结 负载均衡通器常有两种实现手段&#xff0c;一种是服务端负载均衡器&#xff0c;另一种是客户端…

C++线程库

C线程库是C11新增的重要的技术之一&#xff0c;接下来来简单学习一下吧&#xff01; thread类常用接口 函数名功能thread()构造一个线程对象&#xff0c;没有关联任何线程函数&#xff0c;即没有启动任何线程。thread(fn, args1, args2, ...)构造一个线程对象&#xff0c;并…

【数据结构入门指南】二叉树顺序结构: 堆及实现(全程配图,非常经典)

【数据结构入门指南】二叉树顺序结构: 堆及实现&#xff08;全程配图&#xff0c;非常经典&#xff09; 一、前言&#xff1a;二叉树的顺序结构二、堆的概念及结构三、堆的实现&#xff08;本篇博客以实现小堆为例&#xff09;3.1 准备工作3.2 初始化3.3 堆的插入3.3.1 向上调…

微服务中间件--分布式事务

分布式事务 a.理论基础1) CAP定理2) BASE理论 b.Seata1) XA模式1.a) 实现XA模式 2) AT模式3) TCC模式3.a) 代码实现 4) Saga模式5) 四种模式对比6) TC的异地多机房容灾架构 a.理论基础 1) CAP定理 分布式系统有三个指标&#xff1a; Consistency&#xff08;一致性&#xff…

opencv-人脸关键点定位

#导入工具包 from collections import OrderedDict import numpy as np import argparse import dlib import cv2#https://ibug.doc.ic.ac.uk/resources/facial-point-annotations/ #http://dlib.net/files/# 参数 ap argparse.ArgumentParser() ap.add_argument("-p&quo…

【Java】常见面试题:HTTP/HTTPS、Servlet、Cookie、Linux和JVM

文章目录 1. 抓包工具&#xff08;了解&#xff09;2. 【经典面试题】GET和POST的区别&#xff1a;3. URL中不是也有这个服务器主机的IP和端口吗&#xff0c;为啥还要搞个Host&#xff1f;4. 补充5. HTTP响应状态码6. 总结HTTPS工作过程&#xff08;经典面试题&#xff09;7. H…

vue3 清空/重置reactive

序&#xff1a; 1、适用场景&#xff1a;表单切换验证如下图。 我举个例子&#xff0c;如果下拉选银行卡&#xff0c;提交表单的时候所属银行是要必填验证&#xff0c;但是如果选支付宝&#xff0c;那所属银行就非必填了&#xff0c;然而很多时候from的rules是以props来传的&a…

飞书小程序开发

1.tt.showModal后跳转页面 跳转路径要为绝对路径&#xff0c;相对路径跳转无响应。 2.手机息屏后将不再进入onload()生命周期&#xff0c;直接进入onshow()生命周期。 onLoad()在页面初始化的时候触发&#xff0c;一个页面只调用一次。 onShow()在切入前台时就会触发&#x…

springBoot 配置文件 jpa 相关参数的作用

在Spring Boot应用中&#xff0c;可以通过配置文件来配置JPA&#xff08;Java Persistence API&#xff09;相关的参数。下面是一些常用的JPA配置参数及其作用&#xff1a; spring.jpa.database: 指定JPA使用的数据库类型&#xff0c;默认为自动检测。可选值有HSQL、H2、DERBY…

开放网关架构演进

作者&#xff1a;庄文弘&#xff08;弘智&#xff09; 淘宝开放平台是阿里与外部生态互联互通的重要开放途径&#xff0c;通过开放的产品技术把阿里经济体一系列基础服务&#xff0c;像水、电、煤一样输送给我们的商家、开发者、社区媒体以及其他合作伙伴&#xff0c;推动行业的…