首页 > 其他分享 >FreeRTOS简单内核实现2 双向链表

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

时间:2024-06-15 09:00:22浏览次数:22  
标签:FreeRTOS 链表 ITEM pxNext pxList pxPrevious define 内核

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 )  
{  
    // 链表当前指针指向 xListEnd
    pxList->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 和 pxPrevious
    pxNewListItem->pxNext = pxIndex;
    pxNewListItem->pxPrevious = pxIndex->pxPrevious;
    
	// 2. 改变前一个链表项的 pxNext
    pxIndex->pxPrevious->pxNext = pxNewListItem;
    // 3. 改变后一个链表项的 pxPrevious
    pxIndex->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 和 pxPrevious
	pxNewListItem->pxNext = pxIterator->pxNext;
	pxNewListItem->pxPrevious = pxIterator;
	// 3. 改变后一个链表项的 pxPrevious
	pxNewListItem->pxNext->pxPrevious = pxNewListItem;
	// 4. 改变前一个链表项的 pxNext
	pxIterator->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. 改变后一个链表项 pxPrevious
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    // 2. 改变前一个链表项 pxNext
    pxItemToRemove->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              long

typedef 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
#else
    typedef 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 简单内核关键的双链表数据结构就完成了

标签:FreeRTOS,链表,ITEM,pxNext,pxList,pxPrevious,define,内核
From: https://www.cnblogs.com/lc-guo/p/18248757

相关文章

  • FreeRTOS 简单内核实现1 前言
    0、写在前面为深入理解RTOS内核工作机制,笔者制作了名为“FreeRTOS内核简单实现”的项目专栏,目标为自己动手从0到1编程一个简单的RTOS内核,从而实现任务并行工作的效果,主要实现了以下功能静态创建任务临界段保护支持任务多优先级任务阻塞延时时间片轮询注意:本......
  • 链表的概述
    目录链表的组成:头节点(HeadNode):链表头节点的概念使用头节点进行链表操作尾节点(TailNode):链表尾节点的概念链表尾节点的一般形式链表的分类:静态链表:动态链表:单链表(SinglyLinkedList):双链表(DoublyLinkedList):循环链表(CircularLinkedList):链表的操作:插入(Insert......
  • 数据结构(C/C++)专题一:顺序表与链表
    今天开始一个新的专题:数据结构当然,不仅仅适用于学习也适用于408考研。那么提起数据结构思维导图:总结如下:·1.初识顺序表与链表首先呢我们要明白,数据结构有物理结构,也有逻辑结构物理结构就是电脑实际的结构,链式,非链式,索引,散列eg:链式结构(LinkedStructure)例子:火车车......
  • 用Ubuntu24编译打包6.9.4内核(仅供参考)
    目录环境介绍前期安装下载内核源代码并编译打包并更新内核重启无法进入系统问题注意事项环境介绍Ubuntu24/4U/12G/120G/NAT172.16.186.148/24rambo@test1:~$uname-aLinuxtest1.lab.example.com6.8.0-35-generic#35-UbuntuSMPPREEMPT_DYNAMICMonMay2015:51:52UT......
  • 代码随想录——链表1——基本介绍与3种语言实现
    ......
  • 每日一练——随机链表的复制
    138.随机链表的复制-力扣(LeetCode)关键点:通过“相互插入”式的复制方法来把源链表和目标链表的random联系起来。  /***DefinitionforaNode.*structNode{*intval;*structNode*next;*structNode*random;*};*/typedefintLD......
  • 带头+双向+循环链表的实现
    目录1.链表1.1带头双向循环链表2.链表的实现2.1结构体2.2初始化2.3打印2.4判断空不能删2.5尾插2.6头插2.7尾删2.8头删2.9查找2.10在pos之前插入2.11删除pos位置的值2.12销毁2.13创建节点3.test主函数4.List.c文件5.List.h文件1.链表1.1带头......
  • 单向链表————遍历、查找、插入结点 (基于C语言实现)
    #include<stdio.h>#include<stdbool.h>#include<stdlib.h>#include<stdbool.h>//指的是单向链表中的结点有效数据类型,用户可以根据需要进行修改typedefintDataType_t;//构造链表的结点,链表中所有结点的数据类型应该是相同的typedefstructLinkedList{Dat......
  • C语言数据结构实现-静态链表2-基本操作
    上节,我们初步创建了一个静态链表,本节学习有关静态链表的一些基本操作,包括对表中数据元素的添加、删除、查找和更改。本节是建立在已能成功创建静态链表的基础上,因此我们继续使用上节中已建立好的静态链表学习本节内容,建立好的静态链表如图1所示:静态链表添加元素例如,在图1......
  • C语言数据结构实现-静态链表1-初始化
    《顺序表和链表优缺点》一节,我们了解了两种存储结构各自的特点,那么,是否存在一种存储结构,可以融合顺序表和链表各自的优点,从而既能快速访问元素,又能快速增加或删除数据元素。静态链表,也是线性存储结构的一种,它兼顾了顺序表和链表的优点于一身,可以看做是顺序表和链表的升级版。使......