野火电子论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 6895|回复: 13

[freertos] 【连载】从单片机到操作系统⑤——FreeRTOS列表&列表项的源码解读

[复制链接]
发表于 2018-6-12 12:16:56 | 显示全部楼层 |阅读模式
FreeRTOS列表&列表项的源码解读
     第一次看列表与列表项的时候,感觉很像是链表,虽然我自己的链表也不太会,但是就是感觉很像。
     FreeRTOS中,列表与列表项使用得非常多,是FreeRTOS的一个数据结构,学习过数据结构的同学都知道,数据结构能使我们处理数据更加方便快速,能快速找到数据,在FreeRTOS中,这种列表与列表项更是必不可少的,能让我们的系统跑起来更加流畅迅速。

     言归正传,FreeRTOS中使用了大量的列表(List)与列表项(Listitem),在FreeRTOS调度器中,就是用到这些来跟着任务,了解任务的状态,处于挂起、阻塞态、还是就绪态亦或者是运行态。这些信息都会在各自任务的列表中得到。
看任务控制块(tskTaskControlBlock)中的两个列表项:
[mw_shl_code=c,false]1ListItem_t xStateListItem; / * <任务的状态列表项目引用的列表表示该任务的状态(就绪,已阻止,暂停)。*/
2ListItem_t xEventListItem; / * <用于从事件列表中引用任务。*/[/mw_shl_code]
一个是状态的列表项,一个是事件列表项。他们在创建任务就会被初始化,列表项的初始化是根据实际需要来初始化的,下面会说。

FreeRTOS列表&列表项的结构体  
   既然知道列表与列表项的重要性,那么我们来解读FreeRTOS中的list.clist.h的源码吧。从头文件lsit.h开始,看到定义了一些结构体:
[mw_shl_code=c,false]1struct xLIST_ITEM
2{
3listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE / * <如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。* /
4configLIST_VOLATILE TickType_t xItemValue; / * <正在列出的值。在大多数情况下,这用于按降序对列表进行排序。 * /
5struct xLIST_ITEM * configLIST_VOLATILE pxNext; / * <指向列表中下一个ListItem_t的指针。 * /
6struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; / * <指向列表中前一个ListItem_t的指针。 * /
7void * pvOwner; / * <指向包含列表项目的对象(通常是TCB)的指针。因此,包含列表项目的对象与列表项目本身之间存在双向链接。 * /
8void * configLIST_VOLATILE pvContainer; / * <指向此列表项目所在列表的指针(如果有)。 * /
9listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE / * <如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。* /
10};
11typedef struct xLIST_ITEM ListItem_t; / *由于某种原因,lint希望将其作为两个单独的定义。 * /[/mw_shl_code]
列表项结构体的一些注意的地方:
     xItemValue 用于列表项的排序,类似1234
     pxNext 指向下一个列表项的指针
     pxPrevious指向上(前)一个列表项的指针
这两个指针实现了类似双向链表的功能
     pvOwner 指向包含列表项目的对象(通常是任务控制块TCB)的指针。因此,包含列表项目的对象与列表项目本身之间存在双向链接。
     pvContainer记录了该列表项属于哪个列表,说白点就是这个儿子是谁生的。。。

     同时定义了一个MINI的列表项,MINI列表项是删减版的列表项,因为很多时候不需要完全版的列表项。就不用浪费那么多内存空间了,这或许就是FreeRTOS是轻量级操作系统的原因吧,能省一点是一点。MINI列表项:
[mw_shl_code=c,false]1struct xMINI_LIST_ITEM
2{
3    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
4    configLIST_VOLATILE TickType_t xItemValue;
5    struct xLIST_ITEM * configLIST_VOLATILE pxNext;
6    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
7};
8typedef struct xMINI_LIST_ITEM MiniListItem_t;[/mw_shl_code]
  再定义了一个列表,可能看到这里,一些同学已经蒙了,列表与列表项是啥关系啊,按照杰杰的理解,是类似父子关系的,一个列表中,包含多个列表项,就像一个父亲,生了好多孩子,而列表就是父亲,列表项就是孩子。
[mw_shl_code=c,false]1typedef struct xLIST
2{
3listFIRST_LIST_INTEGRITY_CHECK_VALUE / * <如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。* /
4configLIST_VOLATILE UBaseType_t uxNumberOfItems;
5ListItem_t * configLIST_VOLATILE pxIndex; / * <用于遍历列表。 指向由listGET_OWNER_OF_NEXT_ENTRY()调用返回的后一个列表项。*/
6MiniListItem_t xListEnd; / * <List item包含最大可能的项目值,这意味着它始终在列表的末尾,因此用作标记。*/
7listSECOND_LIST_INTEGRITY_CHECK_VALUE / * <如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。* /
8} List_t;[/mw_shl_code]
列表的结构体中值得注意的是:
     uxNumberOfItems是用来记录列表中列表项的数量的,就是记录父亲有多少个儿子,当然女儿也行~。
     pxIndex是索引编号,用来遍历列表的,调用宏listGET_OWNER_OF_NEXT_ENTRY()之后索引就会指向返回当前列表项的下一个列表项。
     xListEnd指向的是最后一个列表项,并且这个列表项是MiniListItem属性的,是一个迷你列表项。

列表的初始化  函数:
[mw_shl_code=c,true]1void vListInitialise( List_t * const pxList )
2{
3    pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );           /*lint The mini list structure is used as the list end to save RAM.  This is checked and valid. */
4    pxList->xListEnd.xItemValue = portMAX_DELAY;
5    pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );   /*lint The mini list structure is used as the list end to save RAM.  This is checked and valid. */
6    pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/*lint The mini list structure is used as the list end to save RAM.  This is checked and valid. */
7    pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
8    listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
9    listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
10}[/mw_shl_code]
将列表的索引指向列表中的xListEnd,也就是末尾的列表项(迷你列表项)
     列表项的xItemValue数值为portMAX_DELAY,也就是0xffffffffUL,如果在16位处理器中为0xffff
     列表项的pxNextpxPrevious这两个指针都指向自己本身xListEnd
      初始化完成的时候列表项的数目为0个。因为还没添加列表项嘛~


列表项的初始化 函数:
[mw_shl_code=c,false]1void vListInitialiseItem( ListItem_t * const pxItem )
2{
3    /* Make sure the list item is not recorded as being on a list. */
4    pxItem->pvContainer = NULL;
5    /* Write known values into the list item if
6    configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
7    listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
8    listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
9}[/mw_shl_code]
  只需要让列表项的pvContainer指针指向NULL即可,这样子就使得列表项不属于任何一个列表,因为列表项的初始化是要根据实际的情况来进行初始化的。
  例如任务创建时用到的一些列表项初始化:
[mw_shl_code=c,false]1pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
2pxNewTCB->uxPriority = uxPriority;
3pxNewTCB->uxBasePriority = uxPriority;
4pxNewTCB->uxMutexesHeld = 0;
5
6vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
7vListInitialiseItem( &( pxNewTCB->xEventListItem ) );[/mw_shl_code]
  又或者是在定时器相关的初始化中:
[mw_shl_code=c,false]1pxNewTimer->pcTimerName = pcTimerName;
2pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;
3pxNewTimer->uxAutoReload = uxAutoReload;
4pxNewTimer->pvTimerID = pvTimerID;
5pxNewTimer->pxCallbackFunction = pxCallbackFunction;
6
7vListInitialiseItem( &( pxNewTimer->xTimerListItem ) );
[/mw_shl_code]
列表项的末尾插入
  函数:
[mw_shl_code=c,false]1void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
2{
3    ListItem_t * const pxIndex = pxList->pxIndex;
4    listTEST_LIST_INTEGRITY( pxList );
5    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
6    listGET_OWNER_OF_NEXT_ENTRY(). */
7    pxNewListItem->pxNext = pxIndex;    //  1
8    pxNewListItem->pxPrevious = pxIndex->pxPrevious;    //  2
9    /* Only used during decision coverage testing. */
10    mtCOVERAGE_TEST_DELAY();
11    pxIndex->pxPrevious->pxNext = pxNewListItem;        //  3
12    pxIndex->pxPrevious = pxNewListItem;                //  4
13    /* Remember which list the item is in. */
14    pxNewListItem->pvContainer = ( void * ) pxList;
15    ( pxList->uxNumberOfItems )++;
16}[/mw_shl_code]
传入的参数:
         pxList:列表项要插入的列表。
         pxNewListItem:要插入的列表项是什么。
         从末尾插入,那就要先知道哪里是头咯,我们在列表中的成员pxIndex就是用来遍历列表项的啊,那他指向的地方就是列表项的头,那么既然FreeRTOS中的列表很像数据结构中的双向链表,那么,我们可以把它看成一个环,是首尾相连的,那么函数中说的末尾,就是列表项头的前一个,很显然其结构图应该是这样子的(初始化结束后pxIndex指向xListEnd):

为什么是这样子的呢,一句句代码来解释:
一开始:
[mw_shl_code=c,false]1ListItem_t * const pxIndex = pxList->pxIndex;保存了一开始的索引列表项(xListEnd)的指向。
1pxNewListItem->pxNext = pxIndex;         //  1新列表项的下一个指向为索引列表项,也就是绿色的箭头。
1pxNewListItem->pxPrevious = pxIndex->pxPrevious;      //  2刚开始我们初始化完成的时候pxIndex->pxPrevious的指向为自己xListEnd,那么xNewListItem->pxPrevious的指向为xListEnd。如2紫色的箭头。
1pxIndex->pxPrevious->pxNext = pxNewListItem;             //  3索引列表项(xListEnd)的上一个列表项还是自己,那么自己的下一个列表项指向就是指向了pxNewListItem。
1pxIndex->pxPrevious = pxNewListItem;                              //  4这句就很容易理解啦。如图的4橙色的箭头。[/mw_shl_code]
插入完毕的时候标记一下新的列表项插入了哪个列表,并且将uxNumberOfItems进行加一,以表示多了一个列表项。
为什么源码要这样子写呢?因为这只是两个列表项,一个列表含有多个列表项,那么这段代码的通用性就很强了。


看看是不是按照源码中那样插入呢?

列表项的插入
源码:
[mw_shl_code=c,false]1void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
2{
3ListItem_t *pxIterator;
4const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
5    listTEST_LIST_INTEGRITY( pxList );
6    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
7    if( xValueOfInsertion == portMAX_DELAY )
8    {
9        pxIterator = pxList->xListEnd.pxPrevious;
10    }
11    else
12    {
13        for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 The mini list structure is used as the list end to save RAM.  This is checked and valid. */
14        {
15            /* There is nothing to do here, just iterating to the wanted
16            insertion position. */
17        }
18    }
19    pxNewListItem->pxNext = pxIterator->pxNext;
20    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
21    pxNewListItem->pxPrevious = pxIterator;
22    pxIterator->pxNext = pxNewListItem;
23    /* Remember which list the item is in.  This allows fast removal of the
24    item later. */
25    pxNewListItem->pvContainer = ( void * ) pxList;
26    ( pxList->uxNumberOfItems )++;
27}[/mw_shl_code]
传入的参数:
     pxList:列表项要插入的列表。
     pxNewListItem:要插入的列表项是什么。

pxList决定了插入哪个列表,pxNewListItem中的xItemValue值决定了列表项插入列表的位置。
1ListItem_t *pxIterator;  
2const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;  定义一个辅助的列表项pxIterator,用来迭代找出插入新列表项的位置,并且保存获取要插入的列表项pxNewListItemxItemValue
  如果打开了列表项完整性检查,就要用户实现configASSERT(),源码中有说明。
  既然是要插入列表项,那么肯定是要知道列表项的位置了,如果新插入列表项的xItemValue是最大的话(portMAX_DELAY),就直接插入列表项的末尾。否则就需要比较列表中各个列表项的xItemValue的大小来进行排列。然后得出新列表项插入的位置。
1for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext )  就是实现比较的过程。
与上面的从列表项末尾插入的源码一样,FreeRTOS的代码通用性很强,逻辑思维也很强。

         如果列表中列表项的数量为0,那么插入的列表项就是在初始化列表项的后面。
新列表的pxNext指向pxIterator->pxNext,也就是xListEndpxIterator)。
1pxNewListItem->pxNext = pxIterator->pxNext;    xListEndpxIterator)的pxPrevious指向则为pxNewListItem
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
     新列表的(pxPrevious)与(pxNext)列表项指针指向xListEndpxIterator
1pxNewListItem->pxPrevious = pxIterator;
2pxIterator->pxNext = pxNewListItem;与从末尾插入列表项其实是一样的,前提是当前列表中列表项的数目为0

     假如列表项中已经有了元素呢,过程又是不一样的了。

  假设插入的列表项的xItemValue2,而原有的列表项的xItemValue值是3,那么,按照源码,我们插入的列表项是在中间了。而pxIterator则是①号列表项。
插入后的效果:

分析一下插入的过程:
      新的列表项的pxNext指向的是pxIterator->pxNext,也就是③号列表项。
[mw_shl_code=c,false]1pxNewListItem->pxNext = pxIterator->pxNext;     而pxNewListItem->pxNext 即③好列表项的指向上一个列表项指针(pxPrevious)的则指向新插入的列表项,也就是②号列表项了。
1pxNewListItem->pxNext->pxPrevious = pxNewListItem;     新插入列表项的指向上一个列表项的指针pxNewListItem->pxPrevious指向了复制的列表项pxIterator。
1pxNewListItem->pxPrevious = pxIterator;          同理,pxIterator列表项的指向下一个列表项的指针则指向新插入的列表项了pxNewListItem。
1pxIterator->pxNext = pxNewListItem;而其他没改变指向的地方不需改动。[/mw_shl_code]
当插入完成的时候,记录一下新插入的列表项属于哪个列表。并且让该列表下的列表项数目加一。
1pxNewListItem->pvContainer = ( void * ) pxList;
2         ( pxList->uxNumberOfItems )++;

删除列表项    源码:
[mw_shl_code=c,false]1UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
2{
3/* The list item knows which list it is in.  Obtain the list from the list
4item. */
5List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
6    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
7    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
8    /* Only used during decision coverage testing. */
9    mtCOVERAGE_TEST_DELAY();
10    /* Make sure the index is left pointing to a valid item. */
11    if( pxList->pxIndex == pxItemToRemove )
12    {
13        pxList->pxIndex = pxItemToRemove->pxPrevious;
14    }
15    else
16    {
17        mtCOVERAGE_TEST_MARKER();
18    }
19    pxItemToRemove->pvContainer = NULL;
20    ( pxList->uxNumberOfItems )--;
21    return pxList->uxNumberOfItems;
22}[/mw_shl_code]
  其实删除是很简单的,不用想都知道,要删除列表项,那肯定要知道该列表项是属于哪个列表吧,pvContainer就是记录列表项是属于哪个列表的。
  删除就是把列表中的列表项从列表中去掉,其本质其实就是把他们的连接关系删除掉,然后让删除的列表项的前后两个列表连接起来就行了,假如是只有一个列表项,那么删除之后,列表就回到了初始化的状态了。
[mw_shl_code=c,false]1 pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
2 pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;这两句代码就实现了将删除列表项的前后两个列表项连接起来。[/mw_shl_code]
按照上面的讲解可以理解这两句简单的代码啦。
  假如删除的列表项是当前索引的列表项,那么在删除之后,列表中的pxIndex就要指向删除列表项的上一个列表项了。
[mw_shl_code=c,true]1if( pxList->pxIndex == pxItemToRemove )
2  {
3      pxList->pxIndex = pxItemToRemove->pxPrevious;
4  } [/mw_shl_code] 当然还要把当前删除的列表项的pvContainer指向NULL,让它不属于任何一个列表,因为,删除的至少关系,其内存是没有释放掉的,假如是动态内存分配的话。
  并且要把当前列表中列表项的数目返回一下。

至此,列表的源码基本讲解完毕。

最后大家还可以了解一下遍历列表的宏,它在list.h文件中:

[mw_shl_code=c,false]1define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                                        \
2{                                                                                            \
3List_t * const pxConstList = ( pxList );                                                    \
4    /* Increment the index to the next item and return the item, ensuring */                \
5    /* we don't return the marker used at the end of the list.  */                          \
6    ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                            \
7    if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )  \
8    {                                                                                       \
9        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                        \
10    }                                                                                       \
11    ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;                                          \
12}[/mw_shl_code]
  这是一个宏,用于列表的遍历,返回的是列表中列表项的pxOwner成员,每次调用这个宏(函数)的时候,其pxIndex索引会指向当前返回列表项的下一个列表项。
本文为杰杰原创,转载请说明出处
【连载】从单片机到操作系统⑤——FreeRTOS列表&列表项的源码解读









回复

使用道具 举报

发表于 2018-6-12 14:11:18 | 显示全部楼层
谢谢分享,回复来学习下
回复 支持 反对

使用道具 举报

发表于 2018-6-21 18:47:24 | 显示全部楼层
回复

使用道具 举报

发表于 2018-6-22 01:42:25 | 显示全部楼层
跟着老大学习FreeRTOS
回复 支持 反对

使用道具 举报

发表于 2018-6-28 13:16:43 | 显示全部楼层
支持杰哥分享。
回复 支持 反对

使用道具 举报

发表于 2018-11-24 16:12:03 | 显示全部楼层
前来学习,谢谢楼主!
回复 支持 反对

使用道具 举报

发表于 2018-12-6 10:27:44 | 显示全部楼层
好贴,谢谢分享
回复 支持 反对

使用道具 举报

发表于 2019-1-6 18:40:40 | 显示全部楼层
谢谢分享,回复来学习下
回复 支持 反对

使用道具 举报

发表于 2019-1-11 21:38:27 | 显示全部楼层
顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶
回复 支持 反对

使用道具 举报

发表于 2019-2-18 13:52:35 | 显示全部楼层
学习学习,谢谢楼主!
回复 支持 反对

使用道具 举报

发表于 2019-2-21 10:13:21 | 显示全部楼层
学习了,多谢分享!
回复 支持 反对

使用道具 举报

发表于 2019-2-21 17:05:22 | 显示全部楼层
6666666666
回复 支持 反对

使用道具 举报

发表于 2020-3-23 11:43:13 | 显示全部楼层
任务创建后,下载到板子中会一直进入.s文件的中断里
回复 支持 反对

使用道具 举报

发表于 2020-3-30 20:17:34 | 显示全部楼层
的点点滴滴ddddddddddddd
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

联系站长|手机版|野火电子官网|野火淘宝店铺|野火电子论坛 ( 粤ICP备14069197号 ) 大学生ARM嵌入式2群

GMT+8, 2024-5-5 06:38 , Processed in 0.059705 second(s), 24 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表