freertos 双向循环链表插入删除的实现与直观理解

freertos 双向循环链表插入删除的实现与直观理解

    • main.c
    • list.h
    • 其他头文件
      • FreeRTOS.h
      • FreeRTOSConfig.h
      • portable.h
      • portmacro.h
    • list.c
      • vListInsertEnd函数
      • vListInsert函数
      • uxListRemove函数

main.c

用debug之后,查看观察窗口,结果如下。这个实验目的就是,建一个根节点,三个普通节点的链表,并且这三个普通节点按照xitemValue的值进行升序排列。

在这里插入图片描述

main.c里面

首先看节点的类型:链表根节点类型是xLIST、普通节点类型是xLIST_ITEM。

然后看main函数:vListInitialise这个是链表根节点初始化函数、vListInitialiseItem这个是普通节点初始化函数、xItemValue这个是普通节点里面的成员变量、vListInsert这个函数实现将节点插入链表,按照升序排列功能。

接下来分别研究上面这些东西。

#include "list.h"/* 定义链表根节点 */
struct xLIST       List_Test;/* 定义节点 */
struct xLIST_ITEM  List_Item1;
struct xLIST_ITEM  List_Item2;
struct xLIST_ITEM  List_Item3;int main(void)
{	/* 链表根节点初始化 */vListInitialise( &List_Test );/* 节点1初始化 */vListInitialiseItem( &List_Item1 );List_Item1.xItemValue = 1;/* 节点2初始化 */    vListInitialiseItem( &List_Item2 );List_Item2.xItemValue = 2;/* 节点3初始化 */vListInitialiseItem( &List_Item3 );List_Item3.xItemValue = 3;/* 将节点插入链表,按照升序排列 */vListInsert( &List_Test, &List_Item2 );    vListInsert( &List_Test, &List_Item1 );vListInsert( &List_Test, &List_Item3 );    for(;;){/* 啥事不干 */}
}

list.h

直接看下面这个图,就可以知道根节点结构体和其他节点结构体里面的成员变量了。

在这里插入图片描述

#ifndef LIST_H
#define LIST_H
#include "FreeRTOS.h"/* 节点结构体定义 */
struct xLIST_ITEM
{TickType_t xItemValue;             /* 辅助值,用于帮助节点做顺序排列 */			struct xLIST_ITEM *  pxNext;       /* 指向链表下一个节点 */		struct xLIST_ITEM *  pxPrevious;   /* 指向链表前一个节点 */	void * pvOwner;					   /* 指向拥有该节点的内核对象,通常是TCB (用于表示该节点内嵌在哪个数据结构中)*/void * pvContainer;		           /* 指向该节点所在的链表,通常指向链表的根节点*/
};
typedef struct xLIST_ITEM ListItem_t;  /* 节点数据类型重定义 *//* mini节点结构体定义,作为双向链表的结尾因为双向链表是首尾相连的,头即是尾,尾即是头 */
struct xMINI_LIST_ITEM
{TickType_t xItemValue;                      /* 辅助值,用于帮助节点做升序排列 */struct xLIST_ITEM *  pxNext;                /* 指向链表下一个节点 */struct xLIST_ITEM *  pxPrevious;            /* 指向链表前一个节点 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;  /* 最小节点数据类型重定义 *//* 链表结构体定义 */
typedef struct xLIST
{UBaseType_t uxNumberOfItems;    /* 链表节点计数器,用于表示该链表下有多少个节点,根节点除外*/ListItem_t *  pxIndex;			/* 链表节点索引指针,用于遍历节点 */MiniListItem_t xListEnd;		/* 链表最后一个节点 */
} List_t;/*
************************************************************************
*                                宏定义
************************************************************************
*/
/* 初始化节点的拥有者 */
#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 )	( ( BaseType_t ) ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) )/* 获取链表的节点数 */
#define listCURRENT_LIST_LENGTH( pxList )	( ( pxList )->uxNumberOfItems )/* 获取链表节点的OWNER,即TCB */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										\
{																							\List_t * const pxConstList = ( pxList );											    \/* 节点索引指向链表第一个节点调整节点索引指针,指向下一个节点,如果当前链表有N个节点,当第N次调用该函数时,pxInedex则指向第N个节点 */\( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\/* 当前链表为空 */                                                                       \if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\{																						\( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\}																						\/* 获取节点的OWNER,即TCB */                                                             \( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											 \
}#define listGET_OWNER_OF_HEAD_ENTRY( pxList )  ( (&( ( pxList )->xListEnd ))->pxNext->pvOwner )/*
************************************************************************
*                                函数声明
************************************************************************
*/
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 );#endif /* LIST_H */

其他头文件

FreeRTOS.h

#ifndef INC_FREERTOS_H
#define INC_FREERTOS_H#include "FreeRTOSConfig.h"
#include "portable.h"#endif /* INC_FREERTOS_H */

FreeRTOSConfig.h

这个里面由于下一句话,

#define configUSE_16_BIT_TICKS		0

使得portmacro.h里面进行下面的操作,也就是让TickType_t表示32位。

typedef uint32_t TickType_t;
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H#define configUSE_16_BIT_TICKS		0#endif /* FREERTOS_CONFIG_H */

portable.h

#ifndef PORTABLE_H
#define PORTABLE_H#include "portmacro.h"#endif /* PORTABLE_H */

portmacro.h

这里面是一些freertos里面的数据类型重定义。

#ifndef PORTMACRO_H
#define PORTMACRO_H#include "stdint.h"
#include "stddef.h"/* 数据类型重定义 */
#define portCHAR		char
#define portFLOAT		float
#define portDOUBLE		double
#define portLONG		long
#define portSHORT		short
#define portSTACK_TYPE	uint32_t
#define portBASE_TYPE	longtypedef 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
#elsetypedef uint32_t TickType_t;#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#endif#endif /* PORTMACRO_H */

list.c

vListInitialise函数进行链表根节点初始化。初始化有什么效果可以看下面这个图。

根节点初始化后,里面的指针全指向了一个地方。

在这里插入图片描述

直白点表述:

在这里插入图片描述

vListInsertEnd函数

vListInsertEnd函数将节点插入到链表的尾部。将一个节点插入后,效果如下。

在这里插入图片描述

用图表示的话:

在这里插入图片描述

更直白点:

在这里插入图片描述

如果按下面这个来编写插入的代码,是错误的。

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

试一试就知道了,如果按上面这个代码来,插入三个节点,最后效果如下。这个后果就是,新插上来的节点,他们之间是没有建立联系的,只是和根节点建立了联系。

在这里插入图片描述

下面将三个节点插入,正确的变量变化情况如下:

在这里插入图片描述

这里面就很清楚了,这个是属于循环双向链表。用图表示的话就是:

在这里插入图片描述

如果说新加进来一个item3,在item3没加进来的时候图的表示:

在这里插入图片描述

结合上面的两幅图,编代码。下面这个代码就考虑到了节点之间的联系。

pxNewListItem->pxNext = pxIndex;//上图右边的pxNext连到最左边的pxIndex
pxNewListItem->pxPrevious = pxIndex->pxPrevious;//第二行代码
pxIndex->pxPrevious->pxNext = pxNewListItem;//第三行代码
pxIndex->pxPrevious = pxNewListItem;//第四行代码

第一行代码直观表示如下面绿线:

在这里插入图片描述

pxIndex->pxPrevious一开始指向的是item2,加了item3,那么item3的pxPrevious就得指向item2,也就是pxIndex->pxPrevious。于是有了下面第二行代码。第二行代码直观表示如下。

在这里插入图片描述

第三行代码,pxIndex->pxPrevious->pxNext也就相当于item2的pxNext,加了item3,那么item2的pxNext应该指向item3。第三行代码直观表示如下。

在这里插入图片描述

此时可以看出原先的item2的pxNext已经和pxIndex彻底断了联系。整个链表可以表示成下面样子。

在这里插入图片描述

那么很明显,第四行代码就是要把item3给pxIndex的pxPrevious了。

在这里插入图片描述

vListInsert函数

函数功能是将节点按照升序排列插入链表。

先从xListEnd开始找,其实也相当于从第一个根节点找,依次找pxNext所指向节点的xItemValue值,如果这个值大于当前待插入节点的xItemValue值,就把这个节点插到当前的这个节点后面(当前这个节点的pxNext所指向节点的前面)。

核心代码如下。

	pxNewListItem->pxNext = pxIterator->pxNext;pxNewListItem->pxNext->pxPrevious = pxNewListItem;pxNewListItem->pxPrevious = pxIterator;pxIterator->pxNext = pxNewListItem;

举个例子,item2插入到item1后面。进行到第三步的时候,直观表示如下。

在这里插入图片描述

此时可以发现,item3的pxPrevious已经和item1断了联系。所以上图可表示如下。

在这里插入图片描述

那么最后一步显而易见,把item1的pxNext指向item2即可。此时item2就插进去了。

在这里插入图片描述

uxListRemove函数

这个函数功能是将节点从链表中删除。核心代码如下。

pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

如果删除item2,直观表示如下:

在这里插入图片描述

#include "FreeRTOS.h"
#include <stdlib.h>
#include "list.h"/* 链表根节点初始化 */
void vListInitialise( List_t * const pxList )
{/* 将链表索引指针指向最后一个节点 */pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );/* 将链表最后一个节点的辅助排序的值设置为最大,确保该节点就是链表的最后节点 */pxList->xListEnd.xItemValue = portMAX_DELAY;/* 将最后一个节点的pxNext和pxPrevious指针均指向节点自身,表示链表为空 */pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/* 初始化链表节点计数器的值为0,表示链表为空 */pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
}/* 节点初始化 */
void vListInitialiseItem( ListItem_t * const pxItem )
{/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */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;/* 记住该节点所在的链表 */pxNewListItem->pvContainer = ( void * ) pxList;/* 链表节点计数器++ */( pxList->uxNumberOfItems )++;
}/* 将节点按照升序排列插入到链表 */
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{ListItem_t *pxIterator;/* 获取节点的排序辅助值 */const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;/* 寻找节点要插入的位置 */if( xValueOfInsertion == portMAX_DELAY ){pxIterator = pxList->xListEnd.pxPrevious;}else{for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ){/* 没有事情可做,不断迭代只为了找到节点要插入的位置 */			}}pxNewListItem->pxNext = pxIterator->pxNext;pxNewListItem->pxNext->pxPrevious = pxNewListItem;pxNewListItem->pxPrevious = pxIterator;pxIterator->pxNext = pxNewListItem;/* 记住该节点所在的链表 */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;/* Make sure the index is left pointing to a valid item. *//*调整链表的节点索引指针*/if( pxList->pxIndex == pxItemToRemove ){pxList->pxIndex = pxItemToRemove->pxPrevious;}/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */pxItemToRemove->pvContainer = NULL;/* 链表节点计数器-- */( pxList->uxNumberOfItems )--;/* 返回链表中剩余节点的个数 */return pxList->uxNumberOfItems;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/560192.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cortex-m3 操作模式 寄存器组 异常类型 堆栈 中断

cortex-m3操作模式寄存器组异常类型堆栈中断参考操作模式 处理器的操作模式&#xff1a;为了区别正在执行代码的类型。复位后&#xff0c;处理器进入线程模式、特权级。 处理者模式&#xff08;handler mode&#xff09;&#xff1a;异常服务例程的代码 &#xff0c;包括中断…

freertos里面用到的汇编语言总结

汇编语言基础知识多重存储器访问MRS 和 MSRisb 和 dsbldr 和 strmovbl 和 bxcps多重存储器访问 感叹号&#xff01;表示要自增(Increment)或自减(Decrement)基址寄存器 Rd 的值&#xff0c;时机是在每次访问前(Before)或访问后(After)。增/减&#xff0c;单位&#xff1a;字&am…

freertos内核 任务定义与切换 原理分析

freertos内核 任务定义与切换 原理分析主程序任务控制块任务创建函数任务栈初始化就绪列表调度器总结任务切换主程序 这个程序目的就是&#xff0c;使用freertos让两个任务不断切换。看两个任务中变量的变化情况(波形)。 下面这个图是任务函数里面delay(100)的结果。 下面这个…

freertos临界段保护

freertos临界段保护中断的基础知识cortex-m里面开中断、关中断指令关中断和开中断进入临界段和退出临界段中断的基础知识 嵌套&#xff1a; 嵌套向量中断控制器 NVIC(Nested Vectored Interrupt Controller与内核是紧耦合的。提供如下的功能&#xff1a;可嵌套中断支持、向量…

改进版的CBOW模型

复习 首先复习一下之前的CBOW笔记。 采用推理的方法认知单词、CBOW模型这里面主要是&#xff1a; CBOW模型的核心思路&#xff1a;给出周围的单词&#xff08;上下文&#xff09;时&#xff0c;预测目标词处会出现什么单词。 要用神经网络处理单词&#xff0c;需要先将单词…

freertos空闲任务、阻塞延时

freertos空闲任务、阻塞延时空闲任务阻塞延时SysTick实验现象阻塞态&#xff1a;如果一个任务当前正在等待某个外部事件&#xff0c;则称它处于阻塞态。 rtos中的延时叫阻塞延时&#xff0c;即任务需要延时的时候&#xff0c;会放弃CPU的使用权,进入阻塞状态。在任务阻塞的这段…

树莓派配置

树莓派配置1.安装操作系统2.修改镜像源3.配置VNC连接1.安装操作系统 安装操作系统&#xff0c;首先在官网下载https://www.raspberrypi.com/software/operating-systems/ 下载出来的解压后是一个光盘映像文件&#xff0c;接下来就需要把这个文件写入到树莓派的tf卡里。 安装树…

远程桌面连机器人

这里需要PC和机器人处于同一局域网下。 PC使用xhell连接机器人&#xff0c;通过 ifconfig 查看机器人的无线 IP 地址。 然后退出xshell&#xff0c;打开NoMachine软件进行远程桌面连接。输入机器人无线ip地址。 输入用户名和密码 然后就远程连接上了 现在开一个终端如下所示&…

RNNLM

RNNLM 基于RNN的语言模型称为RNNLM(Language Model)。 Embedding 层&#xff1a;将单词ID转化为单词的分布式表示&#xff08;单词向量&#xff09;。 RNN层&#xff1a;向下一层(上方)输出隐藏状态&#xff0c;同时也向下一时刻的RNN层(右边)输出隐藏状态。 对于“you say …

使用c#制作赛尔号登录器

使用c#制作赛尔号登录器 需求&#xff1a; 打开赛尔号官网&#xff0c;发现我的chrome浏览器无法运行Flash。这是因为Adobe 公司放弃了对 Flash Player 的支持。 那么如果我想要玩游戏&#xff0c;又不想下载别的浏览器&#xff0c;只好自己写一个登陆器了。 创建项目 首先…

普通RNN的缺陷—梯度消失和梯度爆炸

之前的RNN&#xff0c;无法很好地学习到时序数据的长期依赖关系。因为BPTT会发生梯度消失和梯度爆炸的问题。 RNN梯度消失和爆炸 对于RNN来说&#xff0c;输入时序数据xt时&#xff0c;RNN 层输出ht。这个ht称为RNN 层的隐藏状态&#xff0c;它记录过去的信息。 语言模型的任…

LSTM的结构

RNN和LSTM 简略表示RNN层&#xff1a;长方形节点中包含了矩阵乘积、偏置的和、tanh函数的变换。将下面这个公式表示成一个tanh节点。 LSTM&#xff1a;Long Short-Term Memory&#xff08;长短期记忆&#xff09;&#xff0c;长时间维持短期记忆。 LSTM与RNN的接口(输入输出)…

STM32 USART 补充

串口通讯的数据包&#xff1a;发送设备通过自身的TXD接口传输到接收设备的RXD接口。 串口通讯的协议层中&#xff0c;规定了数据包的内容&#xff0c;由起始位、主体数据、校验位、停止位组成&#xff0c;通讯双方的数据包格式要约定一致才能正常收发数据。 异步通讯&#xf…

ROS TF变换

静态坐标转换&#xff1a;机器人本体中心到雷达中心的转换。因为激光雷达可能没安装到机器人的中心。 动态坐标转换&#xff1a;机器人中心和里程计坐标的变换。机器人从起点出发后&#xff0c;里程计坐标相对于本体就会产生一个偏移&#xff0c;这个偏移随着机器人的运动不断…

ROS底盘控制节点 源码分析

先在机器人端通过launch文件启动底盘控制。 robot:~$ roslaunch base_control base_control.launch ... logging to /home/jym/.ros/log/3e52acda-914a-11ec-beaa-ac8247315e93/roslaunch-robot-8759.log Checking log directory for disk usage. This may take a while. Pres…

ROS + OpenCV

视觉节点测试 先进行一些测试。并记录数据。 圆的是节点&#xff0c;方的是话题。 1.robot_camera.launch robot:~$ roslaunch robot_vision robot_camera.launch ... logging to /home/jym/.ros/log/bff715b6-9201-11ec-b271-ac8247315e93/roslaunch-robot-8830.log Check…

ROS+雷达 运行数据记录

先测试一下雷达&#xff0c;记录数据。方便接下来分析源码。 1.roslaunch robot_navigation lidar.launch robot:~$ roslaunch robot_navigation lidar.launch ... logging to /home/jym/.ros/log/7136849a-92cc-11ec-acff-ac8247315e93/roslaunch-robot-9556.log Checking l…

ROS 找C++算法源码的方法

在gmapping的launch文件中看到&#xff0c;type“slam_gmapping”&#xff0c;这里的slam_gmapping是c编译后的可执行文件。 如果想要修改gmapping算法&#xff0c;就需要找到slam_gmapping的c源码。 但是这是用apt下载的包&#xff0c;是二进制类型的&#xff0c;没有下载出…

ros 雷达 slam 导航 文件分析

ros 雷达 slam 导航 文件分析robot_slam_laser.launchrobot_lidar.launchlidar.launchraplidar.launchkarto.launchgmapping.launchcartographer.launchrobot_navigation.launchmap.yamlmap.pgmamcl_params.yamlmove_base.launchcostmap_common_params.yamllocal_costmap_param…

Apprentissage du français partie 1

Apprentissage du franais partie 1 键盘转换图&#xff1a; 字母&#xff1a;26个 元音字母&#xff1a;a、e、i、o、u、y b浊辅音(声带)-p清辅音 d-t 音符 音符&#xff1a;改变字母发音。 &#xff1a;闭音符 [e] &#xff1a;开音符 /ε/ &#xff1a;长音符 /ε/…