FreeRTOS 空闲任务

news2024/9/26 1:24:42

文章目录

  • 一、空闲任务详解
    • 1. 空闲任务简介
    • 2. 空闲任务的创建
    • 3. 空闲任务函数
  • 二、空闲任务钩子函数详解
    • 1. 钩子函数
    • 2. 空闲任务钩子函数
  • 三、空闲任务钩子函数实验


一、空闲任务详解

1. 空闲任务简介

当 FreeRTOS 的调度器启动以后就会自动的创建一个空闲任务,这样就可以确保至少有一任务可以运行。但是这个空闲任务使用最低优先级,如果应用中有其他高优先级任务处于就绪态的话这个空闲任务就不会跟高优先级的任务抢占 CPU 资源。空闲任务还有另外一个重要的职责,如果某个任务要调用函数 vTaskDelete()删除自身,那么这个任务的任务控制块 TCB 和任务堆栈等这些由 FreeRTOS 系统自动分配的内存需要在空闲任务中释放掉,如果删除的是别的任务那么相应的内存就会被直接释放掉,不需要在空闲任务中释放。因此,一定要给空闲任务执行的机会!除此以外空闲任务就没有什么特别重要的功能了,所以可以根据实际情况减少空闲任务使用 CPU 的时间(比如,当 CPU 运行空闲任务的时候使处理器进入低功耗模式)。

用户可以创建与空闲任务优先级相同的应用任务,当宏 configIDLE_SHOULD_YIELD 为 1的话应用任务就可以使用空闲任务的时间片,也就是说空闲任务会让出时间片给同优先级的应用任务。这种方法在 介绍configIDLE_SHOULD_YIELD 的时候就讲过了,这种机制要求FreeRTOS 使用抢占式内核。

2. 空闲任务的创建

当调用函数 vTaskStartScheduler()启动任务调度器的时候此函数就会自动创建空闲任务,代码如下:

void vTaskStartScheduler( void )
{
	BaseType_t xReturn;
	//创建空闲任务,使用最低优先级
	#if( configSUPPORT_STATIC_ALLOCATION == 1 ) (1)
	{
		StaticTask_t *pxIdleTaskTCBBuffer = NULL;
		StackType_t *pxIdleTaskStackBuffer = NULL;
		uint32_t ulIdleTaskStackSize;
		vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, \
		&ulIdleTaskStackSize );
		xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
		"IDLE",
		ulIdleTaskStackSize,
		( void * ) NULL,
		( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
		pxIdleTaskStackBuffer,
		pxIdleTaskTCBBuffer ); 
		if( xIdleTaskHandle != NULL )
		{
			xReturn = pdPASS;
		}
		else
		{
			xReturn = pdFAIL;
		}
	}
	#else (2)
	{
		xReturn = xTaskCreate( prvIdleTask,
		"IDLE", 
		configMINIMAL_STACK_SIZE,
		( void * ) NULL,
		( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
		&xIdleTaskHandle );
	}
	#endif /* configSUPPORT_STATIC_ALLOCATION */
	/*********************************************************************/
	/**************************省略其他代码*******************************/
	/*********************************************************************/
}

(1)、使用静态方法创建空闲任务。
(2)、使用动态方法创建空闲任务,空闲任务的任务函数为 prvIdleTask(),任务堆栈大小为configMINIMAL_STACK_SIZE,任务堆栈大小可以在 FreeRTOSConfig.h 中修改。任务优先级为tskIDLE_PRIORITY,宏 tskIDLE_PRIORITY 为 0,说明空闲任务优先级最低,用户不能随意修改空闲任务的优先级!

3. 空闲任务函数

空闲任务的任务函数为 prvIdleTask(),但是实际上是找不到这个函数的,因为它是通过宏定义来实现的,在文件 portmacro.h 中有如下宏定义:

#define portTASK_FUNCTION( vFunction, pvParameters ) void vFunction( void *pvParameters )

其中 portTASK_FUNCTION()在文件 tasks.c 中有定义,它就是空闲任务的任务函数,源码如下:

static portTASK_FUNCTION( prvIdleTask, pvParameters ) (1)
{
	( void ) pvParameters; //防止报错
	//本函数为 FreeRTOS 的空闲任务任务函数,当任务调度器启动以后空闲任务会自动
	//创建
	for( ;; )
	{
		//检查是否有任务要删除自己,如果有的话就释放这些任务的任务控制块 TCB 和
		//任务堆栈的内存
		prvCheckTasksWaitingTermination(); (2)
		#if ( configUSE_PREEMPTION == 0 )
		{
			//如果没有使用抢占式内核的话就强制进行一次任务切换查看是否有其他
			//任务有效,如果有使用抢占式内核的话就不需要这一步,因为只要有任
			//何任务有效(就绪)之后都会自动的抢夺 CPU 使用权
			taskYIELD();
		}
		#endif /* configUSE_PREEMPTION */
		#if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) (3)
		{
			//如果使用抢占式内核并且使能时间片调度的话,当有任务和空闲任务共享
			//一个优先级的时候,并且此任务处于就绪态的话空闲任务就应该放弃本时
			//间片,将本时间片剩余的时间让给这个就绪任务。如果在空闲任务优先级
			//下的就绪列表中有多个用户任务的话就执行这些任务。
			if( listCURRENT_LIST_LENGTH( \ (4)
			&( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) )> ( UBaseType_t ) 1 )
			{
				taskYIELD();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif 
		#if ( configUSE_IDLE_HOOK == 1)
		{
			extern void vApplicationIdleHook( void );
			//执行用户定义的空闲任务钩子函数,注意!钩子函数里面不能使用任何
			//可以引起阻塞空闲任务的 API 函数。
			vApplicationIdleHook(); (5)
		}
		#endif /* configUSE_IDLE_HOOK */
		//如果使能了 Tickless 模式的话就执行相关的处理代码
		#if ( configUSE_TICKLESS_IDLE != 0 ) (6)
		{
			TickType_t xExpectedIdleTime;
			xExpectedIdleTime = prvGetExpectedIdleTime(); (7)
			if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) (8)
			{
				vTaskSuspendAll(); (9)
				{
				//调度器已经被挂起,重新采集一次时间值,这次的时间值可以
				//使用
				configASSERT( xNextTaskUnblockTime >= xTickCount );
				xExpectedIdleTime = prvGetExpectedIdleTime(); (10)
				if( xExpectedIdleTime >=\
				configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
				{
					traceLOW_POWER_IDLE_BEGIN();
					portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ); (11)
					traceLOW_POWER_IDLE_END();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		( void ) xTaskResumeAll(); (12)
		}
		else
		{
		mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configUSE_TICKLESS_IDLE */
	}
}

(1)、将此行展开就是 static void prvIdleTask(void *pvParameters),创建空闲任务的时候任务函数名就是 prvIdleTask()。

(2)、调用函数 prvCheckTasksWaitingTermination()检查是否有需要释放内存的被删除任务,当 有 任 务 调 用 函 数 vTaskDelete() 删 除 自 身 的 话 , 此 任 务 就 会 添 加 到 列 表xTasksWaitingTermination 中 。 函 数 prvCheckTasksWaitingTermination() 会 检 查 列 表xTasksWaitingTermination 是否为空,如果不为空的话就依次将列表中所有任务对应的内存释放掉(任务控制块 TCB 和任务堆栈的内存)。

(3)、使用抢占式内核并且 configIDLE_SHOULD_YIELD 为 1,说明空闲任务需要让出时间片给同优先级的其他就绪任务。

(4)、检查优先级为 tskIDLE_PRIORITY(空闲任务优先级)的就绪任务列表是否为空,如果不为空的话就调用函数 taskYIELD()进行一次任务切换。

(5)、如果使能了空闲任务钩子函数的话就执行这个钩子函数,空闲任务钩子函数的函数名为 vApplicationIdleHook(),这个函数需要用户自行编写!在编写这个这个钩子函数的时候一定不能调用任何可以阻塞空闲任务的 API 函数。

(6)、configUSE_TICKLESS_IDLE 不为 0,说明使能了 FreeRTOS 的低功耗 Tickless 模式。

(7)、调用函数 prvGetExpectedIdleTime()获取处理器进入低功耗模式的时长,此值保存在变量 xExpectedIdleTime 中,单位为时钟节拍数。

(8)、xExpectedIdleTime 值要大于 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 才有效。

(9)、处理 Tickless 模式,挂起任务调度器,其实就是起到临界段代码保护功能

(10)、重新获取一次时间值,这次的时间值是直接用于portSUPPRESS_TICKS_AND_SLEEP()的。

(11)、调用 portSUPPRESS_TICKS_AND_SLEEP()进入低功耗 Tickless 模式。

(12)、恢复任务调度器。

二、空闲任务钩子函数详解

1. 钩子函数

FreeRTOS 中有多个钩子函数,钩子函数类似回调函数,当某个功能(函数)执行的时候就会调用钩子函数,至于钩子函数的具体内容那就由用户来编写。如果不需要使用钩子函数的话就什么也不用管,钩子函数是一个可选功能,可以通过宏定义来选择使用哪个钩子函数,可选的钩子函数如下表所示
在这里插入图片描述
钩子函数的使用方法基本相同,用户使能相应的钩子函数,然后自行根据实际需求编写钩子函数的内容,下一节我们会以空闲任务钩子函数为例讲解如何使用钩子函数。

2. 空闲任务钩子函数

在每个空闲任务运行周期都会调用空闲任务钩子函数,如果想在空闲任务优先级下处理某个任务有两种选择:
● 在空闲任务钩子函数中处理任务。
不管什么时候都要保证系统中至少有一个任务可以运行,因此绝对不能在空闲任务钩子函数中调用任何可以阻塞空闲任务的 API 函数,比如 vTaskDelay(),或者其他带有阻塞时间的信号量或队列操作函数。

● 创建一个与空闲任务优先级相同的任务。
创建一个任务是最好的解决方法,但是这种方法会消耗更多的 RAM。
要使用空闲任务钩子函数首先要在 FreeRTOSConfig.h 中将宏 configUSE_IDLE_HOOK 改为 1,然后编写空闲任务钩子函数 vApplicationIdleHook()。通常在空闲任务钩子函数中将处理器设置为低功耗模式来节省电能,为了与 FreeRTOS 自带的 Tickless 模式做区分,这里我暂且将这种低功耗的实现方法称之为通用低功耗模式(因为几乎所有的 RTOS 系统都可以使用这种方法实现低功耗)。这种通用低功耗模式和 FreeRTOS 自带的 Tickless 模式的区别我们通过下图来对比分析一下。
在这里插入图片描述
图中有三个任务,它们分别为一个空闲任务(Idle),两个用户任务(Task1 和 Task2),其中空闲任务一共有运行了三次,分别为(1)、(2)、(3),其中 T1 到 T12 是 12 个时刻,下面我们分别从这两种低功耗的实现方法去分析一下整个过程。

(1)通用低功耗模式
如果使用通用低功耗模式的话每个滴答定时器中断都会将处理器从低功耗模式中唤醒,以(1)为例,再 T2 时刻处理器从低功耗模式中唤醒,但是接下来由于没有就绪的其他任务所以处理器又再一次进入低功耗模式。T2、T3 和 T4 这三个时刻都一样,反复的进入低功耗、退出低功耗,最理想的情况应该是从 T1 时刻就进入低功耗,然后在 T5 时刻退出。

在(2)中空闲任务只工作了两个时钟节拍,但是也执行了低功耗模式的进入和退出,显然这个意义不大,因为进出低功耗也是需要时间的。

(3)中空闲任务在 T12 时刻被某个外部中断唤醒,中断的具体处理过程在任务 2(使用信号量实现中断与任务之间的同步)。

(2)低功耗 Tickless 模式
在(1)中的 T1 时刻处理器进入低功耗模式,在 T5 时刻退出低功耗模式。相比通用低功耗模式少了 3 次进出低功耗模式的操作。

在(2)中由于空闲任务只运行了两个时钟节拍,所以就没必要进入低功耗模式。说明在
Tickless 模式中只有空闲任务要运行时间的超过某个最小阈值的时候才会进入低功耗模式,此阈值通过 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 来设置,上一章已经讲过了。

(3)中的情况和通用低功耗模式一样。
可以看出相对与通用低功耗模式,FreeRTOS 自带的 Tickless 模式更加合理有效,所以如果有低功耗设计需求的话大家尽量使用 FreeRTOS 再带的 Tickless 模式。当然了,如果对于功耗要求不严格的话通用低功耗模式也可以使用,下一节将通过一个实验讲解如何在空闲任务钩子函数中实现低功耗。

三、空闲任务钩子函数实验

1、实验目的
学习如何在 FreeRTOS 空闲任务钩子函数中实现低功耗。

2、实验设计
FreeRTOS 低功耗 Tickless 模式,关闭 Tickless 模式,在空闲任务钩子函数中使用 WFI 指令是处理器进入睡眠模式。

3、实验程序与分析
● 相关宏设置

#define configUSE_TICKLESS_IDLE 0 //关闭低功耗 tickless 模式
#define configUSE_IDLE_HOOK 1 //使能空闲任务钩子函数

● 空闲任务钩子函数

//进入低功耗模式前需要处理的事情
void BeforeEnterSleep(void)
{
	//关闭某些低功耗模式下不使用的外设时钟,此处只是演示性代码
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,DISABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,DISABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,DISABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,DISABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,DISABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,DISABLE);
}

//退出低功耗模式以后需要处理的事情
void AfterExitSleep(void)
{
	//退出低功耗模式以后打开那些被关闭的外设时钟,此处只是演示性代码
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,ENABLE); 
}

//空闲任务钩子函数
void vApplicationIdleHook(void)
{
	__disable_irq();
	__dsb(portSY_FULL_READ_WRITE );
	__isb(portSY_FULL_READ_WRITE );
	BeforeEnterSleep(); //进入睡眠模式之前需要处理的事情
	__wfi(); //进入睡眠模式
	AfterExitSleep(); //退出睡眠模式之后需要处理的事情
	__dsb(portSY_FULL_READ_WRITE );
	__isb(portSY_FULL_READ_WRITE );
	__enable_irq();
}

空闲任务钩子函数主要目的就是调用 WFI 指令使 STM32F103 进入睡眠模式,在进入和退出低功耗模式的时候也可以做一些其他处理,比如关闭外设时钟等等,用法和 FreeRTOS 的Tickless 模式类似。

● 其他任务函数和设置
其他有关设置和任务函数的内容同“FreeRTOS 实验 18-1 FreeRTOS 低功耗 Tickless 模式实验”一样,这里就不列出来了。

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

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

相关文章

【C++从0到王者】第二站:类和对象(中)构造函数与析构函数

文章目录 一、C的六个默认成员函数二、构造函数和析构函数1.构造函数①构造函数的概念②构造函数的特性 2.析构函数①析构函数的概念②析构函数的特性 3.构造函数的其他特性4.构造函数总结5.一些不写构造函数的样例6.析构函数的其他特性 一、C的六个默认成员函数 如果一个类中什…

go与其他语言区别,go与Java、Python有什么区别

零、go与其他语言 0、什么是面向对象 在了解 Go 语言是不是面向对象(简称:OOP) 之前,我们必须先知道 OOP 是啥,得先给他 “下定义” 根据 Wikipedia 的定义,我们梳理出 OOP 的几个基本认知: …

【转存】Go语言设计模式

导语| 设计模式是针对软件设计中常见问题的工具箱,其中的工具就是各种经过实践验证的解决方案。即使你从未遇到过这些问题,了解模式仍然非常有用,因为它能指导你如何使用面向对象的设计原则来解决各种问题,提高开发效率&#xff0…

[漏洞分析] 用chatGPT分析CVE-2023-0386 overlay内核提权

文章目录 漏洞简介环境搭建漏洞原理补丁分析命名空间用户命名空间 overlay文件系统原理创建一个overlay文件系统 漏洞触发逻辑 漏洞利用fuse文件系统漏洞利用touch命令冷知识exp 总结参考 本文的理论知识(命名空间、overlay文件系统、fuse文件系统等)均来…

档案馆库房环境温湿度空气质量等相关要求

档案库房防潮工作,就是要将库内相对湿度控制在规定的范围之内,这个范围就是由建设部和国家档案局共同批准颁布的强制性行业标准《档案馆建设设计规范》对档案库房的温湿度范围已作出明确的要求:温度14℃~24℃,湿度45%&…

深入理解Java虚拟机——垃圾回收算法

1.前言 垃圾回收需要完成的三件事 首先我们需要明白垃圾回收需要完成的三件事: 哪些内存需要回收 堆内存中的对象所使用的内存方法区中的废弃的常量以及不再使用的类型 什么时候回收 当对象死亡方法区中某些内容(常量和类型)不再被使用 如…

AI绘画天花板——Midjourney注册使用保姆级教程(5月5日验证有效)

大家好,我是可夫小子,关注AIGC、读书和自媒体。解锁更多ChatGPT、AI绘画玩法。加我,备注:aigc,拉你进群。 现在市面上AI绘图大概有三大阵营:Midjourney、Stable Diffusion,还有一个就是OpenAI实…

HashSet和HashMap内部结构分析

首先明确一点:HashSet的底层就是HashMap HashSet与HashMap的不同点: HashMap存储的是键值对(也就是key-value),即在调用HashMap的put方法时传入的两个值,而HashSet其实也是存储的键值对,但是键…

TR0ll

总结:提权思路上,利用内核漏洞提权;找可编辑的计划任务脚本:反弹shell;创建可执行的root文件,获取root权限;写入ssh公钥。 思路:思路是来说就是正常的思路,找ip&#xf…

【c语言】字符串复制 | API仿真

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ …

CentOS 7.x 安装 JDK1.8

1. JDK 下载 地址: Java Archive | Oracle 我这里选择的版本为 jdk-8u361-linux-x64.rpm 将 JDK 安装包通过 ftp 工具上传到 CentOS 系统&#xff0c;我这里使用 WinSCP 上传到 /usr/java 目录下(目录不存在的话就新建)。 2、进入 CentOS 终端&#xff0c;查看是否有默认安装…

Java简介和基础语法

文章目录 一、java简介二、Java 基础语法总结 一、java简介 通过一个简单的实例来展示 Java 编程&#xff0c;创建文件 HelloWorld.java(文件名需与类名一致), 代码如下&#xff1a; public class HelloWorld {public static void main(String[] args) {System.out.println(&q…

pycharm 安装gerrit插件

安装Gerrit File -> Settings -> Plugins&#xff0c;搜索Gerrit&#xff0c;如果没有安装&#xff0c;就选择install&#xff0c;安装完成后重启IDEA 配置Gerrit File -> Settings -> Version Control&#xff0c;输入Gerrit web-ui登录地址以及账号密码 Passow…

c#笔记-方法

方法 方法定义 方法可以将一组复杂的代码进行打包。 声明方法的语法是返回类型 方法名 括号 方法体。 void Hello1() {for (int i 0; i < 10; i){Console.WriteLine("Hello");} }调用方法 方法的主要特征就是他的括号。 调用方法的语法是方法名括号。 He…

K8S:K8S自动化运维容器化(Docker)集群程序

目录 一、K8S概述 1、什么是K8S 2、为什么要用K8S 3、作用及功能 二、K8S的特性 1、弹性伸缩 2、自我修复 3、服务发现和复制均衡 5、自动发布和回滚 6、集中化配置管理和秘钥管理 7、存储编排 8、任务批量处理运行 三、K8S的集群架构 四、K8S的核心组件 1、Mast…

Type-C接口供电小功率设备解决方案

随着Type-C接口的普及&#xff0c;全球使用Type-C接口的设备在稳步上升&#xff0c;因为它更方便&#xff0c;更安全&#xff0c;更环保&#xff0c;如今在生活中可谓是随处可见。 那么在传统的小功率设备大部分还在使用DC圆头供电&#xff0c;虽然成本很低&#xff0c;但是此类…

数字化转型利器,云表无代码“打破”工业软件开发壁垒

近年来&#xff0c;“数字化”概念成为了各行各业的“热词”&#xff0c;作为与信息化程度高度相关的工业软件&#xff0c;在数字化转型中扮演着不可或缺的角色。据 Gartner最新研究数据显示&#xff0c;目前中国工业软件市场规模已经达到了380亿美元&#xff0c;但与发达国家相…

ArcMap最短路径分析和网络数据集的构建

打断相交点 1.单击【编辑器】工具条上的编辑工具。 2.选择要在交叉点处进行分割的线要素。 3.单击【高级编辑】工具条上的打断相交线工具。 4.默认或可输入拓扑容差。 5.单击确定。 结果:所选线在相交处分割为多个新要素。“打断”操作还会移除叠置的线段-例如&#xff0…

HR如何快速提升工作效率?

从招聘到用人管理各个环节&#xff0c;人力资源部门都是公司最重要的职能部门之一&#xff0c;hr的日常工作涉及众多复杂繁琐的内容&#xff0c;比如人员招聘&#xff0c;考核培训等都离不开大量的数据整理和录入操作&#xff0c;但那些和“人”相关的数据信息&#xff0c;经常…

2023.03 青少年机器人技术等级考试理论综合试卷(四级)

2023 年 3 月青少年机器人技术等级考试理论综合试卷&#xff08;四级&#xff09; 一、单选题(共 20 题&#xff0c;共 80 分) 1. Arduino C 语言中&#xff0c;前缀 0x 表示的数制是&#xff1f;&#xff08;D &#xff09; A. 二进制 B. 八进制 C. 十进制 D. 十六进制 2. Ard…