FreeRTOSFreeRTOS学习笔记— 列表和列表项(链表和节点)

Posted 果果小师弟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FreeRTOSFreeRTOS学习笔记— 列表和列表项(链表和节点)相关的知识,希望对你有一定的参考价值。

1、什么是列表和列表项?

在 FreeRTOS中存在着大量的基础数据结构列表和列表项的操作,要想读懂 FreeRTOS的源码或者从0到1开始实现FreeRTOS,就必须弄懂列表和列表项的操作,其实也没那么难列表和列表项是直接从FreeRTos源码的注释中的list和list_item翻译过来的,其实就是对应我们C语言当中的链表和节点。所以在FreeRTOS我们也可以认为链表就是列表,节点就是列表项

1.1、C语言链表简介

链表作为 C 语言中一种基础的数据结构,在平时写程序的时候用的并不多,但在操作系统里面使用的非常多。链表就好比一个圆形的晾衣架,晾衣架上面有很多钩子,钩子首尾相连。链表也是,链表由节点组成,节点与节点之间首尾相连。

晾衣架的钩子本身不能代表很多东西,但是钩子本身却可以挂很多东西。同样,链表也类似,链表的节点本身不能存储太多东西,或者说链表的节点本来就不是用来存储大量
数据的,但是节点跟晾衣架的钩子一样,可以挂很多数据

1.2、单向链表

单向链链表中共有 n个节点,前一个节点都有一个箭头指向后一个节点,首尾相连,组成一个圈。

节点本身必须包含一个节点指针,用于指向后一个节点,除了这个节点指针是必须有
的之外,节点本身还可以携带一些私有信息,怎么携带?

节点都是一个自定义类型的数据结构,在这个数据结构里面可以有单个的数据、数组、
指针数据和自定义的结构体数据类型等等信息,

struct node
{
	struct node *next; 			/* 指向链表的下一个节点 */
	char data1; 						/* 单个的数据 */
	unsigned char array[]; 	/* 数组 */
	unsigned long *prt; 			/* 指针数据 */
	struct userstruct data2; /* 自定义结构体类型数据 */
	/* ...... */
}

除了 struct node *next 这个节点指针之外,剩下的成员都可以理解为节点携带的数据,但是这种方法很少用。通常的做法是节点里面只包含一个用于指向下一个节点的指针。要通过链表存储的数据内嵌一个节点即可,这些要存储的数据通过这个内嵌的节点即可挂接到链表中,就好像晾衣架的钩子一样,把衣服挂接到晾衣架中。具体的伪代码实现见代码清单

/* 节点定义 */
struct node
{
	struct node *next; /* 指向链表的下一个节点 */
}
struct userstruct
{
	/* 在结构体中,内嵌一个节点指针,通过这个节点将数据挂接到链表 */
	struct node *next;
	/* 各种各样......,要存储的数据 */
}

1.3、列表和列表项

列表是FreeRTOS中的一个数据结构,概念上和链表差不多,列表被用来跟踪 FreeRTOS中的任务。与列表相关的全部东西都在文件list.clist.h 中。在FreeRTOS认为链表就是列表,节点就是列表项,这一点要注意。


2、列表和列表项结构体介绍

在FreeRTOS中关与列表和列表项,也就是链表和节点的结构体不多,只有三个。链表节点 结构体定义xLIST_ITEM、链表精简节点结构体定义xMINI_LIST_ITEM、链表根节点数据结构体定义List_t

/*
 * 链表节点 数据结构定义
 */
struct xLIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           /*< 如果listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE设置为1,则设置为已知值. */
    configLIST_VOLATILE TickType_t xItemValue;          /*< 辅助值,用于帮助节点做顺序排列.uint32_t类型 */
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;     /*< 用于指向链表下一个节点. */
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*< 用于指向链表前一个节点. */
    void * pvOwner;                                     /*< 用于指向该节点的拥有者,即该节点内嵌在哪个数据结构中,属于哪个数据结构的一个成员,通常是 TCB . */
    struct xLIST * configLIST_VOLATILE pxContainer;     /*< 用于指向该节点所在的链表,通常指向链表的根节点. */
    listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE          /*< 如果listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE设置为1,则设置为已知值. */
};
typedef struct xLIST_ITEM ListItem_t;                   /* 节点数据类型重定义. */

/*
 * 链表精简节点 结构体定义
 */
struct xMINI_LIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE 			/*< 如果listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE设置为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;			/* 精简节点数据类型重定义 */

/*
 * 链表根节点 数据结构定义
 */
typedef struct xLIST
{
    listFIRST_LIST_INTEGRITY_CHECK_VALUE      /*< 如果listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE设置为1,则设置为已知值. */
    volatile UBaseType_t uxNumberOfItems;	  /*< 链表节点计数器,用于表示该链表下有多少个节点,根节点除外. */
    ListItem_t * configLIST_VOLATILE pxIndex; /*< 链表节点索引指针,用于遍历节点. */
    MiniListItem_t xListEnd;                  /*< 链表最后一个节点,链表是首尾相连的,是一个圈,首就是尾,尾就是首,
												  这里从字面上理解就是链表的最后一个节点,实际也就是链表的第一个节点,我们称之为生产者. */
    listSECOND_LIST_INTEGRITY_CHECK_VALUE     /*< 如果listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE设置为1,则设置为已知值. */
} List_t;

1、节点数据结构体

链表节点的数据结构在list.h中定义。结构体名就是xLIST_ITEM,根据这个拼音就是知道LIST代表列表,ITEM表示项目,合起来就是列表项,也就是节点。所以根据这个结构体名就知道这是一个关于节点的结构体。所以不要觉得链表好难,结构体看不懂。其实你看名字就知道这是结构体的功能了,既然是关于节点的结构体,那么这个结构体肯定包含了一个节点的必要信息对吧。节点是干嘛的?节点是用来存储数据的,就是用来挂衣服的。然后再来看这个结构体的成员变量:

/*
 * 节点——数据结构体
 */
struct xLIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           /*< 如果listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE设置为1,则设置为已知值. */
    configLIST_VOLATILE TickType_t xItemValue;          /*< 辅助值,用于帮助节点做顺序排列.uint32_t类型 */
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;     /*< 用于指向链表下一个节点. */
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*< 用于指向链表前一个节点. */
    void * pvOwner;                                     /*< 用于指向该节点的拥有者,即该节点内嵌在哪个数据结构中,属于哪个数据结构的一个成员,通常是 TCB . */
    struct xLIST * configLIST_VOLATILE pxContainer;     /*< 用于指向该节点所在的链表,通常指向链表的根节点. */
    listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE          /*< 如果listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE设置为1,则设置为已知值. */
};
typedef struct xLIST_ITEM ListItem_t;                   /* 节点数据类型重定义. */
  • listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE:忽略不管
  • xItemValue:一个辅助值,用于帮助节点做顺序排列。该辅助值的数据类型为TickType_t,也就是uint32_t
  • pxNext:用于指向链表下一个节点。
  • pxPrevious:用于指向链表前一个节点。
  • pvOwner:用于指向该节点的拥有者,即该节点内嵌在哪个数据结构中,属于哪个数据结构的一个成员,通常是TCB,也就是任务控制块。
  • pxContainer:用于指向该节点所在的链表,通常指向链表的根节点。
  • listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE:忽略不管。

这个结构体乍一看有7个成员变量,其实只有5个成员。第一个和对后一个这两个都是用来检查节点完整性的,需要将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为 1,开启以后会向这两个地方分别添加一个变量xListIntegrityValue1xListIntegrityValue2,在初始化链表的时候会这两个变量中写入一个特殊的值,默认不开启这个功能。在学习链表的时候不讨论这个功能。

2、链表数据结构体

链表的数据结构体在list.h中定义。结构体名就是xLIST,根据这个拼音就是知道LIST代表列表,也就是链表。所以根据这个结构体名就知道这是一个关于链表的结构体。既然是关于链表的结构体,那么这个结构体肯定包肯定就没有节点结构体那么复杂,链表只是一个圆环。链表是干嘛的?链表是用来挂节点的,就是用来挂挂钩的的。然后再来看这个结构体的成员变量:

/*
 * 链表——数据结构定义.
 */
typedef struct xLIST
{
    listFIRST_LIST_INTEGRITY_CHECK_VALUE      /*< 如果listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE设置为1,则设置为已知值. */
    volatile UBaseType_t uxNumberOfItems;	  /*< 链表节点计数器,用于表示该链表下有多少个节点,根节点除外. */
    ListItem_t * configLIST_VOLATILE pxIndex; /*< 链表节点索引指针,用于遍历节点. */
    MiniListItem_t xListEnd;                  /*< 链表最后一个节点,链表是首尾相连的,是一个圈,首就是尾,尾就是首,
												  这里从字面上理解就是链表的最后一个节点,实际也就是链表的第一个节点,我们称之为生产者. */
    listSECOND_LIST_INTEGRITY_CHECK_VALUE     /*< 如果listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE设置为1,则设置为已知值. */
} List_t;
  • listFIRST_LIST_INTEGRITY_CHECK_VALUE:忽略不管
  • uxNumberOfItems:链表节点计数器,用于表示该链表下有多少个节点,根节点除外。
  • pxIndex:链表节点索引指针,用于遍历节点。
  • xListEnd:链表最后一个节点,链表是首尾相连的,是一个圈,首就是尾,尾就是首。
  • listSECOND_LIST_INTEGRITY_CHECK_VALUE:忽略不管。

这个结构体乍一看有5个成员变量,其实只有3个成员。第一个和对后一个这两个跟节点用法一样,都是用来检查链表完整性的,需要将宏listFIRST_LIST_INTEGRITY_CHECK_VALUE设置为 1,开启以后会向这两个地方分别添加一个变量xListIntegrityValue1xListIntegrityValue2,在初始化列表的时候会这两个变量中写入一个特殊的值,默认不开启这个功能。在学习列表的时候不讨论这个功能。

3、精简节点结构体

在链表结构体中xListEnd表示链表最后一个节点。我们知道,链表是首尾相连的,是一个圈,首就是尾,尾就是首,这里从字面上理解就是链表的最后一个节点,实际也就是链表的第一个节点,我们称之为生产者。该生产者的数据类型是一个精简的节点,这个精简的节点也在list.h中定义

/*
 * 精简节点——结构体
*/
struct xMINI_LIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE 			/*< 如果listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE设置为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;			/* 精简节点数据类型重定义 */
  • listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE:忽略不管
  • xItemValue:记录链表节点值。
  • pxNext:指向下一个节点。
  • pxPrevious:指向上一个节点。

可以看出迷你列表项只是比列表项少了几个成员变量,迷你列表项有的成员变量列表项都有的,没感觉有什么本质区别啊?那为什么要弄个迷你列表项出来呢?那是因为有些情况下我们不需要列表项这么全的功能,可能只需要其中的某几个成员变量,如果此时用列表项的话会造成内存浪费!比如上面列表结构体List_t中表示最后一个列表项的成员变量xListEnd就是MiniListItem_t类型的。

3、列表和列表项结函数介绍

3.1、链表初始化函数

/**
 * @brief		列表(链表)根节点初始化函数
 * @details	    新创建或者定义的列表需要对其做初始化处理,列表的初始化其实就是初始化列表结构体
 * 				List_t 中的各个成员变量,列表的初始化通过使函数 vListInitialise()来完成,
 *
 * @param[in]	pxList  List_t结构体句柄,初始化时请定义该句柄,并用其地址来传参。
 *                      List_t structure handle. When initializing, 
 *                      please define the handle and use its address
 *                      to pass parameters.
 *
 * @return      无.
**/
void vListInitialise( List_t * const pxList )
{
	/* 将链表索引指针指向最后一个节点,即第一个节点*/
    pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM.  This is checked and valid. */

	/* 将链表最后一个节点(也可以理解为第一个节点)的辅助排序的值设置为最大,确保该节点就是链表的最后节点 */
    pxList->xListEnd.xItemValue = portMAX_DELAY;

	/* 将最后一个节点(也可以理解为第一个节点)的 pxNext 和 pxPrevious 指针均指向节点自身,表示链表为空 */
    pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );     /*lint !e826 !e740 !e9087 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 !e9087 The mini list structure is used as the list end to save RAM.  This is checked and valid. */

	/* 初始化链表节点计数器的值为 0,表示链表为空 */
    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 );
}

3.2、节点初始化函数

表节点ListItem_t总共有5个成员,但是初始化的时候只需将pvContainer初始化为空即可,表示该节点还没有插入到任何链表。

/*-----------------------------------------------------------*/
/**
 * @brief		列表项(节点)初始化函数。
 * @details	    列表项(链表)的初始化很简单,只是将列表项成员变量 pvContainer 初始化为 NULL,并且给用于完整性检查的变量赋值。
 * @param[in]	pxItem  ListItem_t结构体句柄,初始化时请定义该句柄,并用其地址来传参。
 *                      ListItem_t structure handle. When initializing, 
 *                      please define the handle and use its address
 *                      to pass parameters.
 * @return      无.
**/
void vListInitialiseItem( ListItem_t * const pxItem )
{
    /* 初始化该节点所在的链表为空,表示节点还没有插入任何链表. */
    pxItem->pxContainer = 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 );
}

3.3、将节点插入到链表的尾部函数

将节点插入到链表的尾部(可以理解为头部)就是将一个新的节点插入到一个空的链表。

/**
 * @brief		列表项(节点)末尾插入函数,将节点插入到链表的尾部  
 * @details	    将节点插入到链表的尾部(可以理解为头部)就是将一个新的节点插入到一个空的链表
 *
 * @param[in]	pxList        列表项要插入的列表(节点要插入的链表)
 *              pxNewListItem 要插入的列表项 (要插入的节点)
 *
 * @return      无.
**/
void vListInsertEnd( List_t * const pxList,
                     ListItem_t * const pxNewListItem )
{
    ListItem_t * const pxIndex = pxList->pxIndex;

    /* Only effective when configASSERT() is also defined, these tests may catch
     * the list data structures being overwritten in memory.  They will not catch
     * data errors caused by incorrect configuration or use of FreeRTOS. */
    listTEST_LIST_INTEGRITY( pxList );
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

    /* Insert a new list item into pxList, but rather than sort the list,
     * makes the new list item the last item to be removed by a call to
     * listGET_OWNER_OF_NEXT_ENTRY(). */
    pxNewListItem->pxNext = pxIndex;
    pxNewListItem->pxPrevious = pxIndex->pxPrevious;

    /* Only used during decision coverage testing. */
    mtCOVERAGE_TEST_DELAY();

    pxIndex->pxPrevious->pxNext = pxNewListItem;
    pxIndex->pxPrevious = pxNewListItem;

    /* 记住该节点所在的链表. */
    pxNewListItem->pxContainer = pxList;

    ( pxList->uxNumberOfItems )++;
}

3.4、将节点插入到链表

将节点按照升序排列插入到链表,如果有两个节点的值相同,则新节点在旧节点的后面插入

/**
 * @brief		列表项(节点)插入函数。将节点按照升序排列插入到链表
 * @details	    将节点按照升序排列插入到链表,如果有两个节点的值相同,则新节点在旧节点的后面插入
 *
 * @param[in]	pxList        列表项要插入的列表(节点要插入的链表)
 *              pxNewListItem 要插入的列表项 (要插入的节点)
 *
 * @return      无.
**/
void vListInsert( List_t * const pxList,
                  ListItem_t * const pxNewListItem )
{
    ListItem_t * pxIterator;
    const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;

    listTEST_LIST_INTEGRITY( pxList );
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

	/*寻找节点要插入的位置*/
    if( xValueOfInsertion == portMAX_DELAY )
    {
        pxIterator = pxList->xListEnd.pxPrevious;
    }
    else
    {
        for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM.  This is checked and valid. *//*lint !e440 The iterator moves to a different value, not xValueOfInsertion. */
        {
            /* 没有事情可做,不断迭代只为了找到节点要插入的位置 */ 
        }
    }
	/* 根据升序排列,将节点插入 */ 
    pxNewListItem->pxNext = pxIterator->pxNext;
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
    pxNewListItem->pxPrevious = pxIterator;
    pxIterator->pxNext = pxNewListItem;

    /* 记住该节点所在的链表 */
    pxNewListItem->pxContainer = pxList;
	
	/* 链表节点计数器++ */
    ( pxList->uxNumberOfItems )++;
}

3.5、将节点从链表删除

将节点从链表删除。假设将一个有三个节点的链表中的中间节点节点删除。

/**
 * @brief		列表项(节点)删除函数。
 * @details	    将节点从链表删除.
 *
 * @param[in]	pxItemToRemove  要删除的列表项(节点)
 *              pxNewListItem   要插入的列表项(节点)
 *
 * @return      UBaseType_t     返回删除列表项(节点)以后的列表剩余列表项(节点)数目.
**/
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
	/* 获取节点所在的链表 */
    List_t * const pxList = pxItemToRemove->pxContainer;
	/* 将指定的节点从链表删除*/
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;    
    
    mtCOVERAGE_TEST_DELAY();

    /*调整链表的节点索引指针 */
    if( pxList->pxIndex == pxItemToRemove )
    {
        pxList->pxIndex = pxItemToRemove->pxPrevious;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
	/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
    pxItemToRemove->pxContainer = NULL;
	/* 链表节点计数器-- */
    ( pxList->uxNumberOfItems )--;
	/* 返回链表中剩余节点的个数 */
    return pxList->uxNumberOfItems;
}

3.6、节点带参宏小函数

/* 初始化节点的拥有者 */
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )    ( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )

/* 获取节点拥有者 */
#define listGET_LIST_ITEM_OWNER( pxListItem )             ( ( pxListItem )->pvOwner )

/* 初始化节点排序辅助值 */
#define listSET_LIST_ITEM_VALUE( pxListItem, xValue )     ( ( pxListItem )->xItemValue = ( xValue ) )

/* 获取节点排序辅助值 */
#define listGET_LIST_ITEM_VALUE( pxListItem )             ( ( pxListItem )->xItemValue )

/* 获取链表根节点的节点计数器的值 */
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList )        ( ( ( pxList )->xListEnd ).pxNext->xItemValue )

/* 获取链表的入口节点 */
#define listGET_HEAD_ENTRY( pxList )                      ( ( ( pxList )->xListEnd ).pxNext )

/* 获取节点的下一个节点 */
#define listGET_NEXT( pxListItem )                        ( ( pxListItem )->pxNext )

/* 获取链表的最后一个节点 */
#define listGET_END_MARKER( pxList )                      ( ( ListItem_t const * ) ( &( ( pxList )->xListEnd ) ) )

/* 判断链表是否为空 */
#define listLIST_IS_EMPTY( pxList )                       ( ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) ? pdTRUE : pdFALSE )

/* 获取链表的节点数 */
#define listCURRENT_LIST_LENGTH( pxList )                 ( ( pxList )->uxNumberOfItems )

4、链表节点插入试验

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "lcd.h"
#include "SEGGER_RTT.h"
#include "math.h"
#include "FreeRTOS.h"
#include "task.h"

#define START_TASK_PRIO		1				//任务优先级
#define TASK1_TASK_PRIO		2				//任务优先级
#define TASK2_TASK_PRIO		3 				//任务优先级
#define LIST_TASK_PRIO		3				//任务优先级

#define START_STK_SIZE 		128  			//任务堆栈大小
#define TASK1_STK_SIZE 		128  			//任务堆栈大小	
#define TASK2_STK_SIZE 		128  			//任务堆栈大小
#define LIST_STK_SIZE 		128  			//任务堆栈大小

TaskHandle_t StartTask_Handler;				//任务句柄
TaskHandle_t Task1Task_Handler;				//任务句柄
TaskHandle_t Task2Task_Handler;				//任务句柄
TaskHandle_t ListTask_Handler;				//任务句柄

void start_task(void *pvParameters);		//任务函数声明
void task1_task(void *pvParameters);		//任务函数声明
void task2_task(void *pvParameters);		//任务函数声明
void list_task(void *pvParameters);			//任务函数声明


//定义一个测试用的列表(链表)和3个列表项(节点)
List_t TestList;		//测试用列表(链表)
ListItem_t ListItem1;	//测试用列表项(节点)1
ListItem_t ListItem2;	//测试用列表项(节点)2
ListItem_t ListItem3;	//测试用列表项(节点)3



int main(void)
{
    HAL_Init();                 //初始化HAL库
    Stm32_Clock_Init(8, 336, 2, 7); //设置时钟,168Mhz
    delay_init(168);  	        //初始化延时函数
    KEY_Init();
	LED_Init();		        //初始化LED端口
    //动态创建开始任务
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄
    //开启任务调度
    /* 启动RTOS,其实就是启动“任务管理器”,启动之后任务管理器就开始调度线程,
     * 此时pc(程序计数器)就会指向某线程的指令,开始多线程并发运行。
     * 如果没有创建多线程的话,那就只有一个线程。*/
    vTaskStartScheduler();

    /* 由于调用了vTaskStartScheduler之后,PC就指向了线程中的指令,因此vTaskStartScheduler后面代码
    * 并不会被CPU执行,所以vTaskStartScheduler后的代码没有意义。 */
    while(1)
    {
        //这里的代码不会被执行,写了也没用
    }
}

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区
    //创建TASK1任务
    xTaskCreate((TaskFunction_t )task1_task,
                (const char*    )"task1_task",
                (uint16_t       )TASK1_STK_SIZE,
                

以上是关于FreeRTOSFreeRTOS学习笔记— 列表和列表项(链表和节点)的主要内容,如果未能解决你的问题,请参考以下文章

FreeRTOSFreeRTOS学习笔记— 任务创建删除挂起和恢复

FreeRTOSFreeRTOS学习笔记— 开始创建任务并测试任务代码

FreeRTOSFreeRTOS学习笔记(13)— FreeRTOS创建任务和任务管理(原生API)

FreeRTOSFreeRTOS学习笔记(14)— FreeRTOS的消息队列(原生API)

FreeRTOSFreeRTOS学习笔记— 中断+临界区的保护

FreeRTOSFreeRTOS学习笔记— 手写FreeRTOS双向链表/源码分析