FreeRTOS简单内核实现2 双向链表

news/2024/10/6 1:38:59

FreeRTOS Kernel V10.3.1

FreeRTOS 的 list.c / list.h 文件中有 3 个数据结构、2 个初始化函数、2 个插入函数、1 个移除函数和一些宏函数,链表是 FreeRTOS 中的重要数据结构,下述 “1、数据结构” 和 “2、操作链表” 两个小节内容主要对其原理进行讲解

1、数据结构

1.1、xLIST_ITEM

链表项,即节点,通常用链表项来表示一个任务

struct xLIST_ITEM
{// 检验一个 链表项 数据是否完整listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE// 排序值configLIST_VOLATILE TickType_t xItemValue;// 下一个 链表项struct xLIST_ITEM * configLIST_VOLATILE pxNext;// 前一个 链表项struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;// 记录此 链表项 归谁拥有,通常是 TCB (任务控制块)void * pvOwner;// 拥有该 链表项 的 链表 struct xLIST * configLIST_VOLATILE pxContainer;// 检验一个 链表项 数据是否完整listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE
};
typedef struct xLIST_ITEM ListItem_t;

1.2、XMINI_LIST_ITEM

MINI 链表项,链表项的缩减版,专门用于表示链表尾节点,在 32 位 MCU 上不启用链表项数据完整性校验的情况下相比于普通的链表项节省了 8 个字节(两个指针)

struct xMINI_LIST_ITEM
{// 检验一个 MINI链表项 数据是否完整listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE// 排序值configLIST_VOLATILE TickType_t xItemValue;// 下一个 链表项struct xLIST_ITEM * configLIST_VOLATILE pxNext;// 前一个 链表项struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

1.3、xLIST

由多个链表项构成的链表,常用于区分不同任务状态或任务优先级,比如就绪状态的任务放在就绪链表中,阻塞状态的任务放在阻塞链表中,方便任务管理

typedef struct xLIST
{// 检验一个 链表 数据是否完整listFIRST_LIST_INTEGRITY_CHECK_VALUE // 记录 链表 中 链表项 数目volatile UBaseType_t uxNumberOfItems;// 遍历 链表 的指针ListItem_t * configLIST_VOLATILE pxIndex;// 使用 MINI链表项 表示 链表尾部MiniListItem_t xListEnd;// 检验一个 链表 数据是否完整listSECOND_LIST_INTEGRITY_CHECK_VALUE
} List_t;

2、操作链表

注意:由于不涉及数据校验完整性,因此下述函数中关于校验的所有部分将被删除

2.1、初始化

2.1.1、vListInitialiseItem( )

初始化链表项函数

将链表项的 pxContainer 成员设置为 NULL ,因为初始化的时候该链表项未被任何链表包含

void vListInitialiseItem( ListItem_t * const pxItem )  
{  // 确保链表项未被记录在链表中pxItem->pxContainer = NULL;
}

2.1.2、vListInitialise( )

初始化链表函数,具体步骤如下

  1. 将链表当前指针 pxIndex 指向尾链表项 xListEnd
  2. 确保尾链表项 xListEnd 被排在链表最尾部
  3. 将尾链表项 xListEnd 的前/后链表项指针均指向自己,因为初始化链表时只有尾链表项
  4. 链表中有 0 个链表项
void vListInitialise( List_t * const pxList )  
{  // 链表当前指针指向 xListEndpxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );// 设置链表尾链表项排序值为最大, 保证 xListEnd 会被放在链表的尾部pxList->xListEnd.xItemValue = portMAX_DELAY;// 尾链表项 xListEnd 的前/后链表项指针均指向自己pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );// 初始化时链表中有 0 个链表项pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
}

2.2、插入

2.2.1、vListInsertEnd( )

将一个链表项插入到链表当前指针 pxIndex 指向的链表项前面,具体步骤如下

  1. 改变要插入链表项自身的 pxNext 和 pxPrevious
  2. 改变前一个链表项(也就是 pxIndex 指向的链表项的 Previous)的 pxNext
  3. 改变后一个链表项(也就是 pxIndex 指向的链表项)的 pxPrevious
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )  
{// 获取链表中当前指针 pxIndex 位置ListItem_t * const pxIndex = pxList->pxIndex;// 1. 改变自身 pxNext 和 pxPreviouspxNewListItem->pxNext = pxIndex;pxNewListItem->pxPrevious = pxIndex->pxPrevious;// 2. 改变前一个链表项的 pxNextpxIndex->pxPrevious->pxNext = pxNewListItem;// 3. 改变后一个链表项的 pxPreviouspxIndex->pxPrevious = pxNewListItem;// 标记新插入的链表项所在的链表 pxNewListItem->pxContainer = pxList;// 链表数量增加一( pxList->uxNumberOfItems )++;
}

为方便绘图演示,将链表的结构在图上做了简化,具体如下图所示

注意:这里 pxList->pxIndex 自初始化以来从未修改过,保持指向链表 xListEnd 链表项,下图所有演示中,橙色虚线表示该步骤做了修改,黑色实线表示与上一步骤相比无修改

插入一个链表项

插入第二个链表项

插入第三个链表项

插入第四个链表项

2.2.2、vListInsert( )

将一个链表项按照链表中所有链表项的 xItemValue 值大小升序插入链表中,具体步骤如下

  1. 找到应该插入的位置(应该插入到 pxIterator 的后面)
  2. 改变要插入链表项自身 pxNext 和 pxPrevious
  3. 改变后一个链表项的 pxPrevious
  4. 改变前一个链表项的 pxNext (也即 pxIterator 指向的链表项的 pxNext)
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )  
{ListItem_t *pxIterator;// 记录要插入的链表项的排序值const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;// 如果新插入的链表项排序值为最大值,直接插到尾节点 xListEnd 的前面if( xValueOfInsertion == portMAX_DELAY )  {  pxIterator = pxList->xListEnd.pxPrevious;  }else  {/*1. 遍历链表,将当前链表项 pxIterator 与要插入的新的链表项 pxNewListItem 的 xItemValue 值比较,直到 pxIterator 的 xItemValue 大于 pxNewListItem 的 xItemValue 值,此时 pxNewListItem 应该插入到 pxIterator 的后面*/for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ){}}// 2. 改变要插入链表项自身 pxNext 和 pxPreviouspxNewListItem->pxNext = pxIterator->pxNext;pxNewListItem->pxPrevious = pxIterator;// 3. 改变后一个链表项的 pxPreviouspxNewListItem->pxNext->pxPrevious = pxNewListItem;// 4. 改变前一个链表项的 pxNextpxIterator->pxNext = pxNewListItem;// 标记新插入的链表项所在的链表pxNewListItem->pxContainer = pxList;// 链表数量增加一( pxList->uxNumberOfItems )++;
}

2.3、移除

2.3.1、uxListRemove( )

从链表中移除指定的链表项,具体步骤如下

  1. 改变后一个链表项的 pxPrevious
  2. 改变前一个链表项的 pxNext
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )  
{  List_t * const pxList = pxItemToRemove->pxContainer;  // 1. 改变后一个链表项 pxPreviouspxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;// 2. 改变前一个链表项 pxNextpxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;// 确保索引指向有效的项目if( pxList->pxIndex == pxItemToRemove )  {  pxList->pxIndex = pxItemToRemove->pxPrevious;  }// 从链表中移除链表项后,该链表项不属于任何链表pxItemToRemove->pxContainer = NULL;  // 链表中链表项的数量减一( pxList->uxNumberOfItems )--;  // 返回链表中链表项的数量return pxList->uxNumberOfItems;  
}

2.4、宏函数

2.4.1、设置相关

// 设置 pxListItem 的 pxOwner 成员
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )// 设置 pxListItem 的 xValue 成员值
#define listSET_LIST_ITEM_VALUE( pxListItem, xValue )

2.4.2、获取相关

// 获取 pxListItem 的 pxOwner 成员
#define listGET_LIST_ITEM_OWNER( pxListItem )// 获取 pxListItem 的 xValue 成员值
#define listGET_LIST_ITEM_VALUE( pxListItem )// 获取链表中头链表项的 xItemValue 成员值
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList )// 获取链表中头链表项地址
#define listGET_HEAD_ENTRY( pxList )// 获取某个链表项的下一个链表项地址
#define listGET_NEXT( pxListItem )// 获取链表中 xListEnd 的地址
#define listGET_END_MARKER( pxList )// 获取链表当前长度
#define listCURRENT_LIST_LENGTH( pxList )// 将链表中 pxIndex 指向下一个链表项,用于获取下一个链表项(任务)
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )// 获取链表中头链表项的 pvOwner 成员
#define listGET_OWNER_OF_HEAD_ENTRY( pxList )// 获取链表项的 pxContainer 成员
#define listLIST_ITEM_CONTAINER( pxListItem )

2.4.3、判断相关

// 判断链表是否为空
#define listLIST_IS_EMPTY( pxList )// 判断链表项是否和链表匹配(链表项是否在该链表中)
#define listIS_CONTAINED_WITHIN( pxList, pxListItem )// 判断链表是否被初始化
#define listLIST_IS_INITIALISED( pxList )

3、链表移植

下面直接将 FreeRTOS 内核中 list.c / list.h 文件移植到自己的工程中使用(当然,如果你已经懂得双向链表数据结构的原理,你也可以构建属于你自己的 list.c / list.h 文件)

移植可以总结为以下 4 个步骤

  1. 用 FreeRTOS Kernel V10.3.1 内核中 list.c / list.h 替换掉 RTOS 文件夹中的同名文件
  2. 在 portMacro.h 中统一一些用到基本类型
/* portMacro.h */
#include <stdint.h>#define port_CHAR                   char
#define port_FLOAT                  float
#define port_DOUBLE                 double
#define port_LONG                   long
#define port_SHORT                  short
#define port_STACK_TYPE             unsigned int
#define port_BASE_TYPE              longtypedef port_STACK_TYPE             StackType_t;
typedef long                        BaseType_t;
typedef unsigned long               UBaseType_t;typedef port_STACK_TYPE*            StackType_p;
typedef long*                       BaseType_p;
typedef unsigned long*              UBaseType_p;#if(configUSE_16_BIT_TICKS == 1)typedef uint16_t                TickType_t;#define portMAX_DELAY           (TickType_t) 0xffff
#elsetypedef uint32_t                TickType_t;#define portMAX_DELAY           (TickType_t) 0xffffffffUL
#endif#define pdFALSE                     ((BaseType_t) 0)
#define pdTRUE                      ((BaseType_t) 1)
#define pdPASS                      (pdTRUE)
#define pdFAIL                      (pdFALSE)

configUSE_16_BIT_TICKS 是一个宏,用于 TickType_t 类型位数,具体定义如下

/* FreeRTOSConfig.h */
// 设置 TickType_t 类型位 16 位 
#define configUSE_16_BIT_TICKS                  0
  1. 删除掉 list.c 文件中所有的 mtCOVERAGE_TEST_DELAY() 测试函数
  2. 删除掉 list.h 文件中所有的 PRIVILEGED_FUNCTION 宏

完成后编译工程应该不会出现错误,这样实现 RTOS 简单内核关键的双链表数据结构就完成了

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

FastAPI快速入门2 Pydantic错误处理

2.1 Pydantic简介 Pydantic使用python类型注解进行数据验证和配置管理。这是一款能让您更精确地处理数据结构的工具。例如,到目前为止,我们一直依赖字典来定义项目中的典型配方。有了Pydantic,我们可以这样定义配方: from pydantic import BaseModelclass Recipe(BaseModel…

Winform .net 4.x 请求被中止: 未能创建 SSL/TLS 安全通道问题解决

参考https://www.cnblogs.com/Can-daydayup/p/14609089.html环境环境 版本 说明Windows Windows 10 家庭中文版 22H2 19045.4412VS Code 1.90.0Microsoft Visual Studio Microsoft Visual Studio Community 2022 (64 位) - 17.6.5 支持.net 4.x 程序需要额外配置。Microsoft .N…

更换IDEA项目的JAVA版本(8-11),

1、官网下载11的安装包 2、安装 3、修改环境变量里的JAVA_HOME变量为JAVA11的安装位置,例如C:\Program Files\Java\jdk-11 4、打开IDEA的项目,修改以下三处为java11: 4.1、修改的位置:4.2、修改complier4.3、修改project、module(我的是微服务项目,有好几个module)

1.1. 电阻篇----硬件设计指南(持续补充更新)

本系列文章是笔者对多年工作经验进行总结,结合理论与实践进行整理备忘的笔记。希望能直接指导硬件工程师的设计实操,而不是看的云里雾里的理论描述。既能帮助自己温习整理避免遗忘也能帮助其他需要参考的朋友。笔者也会不定期根据遇到的问题和想起的要点进行查漏补缺。如有谬…

stm32笔记[16]-使用usb-cdc串口.md

在stm32f103cbt6核心板使用usb cdc虚拟串口,回环发送的字符串.摘要 在stm32f103cbt6核心板使用usb cdc虚拟串口,回环发送的字符串. 关键信息STM32CubeIDE JLINK stm32f103cbt6 外部晶振:8MHz原理简介 usb-cdc简介 [https://blog.csdn.net/weixin_52296952/article/details/1357…

如何在 Linux 中使用 grep 命令的排除功能

来自grep 是一种强大的命令行工具,用于在一个或多个输入文件中搜索与正则表达式匹配的行,并将匹配的行标准输出。在本文中介绍如何在使用 grep 搜索时排除一个或多个单词或目录。排除单词或多个条件 要仅显示与搜索模式不匹配的行,请使用-v选项。例如,显示不包含nologin的…

MAVEN中的profile

---------------------------------------------------------------------------------------------------------------------------------------------------------maven的settings.xml文件用途Maven全局配置文件settings.xml详解_maven setting.xml-CSDN博客https://blog.csd…

Linux查看内存

查看 1. 使用 free 命令显示系统上可用和已用物理内存和交换内存的总量,以及内核使用的缓冲区和缓存。 # 查看命令 freetotal 总内存大小used 已被使用的内存(used= total – free – buff/cache)free 未被使用的内存 (free= total – used – buff/cache)shared…