1. 引言
本来第二节想直接写任务调度的,发现调度器里很多用到的list这个结构体。
freeRTOS中的list.c和list.h文件又挺短的(list.c 198行 list.h 412行),就是一个链表,
所以作为一个基础知识(软柿子),先补一波。
(本文FreeRTOS版本 v10.3.1)
2. 结构体
list.h里重要的结构体就3个。
- 列表
- 列表项
- 迷你列表项
2.1 xLIST 列表
源码
/*
* Definition of the type of queue used by the scheduler.
*/
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
volatile UBaseType_t uxNumberOfItems;
ListItem_t * configLIST_VOLATILE pxIndex; /*< Used to walk through the list. Points to the last item returned by a call to listGET_OWNER_OF_NEXT_ENTRY (). */
MiniListItem_t xListEnd; /*< List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */
listSECOND_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
} List_t;
挨个解释一下成员。
- listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE 和 listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE是两个固定值,0x5a5a…如果需要进行完整性判断,可以用这个。
- uxNumberOfItems 是这个列表中所拥有的列表项的数量
- pxIndex,用来遍历列表。指向调用listGET_OWNER_OF_NEXT_ENTRY()返回的最后一个项。这个是不容易理解的点,放最后说
- xListEnd 是一个迷你列表项,作为结尾。
2.2 xLIST_ITEM 列表项
/*
* Definition of the only type of object that a list can contain.
*/
struct xLIST;
struct xLIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
configLIST_VOLATILE TickType_t xItemValue; /*< The value being listed. In most cases this is used to sort the list in descending order. */
struct xLIST_ITEM * configLIST_VOLATILE pxNext; /*< Pointer to the next ListItem_t in the list. */
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*< Pointer to the previous ListItem_t in the list. */
void * pvOwner; /*< Pointer to the object (normally a TCB) that contains the list item. There is therefore a two way link between the object containing the list item and the list item itself. */
struct xLIST * configLIST_VOLATILE pxContainer; /*< Pointer to the list in which this list item is placed (if any). */
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
};
- listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE 和 listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE依然是完整性判断
- pxNext 和 pxPrevious是指向前一个和后一个的指针。
- pvOwner 是指向的TCB
- pxContainer是属于哪个列表
- xItemValue 是用来排序的值,做有序插入的时候,要根据这个值做升序
2.3 xMINI_LIST_ITEM 迷你列表项
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
xLIST_ITEM的迷你版。为了省一点点RAM。
目前我只在List_t结构体里看到了使用。
3. 接口
list的接口也非常简单,就是常用链表的几个操作。
再加一个宏定义 listGET_OWNER_OF_NEXT_ENTRY
3.1 初始化列表 void vListInitialise( List_t * const pxList )
列表的初始化,十分简单
- 把数量清零
- 把pxIndex指针指向自己的xListEnd
- 把xListEnd的前后指针pxNext和pxPrevious都指向自己,组成一个环形链表
- 把xListEnd的值设为最大值
3.2 初始化列表项 void vListInitialiseItem( ListItem_t * const pxItem )
初始化列表项。
非常简单,只是赋值了pxContainer,列表项中的其他成员都不是在这里赋值的。
3.3 删除 UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
删除一个列表项。
把当前项的前一项的next指到当前项的下一项,即可。
要删除的这个列表项只是从链表中删除,而不是释放空间的删除。
3.4 插入 void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
把 pxNewListItem 这个列表项 插入到 pxList 这个列表里。
按照xItemValue升序的原则来排列。
通过一个循环来找到要插入的位置。
插入操作就是典型的链表操作。
插入前:
插入后
3.5 插入到最后 void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
这个接口没有做排序,就是插入到列表的最后。也是做了一个典型链表操作,修改了指向指针。
这些都很好理解。
但是,插入最后,就是列表的最后吗?就是xListEnd的位置吗?
其实是插在pxIndex指向列表项的前面。
为什么这么做?
pxIndex指到哪里?
4. 难点
4.1 pxIndex指的是什么
初始化就是在end,搜索了代码,中间对pxIndex赋值的地方只有listGET_OWNER_OF_NEXT_ENTRY这个接口。
每调用一次listGET_OWNER_OF_NEXT_ENTRY,列表的pxIndex会指向下一个列表项。
而调用listGET_OWNER_OF_NEXT_ENTRY,主要是
4.2 xItemValue 存的是什么
列表项值。
都是通过listSET_LIST_ITEM_VALUE这个宏来赋值的。
搜了一下,主要赋值了两类值
- 优先级(对于事件列表)
- 醒来时间(对于状态列表)
4.3 vListInsertEnd()的插入位置
从名字上看,是说插入到list的尾部。
那是End?并不是,列表是一个双向环形链表,默认把pxIndex所指作为列表的开头。所以插入End,就是插入到目前开始的位置(pxIndex指的列表项)之前。
4.4 List的头尾在哪里?
结尾是在xListEnd吗?
这个是初始化时候的结尾,名字也是结尾。
但是vListInsertEnd又不是根据xListEnd来定的。是插在pxIndex的前面,那默认pxIndex是开头,
查了一些资料,头尾好像没怎么分,但是第二种的认可度比较高,因为vListInsertEnd是这么用的。
4.5 列表是不是个有序链表?还是个无序的?
个人感觉有些是需要有序的,就按照vListInsert来插入(升序),有些是无序的,就按照vListInsertEnd来插入。
也看到一篇文章总结(参考链接第4条)
5. 参考链接
- 《STM32F103 + FreeRTOS 开发手册1.1》 正点原子
- 第12.2讲 FreeRTOS列表与列表项实验 from 正点原子 (35分钟介绍末尾插入)
- FreeRTOS 内核中的链表
- 有序插入和无序插入