# FreeRTOS列表&列表项的源码解读
   

第一次看列表与列表项的时候,感觉很像是链表,虽然我自己的链表也不太会,但是就是感觉很像。

在`FreeRTOS`中,列表与列表项使用得非常多,是`FreeRTOS`的一个数据结构,学习过数据结构的同学都知道,数据结构能使我们处理数据更加方便快速,能快速找到数据,在`FreeRTOS`中,这种列表与列表项更是必不可少的,能让我们的系统跑起来更加流畅迅速。

言归正传,`FreeRTOS`中使用了大量的列表`(List)`与列表项`(Listitem)`,在`FreeRTOS`调度器中,就是用到这些来跟着任务,了解任务的状态,处于挂起、阻塞态、还是就绪态亦或者是运行态。这些信息都会在各自任务的列表中得到。

看任务控制块`(tskTaskControlBlock)`中的两个列表项:
```js
ListItem_t xStateListItem; / * pxIndex = ( ListItem_t * ) &( pxList->xListEnd );           /*lint The mini list structure is used as the list end to save RAM.  This is checked and valid. */
   pxList->xListEnd.xItemValue = portMAX_DELAY;
   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. */
   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. */
   pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
   listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
   listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
```

将列表的索引指向列表中的`xListEnd`,也就是末尾的列表项`(迷你列表项)`

列表项的`xItemValue`数值为`portMAX_DELAY`,也就是`0xffffffffUL`,如果在16位处理器中则为`0xffff`。

列表项的pxNext与pxPrevious这两个指针都指向自己本身`xListEnd`。

初始化完成的时候列表项的数目为`0`个。因为还没添加列表项嘛~。

![freertos4](https://img2018.cnblogs.com/blog/1834930/201910/1834930-20191015203552302-1801179784.png)

# 列表项的初始化
 函数:
```js
void vListInitialiseItem( ListItem_t * const pxItem )
{
   /* Make sure the list item is not recorded as being on a list. */
   pxItem->pvContainer = NULL;
   /* Write known values into the list item if
   configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
   listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
   listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}
```

只需要让列表项的pvContainer指针指向NULL即可,这样子就使得列表项不属于任何一个列表,因为列表项的初始化是要根据实际的情况来进行初始化的。

例如任务创建时用到的一些列表项初始化:

```js
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
pxNewTCB->uxPriority = uxPriority;
pxNewTCB->uxBasePriority = uxPriority;
pxNewTCB->uxMutexesHeld = 0;

vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
```

又或者是在定时器相关的初始化中:

```js
pxNewTimer->pcTimerName = pcTimerName;
pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;
pxNewTimer->uxAutoReload = uxAutoReload;
pxNewTimer->pvTimerID = pvTimerID;
pxNewTimer->pxCallbackFunction = pxCallbackFunction;

vListInitialiseItem( &( pxNewTimer->xTimerListItem ) );
```

# 列表项的末尾插入
  函数:
```js
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 );
   listGET_OWNER_OF_NEXT_ENTRY(). */
   pxNewListItem->pxNext = pxIndex;    //  1
   pxNewListItem->pxPrevious = pxIndex->pxPrevious;    //  2
   /* Only used during decision coverage testing. */
   mtCOVERAGE_TEST_DELAY();
   pxIndex->pxPrevious->pxNext = pxNewListItem;        //  3
   pxIndex->pxPrevious = pxNewListItem;                //  4
   /* Remember which list the item is in. */
   pxNewListItem->pvContainer = ( void * ) pxList;
   ( pxList->uxNumberOfItems )++;
}
```

传入的参数:

`pxList`:列表项要插入的列表。

`pxNewListItem`:要插入的列表项是什么。

从末尾插入,那就要先知道哪里是头咯,我们在列表中的成员`pxIndex`就是用来遍历列表项的啊,那它指向的地方就是列表项的头,那么既然FreeRTOS中的列表很像数据结构中的双向链表,那么,我们可以把它看成一个环,是首尾相连的,那么函数中说的末尾,就是列表项头的前一个,很显然其结构图应该是下图这样子的(初始化结束后`pxIndex`指向了`xListEnd`):

![freertos5](https://img2018.cnblogs.com/blog/1834930/201910/1834930-20191015203552901-1444002737.png)

为什么是这样子的呢,一句句代码来解释:

一开始:

```js
ListItem_t * const pxIndex = pxList->pxIndex;
```
保存了一开始的索引列表项(`xListEnd`)的指向。
```js
pxNewListItem->pxNext = pxIndex;         //  1
```
新列表项的下一个指向为索引列表项,也就是绿色的箭头。

```js
pxNewListItem->pxPrevious = pxIndex->pxPrevious;    //  2
```
刚开始我们初始化完成的时候`pxIndex->pxPrevious`的指向为自己xListEnd,那么`xNewListItem->pxPrevious`的指向为xListEnd。如2紫色的箭头。

```js
pxIndex->pxPrevious->pxNext = pxNewListItem;       //  3
```
索引列表项(xListEnd)的上一个列表项还是自己,那么自己的下一个列表项指向就是指向了`pxNewListItem`。

```js
pxIndex->pxPrevious = pxNewListItem;            //  4
```
这句就很容易理解啦。如图的4橙色的箭头。

插入完毕的时候标记一下新的列表项插入了哪个列表,并且将`uxNumberOfItems`进行加一,以表示多了一个列表项。

为什么源码要这样子写呢?因为这只是两个列表项,一个列表含有多个列表项,那么这段代码的通用性就很强了。无论原本列表中有多少个列表项,也无论`pxIndex`指向哪个列表项!

![freertos6](https://img2018.cnblogs.com/blog/1834930/201910/1834930-20191015203553390-1981103039.png)

![freertos7](https://img2018.cnblogs.com/blog/1834930/201910/1834930-20191015203553935-1570955549.png)

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

# 列表项的插入
 源码:
```js
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
   listTEST_LIST_INTEGRITY( pxList );
   listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
   if( xValueOfInsertion == portMAX_DELAY )
   {
       pxIterator = pxList->xListEnd.pxPrevious;
   }
   else
   {
       for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue pxNext ) /*lint !e826 !e740 The mini list structure is used as the list end to save RAM.  This is checked and valid. */
       {
           /* There is nothing to do here, just iterating to the wanted
           insertion position. */
       }
   }
   pxNewListItem->pxNext = pxIterator->pxNext;
   pxNewListItem->pxNext->pxPrevious = pxNewListItem;
   pxNewListItem->pxPrevious = pxIterator;
   pxIterator->pxNext = pxNewListItem;
   /* Remember which list the item is in.  This allows fast removal of the
   item later. */
   pxNewListItem->pvContainer = ( void * ) pxList;
   ( pxList->uxNumberOfItems )++;
}
```

传入的参数:

`pxList`:列表项要插入的列表。
`pxNewListItem`:要插入的列表项是什么。

pxList决定了插入哪个列表,`pxNewListItem`中的`xItemValue`值决定了列表项插入列表的位置。
```js
ListItem_t *pxIterator;  
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
```

定义一个辅助的列表项pxIterator,用来迭代找出插入新列表项的位置,并且保存获取要插入的列表项`pxNewListItem`的xItemValue。

如果打开了列表项完整性检查,就要用户实现`configASSERT()`,源码中有说明。

既然是要插入列表项,那么肯定是要知道列表项的位置了,如果新插入列表项的`xItemValue`是最大的话`(portMAX_DELAY)`,就直接插入列表项的末尾。否则就需要比较列表中各个列表项的`xItemValue`的大小来进行排列。然后得出新列表项插入的位置。
```js
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue pxNext )
```
上面源码就是实现比较的过程。

与上面的从列表项末尾插入的源码一样,FreeRTOS的代码通用性很强,逻辑思维也很强。

如果列表中列表项的数量为0,那么插入的列表项就是在初始化列表项的后面。如下图所示:

![freertos8](https://img2018.cnblogs.com/blog/1834930/201910/1834930-20191015203554420-685168947.png)

## 过程分析:

新列表项的`pxNext`指向`pxIterator->pxNext`,也就是指向了`xListEnd(pxIterator)`。
```js
pxNewListItem->pxNext = pxIterator->pxNext;
```
而xListEnd(pxIterator)的pxPrevious指向则为pxNewListItem。

```js
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
```
新列表项的`(pxPrevious)`指针指向`xListEnd(pxIterator)`

`pxIterator` 的 `pxNext` 指向了`新`列表项

```js
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;
```

与从末尾插入列表项其实是一样的,前提是当前列表中列表项的数目为0。

假如列表项中已经有了元素呢,过程又是不一样的了。原来的列表是下图这样子的:

![freertos9](https://img2018.cnblogs.com/blog/1834930/201910/1834930-20191015203554755-1011709305.png)

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

插入后的效果:

![freertos10](https://img2018.cnblogs.com/blog/1834930/201910/1834930-20191015203555207-704275133.png)

分析一下插入的过程:

新的列表项的`pxNext`指向的是`pxIterator->pxNext`,也就是③号列表项。因为一开始pxIterator->pxNext=指向的就是③号列表项!!
```js
pxNewListItem->pxNext = pxIterator->pxNext;
```
而pxNewListItem->pxNext 即③号列表项的指向上一个列表项指针(`pxPrevious`)的则指向新插入的列表项,也就是②号列表项了。
```js
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
```
新插入列表项的指向上一个列表项的指针`pxNewListItem->pxPrevious`指向了辅助列表项`pxIterator`。很显然要连接起来嘛!

```js
pxNewListItem->pxPrevious = pxIterator;    
```
同理,`pxIterator`列表项的指向下一个列表项的指针则指向新插入的列表项了`pxNewListItem`。

```js
pxIterator->pxNext = pxNewListItem;
```
而其他没改变指向的地方不需改动。(图中的两条直线做的连接线是不需要改动的)

当插入完成的时候,记录一下新插入的列表项属于哪个列表。并且让该列表下的列表项数目加一。
```js
pxNewListItem->pvContainer = ( void * ) pxList;
        ( pxList->uxNumberOfItems )++;
```

# 删除列表项
    源码:
```js
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* The list item knows which list it is in.  Obtain the list from the list
item. */
List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
   pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
   pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
   /* Only used during decision coverage testing. */
   mtCOVERAGE_TEST_DELAY();
   /* Make sure the index is left pointing to a valid item. */
   if( pxList->pxIndex == pxItemToRemove )
   {
       pxList->pxIndex = pxItemToRemove->pxPrevious;
   }
   else
   {
       mtCOVERAGE_TEST_MARKER();
    }
   pxItemToRemove->pvContainer = NULL;
   ( pxList->uxNumberOfItems )--;
   return pxList->uxNumberOfItems;
}
```
  其实删除是很简单的,不用想都知道,要删除列表项,那肯定要知道该列表项是属于哪个列表吧,pvContainer就是记录列表项是属于哪个列表的。

  删除就是把列表中的列表项从列表中去掉,其本质其实就是把他们的连接关系删除掉,然后让删除的列表项的前后两个列表连接起来就行了,假如是只有一个列表项,那么删除之后,列表就回到了初始化的状态了。
```js
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
```
这两句代码就实现了将删除列表项的前后两个列表项连接起来。

按照上面的讲解可以理解这两句简单的代码啦。

假如删除的列表项是当前索引的列表项,那么在删除之后,列表中的pxIndex就要指向删除列表项的上一个列表项了。
```js
if( pxList->pxIndex == pxItemToRemove )  
{
     pxList->pxIndex = pxItemToRemove->pxPrevious;
}
```
当然还要把当前删除的列表项的`pvContainer`指向`NULL`,让它不属于任何一个列表,因为,删除的本质是删除的仅仅是列表项的连接关系,其内存是没有释放掉的,假如是动态内存分配的话。

并且要把当前列表中列表项的数目返回一下。

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

# 最后
大家还可以了解一下遍历列表的宏,它在list.h文件中:
```js
define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                                        \
{                                                                                            \
List_t * const pxConstList = ( pxList );                                                    \
   /* Increment the index to the next item and return the item, ensuring */                \
   /* we don't return the marker used at the end of the list.  */                          \
   ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                            \
   if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )  \
   {                                                                                       \
       ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                        \
   }                                                                                       \
   ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;                                          \
}
```
这是一个宏,用于列表的遍历,返回的是列表中列表项的`pxOwner`成员,每次调用这个宏(函数)的时候,其pxIndex索引会指向当前返回列表项的下一个列表项。

![欢迎关注我公众号](https://img2018.cnblogs.com/blog/1834930/201910/1834930-20191015203555914-1160405120.jpg)

更多资料欢迎关注“物联网IoT开发”公众号!

从0开始学FreeRTOS-(列表与列表项)-3的更多相关文章

  1. 从0开始学FreeRTOS-(列表&列表项)-6

    # FreeRTOS列表&列表项的源码解读 第一次看列表与列表项的时候,感觉很像是链表,虽然我自己的链表也不太会,但是就是感觉很像. 在FreeRTOS中,列表与列表项使用得非常多,是Free ...

  2. FreeRTOS列表和列表项

    FreeRTOS中的列表和列表项类似于数据结构中的链表和节点: 相关的文件是list.c和list.h两个文件: List_t列表结构体 具体定义如下: /* * Definition of the ...

  3. 【学习笔记】--- 老男孩学Python,day5 列表 元祖

    今日主要内容1. list(增删改查) 列表可以装大量的数据. 不限制数据类型. 表示方式:[] 方括号中的每一项用逗号隔开 列表和字符串一样.也有索引和切片 常用的功能: 1. 增: append( ...

  4. 跟我学SharePoint 2013视频培训课程——怎样创建列表和列表项(7)

    课程简介 第7天,怎样在SharePoint 2013中创建列表和列表项 视频 SharePoint 2013 交流群 41032413

  5. FreeRTOS-04列表和列表项

    根据正点原子FreeRTOS视频整理 单片机:STM32F207VC FreeRTOS源码版本:v10.0.1 实验说明:1. 验证列表项的插入.末尾插入.删除操作备注:  末尾插入感觉不是末尾插入, ...

  6. 从0系统学Android--4.1探究碎片

    从0系统学Android--4.1探究碎片 本系列文章目录:更多精品文章分类 本系列持续更新中.... 初级阶段内容参考<第一行代码> 第四章:手机平板要兼顾--探究碎片 平板电脑和手机最 ...

  7. 从0系统学Android--3.7 聊天界面编写

    从0系统学Android--3.7 聊天界面编写 本系列文章目录:更多精品文章分类 本系列持续更新中.... 3.7 编写界面的最佳实践 前面学习了那么多 UI 开发的知识,下面来进行实践,做一个美观 ...

  8. 从0开始学爬虫2之json的介绍和使用

    从0开始学爬虫2之json的介绍和使用 Json 一种轻量级的数据交换格式,通用,跨平台 键值对的集合,值的有序列表 类似于python中的dict Json中的键值如果是字符串一定要用双引号 jso ...

  9. 《Python CookBook2》 第四章 Python技巧 - 若列表中某元素存在则返回之 && 在无须共享引用的条件下创建列表的列表

    若列表中某元素存在则返回之 任务: 你有一个列表L,还有一个索引号i,若i是有效索引时,返回L[i],若不是,则返回默认值v 解决方案: 列表支持双向索引,所以i可以为负数 >>> ...

随机推荐

  1. A - 猜数字

    http://acm.hdu.edu.cn/showproblem.php?pid=1172 猜数字 猜数字游戏是gameboy最喜欢的游戏之一.游戏的规则是这样的:计算机随机产生一个四位数,然后玩家 ...

  2. Mac 不显示未知来源选项的解决办法/连接不上网络

    原文来自百度经验: http://jingyan.baidu.com/article/eae078278b37d41fec5485b2.html 灰常感谢原作 关于mac无法连接wifi,我的解决办法 ...

  3. 【LeetCode】230#二叉搜索树中第K小的元素

    题目描述 给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素. 说明: 你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数. 示例 1: 输入: ro ...

  4. Go语言获取系统性能数据gopsutil库

    psutil是一个跨平台进程和系统监控的Python库,而gopsutil是其Go语言版本的实现.本文介绍了它的基本使用. Go语言部署简单.性能好的特点非常适合做一些诸如采集系统信息和监控的服务,本 ...

  5. netcore 基于 DispatchProxy 实现一个简单Rpc远程调用

    前言 netcore 发布以来,一直很关注netcore的进程.目前在公司负责的网站也历经波折的全部有.net framework 4.0 全部切换到netcore 2.2 版本中.虽然过程遇到的坑不 ...

  6. WPF 自定义UI控件学习

    最近项目中运用到了WPF处理三维软件,在C/S结构中WPF做UI还是有很多优越性,简单的学了一点WPF知识,成功的完成项目目标.项目过度阶段对于WPF的一些基本特点有了进一步了解 .至此花费一点时间研 ...

  7. Ajax发送请求的四个步骤

    1.创建XMLHttpRequest let xhr=new XMLHttpRequest; 2.连接服务器 xhr.open("get","goods.json&quo ...

  8. 利用maven命令将外部jar包导进maven仓库

    命令如下:mvn install:install-file -DgroupId=com.zebra -DartifactId=ZSDK_API -Dversion=v2.12.3782 -Dpacka ...

  9. thinkphp6.0 composer 安装 web-token/jwt-framework 常见出错原因分析及解决方法

    composer require web-token/jwt-framework 安装JWT出现错误提示 - web-token/jwt-framework v2.0.1 requires ext-g ...

  10. 渐进深入理解Nginx

    文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. 之前其实写过一篇文章具体介绍过:最基础的Nginx教学,当时有提到过Nginx有一个重要的功能:负载均衡.所以 ...