串列和串列項
- 前言
- 一、串列
- 二、串列項
- 1.串列項
- 2.迷你串列項
- 三、串列和串列項初始化
- 1.串列初始化
- 2.串列項初始化
- 串列項的插入和洗掉
- 插入串列項1
- 插入串列項2
- 插入串列項3
- 串列末尾插入
- 串列項洗掉
前言
這是一個學習RTOS記錄的系列文章,
在上一篇我們講到任務的掛起與恢復,接下來介紹的如題“串列和串列項”
#基礎知識
一、串列
什么是串列?
串列項是一個資料結構,用來跟蹤任務
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
configLIST_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;
uxNumberOfItems :串列項的數量
pxIndex:儲存當前串列項索引號
xListEnd:串列中最后一個串列項
二、串列項
1.串列項
串列中存放的專案
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. */
void * configLIST_VOLATILE pvContainer; /*< 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. */
};
typedef struct xLIST_ITEM ListItem_t; /* For some reason lint wants this as two separate definitions. */
xItemValue:串列項值
pxNext:指向下一個串列項
pxPrevious:指向先前的串列項
pvOwner:串列項屬于哪個任務控制塊
pvContainer:串列項屬于哪個串列
2.迷你串列項
在不需要pvOwner和pvContainer的情況下的刪減版
目的減少記憶體使用,
struct 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;
三、串列和串列項初始化
1.串列初始化
void vListInitialise( List_t * const pxList )
{
/* The list structure contains a list item which is used to mark the
end of the list. To initialise the list the list end is inserted
as the only list entry. */
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */
/* The list end value is the highest possible value in the list to
ensure it remains at the end of the list. */
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* The list end next and previous pointers point to itself so we know
when the list is empty. */
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 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 !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
/* Write known values into the list if
configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
使用或者創建串列之前需要對串列的各個物件初始化,
初始化呼叫函式vListInitialise()
2.串列項初始化
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 );
}
串列項的插入和洗掉
需要創建start_task()、task1_task()、list_task()任務函式
1.task1_task()讓LED閃爍提示程式正運行
2.list_task()呼叫串列和串列項的函式,通過串口列印觀察運行程序
我們的目的是通過呼叫void vListInsert()來插入串列項
觀察TestList、pxList->pxIndex、pxList->xListEnd…等串列中的地址,
插入串列項1
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#define START_STK_SIZE 120
#define START_TASK_PRIO 1 /*因為空閑任務在任務調度器中優先級是0,
定時器任務優先級是32-1,所以這倆個我們不能用*/
TaskHandle_t StartTask_Handler;
#define TASK1_STK_SIZE 120
#define TASK1_TASK_PRIO 2
TaskHandle_t Task1_Handler;
#define LIST_STK_SIZE 128
#define LIST_TASK_PRIO 3
TaskHandle_t List_Handler;
//串列和三個串列項
List_t TestList;
ListItem_t TestItem1;
ListItem_t TestItem2;
ListItem_t TestItem3;
void start_task(void *pvParameters);
void task1(void *pvParameters);
void list_task(void *pvParameters);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//設定系統中斷優先級分組4
delay_init(168); //初始化延時函式
uart_init(115200); //初始化串口
LED_Init(); //初始化LED埠
xTaskCreate( (TaskFunction_t) start_task, //函式名
( char *) "start_task", //任務名
( uint16_t) START_STK_SIZE, //堆疊大小
( void *) NULL, //任務引數
( UBaseType_t ) START_TASK_PRIO, //任務優先級
( TaskHandle_t *) &StartTask_Handler ); //任務句柄
vTaskStartScheduler(); //開啟任務調度器
}
//開始任務任務函式
void start_task(void *pvParameters)
{
xTaskCreate( (TaskFunction_t) task1, //函式名
( char *) "task1", //任務名
( uint16_t) TASK1_STK_SIZE, //堆疊大小
( void *) NULL, //任務引數
( UBaseType_t ) TASK1_TASK_PRIO, //任務優先級
( TaskHandle_t *) &Task1_Handler ); //任務句柄
xTaskCreate( (TaskFunction_t) list_task, //函式名
( char *) "list_task", //任務名
( uint16_t) LIST_STK_SIZE, //堆疊大小
( void *) NULL, //任務引數
( UBaseType_t ) LIST_TASK_PRIO, //任務優先級
( TaskHandle_t *) &List_Handler ); //任務句柄
vTaskDelete(NULL);
taskEXIT_CRITICAL(); //退出臨界區
}
void task1(void *pvParameters)
{
while(1)
{
LED0 = ~LED1 ;
vTaskDelay(300);
}
}
void list_task(void *pvParameters)
{
//初始化串列和串列項
vListInitialise(&TestList);
vListInitialiseItem(&TestItem1);
vListInitialiseItem(&TestItem2);
vListInitialiseItem(&TestItem3);
//給串列項賦初值
TestItem1.xItemValue = 40;
TestItem2.xItemValue = 70;
TestItem3.xItemValue = 50;
while(1)
{
LED1=!LED1;
printf(" 串列和串列項的地址 \r\n");
printf("TestList %#x \r\n",(int)&TestList);
printf("pxList->pxIndex %#x \r\n",(int)TestList.pxIndex);
printf("pxList->xListEnd %#x \r\n",(int)(&TestList.xListEnd));
printf("TestItem1 %#x \r\n",(int)&TestItem1);
printf("TestItem2 %#x \r\n",(int)&TestItem2);
printf("TestItem3 %#x \r\n",(int)&TestItem3);
}
}
除錯中遇到的問題
1.LED0亮但不閃爍
原因:list_task()中沒有添加回圈,程式卡住
反思:任務函式就是執行緒,每個執行緒就是一個獨立的回圈
列印結果

分析
pxList->pxIndex 0x200000b8
pxList->xListEnd 0x200000b8
這倆的地址相同是巧合嘛?
不是,是因為串列在沒有串列項的時候pxIndex(指標) 指向了 xListEnd(串列的末尾)所以地址相同,
接下來我們試著插入一個串列項到串列TestList中
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
//傳入引數1.要插入的串列地址,2.串列項的地址
vListInsert(&TestList,&TestItem1);//插入串列項1
while(1)
{
LED1=!LED1;
printf(" 串列和串列項的地址 \r\n");
printf("TestList %#x \r\n",(int)&TestList);
printf("pxList->TestItem1->pxNext %#x \r\n",(int)TestItem1.pxNext);
printf("pxList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("pxList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("pxList->TestItem1->pxPrevious %#x \r\n",(int)TestItem1.pxPrevious);
printf("TestItem3 %#x \r\n",(int)&TestItem3);
vTaskDelay(1000);
}
除錯中遇到的問題
2.列印出的地址不是TestItem1的地址
大家看下面代碼試試能不能找出問題
//錯誤代碼
printf("pxList->xListEnd->pxNext %#x \r\n",(int)(&TestList.xListEnd.pxNext));
printf("pxList->xListEnd->pxPrevious %#x \r\n",(int)(&TestList.xListEnd.pxPrevious));
找不出的話看看這個再試試
struct 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;
聰明的你想必找出問題了叭!
原因就是:pxNext和pxPrevious是結構體指標變數,如果在其前面加取地址符
&TestList.xListEnd.pxNext;
&TestList.xListEnd.pxPrevious;
那么取到的是結構體指標變數的地址,而我們要的不是這個,
TestItem1 0x200000c4
/*
PS:xListEnd->pxNext和xListEnd->pxPrevious
應該與插入的串列項的地址相同
*/
pxList->TestItem1->pxNext 0x200000b8
pxList->xListEnd->pxNext 0x200000bc //這是加了&
pxList->xListEnd->pxPrevious 0x200000c0 //這是加了&
pxList->TestItem1->pxPrevious 0x200000b8
pxList->TestItem1->pxNext 0x200000b8
pxList->xListEnd->pxNext 0x200000c4 //這是正確的
pxList->xListEnd->pxPrevious 0x200000c4 //這是正確的
pxList->TestItem1->pxPrevious 0x200000b8
這時候就有人問了:為什么串列的pxNext和pxPrevious地址跟插入的串列項地址相同?
看下面的圖應該就明白了,

插入串列項2
插入串列項2列印的地址
TestList 0x200000b0
TestItem1 0x200000c4
TestItem2 0x200000d8
pxList->pxIndex 0x200000b8
pxList->xListEnd 0x200000b8
pxList->TestItem1->pxNext 0x200000d8
pxList->TestItem2->pxNext 0x200000b8
pxList->xListEnd->pxNext 0x200000c4
pxList->xListEnd->pxPrevious 0x200000d8
pxList->TestItem1->pxPrevious 0x200000b8
pxList->TestItem2->pxPrevious 0x200000c4
分析
pxList->TestItem1->pxNext 0x200000d8
pxList->TestItem2->pxNext 0x200000b8
TestItem1->pxNext的下一個指向的是TestItem2地址0x200000d8
TestItem2->pxNext的下一個指向的是串列的xListEnd的地址0x200000b8
其他的不一一細說了,靜下心分析都能看懂的,下面附上圖片幫助大家理解,

插入串列項3
TestList 0x200000b0
TestItem1 0x200000c4
TestItem2 0x200000d8
TestItem3 0x200000ec
pxList->pxIndex 0x200000b8
pxList->xListEnd 0x200000b8
pxList->TestItem1->pxNext 0x200000ec
pxList->TestItem2->pxNext 0x200000b8
pxList->TestItem3->pxNext 0x200000d8
pxList->xListEnd->pxNext 0x200000c4
pxList->xListEnd->pxPrevious 0x200000d8
pxList->TestItem1->pxPrevious 0x200000b8
pxList->TestItem2->pxPrevious 0x200000ec
pxList->TestItem3->pxPrevious 0x200000c4
分析
TestItem1->pxNext居然指向了TestItem3,不應該是TestItem2么?
原因是 :
TestItem1.xItemValue = 40;
TestItem2.xItemValue = 70;
TestItem3.xItemValue = 50;
結合下面這張圖

我們可以發現原來串列中串列項的排序跟串列項的value有關小的排前面,
串列末尾插入
與串列項插入串列函式使用類似
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
//引數1.串列地址2.串列項地址
考慮到篇幅長度這里就不加入列印的地址了

觀察上圖,跟我們之前理解的按照串列項的value由小到大排序這邏輯沖突了,
為啥?原因是我們呼叫vListInsertEnd()相當于讓TestItem3有特權直接插到pxIndex的前面也就是串列的末尾,
串列項洗掉
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
引數:洗掉的串列項
回傳值:串列中剩余的串列項
將要洗掉的串列項地址放入就行了
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/286465.html
標籤:其他
上一篇:【Java虛擬機JVM系列】學弟們總問我什么是JVM?直到我給他看了這篇總結之后
下一篇:樹莓派4B系統安裝與遠程控制
