# Lab 2
<!-- {%hackmd hackmd-dark-theme %} -->
> 國立成功大學 資訊工程學系
嵌入式作業系統分析與實作 Analysis and Implementation of Embedded Operating Systems [CSIE7618] 2022 Spring
> GitHub: https://github.com/cpt1020/EmbeddedOS-Lab2
## Objective
瞭解FreeRTOS的ready list和delayed list是用什麼data structure去實作的
## Requirement
:::info
- Create four task
- Red_LED_App, Green_LED_App, Delay_App, TaskMonitor_App
- TaskMonitor_App will call `Taskmonitor()` periodicity
- `TaskMonitor()`
- Traverse ReadyTaskList, DelayedTaskList, OverflowDelayedTaskList
- Print TCB information by UART
- Task Name、Priority(Base/actual)、Stack Pointer、TopOfStack Pointer、 Task State
[Demo Video](https://ncku365-my.sharepoint.com/:v:/g/personal/p76104702_ncku_edu_tw/EZ4hmAYudm9EoMgBhwTkWy4BS7zeZbdtmw_t8rAySqzwUQ?e=7RvzLL)

:::
## Prerequisites and Configuration Setup
- Project前置設定可參考這篇 https://hackmd.io/@cpt/embeddedOS_lab0
- USART設定可參考這篇 https://hackmd.io/@cpt/embeddedOS_USART
### Pin Configuration in STM32CubeIDE

記得要設置USART2和紅綠LED
## Add New Code
在 `main.c` 新增以下4個助教給的tasks
```cpp!
void TaskMonitor_App(void *pvParameters){
for(;;){
Taskmonitor();
vTaskDelay(1000);
}
}
void Red_LED_App(void *pvParameters){
uint32_t Redtimer = 800;
for(;;){
HAL_GPIO_TogglePin(GPIOD,Red_LED_Pin);
vTaskDelay(Redtimer);
Redtimer+=1;
}
}
void Green_LED_App(void *pvParameters){
uint32_t Greentimer = 1000;
for(;;){
HAL_GPIO_TogglePin(GPIOD,Green_LED_Pin);
vTaskDelay(Greentimer);
Greentimer+=2;
}
}
void Delay_App(void *pvParameters){
int delayflag=0;
uint32_t delaytime;
while(1){
if(delayflag==0){
delaytime = 1000;
delayflag=1;
}else{
delaytime=0xFFFFFFFF;
}
vTaskDelay(delaytime);
}
}
```
在 `task.h` 新增以下code:
```cpp!
#include "stm32f4xx_hal.h"
extern UART_HandleTypeDef huart2; // for USART2
void Taskmonitor(void);
```
- `UART_HandleTypeDef huart2` 這裡宣告成 `extern` 是因為,在 `.ioc` 設定完USART之後generate code,在 `main.c` 的 `Private variables` 內就會自動產生宣告 `UART_HandleTypeDef huart2;` 的code,所以 `task.h` 的要宣告成 `extern` ,否則編譯時會出現multiple definition的錯誤訊息
在 `tasks.c` 最後面新增助教給的 `TaskMonitor()` 的template,這次lab主要就是要寫 `TaskMonitor()`
```cpp
void Taskmonitor(void)
{
/* Initialize string */
char Monitor_data[130];
memset(Monitor_data,'\0',sizeof(Monitor_data));
/* Stop scheduler */
/* Taskmonitor() will block when UART is transmitting data */
/* Scheduler will change list data when Taskmonitor() is blocked */
vTaskSuspendAll();
/* Print title */
sprintf(Monitor_data,"|Name |Priority(Base/actual) |pxStack |pxTopOfStack |State |\n\r");
HAL_UART_Transmit(&huart2,(uint8_t *)Monitor_data,strlen(Monitor_data),0xffff);
/* pxReadyTasksLists */
/* pxDelayedTaskList*/
/* pxOverflowDelayedTaskList */
/* Resume scheduler */
xTaskResumeAll();
}
```
在 `list.h` 新增以下macro (這個macro滿好用的,後面會用到):
```cpp!
#define listGET_ITEM_OF_HEAD_ENTRY(pxList) ((&((pxList)->xListEnd))->pxNext)
```
在 `FreeRTOSConfig.h` 修改以下此macro:
```cpp!
configMAX_PRIORITIES 15
```
## FreeRTOS需理解的部分
在 `tasks.c` 可看到以下內容:
```cpp
/* Lists for ready and blocked tasks. --------------------
xDelayedTaskList1 and xDelayedTaskList2 could be move to function scople but
doing so breaks some kernel aware debuggers and debuggers that rely on removing
the static qualifier. */
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*< Prioritised ready tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList1; /*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2; /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList; /*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList; /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList; /*< Tasks that have been readied while the scheduler was suspended. They will be moved to the ready list when the scheduler is resumed. */
```
- 我們需要存取的是:
- `pxReadyTasksLists`
- `pxDelayedTaskList`
- `pxOverflowDelayedTaskList`
- 這幾個list都被宣告為 `static` ,我們無法在別的 `.c` 裡面存取這些list,所以我們的 `TaskMonitor()` 必須要在 `task.c` 裡面才可以存取這幾個list
此外,在 `tasks.c` 可看到 `tskTaskControlBlock` 的相關定義:
```cpp
/*
* Task control block. A task control block (TCB) is allocated for each task,
* and stores task state information, including a pointer to the task's context
* (the task's run time environment, including register values)
*/
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
/* 以下僅列出需要印出來的data member */
volatile StackType_t *pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */
StackType_t *pxStack; /*< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
#endif
} tskTCB;
/* The old tskTCB name is maintained above then typedefed to the new TCB_t name
below to enable the use of older kernel aware debuggers. */
typedef tskTCB TCB_t;
```
在 `list.h` 中需要弄懂以下三個struct:
```cpp
/*
* 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. */
};
typedef struct xLIST_ITEM ListItem_t;
/* For some reason lint wants this as two separate definitions. */
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;
/*
* 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;
```
在 `list.h` 中可能會有幫助的macro:
```cpp
/*
* Access macro to determine if a list contains any items. The macro will
* only have the value true if the list is empty.
*
* \page listLIST_IS_EMPTY listLIST_IS_EMPTY
* \ingroup LinkedList
*/
#define listLIST_IS_EMPTY( pxList ) ( ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) ? pdTRUE : pdFALSE )
/*
* Access macro to return the number of items in the list.
*/
#define listCURRENT_LIST_LENGTH( pxList ) ( ( pxList )->uxNumberOfItems )
/*
* Access function to obtain the owner of the first entry in a list. Lists
* are normally sorted in ascending item value order.
*
* This function returns the pxOwner member of the first item in the list.
* The pxOwner parameter of a list item is a pointer to the object that owns
* the list item. In the scheduler this is normally a task control block.
* The pxOwner parameter effectively creates a two way link between the list
* item and its owner.
*
* @param pxList The list from which the owner of the head item is to be
* returned.
*
* \page listGET_OWNER_OF_HEAD_ENTRY listGET_OWNER_OF_HEAD_ENTRY
* \ingroup LinkedList
*/
#define listGET_OWNER_OF_HEAD_ENTRY( pxList ) ( (&( ( pxList )->xListEnd ))->pxNext->pvOwner )
```
<!--
`tasks.c`
```cpp=252
/*
* Place the task represented by pxTCB into the appropriate ready list for
* the task. It is inserted at the end of the list.
*/
#define prvAddTaskToReadyList( pxTCB ) \
traceMOVED_TASK_TO_READY_STATE( pxTCB ); \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
/*-----------------------------------------------------------*/
```
```cpp=3584
static void prvInitialiseTaskLists( void )
{
UBaseType_t uxPriority;
for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
vListInitialise( &xDelayedTaskList1 );
vListInitialise( &xDelayedTaskList2 );
vListInitialise( &xPendingReadyList );
#if ( INCLUDE_vTaskDelete == 1 )
{
vListInitialise( &xTasksWaitingTermination );
}
#endif /* INCLUDE_vTaskDelete */
#if ( INCLUDE_vTaskSuspend == 1 )
{
vListInitialise( &xSuspendedTaskList );
}
#endif /* INCLUDE_vTaskSuspend */
/* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskList
using list2. */
pxDelayedTaskList = &xDelayedTaskList1;
pxOverflowDelayedTaskList = &xDelayedTaskList2;
}
```
-->
:::info
`TCB_t` 的data members,有 `UBaseType_t` 和指向 `StackType_t` 的pointer,以下補充他們是什麼:
在 `FreeRTOS/portable/ARM_CM4F/portmacro.h` 內(找好久才找到原來是定義在這@_@),可看到關於 `StackType_t` 、`BaseType_t` 、 `UBaseType_t` 、以及 `TickType_t` 的相關定義:
```cpp=36
/*-----------------------------------------------------------
* Port specific definitions.
*
* The settings in this file configure FreeRTOS correctly for the
* given hardware and compiler.
*
* These settings should not be altered.
*-----------------------------------------------------------
*/
/* Type definitions. */
#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
/* 32-bit tick type on a 32-bit architecture, so reads of the tick count do
not need to be guarded with a critical section. */
#define portTICK_TYPE_IS_ATOMIC 1
#endif
/*-----------------------------------------------------------*/
```
可以看到 `StackType_t` 其實是 `uint32_t` 是unsigned integer;`BaseType_t` 是 `long`;`UBaseType_t` 是 `unsigned long`
FreeRTOS的官方網站也有這幾個type的說明(https://www.freertos.org/FreeRTOS-Coding-Standard-and-Style-Guide.html#DataTypes)
:::
## Code
這份作業最麻煩的大概就是要弄清楚ready list、delayed list、overflow delayed list的data structure,以及他們是怎麼串起來的(後面會再介紹),弄清楚後其實就只是在做linked list的題目
:::info
一般我們寫C程式若用 `printf` 印出pointer的value,我們都是用 `%p` 這個format specifier來指定我們要印出來的東西是address,像是 `printf("%p", ptr)` ,這樣印出來的結果就會是16進位。
這份lab需要將TCB的 `pxStack` 和 `pxTopOfStack` 這兩個pointer的值印出來,那由於我們是用 `HAL_UART_Transmit` 把資訊輸出,我們必須先把他們的值放到char array,才能用 `HAL_UART_Transmit`。但我們若取得pointer的值,直接放到char array,這樣那個pointer的值會是以10進位表示,這樣就不符合這個lab的要求。
這時可以用 `itoa` 直接指定用16進位而很方便地將值轉換成16進位表示。
:::warning
`itoa`
- 此function可將整數轉換為char array
- 此function通常並非 C 語言標準函式,因此其可用性可能取決於使用的編譯器。有些編譯器可能提供 itoa 函數,而有些則不提供。若是有提供的則需 `#include <stdlib>`
Function prototype
```cpp
char *itoa(int value, char *str, int base);
```
參數:
- `value`: 要轉換的整數值
- `str`: A pointer to the character array (string) where the result will be stored.
- `base`: 表示進制的整數值,例如 10 表示十進制,16 表示十六進制等
- Return Value: Returns a pointer to the resulting string.
:::
```cpp
// 看要補多少個空格到char array以用來對齊
void vPadding(char string [], int padNum) {
for (int i = 0; i < padNum; ++i) {
strcat(string, " ");
}
}
// 印出助教所要求的TCB資訊
void vPrintTCB(tskTCB *tcb, char state []) {
// 以下是處理task name的部分
char taskName [12];
memset(taskName, '\0', sizeof(taskName));
strcat(taskName, tcb->pcTaskName);
vPadding(taskName, 10 - strlen(taskName));
// 以下是處理task priority的部分
char taskPriority [24];
memset(taskPriority, '\0', sizeof(taskPriority));
char basePriority [3];
memset(basePriority, '\0', sizeof(basePriority));
itoa(tcb->uxBasePriority, basePriority, 10);
char priority [3];
memset(priority, '\0', sizeof(priority));
itoa(tcb->uxPriority, priority, 10);
vPadding(taskPriority, 5);
strcat(taskPriority, basePriority);
strcat(taskPriority, (strlen(basePriority) == 1) ? " /" : "/");
strcat(taskPriority, priority);
vPadding(taskPriority, (strlen(priority) == 1) ? 14 : 13);
// 以下是處理pxStack的部分
char task_pxStack [12];
memset(task_pxStack, '\0', sizeof(task_pxStack));
char tmp [10];
itoa( (size_t) tcb->pxStack, tmp, 16);
strcat(task_pxStack, "0x");
strcat(task_pxStack, tmp);
vPadding(task_pxStack, 11 - strlen(task_pxStack));
// 以下是處理pxTopOfStack的部分
char task_pxTopOfStack [17];
memset(task_pxTopOfStack, '\0', sizeof(task_pxTopOfStack));
memset(tmp, '\0', sizeof(tmp));
itoa( (size_t) tcb->pxTopOfStack, tmp, 16);
strcat(task_pxTopOfStack, "0x");
strcat(task_pxTopOfStack, tmp);
vPadding(task_pxTopOfStack, 16 - strlen(task_pxTopOfStack));
// 以下是處理task state的部分
char taskState [9];
memset(taskState, '\0', sizeof(taskState));
strcpy(taskState, state);
// 把前面處理的資訊都strcat到msg裡面
char msg [100];
memset(msg, '\0', sizeof(msg));
vPadding(msg, 1);
strcat(msg, taskName);
vPadding(msg, 1);
strcat(msg, taskPriority);
vPadding(msg, 1);
strcat(msg, task_pxStack);
vPadding(msg, 1);
strcat(msg, task_pxTopOfStack);
vPadding(msg, 1);
strcat(msg, taskState);
strcat(msg, "\n\r");
// 將msg用HAL_UART_Transmit印出來
HAL_UART_Transmit(&huart2, (uint8_t *) msg, strlen(msg), 0xffff);
}
void Taskmonitor(void)
{
/* Initialize string */
char Monitor_data[130];
memset(Monitor_data,'\0',sizeof(Monitor_data));
/* Stop scheduler */
/* Taskmonitor() will block when UART is transmitting data */
/* Scheduler will change list data when Taskmonitor() is blocked */
vTaskSuspendAll();
/* Print title */
sprintf(Monitor_data,"|Name |Priority(Base/actual) |pxStack |pxTopOfStack |State |\n\r");
HAL_UART_Transmit(&huart2, (uint8_t *) Monitor_data, strlen(Monitor_data), 0xffff);
/* pxReadyTasksLists */
for (UBaseType_t priority = 0; priority < configMAX_PRIORITIES; ++priority) {
// 前面有設定過configMAX_PRIORITIES是15,所以priority範圍是0~14
// 從TCB的struct可以看到priority的data type是UBaseType_t,所以才宣告其data type為UBaseType_t (但也可以宣告為int啦)
UBaseType_t readyListLength = listCURRENT_LIST_LENGTH(&pxReadyTasksLists[priority]);
// 取得目前這個priority的ready list裡面有多少個item
if (readyListLength != 0) {
ListItem_t *curNode = listGET_ITEM_OF_HEAD_ENTRY(&pxReadyTasksLists[priority]);
// 取得此priority的ready list的第一個item
for (UBaseType_t i = 0; i < readyListLength; ++i) {
vPrintTCB(curNode->pvOwner, "Ready");
// 將此item的TCB傳到vPrintTCB
curNode = curNode->pxNext;
// 換到下一個item
}
}
}
/* pxDelayedTaskList*/
UBaseType_t delayedListLength = listCURRENT_LIST_LENGTH(pxDelayedTaskList);
// 取得delayed list有多少個item
if (delayedListLength != 0) {
ListItem_t *curNode = listGET_ITEM_OF_HEAD_ENTRY(pxDelayedTaskList);
// 取得delayed list的第一個item
for (UBaseType_t i = 0; i < delayedListLength; ++i) {
vPrintTCB(curNode->pvOwner, "Blocked");
// 將此item的TCB傳到vPrintTCB
curNode = curNode->pxNext;
// 換到下一個item
}
}
/* pxOverflowDelayedTaskList */
UBaseType_t overflowListLength = listCURRENT_LIST_LENGTH(pxOverflowDelayedTaskList);
// 取得overflow delayed list有多少個item
if (overflowListLength != 0) {
ListItem_t *curNode = listGET_ITEM_OF_HEAD_ENTRY(pxOverflowDelayedTaskList);
// 取得overflow delayed list的第一個item
for (UBaseType_t i = 0; i < overflowListLength; ++i) {
vPrintTCB(curNode->pvOwner, "Overflow");
// 將此item的TCB傳到vPrintTCB
curNode = curNode->pxNext;
// 換到下一個item
}
}
/* Resume scheduler */
xTaskResumeAll();
}
```
## Result

## Ready List, Delayed List, & Overflow Delayed List of FreeRTOS
```cpp
// tasks.c
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
PRIVILEGED_DATA static List_t xDelayedTaskList1;
PRIVILEGED_DATA static List_t xDelayedTaskList2;
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;
```
前面有提到,在 `tasks.c` 有這幾個global variable,可以看到 `pxReadyTasksLists` 被宣告成array;那關於 `xDelayedTaskList1` 和 `xDelayedTaskList2` 是什麼?可以另外在 `task.c` 看到 `prvInitialiseTaskLists` 這個function:
```cpp
static void prvInitialiseTaskLists( void ) {
UBaseType_t uxPriority;
for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
vListInitialise( &xDelayedTaskList1 );
vListInitialise( &xDelayedTaskList2 );
vListInitialise( &xPendingReadyList );
#if ( INCLUDE_vTaskDelete == 1 )
{
vListInitialise( &xTasksWaitingTermination );
}
#endif /* INCLUDE_vTaskDelete */
#if ( INCLUDE_vTaskSuspend == 1 )
{
vListInitialise( &xSuspendedTaskList );
}
#endif /* INCLUDE_vTaskSuspend */
/* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskList
using list2. */
pxDelayedTaskList = &xDelayedTaskList1;
pxOverflowDelayedTaskList = &xDelayedTaskList2;
}
```
所以可以看到當 `xDelayedTaskList1` 和 `xDelayedTaskList2` 被初始化之後,`pxDelayedTaskList` 和 `pxOverflowDelayedTaskList` 這兩個pointer就會分別去指向 `xDelayedTaskList1` 和 `xDelayedTaskList2`。
以下是各個list怎麼把他的item串起來的簡單示意圖:

- `pxReadyTasksLists` 是一個 `List_t` 這個struct的array
- `pxReadyTasksLists[0]`
- 第一個element就是priority 0的 `List_t`。這個 `List_t` 會指向所有priority為0的task
- task在這裡是用 `ListItem_t` 來表達,而 `ListItem_t` 的 `pvOwner` 就會指向該task的TCB
- `pxReadyTasksLists[1]`
- 第二個elemnt就是priority 1的 `List_t`,此 `List_t` 會指向priority為1的task
- 後面依此類推
- `pxDelayedTaskList` 和 `pxOverflowDelayedTaskList` 就不是array,他們自己就是一個 `List_t` ,並且各自指向目前是delayed/overflow delayed的tasks
- task在這裡也是用`ListItem_t` 來表達,且 `ListItem_t` 的 `pvOwner` 也會指向該task的TCB

- 前一張圖只是簡單的示意圖,這一張是更詳細的說明 `List_t` 和 `ListItme_t` 之間的關係
- `List_t` 的 `uxNumberOfItems` 會記錄這個List有多少 `ListItem_t` ,每當有新的 `ListItem_t` 被插入,他的值就會 +1
- `List_t` 的 `pxIndex` 是指向 `ListItem_t` 的pointer。此pointer在 `List_t` 被初始化時就會指向 `xListEnd` ,除非有使用 `listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )` 這個macro,否則我看程式碼他應該都是指向 `xListEnd`
- `pxIndex` 的主要的目的是要搭配 `listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )` 來walk through每個item
- `List_t` 的 `xListEnd` 基本上是拿來當作這個List的最後一個item
- `xListEnd` 的struct是 `MiniListItem_t` ,他的data member跟 `ListItem_t` 有點像,但比較少,所以這裡選擇用 `MiniListItem_t` 是為了可以減少記憶體空間的使用
- 在 `List_t` 被初始化時, `xListEnd` 的 `xItemValue` 就會被初始化成 `portMAX_DELAY`,這樣才能確保 `xListEnd` 的value是該list所有item中最大的
- 再來,可以看到 `ListItem_t` 之間是用doubly linked list串在一起
- 所以最後一個 `ListItem_t` 的 `pxNext` 會指向 `xListEnd`,而 `xListEnd` 的 `pxPrevious` 會指向最後一個 `ListItem_t`,這樣才能確保doubly linked list的完整性
- 第一個 `ListItem_t` 的 `pxPrevous` 會指向 `xListEnd`,而 `xListEnd` 的 `pxNext` 會指向第一個 `ListItem_t`
- 在 `tasks.c` 的 `prvInitialiseNewTask()` 內可以看到, `ListItem_t` 的`xItemValue` 會被初始化成 `( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority`
- `ListItem_t` 的 `pvContainer` 是用來指向說我這個Item目前是在哪個List裡面,若是在 `pxReadyTasksLists[0]`,就會指向 `pxReadyTasksLists[0]`

- 來看 `List_t` 是怎麼被初始化的~
- 可以看到 `pxIndex` 會指向 `xListEnd`
- `xListEnd` 的 `xItemValue` 被設定為 `portMAX_DELAY`,這樣才能確保 `xListEnd` 的value是該list所有item中最大的(這個值的大小跟放到delayed list有關)
- `xListEnd` 的 `pxNext` 和 `pxPrevious` 都指向 `xListEnd`,我們也能因此知道這個 `List_t` 什麼時候是空的
- `uxNumberOfItems` 初始化成0
:::info
- 一個 `ListItem_t` 要被放到ready list的話,會藉由呼叫 `prvAddTaskToReadyList( pxTCB )` (在 `tasks.c`)這個macro,此macro會呼叫 `vListInsertEnd` (在 `list.c`)這個function把他放到該priority的ready list的最後一個
- 若是要放到delayed list,則會呼叫 `ListInsert` (在 `list.c`),此function就會依照 `xItemValue` 的值決定插入的位子
:::
以下做了簡單的圖示說明 `vListInsertEnd` 怎麼作用的:


1. 將 `pxNewListItem` 的 `pxNext` 指向 `pxIndex` 所指向的對象,也就是 `xListEnd`
2. 將 `pxNewListItem` 的 `pxPreivous` 指向 `pxIndex` 所指向的對象的 `pxPrevious`,也就是 `xListEnd` 的 `pxPrevious` 所指向的對象,也就是 `xListEnd` 自己
3. `pxIndex` 所指向的對象(即`xListEnd`)的 `pxPrevious` 所指向的對象(此時仍是`xListEnd`)的 `pxNext` 指向 `pxNewListItem`
4. `xListEnd` 的 `pxPrevious` 指向 `pxNewListItem`
5. `pxNewListItem` 的 `pxContainer` 改成指向此list
6. 此list的 `uxNumberOfItems` +1

## listGET_OWNER_OF_NEXT_ENTRY
前面在研究 `List_t` 時,一直很好奇 `pxIndex` 的功能是什麼,一直看到註解說這個pointer要搭配 `listGET_OWNER_OF_NEXT_ENTRY` 這個macro就可以walk through這個list全部的item,並得到該item的TCB,想說這不是超適合這個lab嗎~所以就研究了一下這個macro,以下是此macro的定義:
```cpp
/*
* Access function to obtain the owner of the next entry in a list.
*
* The list member pxIndex is used to walk through a list. Calling
* listGET_OWNER_OF_NEXT_ENTRY increments pxIndex to the next item in the list
* and returns that entry's pxOwner parameter. Using multiple calls to this
* function it is therefore possible to move through every item contained in
* a list.
*
* The pxOwner parameter of a list item is a pointer to the object that owns
* the list item. In the scheduler this is normally a task control block.
* The pxOwner parameter effectively creates a two way link between the list
* item and its owner.
*
* @param pxTCB pxTCB is set to the address of the owner of the next list item.
* @param pxList The list from which the next item owner is to be returned.
*
* \page listGET_OWNER_OF_NEXT_ENTRY listGET_OWNER_OF_NEXT_ENTRY
* \ingroup LinkedList
*/
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \
List_t * const pxConstList = ( pxList ); \
/* Increment the index to the next item and return the item, ensuring */ \
/* we don't return the marker used at the end of the list. */ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
{ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
} \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}
```
來逐一看這個macro的code:
- 首先,參數 `pxTCB` 和 `pxList` 都是pointer型態,`pxTCB` 是指向 `TCB_t` 的pointer,`pxList` 則是指向 `List_t` 的pointer
- 宣告一個指向 `List_t` 的pointer,名稱是 `pxConstList`,並把 `pxList` 的value assign給 `pxConstList`,這樣 `pxConstList` 就指向 `pxList` 所指向的list了
- PS. `pxConstList` 是 `List_t * const` ,代表這個pointer指向的對象是不能更改的,也就是這個pointer的value是不能更改的,他只能指向同一個東西,不能讓他指向其他東西,所以一開始宣告時就必須必須告訴他要指向誰才行
- `( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;`
- 一開始的 `pxIndex` 是指向 `List_t` 的 `xListEnd`,所以這裡讓 `pxIndex` 指向第一個 `ListItem_t`
- `if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )`
- `pxIndex` 是一個指向 `ListItem_t` 的pointer,把這個pointer的value拿出來並轉型成 `(void *)`
- 把list的 `xListEnd` 的address拿出來,並且也轉型成 `(void *)`
- 再來,比較這兩個address是否一樣,若一樣則代表已經到list的end了,因為已經到 `xListEnd` 了,所以這時要 `( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;` ,這樣才能指向第一個item
- `( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;`
- 把目前 `pxIndex` 指向的 `ListItem_t` 的 `pvOwner` 的值assign給 `pxTCB` ,這樣 `pxTCB` 就會指向該TCB,我們後續就可以用 `pxTCB` 去存取TCB
- 這裡因為是用macro,所以可以直接更改pointer指向的對象;但若 `listGET_OWNER_OF_NEXT_ENTRY` 被改成一個function,那 `pxTCB` 這個參數就必須是pointer to pointer才可以
:::info
為什麼要轉型成 `(void *)` 才去做比較?
- 在C,若要比較pointers的值是否相同,很常用的技巧是會先把他轉型成 `(void *)` 後才去做比較,否則編譯器可能會有type mismatch的warning或error
- 另外,也可以轉型成 `size_t` 去做比較
:::
:::info
複習 **Const & Pointers**
++Pointers to constants++
```cpp
int num1 = 100;
int num2 = 200;
const int *ptr = &num1;
*ptr = 1000; // ERROR!
ptr = &num2; // OK
```
- 可以讓pointer去指向不同的物件
- 但是我們不能更改pointer指向的物件的value
++Constant pointers++
```cpp
int num1 = 100;
int num2 = 200;
int *const ptr = &num1;
*ptr = 1000; // OK
ptr = &num2; // ERROR!
```
- 這個pointer本身是constant,不能讓他指向其他物件
- 所以這用法在宣告時就必須一併告訴他要指向哪個物件
- 我們可以修改此pointer指向的物件的value
++Constant pointers to constants++
```cpp
int num1 = 100;
int num2 = 200;
const int *const ptr = &num1;
*ptr = 1000; // ERROR!
ptr = &num2; // ERROR!
```
- 這個pointer本身是constant,不能讓他指向其他物件
- 在宣告此pointer時就必須一併告訴他要指向哪個物件
- 我們不能更改pointer指向的物件的value
:::
再來,我找了一下 `tasks.c` 裡面都是怎麼使用 `listGET_OWNER_OF_NEXT_ENTRY` 這個macro,找到了以下這段code:
```cpp
static UBaseType_t prvListTasksWithinSingleList( TaskStatus_t *pxTaskStatusArray, List_t *pxList, eTaskState eState )
{
configLIST_VOLATILE TCB_t *pxNextTCB, *pxFirstTCB;
UBaseType_t uxTask = 0;
if( listCURRENT_LIST_LENGTH( pxList ) > ( UBaseType_t ) 0 )
{
listGET_OWNER_OF_NEXT_ENTRY( pxFirstTCB, pxList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
/* Populate an TaskStatus_t structure within the
pxTaskStatusArray array for each task that is referenced from
pxList. See the definition of TaskStatus_t in task.h for the
meaning of each TaskStatus_t structure member. */
do
{
listGET_OWNER_OF_NEXT_ENTRY( pxNextTCB, pxList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
vTaskGetInfo( ( TaskHandle_t ) pxNextTCB, &( pxTaskStatusArray[ uxTask ] ), pdTRUE, eState );
uxTask++;
} while( pxNextTCB != pxFirstTCB );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return uxTask;
}
```
可以看到若要使用 `listGET_OWNER_OF_NEXT_ENTRY` 這個macro,基本用法如下:
```cpp!
TCB_t *pxNextTCB, *pxFirstTCB;
if (listCURRENT_LIST_LENGTH( pxList ) > ( UBaseType_t ) 0) {
// 要先確認這個 List 裡面是有 item 的
// PS. pxList 是個 pointer to List_t
listGET_OWNER_OF_NEXT_ENTRY( pxFirstTCB, pxList );
do {
listGET_OWNER_OF_NEXT_ENTRY( pxNextTCB, pxList );
// 這裡就可以用 pxNextTCB 去存取 ListItem 的 TCB
} while ( pxNextTCB != pxFirstTCB );
}
```
所以依據這個macro,我又改寫了一下我的 `Taskmonitor`,並確定可以得到一樣的結果:
```cpp=
void Taskmonitor(void)
{
/* Initialize string */
char Monitor_data[130];
memset(Monitor_data,'\0',sizeof(Monitor_data));
/* Stop scheduler */
/* Taskmonitor() will block when UART is transmitting data */
/* Scheduler will change list data when Taskmonitor() is blocked */
vTaskSuspendAll();
/* Print title */
sprintf(Monitor_data,"|Name |Priority(Base/actual) |pxStack |pxTopOfStack |State |\n\r");
HAL_UART_Transmit(&huart2, (uint8_t *) Monitor_data, strlen(Monitor_data), 0xffff);
TCB_t *pxNextTCB = NULL, *pxFirstTCB = NULL;
/* pxReadyTasksLists */
for (UBaseType_t priority = 0; priority < configMAX_PRIORITIES; ++priority) {
if (listCURRENT_LIST_LENGTH( &pxReadyTasksLists[priority] ) > ( UBaseType_t ) 0) {
listGET_OWNER_OF_NEXT_ENTRY( pxFirstTCB, &pxReadyTasksLists[priority] );
do {
listGET_OWNER_OF_NEXT_ENTRY( pxNextTCB, &pxReadyTasksLists[priority] );
vPrintTCB(pxNextTCB, "Ready");
} while (pxNextTCB != pxFirstTCB);
}
}
/* pxDelayedTaskList*/
if (listCURRENT_LIST_LENGTH( pxDelayedTaskList ) > ( UBaseType_t ) 0) {
listGET_OWNER_OF_NEXT_ENTRY( pxFirstTCB, pxDelayedTaskList );
do {
listGET_OWNER_OF_NEXT_ENTRY( pxNextTCB, pxDelayedTaskList );
vPrintTCB(pxNextTCB, "Blocked");
} while (pxNextTCB != pxFirstTCB);
}
/* pxOverflowDelayedTaskList */
if (listCURRENT_LIST_LENGTH( pxOverflowDelayedTaskList ) > ( UBaseType_t ) 0) {
listGET_OWNER_OF_NEXT_ENTRY( pxFirstTCB, pxOverflowDelayedTaskList );
do {
listGET_OWNER_OF_NEXT_ENTRY( pxNextTCB, pxOverflowDelayedTaskList );
vPrintTCB(pxNextTCB, "Overflow");
} while (pxNextTCB != pxFirstTCB);
}
/* Resume scheduler */
xTaskResumeAll();
}
```