FreeRTOS简单内核实现2 双向链表
FreeRTOS Kernel V10.3.1
FreeRTOS 的 list.c / list.h 文件中有 3 个数据结构、2 个初始化函数、2 个插入函数、1 个移除函数和一些宏函数,链表是 FreeRTOS 中的重要数据结构,下述 “1、数据结构” 和 “2、操作链表” 两个小节内容主要对其原理进行讲解
1、数据结构
1.1、xLIST_ITEM
链表项,即节点,通常用链表项来表示一个任务
struct xLIST_ITEM
{
	// 检验一个 链表项 数据是否完整
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
    // 排序值
    configLIST_VOLATILE TickType_t xItemValue;
    // 下一个 链表项
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;
    // 前一个 链表项
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
    // 记录此 链表项 归谁拥有,通常是 TCB (任务控制块)
    void * pvOwner;
    // 拥有该 链表项 的 链表
    struct xLIST * configLIST_VOLATILE pxContainer;
    // 检验一个 链表项 数据是否完整
    listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE
};
typedef struct xLIST_ITEM ListItem_t;

1.2、XMINI_LIST_ITEM
MINI 链表项,链表项的缩减版,专门用于表示链表尾节点,在 32 位 MCU 上不启用链表项数据完整性校验的情况下相比于普通的链表项节省了 8 个字节(两个指针)
struct xMINI_LIST_ITEM
{
	// 检验一个 MINI链表项 数据是否完整
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
    // 排序值
    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.3、xLIST
由多个链表项构成的链表,常用于区分不同任务状态或任务优先级,比如就绪状态的任务放在就绪链表中,阻塞状态的任务放在阻塞链表中,方便任务管理
typedef struct xLIST
{
	// 检验一个 链表 数据是否完整
    listFIRST_LIST_INTEGRITY_CHECK_VALUE
    // 记录 链表 中 链表项 数目
    volatile UBaseType_t uxNumberOfItems;
    // 遍历 链表 的指针
    ListItem_t * configLIST_VOLATILE pxIndex;
    // 使用 MINI链表项 表示 链表尾部
    MiniListItem_t xListEnd;
    // 检验一个 链表 数据是否完整
    listSECOND_LIST_INTEGRITY_CHECK_VALUE
} List_t;

2、操作链表
注意:由于不涉及数据校验完整性,因此下述函数中关于校验的所有部分将被删除
2.1、初始化
2.1.1、vListInitialiseItem( )
初始化链表项函数
将链表项的 pxContainer 成员设置为 NULL ,因为初始化的时候该链表项未被任何链表包含
void vListInitialiseItem( ListItem_t * const pxItem )
{
    // 确保链表项未被记录在链表中
    pxItem->pxContainer = NULL;
}

2.1.2、vListInitialise( )
初始化链表函数,具体步骤如下
- 将链表当前指针 pxIndex 指向尾链表项 xListEnd
 - 确保尾链表项 xListEnd 被排在链表最尾部
 - 将尾链表项 xListEnd 的前/后链表项指针均指向自己,因为初始化链表时只有尾链表项
 - 链表中有 0 个链表项
 
void vListInitialise( List_t * const pxList )
{
    // 链表当前指针指向 xListEnd
    pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
    // 设置链表尾链表项排序值为最大, 保证 xListEnd 会被放在链表的尾部
    pxList->xListEnd.xItemValue = portMAX_DELAY;
    // 尾链表项 xListEnd 的前/后链表项指针均指向自己
    pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
    pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
	// 初始化时链表中有 0 个链表项
    pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
}

2.2、插入
2.2.1、vListInsertEnd( )
将一个链表项插入到链表当前指针 pxIndex 指向的链表项前面,具体步骤如下
- 改变要插入链表项自身的 pxNext 和 pxPrevious
 - 改变前一个链表项(也就是 pxIndex 指向的链表项的 Previous)的 pxNext
 - 改变后一个链表项(也就是 pxIndex 指向的链表项)的 pxPrevious
 
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
	// 获取链表中当前指针 pxIndex 位置
	ListItem_t * const pxIndex = pxList->pxIndex;
	// 1. 改变自身 pxNext 和 pxPrevious
    pxNewListItem->pxNext = pxIndex;
    pxNewListItem->pxPrevious = pxIndex->pxPrevious;
	// 2. 改变前一个链表项的 pxNext
    pxIndex->pxPrevious->pxNext = pxNewListItem;
    // 3. 改变后一个链表项的 pxPrevious
    pxIndex->pxPrevious = pxNewListItem;
    // 标记新插入的链表项所在的链表
    pxNewListItem->pxContainer = pxList;
	// 链表数量增加一
    ( pxList->uxNumberOfItems )++;
}
为方便绘图演示,将链表的结构在图上做了简化,具体如下图所示

注意:这里 pxList->pxIndex 自初始化以来从未修改过,保持指向链表 xListEnd 链表项,下图所有演示中,橙色虚线表示该步骤做了修改,黑色实线表示与上一步骤相比无修改
插入一个链表项

插入第二个链表项

插入第三个链表项

插入第四个链表项

2.2.2、vListInsert( )
将一个链表项按照链表中所有链表项的 xItemValue 值大小升序插入链表中,具体步骤如下
- 找到应该插入的位置(应该插入到 pxIterator 的后面)
 - 改变要插入链表项自身 pxNext 和 pxPrevious
 - 改变后一个链表项的 pxPrevious
 - 改变前一个链表项的 pxNext (也即 pxIterator 指向的链表项的 pxNext)
 
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
	ListItem_t *pxIterator;
	// 记录要插入的链表项的排序值
	const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
	// 如果新插入的链表项排序值为最大值,直接插到尾节点 xListEnd 的前面
	if( xValueOfInsertion == portMAX_DELAY )
	{
	    pxIterator = pxList->xListEnd.pxPrevious;
	}
	else
	{
		/*
		1. 遍历链表,将当前链表项 pxIterator 与要插入的新的链表项 pxNewListItem
		的 xItemValue 值比较,直到 pxIterator 的 xItemValue 大于 pxNewListItem
		的 xItemValue 值,此时 pxNewListItem 应该插入到 pxIterator 的后面
		*/
		for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
			 pxIterator->pxNext->xItemValue <= xValueOfInsertion;
			 pxIterator = pxIterator->pxNext ){}
	}
	// 2. 改变要插入链表项自身 pxNext 和 pxPrevious
	pxNewListItem->pxNext = pxIterator->pxNext;
	pxNewListItem->pxPrevious = pxIterator;
	// 3. 改变后一个链表项的 pxPrevious
	pxNewListItem->pxNext->pxPrevious = pxNewListItem;
	// 4. 改变前一个链表项的 pxNext
	pxIterator->pxNext = pxNewListItem;
	// 标记新插入的链表项所在的链表
	pxNewListItem->pxContainer = pxList;
	// 链表数量增加一
	( pxList->uxNumberOfItems )++;
}

2.3、移除
2.3.1、uxListRemove( )
从链表中移除指定的链表项,具体步骤如下
- 改变后一个链表项的 pxPrevious
 - 改变前一个链表项的 pxNext
 
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
	List_t * const pxList = pxItemToRemove->pxContainer;  
	// 1. 改变后一个链表项 pxPrevious
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    // 2. 改变前一个链表项 pxNext
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
    // 确保索引指向有效的项目
    if( pxList->pxIndex == pxItemToRemove )
    {
       pxList->pxIndex = pxItemToRemove->pxPrevious;
    }
	// 从链表中移除链表项后,该链表项不属于任何链表
    pxItemToRemove->pxContainer = NULL;
    // 链表中链表项的数量减一
    ( pxList->uxNumberOfItems )--;  
	// 返回链表中链表项的数量
    return pxList->uxNumberOfItems;
}

2.4、宏函数
2.4.1、设置相关
// 设置 pxListItem 的 pxOwner 成员
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )
// 设置 pxListItem 的 xValue 成员值
#define listSET_LIST_ITEM_VALUE( pxListItem, xValue )
2.4.2、获取相关
// 获取 pxListItem 的 pxOwner 成员
#define listGET_LIST_ITEM_OWNER( pxListItem )
// 获取 pxListItem 的 xValue 成员值
#define listGET_LIST_ITEM_VALUE( pxListItem )
// 获取链表中头链表项的 xItemValue 成员值
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList )
// 获取链表中头链表项地址
#define listGET_HEAD_ENTRY( pxList )
// 获取某个链表项的下一个链表项地址
#define listGET_NEXT( pxListItem )
// 获取链表中 xListEnd 的地址
#define listGET_END_MARKER( pxList )
// 获取链表当前长度
#define listCURRENT_LIST_LENGTH( pxList )
// 将链表中 pxIndex 指向下一个链表项,用于获取下一个链表项(任务)
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )
// 获取链表中头链表项的 pvOwner 成员
#define listGET_OWNER_OF_HEAD_ENTRY( pxList )
// 获取链表项的 pxContainer 成员
#define listLIST_ITEM_CONTAINER( pxListItem )
2.4.3、判断相关
// 判断链表是否为空
#define listLIST_IS_EMPTY( pxList )
// 判断链表项是否和链表匹配(链表项是否在该链表中)
#define listIS_CONTAINED_WITHIN( pxList, pxListItem )
// 判断链表是否被初始化
#define listLIST_IS_INITIALISED( pxList )
3、链表移植
下面直接将 FreeRTOS 内核中 list.c / list.h 文件移植到自己的工程中使用(当然,如果你已经懂得双向链表数据结构的原理,你也可以构建属于你自己的 list.c / list.h 文件)
移植可以总结为以下 4 个步骤
- 用 FreeRTOS Kernel V10.3.1 内核中 list.c / list.h 替换掉 RTOS 文件夹中的同名文件
 - 在 portMacro.h 中统一一些用到基本类型
 
/* portMacro.h */
#include <stdint.h>
#define port_CHAR                   char
#define port_FLOAT                  float
#define port_DOUBLE                 double
#define port_LONG                   long
#define port_SHORT                  short
#define port_STACK_TYPE             unsigned int
#define port_BASE_TYPE              long
typedef port_STACK_TYPE             StackType_t;
typedef long                        BaseType_t;
typedef unsigned long               UBaseType_t;
typedef port_STACK_TYPE*            StackType_p;
typedef long*                       BaseType_p;
typedef unsigned long*              UBaseType_p;
#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
#endif
#define pdFALSE                     ((BaseType_t) 0)
#define pdTRUE                      ((BaseType_t) 1)
#define pdPASS                      (pdTRUE)
#define pdFAIL                      (pdFALSE)
configUSE_16_BIT_TICKS 是一个宏,用于 TickType_t 类型位数,具体定义如下
/* FreeRTOSConfig.h */
// 设置 TickType_t 类型位 16 位
#define configUSE_16_BIT_TICKS                  0
- 删除掉 list.c 文件中所有的 mtCOVERAGE_TEST_DELAY() 测试函数
 - 删除掉 list.h 文件中所有的 PRIVILEGED_FUNCTION 宏
 
完成后编译工程应该不会出现错误,这样实现 RTOS 简单内核关键的双链表数据结构就完成了
FreeRTOS简单内核实现2 双向链表的更多相关文章
- 源码级别gdb远程调试(实现OS简单内核)
		
最近在学着编写一个操作系统的简单内核,需要debug工具,我们这里使用gdb来进行调试,由于虚拟机运行和本机是两个部分,所以使用 gdb 的远程调试技术,这里对 gdb 常见调试以及远程调试方式做一个 ...
 - 基于mykernel完成时间片轮询多道进程的简单内核
		
基于mykernel完成时间片轮询多道进程的简单内核 原创作品转载请注明出处+中科大孟宁老师的linux操作系统分析:https://github.com/mengning/linuxkernel/ ...
 - Linux 内核数据结构:双向链表
		
Linux 内核提供一套双向链表的实现,你可以在 include/linux/list.h 中找到.我们以双向链表着手开始介绍 Linux 内核中的数据结构 ,因为这个是在 Linux 内核中使用最为 ...
 - 基于mykernel完成多进程的简单内核
		
学号351 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ mykernel简介 mykernel是由孟宁老师建立的一个用于开发您自己的操 ...
 - Linux内核中的双向链表struct list_head
		
一.双向链表list_head Linux内核驱动开发会经常用到Linux内核中经典的双向链表list_head,以及它的拓展接口和宏定义:list_add.list_add_tail.list_de ...
 - linux简单内核链表排序
		
#include <stdio.h> #include <stdlib.h> #define container_of(ptr, type, mem)(type *)((uns ...
 - [驱动] 一个简单内核驱动,通过qemu调试(1)
		
模块 通过在HOST上修改linux kernel源代码,重新编译一个vmlinux,然后,通过qemu根据这个bzImage 启动一个vm,进行调试 #cat drivers/char/test.c ...
 - freeRTOS内核学习笔记(1)-编程标准
		
在开始具体的学习之前,你应该先了解freeRTOS的编程标准.这能够方便你在接下来的阅读中快速的了解一些内容 的基本信息,并方便记忆.此外,良好的编程风格也是工作效率的保障. 你可以在https:// ...
 - FreeRTOS操作系统最全面使用指南
		
FreeRTOS操作系统最全面使用指南 1 FreeRTOS操作系统功能 作为一个轻量级的操作系统,FreeRTOS提供的功能包括:任务管理.时间管理.信号量.消息队列.内存管理.记录功能等,可基本满 ...
 - 【freertos】004-任务创建与删除及其实现细节
		
前言 后面都是已动态内存任务为例来分析. 注意: 由于当前学习是在linux上跑的freertos,对于freertos底层相关接口,从demo工程来看,都是posix标准相关. 鉴于freertos ...
 
随机推荐
- 更灵活的边缘云原生运维:OpenYurt 单元化部署新增 Patch 特性
			
简介: 在正文开始之前,我们先回顾一下单元化部署的概念和设计理念.在边缘计算场景下,计算节点具有很明显的地域分布属性,相同的应用可能需要部署在不同地域下的计算节点上. 作者 | 张杰(冰羽)来源 | ...
 - Flow vs Jenkins 实操对比,如何将Java应用快速发布至ECS
			
简介:Jenkins 由于其开源特性以及丰富插件能力,长久以来都是中小企业搭建 CICD 流程的首选.不过 Jenkins 存在维护成本高.配置复杂等缺点,云效 Flow 较好地解决了这些问题. 本 ...
 - 【详谈 Delta Lake 】系列技术专题 之 特性(Features)
			
简介: 本文翻译自大数据技术公司 Databricks 针对数据湖 Delta Lake 的系列技术文章.众所周知,Databricks 主导着开源大数据社区 Apache Spark.Delta L ...
 - Flink 1.14 新特性预览
			
简介: 一文了解 Flink 1.14 版本新特性及最新进展 本文由社区志愿者陈政羽整理,内容源自阿里巴巴技术专家宋辛童 (五藏) 在 8 月 7 日线上 Flink Meetup 分享的<F ...
 - Windows 查看端口是被什么程序占用
			
netstat -ano | grep 27017 tasklist | grep 11496 Link:https://www.cnblogs.com/farwish/p/15262813.html
 - 记联软 UniAccess 导致 NSIS 安装包启动进程失效
			
本文记录联软 UniAccess 注入的 C:\Window\LVUAAgentInstBaseRoot\syswow64\MozartBreathCore.dll 导致 NSIS 安装包启动进程失效 ...
 - 2018-8-10-WPF-如何画出1像素的线
			
title author date CreateTime categories WPF 如何画出1像素的线 lindexi 2018-08-10 19:16:53 +0800 2018-2-13 17 ...
 - 获取list集合中最大值、最小值及索引值
			
一.获取最大最小值的同时,获取到最大/小值在list中的索引值 public static void main(String[] args) { List<Integer> numList ...
 - 使用 Docker 部署 WebTop 运行 Linux 系统
			
1)项目介绍 GitHub:https://github.com/linuxserver/docker-webtop WebTop 它是一个基于 Linux ( Ubuntu 和 Alpine 两种版 ...
 - iceoryx源码阅读(三)——共享内存通信(一)
			
目录 0 导引 1 整体通信结构 2 RelativePointer 2.1 原理 2.2 PointerRepository 2.3 构造函数 2.4 get函数 3 ShmSafeUnmanage ...