从零开始,教你写FreeRTOS双向链表

嵌入式ARM 2022-07-11 12:00

摘要:前几天给大家讲了单向链表,今天再结合FreeRTOS的链表源码,说一下双向链表。


注:链表项就是节点,节点就是链表项,都是指的一个东西,叫啥都无所谓。

一、定义链表结构体

//定义链表,同时也是链表头
typedef struct xLIST
{
  
    volatile unsigned   int uxNumberOfItems;   
    ListItem_t *  pxIndex; 
    MiniListItem_t xListEnd;                          
} List_t;

二、定义mini节点项

迷你节点也是节点,但迷你节点仅用于标记链表的末尾和挂载其他插入链表中的节点,用户是用不到迷你节点的,链表头节点和普通节点可以不一样。

typedef struct xMINI_LIST_ITEM
{

    volatile unsigned   int xItemValue;   /* 辅助值,用于帮助节点做升序排列. */
    struct xLIST_ITEM   * pxNext; 
    struct xLIST_ITEM   * pxPrevious;
}MiniListItem_t;

下面这个头即使链表定义,也是链表头,链表头节点和普通节点可以不一样。

三、定义节点

节点在FreeRTOS中叫做链表项。

//定义链表节点
typedef struct xLIST_ITEM
{
       
    volatile unsigned   int  xItemValue;          
    struct xLIST_ITEM *  pxNext;     
    struct xLIST_ITEM *  pxPrevious; 
    void * pvOwner;                       //用于指向该节点的拥有者              
    struct xLIST *  pxContainer;          //用于指向该节点所在的链表,通常指向链表的根节点
              
}ListItem_t;

四、初始化链表

初始化链表就是给链表的头结点各个参数赋值。

void vListInitialise( List_t * const pxList)
{
    /*初始化时,列表中的列表项数量为 0(不包含 xListEnd) */
    pxList->uxNumberOfItems = 0;    
    /* 初始化时,列表中只有 xListEnd,因此 pxIndex 指向 xListEnd */
    pxList->pxIndex = (ListItem_t *) &(pxList->xListEnd); 
    /* xListEnd 的值初始化为最大值,用于列表项升序排序时,排在最后 */
    pxList->xListEnd.xItemValue = portMAX_DELAY;    
    /* 初始化时,列表中只有 xListEnd,因此上一个和下一个列表项都为 xListEnd 本身 */
    pxList->xListEnd.pxNext =(ListItem_t *) &(pxList->xListEnd); 
    pxList->xListEnd.pxPrevious = (ListItem_t *) &(pxList->xListEnd); 
}
左右两个图意思都一样

五、初始化节点

void vListInitialiseItem(ListItem_t * const pxItem )
{
   /* 初始化时,列表项所在列表设为空 */
    pxItem->pxContainer = NULL;
}

六、将节点插入到链表的尾部

void vListInsertEnd( List_t * const pxList,ListItem_t * const pxNewListItem )
{
    /* 获取列表 pxIndex 指向的列表项 */
    ListItem_t * const headEnd = pxList->pxIndex;
    /* 更新待插入列表项的指针成员变量 */
    pxNewListItem->pxNext = headEnd;
    pxNewListItem->pxPrevious = headEnd->pxPrevious;
    /* 更新列表中原本列表项的指针成员变量 */
    headEnd->pxPrevious->pxNext = pxNewListItem;
    headEnd->pxPrevious = pxNewListItem;
        /* 标记待插入列表项的所在列表成员变量 */
    pxNewListItem->pxContainer = pxList;
   /* 列表中列表项的数量+1 */
    (pxList->uxNumberOfItems)++;
}

此函数就是将待插入的列表项插入到列表 pxIndex 指向列表项的前面,要注意的时,pxIndex 不一定指向 xListEnd,而是有可能指向列表中任意一个列表项。

我手画一张图来解释吧!

七、链表节点插入并排序

void vListInsert( List_t * const pxList,ListItem_t * const pxNewListItem )
{
    ListItem_t * pxIterator;
    const int xValueOfInsertion = pxNewListItem->xItemValue;

    /* 如果待插入列表项的值为最大值 */
    if( xValueOfInsertion == portMAX_DELAY)
    {
        /* 插入的位置为列表 xListEnd 前面 */
        pxIterator = pxList->xListEnd.pxPrevious;
    }
    else
    {
         /* 遍历列表中的列表项,找到插入的位置 */
        for( pxIterator = (ListItem_t*)&(pxList->xListEnd); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext )
        {
            
        }
    }
 /* 根据升序排列,将节点插入  在pxIterator后面插入新节点*/   
    pxNewListItem->pxNext = pxIterator->pxNext;
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
    pxNewListItem->pxPrevious = pxIterator;
    pxIterator->pxNext = pxNewListItem;
     
    /* 记住该节点所在的链表 */
    pxNewListItem->pxContainer = pxList;
   /* 链表节点计数器++ */
    ( pxList->uxNumberOfItems )++;
}

在将待插入列表项插入列表之前,会前遍历列表,找到待插入列表项需要插入的位置。待插入列表项需要插入的位置,是依照列表中列表项的值,按照升序排序确定的。

八、删除链表节点

int uxListRemove( ListItem_t * const pxItemToRemove )
{
 /* 获取节点所在的链表 */
    List_t * const pxList = pxItemToRemove->pxContainer;
 
 /* 将指定的节点从链表删除*/
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
    

    /* 如果 pxIndex 正指向待移除的列表项 */
    if( pxList->pxIndex == pxItemToRemove )
    {
        /* pxIndex 指向上一个列表项 */
        pxList->pxIndex = pxItemToRemove->pxPrevious;
    }
    else
    {

    }
   /* 将待移除列表项的所在列表指针清空 */
    pxItemToRemove->pxContainer = NULL;
   /* 链表节点计数器-- */
    ( pxList->uxNumberOfItems )--;
   /* 返回链表中剩余节点的个数 */
    return pxList->uxNumberOfItems;
}

要删除节点,首先就必须要找到节点。

需要注意的是,函数 uxListRemove()移除后的列表项,依然于列表有着单向联系,即移除后列表项中用于指向上一个和下一个列表项的指针,依然指向列表中的列表项。

九、实例

#include 
#include 
#include 

//定义链表节点
typedef struct xLIST_ITEM
{
       
    volatile unsigned   int  xItemValue;          
    struct xLIST_ITEM *  pxNext;     
    struct xLIST_ITEM *  pxPrevious; 
    void * pvOwner;                       //用于指向该节点的拥有者              
    struct xLIST *  pxContainer;          //用于指向该节点所在的链表,通常指向链表的根节点
              
}ListItem_t;

typedef struct xMINI_LIST_ITEM
{

    volatile unsigned   int xItemValue;   /* 辅助值,用于帮助节点做升序排列. */
    struct xLIST_ITEM   * pxNext; 
    struct xLIST_ITEM   * pxPrevious;
}MiniListItem_t;

//定义链表,同时也是链表头
typedef struct xLIST
{
  
    volatile unsigned   int uxNumberOfItems;   
    ListItem_t *  pxIndex; 
    MiniListItem_t xListEnd;                          
} List_t;

void vListInitialise( List_t * const pxList)
{
    pxList->uxNumberOfItems = 0;    
    pxList->pxIndex = (ListItem_t *) &(pxList->xListEnd); 
    pxList->xListEnd.xItemValue = 100;    
    pxList->xListEnd.pxNext =(ListItem_t *) &(pxList->xListEnd);     /*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM.  This is checked and valid. */
    pxList->xListEnd.pxPrevious = (ListItem_t *) &(pxList->xListEnd); /*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM.  This is checked and valid. */
}

void vListInitialiseItem(ListItem_t * const pxItem )
{
    pxItem->pxContainer = NULL;
}
//列表项(节点)末尾插入函数,将节点插入到链表的尾部  
void vListInsertEnd( List_t * const pxList,ListItem_t * const pxNewListItem )
{
    ListItem_t * const headEnd = pxList->pxIndex;

    pxNewListItem->pxNext = headEnd;
    pxNewListItem->pxPrevious = headEnd->pxPrevious;

    headEnd->pxPrevious->pxNext = pxNewListItem;
    headEnd->pxPrevious = pxNewListItem;
    
    /* 记住该节点所在的链表. */
    pxNewListItem->pxContainer = pxList;
 
    (pxList->uxNumberOfItems)++;
}

void vListInsert( List_t * const pxList,ListItem_t * const pxNewListItem )
{
    ListItem_t * pxIterator;
    const int xValueOfInsertion = pxNewListItem->xItemValue;


    if( xValueOfInsertion == 100 )
    {
        pxIterator = pxList->xListEnd.pxPrevious;
    }
    else
    {
        for( pxIterator = (ListItem_t*)&(pxList->xListEnd); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext )
        {
            
        }
    }
 /* 根据升序排列,将节点插入  在pxIterator后面插入新节点*/   
    pxNewListItem->pxNext = pxIterator->pxNext;
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
    pxNewListItem->pxPrevious = pxIterator;
    pxIterator->pxNext = pxNewListItem;
     
    /* 记住该节点所在的链表 */
    pxNewListItem->pxContainer = pxList;
 /* 链表节点计数器++ */
    ( pxList->uxNumberOfItems )++;
}
int uxListRemove( ListItem_t * const pxItemToRemove )
{
 /* 获取节点所在的链表 */
    List_t * const pxList = pxItemToRemove->pxContainer;
 
 /* 将指定的节点从链表删除*/
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
    

    /*调整链表的节点索引指针 */
    if( pxList->pxIndex == pxItemToRemove )
    {
        pxList->pxIndex = pxItemToRemove->pxPrevious;
    }
    else
    {

    }
 /* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
    pxItemToRemove->pxContainer = NULL;
 /* 链表节点计数器-- */
    ( pxList->uxNumberOfItems )--;
 /* 返回链表中剩余节点的个数 */
    return pxList->uxNumberOfItems;
}

/* 定义链表根节点 */
struct xLIST List_Test;

/* 定义节点 */
struct xLIST_ITEM List_Item1;
struct xLIST_ITEM List_Item2;
struct xLIST_ITEM List_Item3;

int main(void)
{
    /* 定义链表根节点 */
    struct xLIST List_Test;

    /* 定义节点 */
    struct xLIST_ITEM List_Item1;
    struct xLIST_ITEM List_Item2;
    struct xLIST_ITEM List_Item3;
    
    /* 链表根节点初始化 */
    vListInitialise(&List_Test); 

    /* 节点 1 初始化 */
    vListInitialiseItem(&List_Item1);
    List_Item1.xItemValue = 1;

    /* 节点 2 初始化 */
    vListInitialiseItem(&List_Item2);
    List_Item2.xItemValue = 2;

    /* 节点 3 初始化 */
    vListInitialiseItem(&List_Item3);
    List_Item3.xItemValue = 3;

    /* 将节点插入链表,按照升序排列 */ 
    vListInsert( &List_Test, &List_Item2);
    vListInsert( &List_Test, &List_Item1);
    vListInsert( &List_Test, &List_Item3);
 
 while(1);
}

然后随便找个32的代码,用keil仿真试一试。

仔细看一下这张图:

这个其实就是FreeRTOS的链表源码,是不是很简单?

工程下载源码:

链接:https://pan.baidu.com/s/1g34z7l3MSrf12lawF4hKLA?pwd=o8xc 
提取码:o8xc

其实这个工程是一个普通的裸机例程,只是加入了FreeRTOS的链表源码,建议大家自己动手试一下,深入理解链表。因为要想学好任何一个RTOS,必须掌握的基础知识就是链表和队列!

END

来源:果果小师弟

版权归原作者所有,如有侵权,请联系删除。

推荐阅读
手把手教你搭建一个轻量级电子实验室
如何写一个健壮且高效的串口接收程序?
大规模裁员后,计算机专业会成下一个土木吗?

→点关注,不迷路←
嵌入式ARM 关注这个时代最火的嵌入式ARM,你想知道的都在这里。
评论
  • 在快速发展的能源领域,发电厂是发电的支柱,效率和安全性至关重要。在这种背景下,国产数字隔离器已成为现代化和优化发电厂运营的重要组成部分。本文探讨了这些设备在提高性能方面的重要性,同时展示了中国在生产可靠且具有成本效益的数字隔离器方面的进步。什么是数字隔离器?数字隔离器充当屏障,在电气上将系统的不同部分隔离开来,同时允许无缝数据传输。在发电厂中,它们保护敏感的控制电路免受高压尖峰的影响,确保准确的信号处理,并在恶劣条件下保持系统完整性。中国国产数字隔离器经历了重大创新,在许多方面达到甚至超过了全球
    克里雅半导体科技 2025-01-03 16:10 122浏览
  • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
    Industio_触觉智能 2025-01-06 10:43 72浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 97浏览
  • PLC组态方式主要有三种,每种都有其独特的特点和适用场景。下面来简单说说: 1. 硬件组态   定义:硬件组态指的是选择适合的PLC型号、I/O模块、通信模块等硬件组件,并按照实际需求进行连接和配置。    灵活性:这种方式允许用户根据项目需求自由搭配硬件组件,具有较高的灵活性。    成本:可能需要额外的硬件购买成本,适用于对系统性能和扩展性有较高要求的场合。 2. 软件组态   定义:软件组态主要是通过PLC
    丙丁先生 2025-01-06 09:23 71浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 76浏览
  • 物联网(IoT)的快速发展彻底改变了从智能家居到工业自动化等各个行业。由于物联网系统需要高效、可靠且紧凑的组件来处理众多传感器、执行器和通信设备,国产固态继电器(SSR)已成为满足中国这些需求的关键解决方案。本文探讨了国产SSR如何满足物联网应用的需求,重点介绍了它们的优势、技术能力以及在现实场景中的应用。了解物联网中的固态继电器固态继电器是一种电子开关设备,它使用半导体而不是机械触点来控制负载。与传统的机械继电器不同,固态继电器具有以下优势:快速切换:确保精确快速的响应,这对于实时物联网系统至
    克里雅半导体科技 2025-01-03 16:11 178浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 89浏览
  • 光耦合器,也称为光隔离器,是一种利用光在两个隔离电路之间传输电信号的组件。在医疗领域,确保患者安全和设备可靠性至关重要。在众多有助于医疗设备安全性和效率的组件中,光耦合器起着至关重要的作用。这些紧凑型设备经常被忽视,但对于隔离高压和防止敏感医疗设备中的电气危害却是必不可少的。本文深入探讨了光耦合器的功能、其在医疗应用中的重要性以及其实际使用示例。什么是光耦合器?它通常由以下部分组成:LED(发光二极管):将电信号转换为光。光电探测器(例如光电晶体管):检测光并将其转换回电信号。这种布置确保输入和
    腾恩科技-彭工 2025-01-03 16:27 171浏览
  • 自动化已成为现代制造业的基石,而驱动隔离器作为关键组件,在提升效率、精度和可靠性方面起到了不可或缺的作用。随着工业技术不断革新,驱动隔离器正助力自动化生产设备适应新兴趋势,并推动行业未来的发展。本文将探讨自动化的核心趋势及驱动隔离器在其中的重要角色。自动化领域的新兴趋势智能工厂的崛起智能工厂已成为自动化生产的新标杆。通过结合物联网(IoT)、人工智能(AI)和机器学习(ML),智能工厂实现了实时监控和动态决策。驱动隔离器在其中至关重要,它确保了传感器、执行器和控制单元之间的信号完整性,同时提供高
    腾恩科技-彭工 2025-01-03 16:28 166浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 93浏览
  • 随着市场需求不断的变化,各行各业对CPU的要求越来越高,特别是近几年流行的 AIOT,为了有更好的用户体验,CPU的算力就要求更高了。今天为大家推荐由米尔基于瑞芯微RK3576处理器推出的MYC-LR3576核心板及开发板。关于RK3576处理器国产CPU,是这些年的骄傲,华为手机全国产化,国人一片呼声,再也不用卡脖子了。RK3576处理器,就是一款由国产是厂商瑞芯微,今年第二季推出的全新通用型的高性能SOC芯片,这款CPU到底有多么的高性能,下面看看它的几个特性:8核心6 TOPS超强算力双千
    米尔电子嵌入式 2025-01-03 17:04 48浏览
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 110浏览
  •     为控制片内设备并且查询其工作状态,MCU内部总是有一组特殊功能寄存器(SFR,Special Function Register)。    使用Eclipse环境调试MCU程序时,可以利用 Peripheral Registers Viewer来查看SFR。这个小工具是怎样知道某个型号的MCU有怎样的寄存器定义呢?它使用一种描述性的文本文件——SVD文件。这个文件存储在下面红色字体的路径下。    例:南京沁恒  &n
    电子知识打边炉 2025-01-04 20:04 82浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦