从0开始学FreeRTOS-(列表&列表项)-6
# 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个。因为还没添加列表项嘛~。

# 列表项的初始化
函数:
```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`):

为什么是这样子的呢,一句句代码来解释:
一开始:
```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指向哪个列表项!


看看是不是按照源码中那样插入呢?
# 列表项的插入
源码:
```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,那么插入的列表项就是在初始化列表项的后面。如下图所示:

过程分析:
新列表项的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。
假如列表项中已经有了元素呢,过程又是不一样的了。原来的列表是下图这样子的:

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

分析一下插入的过程:
新的列表项的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索引会指向当前返回列表项的下一个列表项。
# 关注我

更多资料欢迎关注“物联网IoT开发”公众号!
从0开始学FreeRTOS-(列表&列表项)-6的更多相关文章
- 从0开始学FreeRTOS-(列表与列表项)-3
# FreeRTOS列表&列表项的源码解读 第一次看列表与列表项的时候,感觉很像是链表,虽然我自己的链表也不太会,但是就是感觉很像. 在`FreeRTOS`中,列表与列表项使用得非常多 ...
- iOS开发——UI篇&下拉弹出列表选择项效果
下拉弹出列表选择项效果 右边菜单中的按键,点击弹出一个列表可选择,选择其中一个,响应相应的事件并把文字显示在右边的菜单上:弹出下拉效果使用LMDropdownView插件,可以用POD进行加载pod ...
- AngularJs学习——实现列表内容项的增加删除
效果截图: 说明:引入bootstrap.min.css样式库和angular.min.js的静态资源库,实现列表内容的增加和删除操作. AngularJS代码: <script src=&qu ...
- 使用jQuery 中的显示与隐藏动画效果实现折叠下拉菜单的收缩和展开,在页面的列表中有若干项,列表的每项中有一个二级列表,二级列表默认为隐藏状态。点击列表的项,切换二级列表的显示或隐藏状态
查看本章节 查看作业目录 需求说明: 使用jQuery 中的显示与隐藏动画效果实现折叠下拉菜单的收缩和展开,在页面的列表中有若干项,列表的每项中有一个二级列表,二级列表默认为隐藏状态.点击列表的项,切 ...
- uWSGI参考资料(1.0版本的配置选项列表)
Reference: http://blog.csdn.net/kevin6216/article/details/15378617 uWSGI参考资料(1.0版本的配置选项列表) 下面的内容包含了大 ...
- Android(Lollipop/5.0) Material Design(四) 创建列表和卡片
Material Design系列 Android(Lollipop/5.0)Material Design(一) 简单介绍 Android(Lollipop/5.0)Material Design( ...
- 帝国CMS 6.0功能解密之新版结合项功能,帝国结合项使用
可以用来做A-Z信息检索 某字段等于多少,输出 等等 帝国CMS6.0在继承以往版本结合项功能的基础上又新增很多特性,更强大.今天我们就专门来讲解6.0的结合项改进. 回顾下以往版本的结合项语 ...
- 从0系统学Android--4.1探究碎片
从0系统学Android--4.1探究碎片 本系列文章目录:更多精品文章分类 本系列持续更新中.... 初级阶段内容参考<第一行代码> 第四章:手机平板要兼顾--探究碎片 平板电脑和手机最 ...
- 从0系统学Android--3.7 聊天界面编写
从0系统学Android--3.7 聊天界面编写 本系列文章目录:更多精品文章分类 本系列持续更新中.... 3.7 编写界面的最佳实践 前面学习了那么多 UI 开发的知识,下面来进行实践,做一个美观 ...
随机推荐
- 面试加分项-HashMap源码中这些常量的设计目的
前言 之前周会技术分享,一位同事讲解了HashMap的源码,涉及到一些常量设计的目的,本文将谈谈这些常量为何这样设计,希望大家有所收获. HashMap默认初始化大小为什么是1 << 4( ...
- CVE-2019-0708远程桌面代码执行漏洞复现
漏洞环境 使用VMware 安装Windows7 SP1模拟受害机 利用 攻击工具准备 1.使用如下命令一键更新安装的metasploit框架 curl https://raw.githubuserc ...
- css3-旋转的太极图
123 body { background-color: #aaa; } .div { width: 400px; height: 400px; border-radius: 50%; border: ...
- SpringCloud超简单的入门(1)--一些简单的介绍
简介 简单来说,springcloud的就是由一组springboot应用(服务)组成,相互之间通过REST等方式进行通信. 两个springboot应用,其中一个作为服务提供者,一个作为服务消费者, ...
- FreeSql (三十五)CodeFirst 自定义特性
比如项目内已经使用了其它 orm,如 efcore,这样意味着实体中可能存在 [Key],但它与 FreeSql [Column(IsPrimary = true] 不同. Q: FreeSql 实体 ...
- android 拍照 相册 剪切以及显示功能
一.概述 android的 图片拍照 ,相册选图,以及图片剪切功能可以说非常常用. 尤其是图片上传功能,必然用到此功能. 而公司最近的一个项目中正好用到该功能. 记录下来以便以后再次用到,直接拿来使用 ...
- 【第十一篇】这一篇来说说MVC+EF+easyui datagrid的查询功能
老规矩 直接上代码 <form class="form-horizontal"> <div class="box-body"> < ...
- 《剑指offer》:[62]序列化二叉树
题目:请实现两个函数,分别来序列化和反序列化二叉树. 方案分析:我们知道通过二叉树的中序和任何一个前或者后续遍历都可以反序列化一棵二叉树,但是这样做有一个缺点就是,序列化的数据不能有重复的数据,否则会 ...
- eclipse查看.class文件
要使用jd-gui.exe反编译程序 步骤:window-preferences-general-editors-file associations, 如下图 上面的框选中,*.class witho ...
- ZFNet(2013)及可视化的开端
目录 写在前面 网络架构与动机 特征可视化 其他 参考 博客:blog.shinelee.me | 博客园 | CSDN 写在前面 ZFNet出自论文< Visualizing and Unde ...