今天我们将探讨FreeRTOS中的一个核心概念——列表(List)和列表项(List Item)。在实时操作系统(RTOS)中,任务的管理和调度是至关重要的,而FreeRTOS使用列表来实现这一功能。列表可以说是FreeRTOS中的一种数据结构,通过列表,我们可以高效地组织、排序和管理任务、定时器、事件等各种系统资源。具体来说,列表项是组成列表的基本单位,每个列表项都包含指向具体任务或事件的指针,以及用于排序的数值。希望通过今天的学习,大家能够掌握利用列表和列表项来实现任务调度的原理,并能够独立看懂源码。
目录
一、列表和列表项简介
1.1 列表和列表项的概念
1.2 列表结构体
1.3 列表项结构体
1.4 迷你列表项结构体
1.5 列表和列表项的关系
二、列表相关API函数介绍
2.1 初始化列表函数vListInitialise()
2.2 初始化列表项函数vListInitialiseItem()
2.3 列表项升序插入函数vListInsert()
2.3.1 源码分析
2.3.2 举例理解
2.4 列表项尾插函数vListInsertEnd()
2.4.1 源码分析
2.4.2 举例理解
2.5 列表项删除函数uxListRemove()
三、列表项的插入和删除实验
3.1 创建任务及实现任务函数
3.2 主函数调用入口函数,操作系统开始进行任务的切换和调度
3.3 实验结果
一、列表和列表项简介
1.1 列表和列表项的概念
列表是 FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪 FreeRTOS中的任务。任务处在不同的状态下,就挂载在不同的列表中(比如就绪列表、阻塞列表、挂起列表)。 而列表项就是存放在列表中的项目,一个列表项就关联着一个任务,可以说:列表代表处于某种状态的任务的集合,列表项就代表处于当前状态下的某一个任务。如下图所示:
列表相当于链表,列表项相当于节点,FreeRTOS 中的列表是一个双向循环链表,每个列表项有前驱结点指针prev,同时又有后继结点指针next,这样,双向循环链表的增删改查非常方便,动态改变,节省内存!列表的特点:列表项间的地址非连续的,是人为的连接到一起的。列表项的数目是由后期添加的个数决定的,随时可以改变,而数组的特点:数组成员地址是连续的,数组在最初确定了成员数量后期无法改变。
在操作系统FreeRTOS中,任务的数量是不确定的,随时发生变化,并且任务状态是会发生改变的,所以非常适用列表(链表)这种数据结构。
1.2 列表结构体
有关于列表的东西均在文件 list.c 和 list.h 中,首先我们先看下在list.h中的,列表相关结构体:
- 在该结构体中, 包含了两个宏(第一个和最后一个),这两个宏是确定的已知常量, FreeRTOS通过检查这两个常量的值,来判断列表的数据在程序运行过程中,是否遭到破坏 ,该功能一般用于调试, 默认是不开启的;
- 成员uxNumberOfItems,用于记录列表中列表项的个数(不包含末尾列表项,即最后一个列表项 xListEnd);
- 成员 pxIndex 用于指向列表中的某个列表项,一般用于遍历列表中的所有列表项(其实就是列表项指针);
- 成员变量 xListEnd 是一个迷你列表项,排在最末尾;
1.3 列表项结构体
列表项是列表中用于存放数据的地方(每一个列表项关联着一个任务,在列表项里面有一个成员变量,用来存放任务控制块,描述这个任务的相关属性信息),在 list.h 文件中,有列表项的相关结构体定义:
1、成员变量 xItemValue 为列表项的值,这个值多用于按升序对列表中的列表项进行排序,插入列表项的时候可根据此值来确定插入的位置。比如通过延时阻塞的任务(对应一个列表项),它有一个延时时间,那么就根据延时时间在列表中进行排列,这样就可以实现优先解除时间短的任务。
2、成员变量 pxNext 和 pxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项 ,其实就是双向循环链表的前驱结点指针和后继结点指针;
3、成员变量 pxOwner 用于指向包含列表项的对象(通常是任务控制块,存储该任务的相关属性信息)
4、成员变量 pxContainer 用于指向列表项所在的列表(当前列表项在哪种列表中,就绪?阻塞?挂起?)。即指向归属的列表!
通过成员3和成员4就可以确定该列表项属于哪个任务(任务控制块),并且在哪一种状态下。例如:第3个成员变量为任务1的控制块,第4个成员变量为就绪列表,那么就可以确定任务1处在就绪状态下!
1.4 迷你列表项结构体
迷你列表项也是列表项,但迷你列表项仅用于标记列表的末尾和挂载其他插入列表中的列表项,挂载如何理解:列表初始化的时候,只有一个迷你列表项,当插入新的列表项时,就把新的列表项对应的两只手(前驱指针和后继指针)牵上迷你列表项即可!迷你列表项没有存储数据,不会存储实际的任务数据信息,因此不需要成员变量 pxOwner 和 pxContainer,以节省内存开销!
1、成员变量 xItemValue 为列表项的值,这个值多用于按升序对列表中的列表项进行排序
2、成员变量 pxNext 和 pxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项
1.5 列表和列表项的关系
宏观上看如下图:
二、列表相关API函数介绍
常见的列表的相关API函数如下,其实从数据结构上来讲:就是双向循环链表的初始化、增删操作。
2.1 初始化列表函数vListInitialise()
初始化列表就是为列表的结构体成员赋初值,此时列表只有一个末尾列表项。
函数原型
void vListInitialise( List_t * const pxList );
形参 | 描述 |
pxList | 待初始化列表 |
一开始,列表为空,只有一个末尾列表项,因此:
- 用于记录列表中列表项的个数的变量uxNumberOfItems应为0;
- 成员 pxIndex 用于指向列表中的某个列表项,此时应该指向末尾列表项;
- 末尾列表项的成员变量 xItemValue 应该是最大的,因为要保证放在最后一个位置;
- 末尾列表项的前驱结点指针和后继结点指针都应该指向它自己(和双向循环链表初始化一样);
2.2 初始化列表项函数vListInitialiseItem()
函数原型
void vListInitialiseItem( ListItem_t * const pxItem );
形参 | 描述 |
pxItem | 待初始化列表项 |
2.3 列表项升序插入函数vListInsert()
2.3.1 源码分析
函数原型
void vListInsert ( List_t * const pxList , ListItem_t * const pxNewListItem )
形参 | 描述 |
pxList | 插入到哪个列表 |
pxNewListItem | 待插入列表项 |
此函数用于将待插入列表的列表项按照列表项值升序进行排序,有序地插入到列表中!
可以看到:函数vListInsert() 跟双向循环链表的按位置插入结点思想相同。
2.3.2 举例理解
总结:函数vListInsert(),是将待插入列表的列表项按照列表项值升序进行排序,有序地插入到列表中
2.4 列表项尾插函数vListInsertEnd()
2.4.1 源码分析
函数原型
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem );
形参 | 描述 |
pxList | 插入到哪个列表 |
pxNewListItem | 待插入列表项 |
2.4.2 举例理解
总结:函数vListInsertEnd(),是将待插入的列表项插入到列表 pxIndex 指针指向的列表项前面;它是一种无序的插入方法!!
2.5 列表项删除函数uxListRemove()
函数原型
UBaseType_t uxListRemove ( ListItem_t * const pxItemToRemove )
此函数用于将列表项从列表项所在列表中移除。
形参 | 描述 |
pxItemToRemove | 待移除的列表项 |
返回值 | 描述 |
整数 | 待移除列表项移除后,所在列表剩余列表项的数量 |
三、列表项的插入和删除实验
本实验本质上其实就是对双向链表进行增删操作,为了验证结果,打印列表项的地址和前驱结点地址以及后继结点地址进行验证即可。
3.1 创建任务及实现任务函数
#include "stm32f4xx.h" // Device header
#include "stdio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"
#include "mydelay.h"/**********************START_TASK任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/#define START_TASK_STACK_SIZE 128 //定义堆栈大小为128字(1字等于4字节)
#define START_TASK_PRIO 1 //定义任务优先级,0-31根据任务需求
TaskHandle_t start_task_handler; //定义任务句柄(结构体指针)
void start_task(void* args);/**********************TASK1任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define TASK1_STACK_SIZE 128 //定义堆栈大小为128字(1字等于4字节)
#define TASK1_PRIO 2 //定义任务优先级,0-31根据任务需求
TaskHandle_t task1_handler; //定义任务句柄(结构体指针)
void task1(void* args);/**********************TASK2任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define TASK2_STACK_SIZE 128 //定义堆栈大小为128字(1字等于4字节)
#define TASK2_PRIO 3 //定义任务优先级,0-31根据任务需求
TaskHandle_t task2_handler; //定义任务句柄(结构体指针)
void task2(void* args);/*********开始任务用来创建一个任务,只创建一次,不能是死循环,创建完这个任务后删除开始任务本身***********/
void start_task(void* args)
{taskENTER_CRITICAL(); /*进入临界区*/xTaskCreate( (TaskFunction_t) task1,(char *) "task1", ( configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,(void *) NULL,(UBaseType_t) TASK1_PRIO ,(TaskHandle_t *) &task1_handler );xTaskCreate( (TaskFunction_t) task2,(char *) "task2", ( configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE,(void *) NULL,(UBaseType_t) TASK2_PRIO ,(TaskHandle_t *) &task2_handler ); vTaskDelete(NULL); //删除开始任务自身,传参NULLtaskEXIT_CRITICAL(); /*退出临界区*///临界区内不会进行任务的调度切换,出了临界区才会进行任务调度,抢占式
}/********任务1的任务函数,无返回值且是死循环***********//***任务1:实现LED0每500ms翻转一次*******/
void task1(void* args)
{while(1){printf("22222\n");GPIO_ToggleBits(GPIOF,GPIO_Pin_9 );vTaskDelay(500); //FreeRTOS自带的延时函数,延时500毫秒}
}List_t TestList; /*定义测试列表*/
ListItem_t ListItem1; /*定义测试列表项1*/
ListItem_t ListItem2; /*定义测试列表项2*/
ListItem_t ListItem3; /*定义测试列表项3*//********任务2的任务函数,无返回值且是死循环***********//***任务2:列表项插入和删除实验*******/
void task2(void* args)
{vListInitialise( &TestList ); /*初始化列表*/vListInitialiseItem( &ListItem1); /*初始化列表项1*/vListInitialiseItem( &ListItem2); /*初始化列表项2*/vListInitialiseItem( &ListItem3); /*初始化列表项3*/ListItem1.xItemValue=40;ListItem2.xItemValue=60;ListItem3.xItemValue=50;/*第二步:打印列表和其他列表项的地址*/printf("/************第二步:打印列表和列表项的地址************/\r\n");printf("项目\t\t\t地址\r\n" );printf("TestList\t\t0x%p\t\r\n",&TestList); //列表地址printf("TestList->pxIndex\t0x%p\t\r\n",TestList.pxIndex); //列表中的列表项指针指向的列表项的地址printf("TestList- >xListEnd\t0x%p\t\r\n",(&TestList.xListEnd)); //末尾列表项的地址printf("ListIteml\t\t0x%p\t\r\n",&ListItem1); //列表项1的地址printf("ListItem2\t\t0x%p\t\r\n",&ListItem2); //列表项2的地址printf("ListItem3\t\t0x%p\t\r\n",&ListItem3); //列表项3的地址printf ("/***********************结束**********************/\r\n"); /*第三步:列表项1插入列表: */printf("\r\n***************第三步:列表项1插入列表***************/\r\n");vListInsert((List_t* )&TestList, /* 列表 */(ListItem_t*) &ListItem1); /* 列表项 */printf("项目\t\t\t\t地址:\r\n");printf("TestList->xListEnd->pxNext\tOx%p\r\n ",(TestList.xListEnd.pxNext));printf("ListIteml->pxNext\t\t0x%p\r\n",(ListItem1.pxNext));printf("TestList->xListEnd->pxPrevious\t0x%p\r\n",(TestList.xListEnd.pxPrevious));printf("ListIteml->pxPrevious\t\t0x%p\r\n",(ListItem1.pxPrevious));printf ("/*************************结束**************************/\r\n");/*第四步:列表项2插入列表: */printf ("\r\n/************第四步:列表项2插入列表**************/\r\n");vListInsert((List_t* )&TestList, /* 列表 */(ListItem_t*)&ListItem2); /* 列表项 */printf("项目\t\t\t\t地址\r\n" );printf("TestList->xListEnd->pxNext\t0x%p\r\n",(TestList.xListEnd.pxNext));printf("ListIteml->pxNext\t\t0x%p\r\n",(ListItem1.pxNext));printf("ListItem2->pxNext\t\t0x%p\r\n",(ListItem2.pxNext));printf("TestList->xListEnd->pxPrevious\t0x%p\r\n",(TestList.xListEnd.pxPrevious));printf("ListIteml->pxPrevious\t\t0x%p\r\n",(ListItem1. pxPrevious));printf("ListItem2->pxPrevious\t\t0x%p\r\n",(ListItem2. pxPrevious));printf ("/**************************结束***************************/\r\n");/* 第五步:列表项3插入列表 */printf("\r\n***************第五步:列表项3插入列表***************/\r\n");vListInsert((List_t* ) &TestList, /* 列表 */(ListItem_t*)&ListItem3); /* 列表项 */printf("项目\t\t\t\t地址\r\n");printf("TestList->xListEnd->px.Next\t0x%p\r\n",(TestList.xListEnd.pxNext));printf("ListIteml->pxNext\t\t0x%p\r\n",(ListItem1.pxNext));printf("ListItem2->pxNext\t\t0x%p\r\n",(ListItem2.pxNext));printf("ListItem3->pxNext\t\t0x%p\r\n",(ListItem3.pxNext));printf("TestList->xListEnd->pxPrevious\t0x%p\r\n",(TestList.xListEnd.pxPrevious));printf("ListIteml->pxPrevious\t\t0x%p\r\n",(ListItem1.pxPrevious));printf("ListItem2->pxPrevious\t\t0x%p\r\n",(ListItem2.pxPrevious));printf("ListItem3->pxPrevious\t\t0x%p\r\n",(ListItem3.pxPrevious));printf("/**************************结束***************************/\r\n");/* 第六步:移除列表项2 */printf("\r\n ****************第六步:移除列表项2******************/\r\n");uxListRemove((ListItem_t* )&ListItem2); /* 移除列表项 */printf("项月\t\t\t\t地址\r\n");printf("TestList->xListEnd->pxNext\t0x%p\r\n",(TestList.xListEnd.pxNext));printf("ListIteml->px.Next\t\t0x%p\r\n",(ListItem1.pxNext));printf("ListItem3->pxNext\t\t0x%p\r\n",(ListItem3.pxNext));printf("TestList->xListEnd->pxPrevious\t0x%p\r\n",(TestList.xListEnd.pxPrevious));printf("ListIteml->pxPrevious\t\t0x%p\r\n",(ListItem1.pxPrevious));printf("ListItem3->pxPrevious\t\t0x%p\r\n",(ListItem3.pxPrevious));printf("/**************************结束***************************/\r\n");/* 第七步:列表末尾添加列表项2 */printf("\r\n/ *************第七步:列表末尾添加列表项2**************/\r\n");vListInsertEnd((List_t*)&TestList, /* 列表 */(ListItem_t* )&ListItem2); /* 列表项 */printf("项目\t\t\t\t地址\r\n");printf("TestList->pxIndex\t\t0x%p\r\n",TestList.pxIndex);printf("TestList->xListEnd->pxNext\t0x%p\r\n",(TestList.xListEnd.pxNext));printf("ListIteml->pxNext\t\t0x%p\r\n",(ListItem1.pxNext));printf("ListItem2->pxNext\t\t0x%p\r\n",(ListItem2.pxNext));printf("ListItem3->pxNext\t\t0x%p\r\n",(ListItem3.pxNext));printf("TestList->xListEnd->pxPrevious\t0x%p\r\n",(TestList.xListEnd.pxPrevious));printf("ListIteml->pxPrevious\t\t0x%p\r\n",(ListItem1.pxPrevious));printf("ListItem2->pxPrevious\t\t0x%p\r\n",(ListItem2.pxPrevious));printf("ListItem3->pxPrevious\t\t0x%p\r\n",(ListItem3.pxPrevious));printf("/*********************实验结束***********************/\r\n");while(1){printf("11111\n"); vTaskDelay(1000); //FreeRTOS自带的延时函数,延时1秒}
}//FreeRTO入口例程函数,无参数,无返回值
void freertos_demo(void)
{xTaskCreate( (TaskFunction_t) start_task,(char *) "start_task", ( configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,(void *) NULL,(UBaseType_t) START_TASK_PRIO ,(TaskHandle_t *) &start_task_handler );vTaskStartScheduler(); //开启任务调度器}
3.2 主函数调用入口函数,操作系统开始进行任务的切换和调度
#include "stm32f4xx.h" // Device header
#include "stdio.h"
#include "myled.h"
#include "myusart.h"#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"extern TaskHandle_t Start_Handle;int main(void)
{//硬件初始化My_UsartInit();//调用入口函数freertos_demo();}
3.3 实验结果
根据每一步操作的地址,符合理论!
至此,已经讲解完毕!初次学习,循序渐进,一步步掌握即可!以上就是全部内容!请务必掌握,创作不易,欢迎大家点赞加关注评论,您的支持是我前进最大的动力!下期再见!