我们想从0到1开始实现FreeRTOS,就需要弄懂列表和列表项的操作,因为在FreeRTOS中存在着大量的基础数据结构列表和列表项的操作。
FreeRTOS源码里注释的list和list item翻译过来就是列表和列表项,对应就是C语言中的链表和节点。
本小节的目的就是初始化列表和列表项结构体,FreeRTOS中对将标准的 C 数据类型重新取名,编写列表相关的操作函数,最后实现一个列表的列表项插入实验。
链表是C语言的一种基本的数据结构,在操作系统中使用的非常多。链表是由节点组成,节点和节点直接首尾相连,节点的作用就是可以挂载很多数据。你可以这样想象一下,现在阳台有一个圆形晾衣环(链表)上面有很多衣架(节点),衣架(节点)可以挂衣服(数据)在上面。
链表分为单项链表和双向链表,单项链表比较少用,一般都是用双向链表。
FreeRTOS中列表相关操作的都在list.h和list.c中,我们先新建一个list.h在 freertos/source/include和list.c在freertos/source,然后把它们添加入我们的工程里面来。
在list.h中定义列表和列表项的数据结构,具体实现代码如下
列表项结构体包含:
记录当前列表项所在列表排序位置的辅助值xItemValue
该列表项指向下一个列表项* pxNext
该列表项指向上一个列表项* pxPrevious
该列表项的拥有者* pvOwner
该列表项所在哪一个的列表里面*pvContainer
/* 列表项结构体定义 */
struct xLIST_ITEM
{
TickType_t xItemValue; /* 辅助值,用于帮助列表项做顺序排列 */
struct xLIST_ITEM * pxNext; /* 指向当前列表项的下一个列表项 */
struct xLIST_ITEM * pxPrevious; /* 指向当前列表项的上一个列表项*/
void * pvOwner; /* 指向拥有该列表项的内核对象,通常是 TCB */
void * pvContainer; /* 指向该列表项所在的列表 */
};
typedef struct xLIST_ITEM ListItem_t;/* 列表项数据类型重定义 */
列表结构体包含:
列表的列表项计数器uxNumberOfItems
指向列表的列表项索引*pxIndex
列表的最后一个列表项xListEnd
xListEnd是xMINI_LIST_ITEM结构体,xMINI_LIST_ITEM翻译过来就是精简的列表项。
xMINI_LIST_ITEM结构体包含:
记录当前列表项所在列表排序位置的辅助值xItemValue
该列表项指向下一个列表项* pxNext
该列表项指向上一个列表项* pxPrevious
/* 列表的根列表项结构体定义 */
typedef struct xLIST
{
UBaseType_t uxNumberOfItems; /* 列表的列表项计数器 */
ListItem_t * pxIndex; /* 列表的索引列表项指针 */
MiniListItem_t xListEnd; /* 列表最后列表项 */
} List_t;
/* mini列表项结构体定义,作为列表结尾的列表项
因为列表是首尾相连的,头即是尾,尾即是头 */
struct xMINI_LIST_ITEM
{
TickType_t xItemValue; /* 辅助值,用于帮助列表项做升序排列 */
struct xLIST_ITEM * pxNext; /* 指向列表下一个列表项 */
struct xLIST_ITEM * pxPrevious; /* 指向列表上一个列表项 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t; /* 精简列表项数据类型重定义 */
在 FreeRTOS 中,凡是涉及数据类型的地方,FreeRTOS 都会将标准的 C 数据类型用 typedef重新取一个类型名。这些经过重定义的数据类型放在 portmacro.h。我们需要新建一个portmacro.h在 freertos/source/portable中。
#ifndef PORTMACRO_H
#define PORTMACRO_H
#include "stdint.h"
#include "stddef.h"
/* 数据类型重定义 */
#define portCHAR char
#define portFLOAT float
#define portDOUBLE double
#define portLONG long
#define portSHORT short
#define portSTACK_TYPE uint32_t
#define portBASE_TYPE long
typedef portSTACK_TYPE StackType_t;
typedef long BaseType_t;
typedef unsigned long UBaseType_t;
#if( configUSE_16_BIT_TICKS == 1 )
typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#endif
#endif /* PORTMACRO_H */
再新建FreeRTOS用到的三个头文件FreeRTOS.h和FreeRTOSConfig.h存放在文件夹freertos/source、portable.h存放在freertos/source/portable。
FreeRTOS.h
#ifndef INC_FREERTOS_H
#define INC_FREERTOS_H
#include "FreeRTOSConfig.h"
#include "portable.h"
#endif /* INC_FREERTOS_H */
FreeRTOSConfig.h
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#define configUSE_16_BIT_TICKS 0
#endif /* FREERTOS_CONFIG_H */
portable.h
#ifndef PORTABLE_H
#define PORTABLE_H
#include "portmacro.h"
#endif /* PORTABLE_H */
这样FreeRTOS用到的头文件都基本配置完成了,接下来编写list.c看看列表有关的操作函数怎么实现的。
列表项的初始化只需要初始化pvContainer,让列表项当前所在的列表为空就可以了。
/* 列表项初始化 */
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* 初始化该列表项所在的列表为空,表示列表项还没有插入任何列表 */
pxItem->pvContainer = NULL;
}
/* 列表的根列表初始化 */
void vListInitialise( List_t * const pxList )
{
/* 将列表索引指针指向最后一个列表项 */
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
/* 将列表最后一个列表项的辅助排序的值设置为最大,确保该列表项就是列表的最后列表项 */
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* 将最后一个列表项的pxNext和pxPrevious指针均指向列表项自身,表示列表为空 */
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
/* 初始化列表的列表项计数器的值为0,表示列表为空 */
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
}
将列表项按照升序排列插入到列表,如果有两个列表项的值相同,则新列表项在旧列表项的后面插入。
/* 将列表项按照升序排列插入到列表 */
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem)
{
ListItem_t *pxIterator;
/* 获取列表项的排序辅助值 */
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
/* 寻找列表项要插入的位置 */
if( xValueOfInsertion == portMAX_DELAY )
{
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
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;//⑤
/* 列表的列表项计数器++ */
( pxList->uxNumberOfItems )++;//⑥
}
将列表项插入到列表的尾部(可以理解为头部)就是将一个新的列表项插入到一个空的列表。
/* 将列表项插入到列表的尾部 */
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;
pxNewListItem->pxNext = pxIndex;//①
pxNewListItem->pxPrevious = pxIndex->pxPrevious;//②
pxIndex->pxPrevious->pxNext = pxNewListItem;//③
pxIndex->pxPrevious = pxNewListItem;//④
/* 记住该列表项所在的列表 */
pxNewListItem->pvContainer = ( void * ) pxList;//⑤
/* 列表的列表项计数器++ */
( pxList->uxNumberOfItems )++;//⑥
}
/* 将列表项从列表中删除 */
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;
/* 调整列表的列表项索引指针 */
if( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
/* 初始化该列表项所在的列表为空,表示列表项还没有插入任何列表 */
pxItemToRemove->pvContainer = NULL;
/* 列表的列表项计数器-- */
( pxList->uxNumberOfItems )--;
/* 返回列表中剩余列表项的个数 */
return pxList->uxNumberOfItems;
}
我们新建一个根列表项(也可以理解为列表)和三个普通列表项,然后将这三个普通列表项按照节点的排序辅助值做升序排列插入到列表中
/*
*************************************************************************
* 包含的头文件
*************************************************************************
*/
#include "list.h"
/*
*************************************************************************
* 全局变量
*************************************************************************
*/
/* 定义列表的根列表项 */
struct xLIST List_Test;//定义列表的根列表项,有根了,列表项才能在此基础上生长
/* 定义列表项 */
struct xLIST_ITEM List_Item1;
struct xLIST_ITEM List_Item2;
struct xLIST_ITEM List_Item3;
/*
************************************************************************
* main函数
************************************************************************
*/
int main(void)
{
/* 列表的根列表项初始化 */
vListInitialise( &List_Test );
/* 列表项1初始化 */
vListInitialiseItem( &List_Item1 );
List_Item1.xItemValue = 1;
/* 列表项初始化 */
vListInitialiseItem( &List_Item2 );
List_Item2.xItemValue = 2;
/* 列表项初始化 */
vListInitialiseItem( &List_Item3 );
List_Item3.xItemValue = 3;
/* 将列表项插入列表,按照升序排列 */
vListInsert( &List_Test, &List_Item1);
vListInsert( &List_Test, &List_Item2);
vListInsert( &List_Test, &List_Item3);
for(;;)
{
/*啥事不干*/
}
}
参考资料:参考资料:《FreeRTOS 内核实现与应用开发实战—基于RT1052》