首页 > 其他分享 >基于STM32F103的FreeRTOS系列(七)·任务创建·列表的使用超详细解析

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

时间:2024-08-08 08:58:55浏览次数:13  
标签:STM32F103 FreeRTOS pxIndex xListEnd 插入 pxNext pxList 列表

目录

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博客

标签:STM32F103,FreeRTOS,pxIndex,xListEnd,插入,pxNext,pxList,列表
From: https://blog.csdn.net/MANONGDKY/article/details/140922342

相关文章

  • 如何迭代并将字典的所有值放入另一个具有不同键的字典列表中
    我正在尝试迭代字典列表并获取2个键的值,并将这些值写入另一个具有另一个键的字典列表。下面是我试图写入的输出列表。post_obj=[{"city":place["place"],"display":place["seq"]}]输入:data=[{'place':'SanJose',......
  • 如何修复当我将组合放入这样的列表 ([ 地址 )] 时,它说它是空的并且不打印任何内容的错
    我使用python生成74个字符串的组合,并使用两个组合字符串,即“0123456789abcdef”和“QQQQQQ33”。我希望QQQQQ33位于括号[]中,以便在使用它时,它使用完整的字符串,而不是生成字符串与0123456789abcdef的所有组合。每当我使用括号时,这就像是唯一的方法它完整​​地打印......
  • FreeRTOS中任务创建函数xTaskCreate()的解析
    目录函数xTaskCreate()函数prvInitialiseNewTask()函数pxPortInitialiseStack()函数prvAddNewTaskToReadyList()总结函数xTaskCreate()此函数用于使用动态的方式创建任务,任务的任务控制块以及任务的栈空间所需的内存,均由FreeRTOS从FreeRTOS管理的堆中分配,若使用此函数,......
  • 将iap的接收升级数据部分移植到freertos系统中
    目录前言二、移植过程1.在任务中添加代码三、遇到的问题1.boot跳转卡死在TIM6的中断使能2.代码进入app后却卡死在boot的.s文件的B.处总结前言        在完成基于TCP服务器的iap裸机程序后得到一个新的任务,该任务让我把iap中通过TCP接收数据的代码移植......
  • 处理程序“aspx”在其模块列表中有一个错误模块“ManagedPipelineHandler”
    原文链接:https://www.cnblogs.com/mingcaoyouxin/p/3926800.html开发web项目时需要安装IIS,在安装好IIS的Windows7本上发布asp.net网站时,web程序已经映射到了本地IIS上,但运行如下错误提示“处理程序“PageHandlerFactory-Integrated”在其模块列表中有一个错误模块“ManagedPip......
  • 基于vscode搭建freertos环境
    前言目前网上windows仿真freertos的资料都是比较久远的,不太适合现有的开发,因此重新整理了一下资料.目标:使用Vscode进行FreeRTOS开发和仿真.关键词:freertos,vscode,llvm,cmake,windows环境配置编译器目前使用的是llvm-MinGW-msvcrt:Releases·mstorsjo/llvm-mingw(g......
  • 织梦DEDECMS列表页首页怎么跟其它页使用不同模板
    有些时候我们需要使列表页的首页跟第二页以及后面的页面的样式不同,修改dede:list标签又很难达到理想的效果,那么织梦猫就为大家介绍一个最简单的办法,就是为首页单独指定一个模板页,其余页面则调用另一个模板页。修改的办法如下:打开include目录下的arc.listview.class.php文件,找到D......
  • 织梦列表页分页错位(分页显示为竖排)怎么办
    <divclass="dede_pages"><ulclass="pagelist">{dede:pagelistlistitem="info,index,end,pre,next,pageno,option"listsize="5"/}</ul></DIV>css样式代码.dede_pages{text-align:right;}.ded......
  • QT解析读取XML文件并显示在列表视图里
      背景:本地用数据库管理用户数据不方便,需要手动增删查改账户,存在安全风险,两个方案可供替代:1.调用接口来获取用户信息json,通过软件解析json字符串提取用户账号信息。2.直接跳过调用接口那一步,选择xml文件路径并解析。(由于第一种方案行不通,故使用第二种)步骤一:界面设计添加一......
  • 织梦dedecms调用文章列表时候判断文章自定义属性
    有时候我们需要通过判断文章的属性来给相应的属性以相应的样式,例如为推荐的文章添加推荐的标志等等。例如以下代码就可以判断出文章是否是推荐和图片这两个属性,并作不同的样式输出:[field:arrayrunphp=&#39;yes&#39;]if(@me[&#39;flag&#39;]==&#39;c,p&#39;)@me=&#39;<em>......