【学习日记】【FreeRTOS】延时列表的实现

前言

本文在前面文章的基础上实现了延时列表,取消了 TCB 中的延时参数。
本文是对野火 RTOS 教程的笔记,融入了笔者的理解,代码大部分来自野火。

一、如何更高效地查找延时到期的任务

1. 朴素方式

  • 在本文之前,我们使用了一种朴素的思想进行延时任务的查找:
    • 在 TCB 中设置一个延时参数,需要延时的时候进行初始化
    • 将延时任务挂起(清除就绪优先级位 uxTopReadyPriority)
    • 当 SysTick 中断时,扫描就绪列表中每个 TCB,如果延时参数不为 0 就减 1
    • 如果延时参数被减到 0,就置对应的就绪优先级位 uxTopReadyPriority,然后进行任务切换
      可以看到,上面这种想法非常朴素,但是每次 SysTick 中断的时候都需要扫描一遍就绪列表中的所有任务,当任务多的时候,耗时将会很多。

2. 更高效的方式

  • 设置除就绪列表外的另一个列表——延时列表
  • 当任务要进入延时的时候,将延时到期的值设置为节点的排序值,根据排序值按升序插入延时列表中,然后将该任务从就绪列表中删除
  • 同时更新下一个任务的解锁时刻的变量 xNextTaskUnblockTime,这个变量的意思是,当系统时基计数器 xTickCount 的值与 xNextTaskUnblockTime 相等时,就表示有任务延时到期了,需要将该任务就绪
    可以看到,FreeRTOS 用这种方式避免了扫描所有任务的延时,这点是优于 RT-Thread 和 μC/OS 的。

实际上,有两个延时列表,这是为了解决延时时间溢出的问题。

如图:
在这里插入图片描述

二、代码详解

我们添加或修改以下的代码:

  • 两条延时列表的定义和初始化
  • 下一任务到期时间点变量的定义及初始化
  • 修改延时函数,延时时将任务从就绪列表中删除并添加到延时列表
  • 修改时基计数器中断,每次计时时查看是否有任务到期

还有一些辅助的函数,主要是解决当计时溢出或者延时溢出时两条延时列表的切换:

  • 切换当前延时列表指针和溢出延时列表指针函数
  • 更新任务到期时间点变量的函数

1. 延时列表的定义及初始化

① 定义

  • 定义了两个任务延时列表,当系统时基计数器xTickCount 没有溢出时,用一条列表,当 xTickCount 溢出后,用另外一条列表
  • pxDelayedTaskList 指向 xTickCount 没有溢出时使用的那条列表
  • pxOverflowDelayedTaskList 指向 xTickCount 溢出时使用的那条列表
//延时列表
static List_t xDelayedTaskList1;
static List_t xDelayedTaskList2;
//延时列表指针(用于切换)
static List_t * volatile pxDelayedTaskList;
static List_t * volatile pxOverflowDelayedTaskList;

② 初始化

/* 初始化任务相关的列表 */
void prvInitialiseTaskLists( void )
{
    UBaseType_t uxPriority;
    
    //就绪列表初始化
    for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
	{
		vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );	//初始化每个就绪列表
	}
	
	//延时列表初始化
	vListInitialise( &xDelayedTaskList1 );
	vListInitialise( &xDelayedTaskList2 );
    
    pxDelayedTaskList = &xDelayedTaskList1;
	pxOverflowDelayedTaskList = &xDelayedTaskList2;
}

2. 下一任务到期时间点变量的定义和初始化

  • xNextTaskUnblockTime 用于表示下一个任务的解锁时刻
  • xNextTaskUnblockTime = xTickCount + xTicksToDelay(当前时间 + 延时时间)
  • 当系统时基计数器 xTickCount 的值与 xNextTaskUnblockTime 相等时,就表示有任务延时到期了,需要将该任务就绪

① 定义

//下一个延时任务到期的时间
static volatile TickType_t xNextTaskUnblockTime		= ( TickType_t ) 0U;

② 初始化

  • 在 vTaskStartScheduler 任务调度器函数中初始化为 portMAX_DELAY
  • portMAX_DELAY 是一个 portmacro.h 中定义的宏,默认为 0xffffffffUL
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL

void vTaskStartScheduler( void )
{
/*======================================创建空闲任务start==============================================*/     
    TCB_t *pxIdleTaskTCBBuffer = NULL;
    StackType_t *pxIdleTaskStackBuffer = NULL;
    uint32_t ulIdleTaskStackSize;
    
    /* 获取空闲任务的内存:任务栈和任务TCB */
    vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, 
                                   &pxIdleTaskStackBuffer, 
                                   &ulIdleTaskStackSize );    
    
    xIdleTaskHandle = xTaskCreateStatic( (TaskFunction_t)prvIdleTask,              /* 任务入口 */
					                     (char *)"IDLE",                           /* 任务名称,字符串形式 */
					                     (uint32_t)ulIdleTaskStackSize ,           /* 任务栈大小,单位为字 */
					                     (void *) NULL,                            /* 任务形参 */
                                         (UBaseType_t) tskIDLE_PRIORITY,           /* 任务优先级,数值越大,优先级越高 */
					                     (StackType_t *)pxIdleTaskStackBuffer,     /* 任务栈起始地址 */
					                     (TCB_t *)pxIdleTaskTCBBuffer );           /* 任务控制块 */
/*======================================创建空闲任务end================================================*/ 
    xNextTaskUnblockTime = portMAX_DELAY;
    xTickCount = ( TickType_t ) 0U;

    /* 启动调度器 */
    if( xPortStartScheduler() != pdFALSE )
    {
        /* 调度器启动成功,则不会返回,即不会来到这里 */
    }
}

3. 延时函数的修改

任务调用延时函数时,将任务从就绪列表中转移到延时列表中:

void vTaskDelay( const TickType_t xTicksToDelay )
{
    TCB_t *pxTCB = NULL;
    
    /* 获取当前任务的TCB */
    pxTCB = pxCurrentTCB;
    
    /* 将任务插入到延时列表 */
    prvAddCurrentTaskToDelayedList( xTicksToDelay );
    
    /* 任务切换 */
    taskYIELD();
}
  • 任务插入延时列表使用 prvAddCurrentTaskToDelayedList()
  • 这个函数除了插入操作还处理了延时溢出的情况
  • 笔者画了个流程图方便大家理解:
    在这里插入图片描述
//将任务插入到延时列表
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait )
{
    TickType_t xTimeToWake;
    
    /* 获取系统时基计数器xTickCount的值 */
    const TickType_t xConstTickCount = xTickCount;

    /* 将任务从就绪列表中移除 */
	if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
	{
		/* 将任务在优先级位图中对应的位清除 */
        portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
	}

    /* 计算延时到期时,系统时基计数器xTickCount的值是多少 */
    xTimeToWake = xConstTickCount + xTicksToWait;

    /* 将延时到期的值设置为节点的排序值 */
    listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

    /* 溢出 */
    if( xTimeToWake < xConstTickCount )
    {
        vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
    }
    else /* 没有溢出 */
    {

        vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

        /* 更新下一个任务解锁时刻变量xNextTaskUnblockTime的值 */
        if( xTimeToWake < xNextTaskUnblockTime )
        {
            xNextTaskUnblockTime = xTimeToWake;
        }
    }	
}

4. 修改时基计数器中断

  • 修改时基计数器中断,每次计时时查看是否有任务到期
  • 并且处理时基计数器溢出情况
  • 函数流程图如下
    在这里插入图片描述
  • 函数代码如下:
//系统时基计数
void xTaskIncrementTick( void )
{
	TCB_t * pxTCB;
	TickType_t xItemValue;

	//系统时基计数 + 1
	const TickType_t xConstTickCount = xTickCount + 1;
	xTickCount = xConstTickCount;

	/* 如果xConstTickCount溢出,则切换延时列表 */
	if( xConstTickCount == ( TickType_t ) 0U )
	{
		taskSWITCH_DELAYED_LISTS();
	}

	/* 最近的延时任务延时到期 */
	if( xConstTickCount >= xNextTaskUnblockTime )
	{
		for( ;; )
		{
			if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
			{
				/* 延时列表为空,设置xNextTaskUnblockTime为可能的最大值 */
				xNextTaskUnblockTime = portMAX_DELAY;
				break;
			}
			else /* 延时列表不为空 */
			{
				pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
				xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );

				/* 直到将延时列表中所有延时到期的任务移除才跳出for循环 */
                if( xConstTickCount < xItemValue )
				{
					xNextTaskUnblockTime = xItemValue;
					break;
				}

				/* 将任务从延时列表移除,消除等待状态 */
				( void ) uxListRemove( &( pxTCB->xStateListItem ) );

				/* 将解除等待的任务添加到就绪列表 */
				prvAddTaskToReadyList( pxTCB );
			}
		}
	}/* xConstTickCount >= xNextTaskUnblockTime */
    
    /* 任务切换 */
    portYIELD();
}

5. 交换非溢出延时指针和溢出延时指针

  • 使用一个中间变量进行交换
  • 切换延时列表后记得更新 xNextTaskUnblockTime 的值
/* 
 * 当系统时基计数器溢出的时候,延时列表pxDelayedTaskList 和
 * pxOverflowDelayedTaskList要互相切换
 */
#define taskSWITCH_DELAYED_LISTS()\
{\
	List_t *pxTemp;\
	pxTemp = pxDelayedTaskList;\
	pxDelayedTaskList = pxOverflowDelayedTaskList;\
	pxOverflowDelayedTaskList = pxTemp;\
	xNumOfOverflows++;\
	prvResetNextTaskUnblockTime();\
}

6. 更新任务到期时间点变量的函数

  • 函数流程图:
    在这里插入图片描述

  • 函数代码:

//重新设置变量xNextTaskUnblockTime的值
static void prvResetNextTaskUnblockTime( void )
{
    TCB_t *pxTCB; // 定义一个指向TCB_t类型的指针变量pxTCB

	if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
	{
		/* 新的延时任务列表为空。将xNextTaskUnblockTime设置为最大可能值,以确保在延时列表中有任务项之前,if( xTickCount >= xNextTaskUnblockTime )的测试不会通过。 */
		xNextTaskUnblockTime = portMAX_DELAY;
	}
	else
	{
		/* 新的延时任务列表不为空,获取延时列表头部任务的值。这个值表示了延时列表头部任务应该从阻塞状态中解除的时间。 */
		( pxTCB ) = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
		xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( ( pxTCB )->xStateListItem ) );
	}
}

后记

如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

esp32c3 micropython oled实时天气信息

目录 简介 效果展示 代码 main.py ssd1306.py font.py 实现思路 简介 合宙esp32c3 micropython框架&#xff0c;只支持128*64 I2C oled ssd1306驱动我优化过的&#xff0c;与其他的不一样&#xff0c;为避免出错&#xff0c;使用我的驱动 把下面两个py文件放入单片机内…

阿里云与中国中医科学院合作,推动中医药行业数字化和智能化发展

据相关媒体消息&#xff0c;阿里云与中国中医科学院的合作旨在推动中医药行业的数字化和智能化发展。随着互联网的进步和相关政策的支持&#xff0c;中医药产业受到了国家的高度关注。这次合作将以“互联网 中医药”为载体&#xff0c;致力于推进中医药文化的传承和创新发展。…

【Java并发编程面试题(60道)】

toc Java并发编程面试题(60道) 基础 1.并行跟并发有什么区别&#xff1f; 从操作系统的角度来看&#xff0c;线程是CPU分配的最小单位。 并行就是同一时刻&#xff0c;两个线程都在执行。这就要求有两个CPU去分别执行两个线程。并发就是同一时刻&#xff0c;只有一个执行&…

基于物理场的动态模式分解(piDMD)研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

自动驾驶港口车辆故障及事故处理机制

1、传感器故障&#xff1a; &#xff08;1&#xff09;单一传感器数据异常处理。自动驾驶电动平板传感方案为冗余设置&#xff0c;有其他传感器能够覆盖故障传感器观测区域&#xff0c;感知/定位模块将数据异常情况发给到规划决策模块&#xff0c;由“大脑”向中控平台上报故障…

ElasticSearch的客户端操作

ElasticSearch的客户端操作 1、客户端介绍 官方文档地址&#xff1a; https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html 实际开发中&#xff0c;有多种方式操作Elasticsearch&#xff1a; 客户端工具&#xff1a;发送http请求(RESTful风格)操作…

source insight 添加宏-文件头加注释

source insight 3.5 自带的一些宏&#xff0c;在安装目录下的 utils.em 文件中&#xff0c;用户也可以自己写文件&#xff0c;命令为xxx.em &#xff0c;然后把这个文件添加到项目中即可&#xff0c;添加后在菜单栏 Options -> Key Assignments 里输入macro 就能显示新添加的…

MongoDB升级经历(4.0.23至5.0.19)

MongoDB从4.0.23至5.0.19升级经历 引子&#xff1a;为了解决MongoDB的两个漏洞决定把MongoDB升级至最新版本&#xff0c;期间也踩了不少坑&#xff0c;在这里分享出来供大家学习与避坑~ 1、MongoDB的两个漏洞 漏洞1&#xff1a;MongoDB Server 安全漏洞(CVE-2021-20330) 漏洞2…

总结,由于顺丰的问题,产生了电脑近期一个月死机问题集锦

由于我搬家&#xff0c;我妈搞顺丰发回家&#xff0c;但是没有检查有没有坏&#xff0c;并且我自己由于不可抗力因素&#xff0c;超过了索赔时间&#xff0c;反馈给顺丰客服&#xff0c;说超过了造成了无法索赔的情况&#xff0c;现在总结发生了损坏配件有几件&#xff0c;显卡…

输入输出+暴力模拟入门:魔法之树、染色の树、矩阵、字母加密、玫瑰鸭

秋招实习刷题网站推荐&#xff1a;codefun2000.com&#xff0c;还有题解博客&#xff1a;blog.codefun2000.com/。以下内容都是来自塔子哥的~ 输入输出 2023.04.15-春招-第三题-魔法之树 //#include<bits/stdc.h> #include<vector> #include<iostream>usin…

AndroidStudio升级Gradle之坑

最近在做旧工程的升级&#xff0c;原来的Gradle版本是4.6的&#xff0c;需要升级到7.6&#xff0c;JDK从8升级到17&#xff0c;一路趟了很多坑&#xff0c;逐个记录下吧 1、Maven仓库需要升级到https 你会遇到这个报错 Using insecure protocols with repositories, without …

openLayers实战(八):坐标系及其转换

坐标系介绍 EPSG: 3857 --web地图&#xff0c;基于球体的、web墨卡托投影&#xff08;伪墨卡托投影Pseudo-Mercator&#xff09;的投影坐标系&#xff0c;范围为纬度85度以下&#xff0c;由于google地图最先使用而成为事实标准。至今&#xff0c;大多互联网地图都使用EPSG3857&…

Android 远程真机调研

背景 现有的安卓测试机器较少&#xff0c;很难满足 SDK 的兼容性测试及线上问题&#xff08;特殊机型&#xff09;验证&#xff0c;基于真机成本较高且数量较多的前提下&#xff0c;可以考虑使用云测平台上的机器进行验证&#xff0c;因此需要针对各云测平台进行调研、比较。 …

【环境配置】Windows10终端和VSCode下能够直接打开Anaconda-Prompt

很多小伙伴在 Windows 下做深度学习开发的时候&#xff0c;遇到终端没有在 Linux 那么方便&#xff0c;那么我们现在就可以来设置一下&#xff1b;这样我们也可以在文件夹内部右键打开终端&#xff0c;也可以在 VS Code 里面新建一个虚拟环境的控制台&#xff1b;这里主要是针对…

VS2015项目中,MFC内存中调用DLL函数(VC6生成的示例DLL)

本例主要讲一下&#xff0c;用VC6如何生成DLL&#xff0c;用工具WinHex取得DLL全部内容&#xff0c;VC2015项目加载内存中的DLL函数&#xff0c;并调用函数的示例。 本例中的示例代码下载&#xff0c;点击可以下载 一、VC6.0生成示例DLL项目 1.新建项目&#xff0c;…

爬虫逆向实战(十四)--某培训平台登录

一、数据接口分析 主页地址&#xff1a;某培训平台 1、抓包 通过抓包可以发现登录是表单提交到j_spring_security_check 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看“载荷”模块可以发现有一个j_password加密参数 请求头是否加密&#xff1f; 无响应是…

SAP MM学习笔记23-购买发注的账户分配类型(勘定Category)

SAP中控制财务凭证过账科目的是 账号分配类型&#xff08;勘定Category&#xff09;栏目。 ・账号分配类型&#xff08;勘定Category&#xff09;有&#xff1a; 1&#xff0c;K 原价Center&#xff08;成本中心。用于消耗物料采购 的过账&#xff09; 2&#xff0c;E 得意先…

TPAMI, 2023 | 用压缩隐逆向神经网络进行高精度稀疏雷达成像

CoIR: Compressive Implicit Radar | IEEE TPAMI, 2023 | 用压缩隐逆向神经网络进行高精度稀疏雷达成像 注1:本文系“无线感知论文速递”系列之一,致力于简洁清晰完整地介绍、解读无线感知领域最新的顶会/顶刊论文(包括但不限于Nature/Science及其子刊;MobiCom, Sigcom, MobiSy…

C++之string类的使用

C之string类的使用 1.为什么要学string类2.标准库中的string类3.string类的接口的使用3.1默认成员函数3.2容量操作3.3访问操作3.4遍历操作3.5修改操作3.6字符串操作3.7非成员函数 1.为什么要学string类 我们所学的字符串是以’\0’结尾的一些字符的集合&#xff0c;为了操作方…

工业视觉相机镜头选型方法

一、相机选型 1、首先&#xff0c;根据检测需求确定选用黑白/彩色、面阵/线阵相机&#xff0c;接口类型一般选择GigE 2、确定检测精度要求&#xff08;最小特征尺寸mm&#xff09;、视野范围&#xff0c;一个测量精度对应几个像素数&#xff08;一般取3-5&#xff09; 3、计…
最新文章