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

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

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

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

重要数据

0.  全局TCB指针

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

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;

两种方法

        查找最高优先级的就绪任务有两种方法,具体由宏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 )   

        taskSELECT_HIGHEST_PRIORITY_TASK()

        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;
 

现在可以回答这个问题了,也就是这列链表中节点的个数,其在
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() */

有关任务重要函数

修改: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[])

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

vTaskStartScheduler()维持不变

完成如下功能:

1)创建空闲任务;

2)启动任务调度器xPortStartScheduler;

修改: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;

修改:void vTaskSwitchContext( void )

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

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

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

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

主函数及其调试

重要文件

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

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

相关文章

【C++篇】C++入门基础(二)

&#x1f4ac; 欢迎讨论&#xff1a;在阅读过程中有任何疑问&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;如果你觉得这篇文章对你有帮助&#xff0c;记得点赞、收藏&#xff0c;并分享给更多对C感兴趣的…

Mysql架构之日志讲解:redo log,undo log,bin log 日志

一、buffer pool缓冲区 讲日志之前&#xff0c;我们要先认识一下buffer pool缓冲区。 mysql想完成数据的修改&#xff0c;会先从存储引擎层读取数据&#xff0c;把数据读取到服务层进行数据的修改&#xff0c;再通过存储引擎层把数据更新到数据库中。 mysql每次读取数据都会…

容器主机CPU使用率突增问题一则

关键词 LINUX、文件系统crontab 、mlocate根目录使用率 There are many things that can not be broken&#xff01; 如果觉得本文对你有帮助&#xff0c;欢迎点赞、收藏、评论&#xff01; 一、问题现象 业务一台容器服务器&#xff0c;近期经常收到cpu不定期抖动告警&#x…

simpleITK - Setup - matplotlib‘s imshow

使用 matplotlib 显示内联图像 在此笔记本中&#xff0c;我们将探索使用 matplotlib 显示笔记本中的图像&#xff0c;并致力于开发可重复使用的函数来显示 SimpleITK 图像的 2D、3D、颜色和标签叠加层。 我们还将研究使用需要输入图像重叠的图像过滤器的微妙之处。 %matplot…

Github 热点项目 awesome-mcp-servers MCP 服务器合集,3分钟实现AI模型自由操控万物!

【今日推荐】超强AI工具库"awesome-mcp-servers"星数破万&#xff01; ① 百宝箱式服务模块&#xff1a;AI能直接操作浏览器、读文件、连数据库&#xff0c;比如让AI助手自动整理Excel表格&#xff0c;三分钟搞定全天报表&#xff1b; ② 跨领域实战利器&#xff1a;…

硬件老化测试方案的设计误区

硬件老化测试方案设计中的常见误区主要包括测试周期不足、测试条件过于单一、样品选择不当等方面。其中&#xff0c;测试周期不足尤为突出&#xff0c;容易导致潜在缺陷未被完全暴露。老化测试本质上是通过加速产品老化来模拟长期使用状况&#xff0c;因此测试周期不足会严重削…

CSS学习笔记5——渐变属性+盒子模型阶段案例

目录 通俗易懂的解释 渐变的类型 1、线性渐变 渐变过程 2、径向渐变 如何理解CSS的径向渐变&#xff0c;以及其渐变属性 通俗易懂的解释 渐变属性 1. 形状&#xff08;Shape&#xff09; 2. 大小&#xff08;Size&#xff09; 3. 颜色停靠点&#xff08;Color Sto…

Java StringUtils工具类常用方法详解

StringUtils是Apache Commons Lang库中一个极其常用的工具类&#xff0c;它提供了大量处理字符串的静态方法&#xff0c;能够简化我们的日常开发工作&#xff0c;提高代码的可读性和健壮性。下面我将详细介绍StringUtils类中最常用的方法及其使用场景。 一、StringUtils的基本…

设计模式(创建型)- 原型模式

目录 定义 类图 角色 优缺点 优点 缺点 应用场景 案例展示 浅克隆 深克隆 定义 原型模式旨在创建重复的对象&#xff0c;同时确保良好的性能表现。它通过复制现有对象&#xff08;原型&#xff09;来创建新对象&#xff0c;而非使用传统的构造函数创建方式。这种设计…

MQ的数据一致性,如何保证?

1 数据一致性问题的原因 这些年在Kafka、RabbitMQ、RocketMQ踩过的坑&#xff0c;总结成四类致命原因&#xff1a; 生产者悲剧&#xff1a;消息成功进Broker&#xff0c;却没写入磁盘就断电。消费者悲剧&#xff1a;消息消费成功&#xff0c;但业务执行失败。轮盘赌局&#x…

Angular由一个bug说起之十五:自定义基于Overlay的Tooltip

背景 工具提示&#xff08;tooltip&#xff09;是一个常见的 UI 组件&#xff0c;用于在用户与页面元素交互时提供额外的信息。由于angular/material/tooltip的matTooltip只能显示纯文本&#xff0c;所以我们可以通过自定义Directive来实现一个灵活且功能丰富的tooltip Overlay…

简单介绍一下Unity中的ScriptableObject

ScriptableObject的本质 ScriptableObject是Unity引擎中的一个特殊基类&#xff0c;允许你创建不依附于游戏对象的数据容器&#xff0c;以资产(Asset)形式存储在项目中。这些资产&#xff1a; 可在编辑器中创建和配置 在构建后作为资产打包 可通过Resources或AssetBundle加…

ubuntu24.04.2 NVIDIA GeForce RTX 4060笔记本安装驱动

https://www.nvidia.cn/drivers/details/242281/ 上面是下载地址 sudo chmod x NVIDIA-Linux-x86_64-570.133.07.run # 赋予执行权限把下载的驱动复制到家目录下&#xff0c;基本工具准备&#xff0c;如下 sudo apt update sudo apt install build-essential libglvnd-dev …

LabVIEW 布尔控件回车键触发程序退出

在 LabVIEW 开发过程中&#xff0c;部分用户可能会遇到按下回车键&#xff08;Enter&#xff09;后&#xff0c;程序意外退出的问题。该问题主要源于布尔控件的属性设置冲突&#xff0c;包括键分配、数据绑定及 Tab 键行为等。本文将详细分析问题根源&#xff0c;并提供一套完整…

分布式系统面试总结:3、分布式锁(和本地锁的区别、特点、常见实现方案)

仅供自学回顾使用&#xff0c;请支持javaGuide原版书籍。 本篇文章涉及到的分布式锁&#xff0c;在本人其他文章中也有涉及。 《JUC&#xff1a;三、两阶段终止模式、死锁的jconsole检测、乐观锁&#xff08;版本号机制CAS实现&#xff09;悲观锁》&#xff1a;https://blog.…

WebWorkers在项目中的使用案例

Worker | 文档 worker 线程的关闭在主线程和 worker 线程都能进行操作&#xff0c;但对 worker 线程的影响略有不同。 // main.js&#xff08;主线程&#xff09; const myWorker new Worker(/worker.js); // 创建worker myWorker.terminate(); // 关闭worker 复制代码 // wor…

vue ts+Windi CSS

1、创建vue项目 trae&#xff08;字节&#xff09;打开一个空文件夹 npm install -g vue/cli vue create my-project cd my-project vue add typescript npm run serve vue项目创建完成 2、安装windicss vue add windicss vue.config.js配置 npm install vue-router …

【HTML 基础教程】HTML 编辑器

HTML 编辑器推荐 可以使用专业的 HTML 编辑器来编辑 HTML&#xff0c;菜鸟教程为大家推荐几款常用的编辑器&#xff1a; VS Code&#xff1a;Visual Studio Code - Code Editing. RedefinedSublime Text&#xff1a;http://www.sublimetext.com/在线编辑器&#xff1a;HTML/C…

文件上传的小点总结(2)

4.黑名单绕过(.htaccess方法) 源码一打开&#xff0c;遇到这样的黑名单是不是看的头皮发麻&#xff0c;这么多后缀都禁用。 .htaccess可以启用或禁用apache的功能&#xff0c;利用这个特点&#xff0c;我们可以使用该文件来禁用上述黑名单功能&#xff0c;从而上传**文件。 简…

mysql--主从复制--部署

MySQL 主从复制部署教程 一、主节点&#xff08;Master&#xff09;配置 1. 创建目录结构 mkdir -p /usr/local/src/mysql_demo/master_replica/{logs,configFile,data}2. 编写主节点的 MySQL 配置文件 my.cnf 路径&#xff1a;/usr/local/src/mysql_demo/master_replica/co…