前言
freertos由于其小巧、免费、开源的一些特点得到了越来越广泛的应用,之前也看过ucos的源码,freertos跟他们一个很大的表面上的特点就是 freertos源码中的注释非常多,十分多,在开始的时候会觉得不耐烦看这些注释,甚至打乱自己思路,其实如果可以先浏览一下源码再来看注释,然后精读源码效果要好得多。其中注释包含了很多freertos的设计原理,对理解freertos源码有很大的帮助。
下面这个系列博客都会有一些对源码中注释的翻译,可能由于个人水平有限,会有些许不准确甚至谬误,有心的读者可以下面留言,在分析源码时可能由于篇幅原因一些无关紧要的 空的 宏、函数就会略去。
list.h浅析
list的实现主要是为了调度器(scheduler)的,list的设计是为调度器的需求量身定制的,当然也可以被用在app中的代码。
列表(list)只能存放指向列表元素(list_item)的指针,每个列表元素包含了一个带数字值的元素,大多数情况下列表元素的存放顺序是按照带的那个数字值递减的规则来的。
列表在创建的时候就包含了一个列表项,这个列表项包含的value的值是列表项包含的value的可能最大值,所以这个初始列表项肯定一直在列表的最后作为列表的标记。
出了这个列表项的value,一个列表项还包括一个前指针(指向前一个列表项),后指针(指向后一个列表项)。这列表项中包含两个前后指针主要是为了方便高效的列表操作,这样就有两个way了在包含列表和列表项的时候。
#define configLIST_VOLATILE
//这个宏可以在很多地方看到,其实就是想给list 和 list_item中元素定义为volatile类型的,主要是因为涉及list 和 list_item的操作
//必须要在中断中做,所以有必要定义为volatile类型。这里为什么没有定义为volatile ,目前还不清楚
#if( configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES == 0 )
/* Define the macros to do nothing. */
#define listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
#define listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE
#define listFIRST_LIST_INTEGRITY_CHECK_VALUE
#define listSECOND_LIST_INTEGRITY_CHECK_VALUE
#define listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem )
#define listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem )
#define listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList )
#define listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList )
#define listTEST_LIST_ITEM_INTEGRITY( pxItem )
#define listTEST_LIST_INTEGRITY( pxList )
#else
/* Define macros that add new members into the list structures. */
#define listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE TickType_t xListItemIntegrityValue1;
#define listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE TickType_t xListItemIntegrityValue2;
#define listFIRST_LIST_INTEGRITY_CHECK_VALUE TickType_t xListIntegrityValue1;
#define listSECOND_LIST_INTEGRITY_CHECK_VALUE TickType_t xListIntegrityValue2;
/* Define macros that set the new structure members to known values. */
#define listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem ) ( pxItem )->xListItemIntegrityValue1 = pdINTEGRITY_CHECK_VALUE
#define listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem ) ( pxItem )->xListItemIntegrityValue2 = pdINTEGRITY_CHECK_VALUE
#define listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList ) ( pxList )->xListIntegrityValue1 = pdINTEGRITY_CHECK_VALUE
#define listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList ) ( pxList )->xListIntegrityValue2 = pdINTEGRITY_CHECK_VALUE
/* Define macros that will assert if one of the structure members does not
contain its expected value. */
#define listTEST_LIST_ITEM_INTEGRITY( pxItem ) configASSERT( ( ( pxItem )->xListItemIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxItem )->xListItemIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )
#define listTEST_LIST_INTEGRITY( pxList ) configASSERT( ( ( pxList )->xListIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxList )->xListIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )
#endif /* configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES */
在上面的代码中可以看到有两套宏定义,宏开关是configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES
其实这个宏就是来决定要不要在每个结构中加入特定的字符,如果打开了宏开关,在应用程序执行时如果出现数据覆盖的错误,可以根据这些特殊的符号在内存中捕获到列表数据结构,进而用户可以做自己一些出错应对措施。
struct xLIST_ITEM
{
configLIST_VOLATILE TickType_t xItemValue; /*列表项的值,大多数情况下列表用这个来排序列表项 */
struct xLIST_ITEM * configLIST_VOLATILE pxNext; /*指向下一个列表项 */
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*指向前一个列表项 */
void * pvOwner; /*指向包括这个列表项得到对象(TCB) */
void * configLIST_VOLATILE pvContainer; /*指向放置这个列表项的列表(list) */
};
typedef struct xLIST_ITEM ListItem_t; /* For some reason lint wants this as two separate definitions. */
struct xMINI_LIST_ITEM
{//mini列表项参考上面的列表项,还少了两个元素,其实列表中只能包含列表项,不会去包含mini列表项的
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
typedef struct xLIST
{
configLIST_VOLATILE UBaseType_t uxNumberOfItems;/*列表中列表项的个数 */
ListItem_t * configLIST_VOLATILE pxIndex; /*用来遍历整个列表, */
MiniListItem_t xListEnd; /*列表的最后一个元素,是个Mini列表项,Mini列表项的value值是这个列表中可能的最大值*/
} List_t;
至于为什么要存在mini list_item,这就是答案The mini list structure is used as the list end to save RAM. 节省ram。。。其实也没多少
上面说列表中只能包含列表项,不会去包含mini列表项,不包括列表本身这个mini列表项,我的理解是每个列表就是一个队列,通过每个列表项中嵌入的pxnext、pxprevious指针来链接,这个队列可以是就绪任务列表,也可以是等待列表,那个value就是比如需要等待的时间等,如果是mini列表项的话,是不包括pvOwner这个元素的,所以包含min列表项是根本没有意义的,(前面就说过列表项就是为了调度器设计的,调度器其实就是调度现场,每个任务的现场由任务栈区存放,而任务栈的栈顶指针存放在TCB结构中,如果不能跟TCB产生关系,是不会去包含mini列表项的,列表本身包含的那个mini列表项除外)
后面得到很多宏就是利用list 和 list_item来做的操作。比如
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ){ \
List_t * const pxConstList = ( pxList ); \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) { \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
} \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}
//这个宏就是获取pxList当前指针的下一个的列表项属于的那个TCB结构。
list.c浅析
list.c中主要是下面几个函数,涉及列表初始化,列表项初始化,列表项插入和删除
void vListInitialise( List_t * const pxList );
void vListInitialiseItem( ListItem_t * const pxItem );
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem );
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem );
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove );
void vListInitialise( List_t * const pxList )
{
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
/* xItemValue是列表项value里面可能最大的数值 */
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* 当列表为空时,列表的前后向指针都是指向自己的 */
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;//刚刚初始化,列表项个数为0
}
列表初始化函数,在创建新的列表之后必须先进行初始化。
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* 保证list item没有再任何list中记录挂接 ,这也是说为什么list不会包含 mini list_item的原因*/
pxItem->pvContainer = NULL;
}
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;
/* 记录这个列表项到底挂接在哪个列表中,比如讲一个TCB的列表项从等待列表中移除,加入到就绪列表中,必须记录TCB中的列表项挂接在哪个列表下面*/
pxNewListItem->pvContainer = ( void * ) pxList;
( pxList->uxNumberOfItems )++;/* 列表项个数变化 */
}
这个尾插函数不是按照每个列表项的value值来排序插入的,所以一般不是太常用吧
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
/* 将新的列表项按xItemValue排序插入列表,如果已经存在了有相同xItemValue的列表项,新插入的列表项会在其之后,保证共享CPU */
if( xValueOfInsertion == portMAX_DELAY )
{//这个按照列表项的value值来排序插入的话,因为已经是最大值了,相当于末尾插入
pxIterator = pxList->xListEnd.pxPrevious;
}else{//下面循环就是按照这个xItemValue值的大小来sort排序的过程
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
pxIterator->pxNext->xItemValue <= xValueOfInsertion;
pxIterator = pxIterator->pxNext ){
/* There is nothing to do here, just iterating to the wantedinsertion 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 )++;
}
其实任务调度,将一个任务从一个状态切换到另一个状态,就是将一个列表项从一个列表移除插入另一个列表的过程,其最底层的操作就是这些列表操作了。
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;
/* 保证pxIndex指向的是有效的item*/
if( pxList->pxIndex == pxItemToRemove ){
pxList->pxIndex = pxItemToRemove->pxPrevious;
}else{
mtCOVERAGE_TEST_MARKER();
}
pxItemToRemove->pvContainer = NULL;
( pxList->uxNumberOfItems )--;
return pxList->uxNumberOfItems;//返回剩余的列表项数
}
其实list.c文件就这么多,主要的是这个list列表、list_item列表项是为了freertos调度器量身定制的,在后面学习task 、schedule的时候底层操作 就是 对这些list的操作。