基于STM32F103的FreeRTOS系列(七)·任务创建·列表的使用超详细解析

news2025/1/10 21:37:07

目录

1.  列表和列表项

1.1  列表和列表项简介

1.1.1  列表

1.1.2  列表项

1.1.3  迷你列表项

1.1.4  列表与列表项关系图

1.2  列表初始化

1.3  列表项的初始化

1.4  列表项的插入函数

1.5  列表项的末尾插入

1.6  列表项的删除

1.7  列表的遍历


1.  列表和列表项

1.1  列表和列表项简介

1.1.1  列表

        是 FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪 FreeRTOS中的任务。每个列表项有前驱结点指针prev,同时又有后继结点指针next,这样,双向循环链表的增删改查非常方便,动态改变,节省内存!

C语言菜鸟入门·数据结构·链表超详细解析-CSDN博客

typedef struct xLIST
{
    	listFIRST_LIST_INTEGRITY_CHECK_VALUE			//①校验值
    	volatile UBaseType_t uxNumberOfItems;			//②列表中的列表项数量
   		ListItem_t * configLIST_VOLATILE pxIndex		//③用于遍历列表项的指针
    	MiniListItem_t xListEnd							//④末尾列表项
    	listSECOND_LIST_INTEGRITY_CHECK_VALUE			//⑤校验值
} List_t;

        在上述结构体中, 包含了两个宏,这两个宏是确定的已知常量,FreeRTOS 通过检查这两个常量的值,来判断列表的数据在程序运行过程中,是否遭到破坏(数据的完整性) ,该功能一般用于调试。因为默认是不开启的,所以我们并没有用到这两个宏(如上①和⑤)。

        成员 uxNumberOfItems,用于记录列表中列表项的个数(不包含 xListEnd)。

注意:xListEnd 就是排在某位的。我们的列表里面有很多列表项,可以挂载很多列表项,但是 xListEnd 这个列表项总是排在最底,也就是最末尾的位置。记住这个特性就行了。

        成员 pxIndex 用于指向列表中的某个列表项,一般用于遍历列表中的所有列表项。

        成员变量 xListEnd 是一个迷你列表项,排在最末尾,我们也称它为末尾列表项。


1.1.2  列表项

        就是存放在列表中的项目 。意思它就是列表的子集。每一个列表项关联着一个任务,在列表项里面有一个成员变量,用来存放任务控制块,描述这个任务的相关属性信息。

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	/* ④上一个列表项 */
    	void * pvOwner										/* ⑤列表项的拥有者 */
    	struct xLIST * configLIST_VOLATILE pxContainer; 	/* ⑥列表项所在列表 */
   		listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE			/* ⑦用于检测列表项的数据完整性,我们并没有使用 */
};
typedef struct xLIST_ITEM ListItem_t; 	

        同列表该子集目录下的①和⑦用于检测列表项的数据完整性,我们并没有使用,一般情况下不用去管它;

        ②是代表列表项的值;

        ③和④通过指向下一个列表项和上一个列表项可以实现类似于双向链表的作用;

        ⑤记录列表项归谁拥有的;创建了这个列表,他就会指向这个这个任务的任务控制块,表示任务控制块的任务节点ListItem_t xStateListItem;归任务所有。

        ⑥记录列表项归哪一个列表。指向了就绪列表。


1.1.3  迷你列表项

        有些情况下,不需要列表项那么全的功能,可以使用迷你列表项,省内存。迷你列表项仅用于标记列表的末尾和挂载其他插入列表中的列表项,挂载如何理解:列表初始化的时候,只有一个迷你列表项,当插入新的列表项时,就把新的列表项对应的两只手(前驱指针和后继指针)牵上迷你列表项即可!迷你列表项没有存储数据,不会存储实际的任务数据信息,因此不需要成员变量 pxOwner 和 pxContainer,以节省内存开销!

struct xMINI_LIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE (1)
    configLIST_VOLATILE TickType_t xItemValue; (2)
    struct xLIST_ITEM * configLIST_VOLATILE pxNext; (3)
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; (4)
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

(1)不使用;

(2)代表列表项的值;

(3)指向下一个列表项;

(4)指向上一个列表项。


1.1.4  列表与列表项关系图

1.2  列表初始化

        初始化列表就是为列表的结构体成员赋初值,此时列表只有一个末尾列表项。

void vListInitialise( List_t * const pxList )
{
    pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); (1)
    pxList->xListEnd.xItemValue = portMAX_DELAY; (2)
    pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); (3)
    pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd ); (4)
    pxList->uxNumberOfItems = ( UBaseType_t ) 0U; (5)
    listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList ); (6)
    listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList ); (7)
}

下面我们来逐步分析: 

pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );

        xListEnd 用来表示列表的末尾,而 pxIndex 表示列表项的索引号,此时列表只有一个列表项,那就是 xListEnd,所以 pxIndex 指向 xListEnd。


pxList->xListEnd.xItemValue = portMAX_DELAY; 

        xListEnd 的列表项值初始化为 portMAX_DELAY。

需要注意: portMAX_DELAY 是个宏,在文件portmacro.h 中有定义。根据所使用的 MCU 的不同, portMAX_DELAY 值也不相同,可以为 0xffff或者 0xffffffffUL,本教程中为 0xffffffffUL。


pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );

        初始化列表项 xListEnd 的 pxNext 变量,因为此时列表只有一个列表项 xListEnd,因此 下一个指定的列表项pxNext 只能指向自身。


pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );

        同理,初始化 xListEnd 的 pxPrevious 变量,指向 xListEnd 自身。


pxList->uxNumberOfItems = ( UBaseType_t ) 0U;

        初始化时,列表中的列表项数量为0,不包含末尾列表项xListEnd


    listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList ); 
    listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList ); 

        用于检测列表数据完整性的校验值,只有当指定的宏定义为1,才会使用到,平常情况下我们并没有使用到。

1.3  列表项的初始化

void vListInitialiseItem( ListItem_t * const pxItem )
{
    //初始化 pvContainer 为 NULL
    pxItem->pvContainer = NULL; 
    //初始化用于完整性检查的变量,如果开启了这个功能的话。
    listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
    listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}

        列表项的初始化很简单,只是将列表项成员变量 pvContainer 初始化为 NULL,并且给用于完整性检查的变量赋值。

        在这里我们有个疑问:列表项的成员变量比列表要多,怎么初始化函数就这么短?其他的成员变量什么时候初始化呢?

        这是因为列表项要根据实际使用情况来初始化,比如任务创建函数 xTaskCreate()就会对任务堆栈中的 xStateListItem 和 xEventListItem 这两个列表项中的其他成员变量在做初始化

1.4  列表项的插入函数

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )

参数:pxList列表项要插入的函数
pxNewListItem要插入的列表项
返回值:
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    ListItem_t *pxIterator;
    const TickType_t xValueOfInsertion = pxNewListItem->xItemValue; //获取要插入的列表项值,即列表项成员变量 xItemValue 的值,因为要根据这个值来确定列表项要插入的位置

    listTEST_LIST_INTEGRITY( pxList ); //用来检查列表和列表项的完整性的。其实就是检查列表和列表项中用于完整性检查的变量值是否被改变。这些变量的值在列表和列表项初始化的时候就被写入了,这两行代码需要实现函数 configASSERT()

    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

    //要插入列表项,第一步就是要获取该列表项要插入到什么位置!如果要插入的列表项的值等于 portMAX_DELAY,也就是说列表项值为最大值, 这种情况最好办了,要插入的位置就是列表最末尾了
    if( xValueOfInsertion == portMAX_DELAY ) 
    {
        //获取要插入点
        pxIterator = pxList->xListEnd.pxPrevious; //注意!列表中的 xListEnd 用来表示列表末尾,在初始化列表的时候xListEnd的列表值也是portMAX_DELAY, 此时要插入的列表项的列表值也是portMAX_DELAY。这两个的顺序该怎么放啊?通过这行代码可以看出要插入的列表项会被放到 xListEnd 前面
    }
    else
    {
        //要插入的列表项的值如果不等于 portMAX_DELAY 那么就需要在列表中一个一个的找自己的位置,这个 for 循环就是找位置的过程,当找到合适列表项的位置的时候就会跳出。由于这个 for 循环是用来寻找列表项插入点的,所以 for 循环体里面没有任何东西。 这个查找过程是按照升序的方式查找列表项插入点的
        for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->\ 
        pxNext->xItemValue <=xValueOfInsertion; pxIterator = pxIterator->pxNext )
        {
        //空循环,什么也不做!
        }
    }
    //将列表项插入到列表中, 插入过程和数据结构中双向链表的插入类似
    pxNewListItem->pxNext = pxIterator->pxNext; 
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
    pxNewListItem->pxPrevious = pxIterator;
    pxIterator->pxNext = pxNewListItem;
    pxNewListItem->pvContainer = ( void * ) pxList; //列表项已经插入到列表中了,那么列表项的成员变量 pvContainer 也该记录此列表项属于哪个列表的了
    ( pxList->uxNumberOfItems )++; // 列表的成员变量 uxNumberOfItems 加一,表示又添加了一个列表项
}

上图片来理解一下:

        首先,我们现规定一个列表,此时列表项的数值uxNumberOfItems=0;

        初始化列表项 xListEnd 的 pxNext 变量,因为此时列表只有一个列表项 xListEnd,因此 下一个指定的列表项pxNext 只能指向自身,初始化 xListEnd 的 pxPrevious 变量,指向 xListEnd 自身:

        然后我们在该列表内插入一个列表项,值为40,此时可以看到此时uxNumberOfItems=1,代表有一个列表项,此时pxNext和pxPrevious指向下一个列表项,而下一个列表项的pxNext和pxPrevious指向xListEnd,形成类似于环形链表的结构,并且此时pvContainer=List代表此列表项ListItem1是List的列表项:

        然后我们在该列表内在插入一个列表项,值为60:

        我们将上图简化:

        可以配合这样图,列表类似于链表,每个列表都有指向下一个结点的pxNext,也都有指向上一个结点的pxPrevious,这样形成了类似于双向链表的结构:

         然后我们在该列表内在插入一个列表项,值为50,因为列表是按照升序排列的方式,那么50就需要在40和60之间:

这里可以去看一下数据结构的双向链表,更容易理解。

1.5  列表项的末尾插入

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
参数:pxList列表项要插入的函数
pxNewListItem要插入的列表项
返回值:
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    ListItem_t * const pxIndex = pxList->pxIndex;

    //与下面的一行代码完成对列表和列表项的完整性检查
    listTEST_LIST_INTEGRITY( pxList ); 
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

    //将要插入的列表项插入到列表末尾
    pxNewListItem->pxNext = pxIndex; 
    pxNewListItem->pxPrevious = pxIndex->pxPrevious;
    mtCOVERAGE_TEST_DELAY();
    pxIndex->pxPrevious->pxNext = pxNewListItem;
    pxIndex->pxPrevious = pxNewListItem;

    //标记新的列表项 pxNewListItem 属于列表 pxList
    pxNewListItem->pvContainer = ( void * ) pxList; 

    //记录列表中的列表项数目的变量加一,更新列表项数目
    ( pxList->uxNumberOfItems )++; 
}

1.6  列表项的删除

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
    //得到此列表项处于哪个列表中
    List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer; 

    //与下面一行完成列表项的删除,其实就是将要删除的列表项的前后两个列表项“连接”在一起
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious; 
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

    mtCOVERAGE_TEST_DELAY();
    if( pxList->pxIndex == pxItemToRemove )
    {
        pxList->pxIndex = pxItemToRemove->pxPrevious; //如果列表的 pxIndex 正好指向要删除的列表项,那么在删除列表项以后要重新给pxIndex 找个“对象”啊,这个新的对象就是被删除的列表项的前一个列表项
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
    pxItemToRemove->pvContainer = NULL; //被删除列表项的成员变量 pvContainer 清零
    ( pxList->uxNumberOfItems )--;
    return pxList->uxNumberOfItems; //返回新列表的当前列表项数目
}

1.7  列表的遍历

        每调用一次这个函数列表的 pxIndex 变量就会指向下一个列表项,并且返回这个列表项的 pxOwner变量值。 这个函数本质上是一个宏,这个宏在文件 list.h 中如下定义:

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )               \ (1)
{                                                                  \
    List_t * const pxConstList = ( pxList );                       \
    ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;   \ (2)
    if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )\ (3)
    {                                                                         \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \ (4)
}                                                             \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;                         \ (5)
}

(1)pxTCB 用来保存 pxIndex 所指向的列表项的 pvOwner 变量值,也就是这个列表项属于谁的?通常是一个任务的任务控制块。 pxList 表示要遍历的列表。
(2)列表的 pxIndex 变量指向下一个列表项。
(3)如果 pxIndex 指向了列表的 xListEnd 成员变量,表示到了列表末尾。
(4)如果到了列表末尾的话就跳过 xListEnd, pxIndex 再一次重新指向处于列表头的列表项,这样就完成了一次对列表的遍历。
(5)将 pxIndex 所指向的新列表项的 pvOwner 赋值给 pxTCB。

FreeRTOS_时光の尘的博客-CSDN博客

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

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

相关文章

Open3D 三维重建-Marching Cubes (行进立方体)

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1步骤 2.1.2函数代码 2.2完整代码 三、实现效果 3.1原始点云 3.2重建后点云 Open3D点云算法汇总及实战案例汇总的目录地址&#xff1a; Open3D点云算法与点云深度学习案例汇总&#…

基于Flask框架的豆瓣电影实时数据分析可视化系统【自动爬虫、数据库、Pyecharts】

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍数据抓取数据存储可视化前后端交互登陆界面注册界面数据更新后展示每文一语 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 项目介绍 本项目基于Py…

JavaEE: 线程安全问题的解决方案(synchronized)

发生原因 要想解决线程安全问题,那么我们首先得知道线程安全问题为什么会发生. 发生原因: 线程在操作系统中是"随机调度,抢占式执行的"[根本原因].多个线程,同时修改同一个变量修改操作不是"原子"的内存可见性问题指令重排序 解决方案 原因1和2,我们很…

基于YOLOv8的茶叶病变检测系统

基于YOLOv8的茶叶病变检测系统 (价格85) 包含 [Algal Leaf Spot, Brown Blight, Gray Blight, Healthy, Helopeltis, Red Leaf Spot] 6个类 翻译&#xff1a; [藻类叶斑病&#xff0c;褐疫病&#xff0c;灰疫病&#xff0c;健康&#xff0c;茶角盲蝽&#xff0c; 红叶斑…

08.SQL注入-下(超详细!!!)

1、Access注入 1.1 判断是否存在注入漏洞 ?id10 and 11 //不报错 ?id10 and 12 //报错1.2 判断字段数 ?id10 order by 1 ... ?id10 order by 7 //不报错 ?id10 order by 8 //报错 说明有7个字段1.3 猜表名 ?id10 and exists(select * from administrator) …

IP协议解析

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

C语言宏定义的使用

文章目录 &#x1f34a;自我介绍&#x1f34a;宏定义&#x1f34a;宏函数&#x1f34a;嵌入式开发常用do...while(0)&#x1f34a;字符串化运算符 ‘ # ’&#x1f34a;不定参数宏 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏&#xf…

SQL报错注入之floor

目录 1.简述 2.关键函数说明 1.rand函数 2.floor&#xff08;rand&#xff08;0&#xff09;*2&#xff09;函数 3.group by 函数 4.count&#xff08;*&#xff09;函数 3.报错分析 4.报错流程 4.1寻找注入点 4.2爆数据库名 4.3爆表名 4.4爆字段名 4.5查询数据 1.…

32、Python之面向对象:对象的表示,再论Python是dict包括语法糖

引言 在前面介绍Python容器的时候&#xff0c;我们曾经用过这种夸张的表述&#xff0c;“Python就是包裹在一堆语法糖中的字典”。虽然夸张&#xff0c;其实更多的是为了突出Python中dict的强大之处。今天这篇文章&#xff0c;打算看下Python中类对象、实例对象的表示及内存管理…

甄选范文“论负载均衡技术在Web系统中的应用”软考高级论文系统架构设计师论文

论文真题 负载均衡技术是提升Web系统性能的重要方法。利用负载均衡技术, 可将负载(工作任务) 进行平衡、分摊到多个操作单元上执行, 从而协同完成工作任务, 达到提升Web系统性能的目的。 请围绕“负载均衡技术在Web系统中的应用”论题, 依次从以下三个方面进行论述。 1.…

自动化测试 — selenium + Java

什么是自动化测试 将人为驱动的测试行为转化为机器执行的过程。 自动化测试包括UI 自动化&#xff0c;接口自动化&#xff0c;单元测试自动化。按照这个金字塔模型来进行自动化测试规划&#xff0c;可以产生最佳的自贡话测试产出投入比&#xff08;ROI &#xff09;&#xff0c…

智能氮气柜如何为存储应用提供稳定和安全的环境?

智能氮气柜在保持内部环境的严格控制下&#xff0c;如何为各类高要求的存储应用提供一个稳定和安全的环境&#xff1f; 智能氮气柜内部安装高精度温湿度传感器&#xff0c;持续监测内部环境状况。通过外部连接的氮气供应源&#xff0c;向柜内注入高纯度氮气&#xff0c;当检测到…

k8s—ingress应用

一、ingress和ingress-controller ingress对象&#xff1a; 指的是k8s中的⼀个api对象/资源对象&#xff0c;⼀般⽤yaml配置。作⽤是定义请求如何转发到service的规则&#xff0c;可以理解为配置模板。 ingress-controller&#xff1a; 具体实现反向代理及负载均衡的程序&…

IO-Link通信笔记(十七)——可任意MCU平台移植的面向对象程序设计的IO-Link从站协议栈与接口代码生成和监控上位机与便携式通信主站

一、可任意MCU平台移植的面向对象程序设计的IO-Link从站协议栈 图形化界面与驱动代码库生成功能&#xff0c;是现如今几大半导体芯片供应商选择向广大开发人员推荐的主流开发方式&#xff0c;例如意法的cube-mx。开发人员可以通过这些软件针对所使用芯片的相关外设资源&#xf…

缺失值处理方法:代数/统计/机器学习算法补全数据(附Python-sklearn代码精美可视化绘图)

注&#xff1a;本期的删除或插补方法主要针对连续数据&#xff0c;时间序列数据的插补在后续关于时间序列的博客中讲明。参考鸢尾花丛书&#xff0c;链接如下&#xff1a; 参考书籍及源代码链接https://github.com/Visualize-ML 博客是选出自己感觉用的到的精炼部分加自己的理…

春秋云境 | 文件上传 | CVE-2022-30887

目录 靶标介绍 开启靶场 上传一句话木马 蚁剑连接 找到 flag 靶标介绍 多语言药房管理系统 (MPMS) 是用 PHP 和 MySQL 开发的, 该软件的主要目的是在药房和客户之间提供一套接口&#xff0c;客户是该软件的主要用户。该软件有助于为药房业务创建一个综合数据库&#xff0…

eclipse无法使用jdk1.6编译老项目

主要修改两个地方的配置&#xff1a; 1、eclipse中配置的maven版本不能过高&#xff0c;亲测3.2.5版本是好使的。 2、修改eclipse安装目录下的eclipse.ini文件&#xff0c;将其中的-Dosgi.requiredJavaVersion更改为1.6即可&#xff0c;我得默认是1.7 最后附上maven安装包&…

AGV一体式ARM智能控制主机如何替代传统PLC、工控机等方案

工业自动化的不断发展&#xff0c;AGV&#xff08;自动导引车&#xff09;作为一种重要的物流搬运设备&#xff0c;在各个领域得到了广泛的应用。而 AGV 的控制主机是其核心部件之一&#xff0c;直接影响着 AGV 的性能和稳定性。传统的 AGV 控制主机通常采用 x86 工控机交换机i…

【密码学】密码协议的分类:②认证协议

密码协议的分类有很多种方式&#xff0c;这里我采取的是基于协议实现的目的来分类。可以将密码协议分成三类&#xff1a;认证协议、密钥建立协议、认证密钥建立协议。 一、认证协议是什么&#xff1f; 认证协议都在认证些什么东西呢&#xff1f;认证一般要认证三个东西&#x…

防止老年痴呆的小学题

直角三角形的周长为16,斜边长为7,求三角形的面积(不使用勾股定理) 答案为(9*9-7*7)/4