1. 程式人生 > >freertos之list淺析

freertos之list淺析

前言

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的操作。