在本章中,会涉及如下内容:
9.1 基本概念
对于整个单片机程序,我们称之为application,应用程序。
使用FreeRTOS时,我们可以在application中创建多个任务(task),有些文档把任务也称为线程(thread)。
以日常生活为例,比如这个母亲要同时做两件事:
9.2 任务创建与删除
9.2.1 什么是任务
在FreeRTOS中,任务就是一个函数,原型如下:
要注意的是:
9.2.2 创建任务
创建任务时使用的函数如下:
参数说明:
使用静态分配内存的函数如下:
相比于使用动态分配内存创建任务的函数,最后2个参数不一样:
9.2.3 示例1: 创建任务
代码为: 05_create_task
使用动态、静态分配内存的方式,分别创建多个任务:监测遥控器并在LCD上显示、LED闪烁、全彩LED渐变颜色、使用无源蜂鸣器播放音乐。
9.2.4 示例2: 使用任务参数
代码为:06_create_task_use_params
我们说过,多个任务可以使用同一个函数,怎么体现它们的差别?
我们创建2个任务,使用同一个函数,但是在LCD上打印不一样的信息。
上述代码中的info来自参数pvParameters,pvParameters来自哪里?创建任务时传入的。
代码如下:
9.2.5 任务的删除
删除任务时使用的函数如下:
参数说明:
怎么删除任务?举个不好的例子:
9.2.6 示例3: 删除任务
代码为: 07_delete_task
功能为:当监测到遥控器的Power按键被按下后,删除音乐播放任务。
代码如下:
9.3 任务优先级和Tick
#
9.3.1 任务优先级
怎么让播放的音乐更动听?提高优先级。
优先级的取值范围是:0~(configMAX_PRIORITIES – 1),数值越大优先级越高。
FreeRTOS的调度器可以使用2种方法来快速找出优先级最高的、可以运行的任务。使用不同的方法时,configMAX_PRIORITIES 的取值有所不同。
使用C函数实现,对所有的架构都是同样的代码。对configMAX_PRIORITIES的取值没有限制。但是configMAX_PRIORITIES的取值还是尽量小,因为取值越大越浪费内存,也浪费时间。
configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为0、或者未定义时,使用此方法。
架构相关的汇编指令,可以从一个32位的数里快速地找出为1的最高位。使用这些指令,可以快速找出优先级最高的、可以运行的任务。使用这种方法时,configMAX_PRIORITIES的取值不能超过32。
configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为1时,使用此方法。
在学习调度方法之前,你只要初略地知道:
这无需记忆,就像我们举的例子:
9.3.2 Tick
对于同优先级的任务,它们“轮流”执行。怎么轮流?你执行一会,我执行一会。
"一会"怎么定义?
人有心跳,心跳间隔基本恒定。
FreeRTOS中也有心跳,它使用定时器产生固定间隔的中断。这叫Tick、滴答,比如每10ms发生一次时钟中断。
如下图:
相同优先级的任务怎么切换呢?请看下图:
有了Tick的概念后,我们就可以使用Tick来衡量时间了,比如:
注意,基于Tick实现的延时并不精确,比如vTaskDelay(2)的本意是延迟2个Tick周期,有可能经过1个Tick多一点就返回了。 如下图:
使用vTaskDelay函数时,建议以ms为单位,使用pdMS_TO_TICKS把时间转换为Tick。
这样的代码就与configTICK_RATE_HZ无关,即使配置项configTICK_RATE_HZ改变了,我们也不用去修改代码。
9.3.3 示例4: 优先级实验
代码为:08_task_priority 本程序会:提高音乐播放任务的优先级,使用vTaskDelay进行延时。
代码如下:
9.3.4 修改优先级
使用uxTaskPriorityGet来获得任务的优先级:
使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。
使用vTaskPrioritySet 来设置任务的优先级:
使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。
使用vTaskPrioritySet 来设置任务的优先级:
使用参数xTask来指定任务,设置为NULL表示设置自己的优先级;
参数uxNewPriority表示新的优先级,取值范围是0~(configMAX_PRIORITIES – 1)。
#
9.4 任务状态
以前我们很简单地把任务的状态分为2中:运行(Runing)、非运行(Not Running)。 对于非运行的状态,还可以继续细分,比如前面的FreeRTOS_04_task_priority中:
#
9.4.1 阻塞状态(Blocked)
在日常生活的例子中,母亲在电脑前跟同事沟通时,如果同事一直没回复,那么母亲的工作就被卡住了、被堵住了、处于阻塞状态(Blocked)。重点在于:母亲在等待。
在FreeRTOS_04_task_priority实验中,如果把任务3中的vTaskDelay调用注释掉,那么任务1、任务2根本没有执行的机会,任务1、任务2被"饿死"了(starve)。
在实际产品中,我们不会让一个任务一直运行,而是使用"事件驱动"的方法让它运行:
在阻塞状态的任务,它可以等待两种类型的事件:
在等待一个同步事件时,可以加上超时时间。比如等待队里数据,超时时间设为10ms:
9.4.2 暂停状态(Suspended)
在日常生活的例子中,母亲正在电脑前跟同事沟通,母亲可以暂停:
FreeRTOS中的任务也可以进入暂停状态,唯一的方法是通过vTaskSuspend函数。函数原型如下:
参数xTaskToSuspend表示要暂停的任务,如果为NULL,表示暂停自己。
要退出暂停状态,只能由别人来操作:
实际开发中,暂停状态用得不多。
9.4.3 就绪状态(Ready)
这个任务完全准备好了,随时可以运行:只是还轮不到它。这时,它就处于就绪态(Ready)。
9.4.4 完整的状态转换图
9.6 Delay函数
9.6.1 两个Delay函数
有两个Delay函数:
这2个函数原型如下:
下面画图说明:
所以可以使用xTaskDelayUntil来让任务周期性地运行
9.6.2 示例5: Delay
本节代码为:11_taskdelay。 本程序会比较vTaskDelay和vTaskDelayUntil实际阻塞的时间,并在LCD上打印出来。
代码如下:
9.7 空闲任务及其钩子函数
#
9.7.1 介绍
空闲任务(Idle任务)的作用之一:释放被删除的任务的内存。
除了上述目的之外,为什么必须要有空闲任务?一个良好的程序,它的任务都是事件驱动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。在使用vTaskStartScheduler()函数来创建、启动调度器时,这个函数内部会创建空闲任务:
空闲任务的优先级为0,这意味着一旦某个用户的任务变为就绪态,那么空闲任务马上被切换出去,让这个用户任务运行。在这种情况下,我们说用户任务"抢占"(pre-empt)了空闲任务,这是由调度器实现的。
要注意的是:如果使用vTaskDelete()来删除任务,那么你就要确保空闲任务有机会执行,否则就无法释放被删除任务的内存。
我们可以添加一个空闲任务的钩子函数(Idle Task Hook Functions),空闲任务的循环每执行一次,就会调用一次钩子函数。钩子函数的作用有这些:
9.7.2 使用钩子函数的前提
在FreeRTOSSource asks.c中,可以看到如下代码,所以前提就是:
9.8 调度算法
#
9.8.1 重要概念
这些知识在前面都提到过了,这里总结一下。
正在运行的任务,被称为"正在使用处理器",它处于运行状态。在单处理系统中,任何时间里只能有一个任务处于运行状态。
非运行状态的任务,它处于这3中状态之一:阻塞(Blocked)、暂停(Suspended)、就绪(Ready)。就绪态的任务,可以被调度器挑选出来切换为运行状态,调度器永远都是挑选最高优先级的就绪态任务并让它进入运行状态。
阻塞状态的任务,它在等待"事件",当事件发生时任务就会进入就绪状态。事件分为两类:时间相关的事件、同步事件。所谓时间相关的事件,就是设置超时时间:在指定时间内阻塞,时间到了就进入就绪状态。使用时间相关的事件,可以实现周期性的功能、可以实现超时功能。同步事件就是:某个任务在等待某些信息,别的任务或者中断服务程序会给它发送信息。怎么"发送信息"?方法很多,有:任务通知(task notification)、队列(queue)、事件组(event group)、信号量(semaphoe)、互斥量(mutex)等。这些方法用来发送同步信息,比如表示某个外设得到了数据。
#
9.8.2 配置调度算法
所谓调度算法,就是怎么确定哪个就绪态的任务可以切换为运行状态。
通过配置文件FreeRTOSConfig.h的两个配置项来配置调度算法:configUSE_PREEMPTION、configUSE_TIME_SLICING。
还有第三个配置项:configUSE_TICKLESS_IDLE,它是一个高级选项,用于关闭Tick中断来实现省电,后续单独讲解。现在我们假设configUSE_TICKLESS_IDLE被设为0,先不使用这个功能。 调度算法的行为主要体现在两方面:高优先级的任务先运行、同优先级的就绪态任务如何被选中。调度算法要确保同优先级的就绪态任务,能"轮流"运行,策略是"轮转调度"(Round Robin Scheduling)。轮转调度并不保证任务的运行时间是公平分配的,我们还可以细化时间的分配方法。 从3个角度统一理解多种调度算法:
注:
9.8.3 示例6: 调度
9.8.4 对比效果: 抢占与否
在 FreeRTOSConfig.h 中,定义这样的宏,对比逻辑分析仪的效果:
对比结果为:
9.8.5 对比效果: 时间片轮转与否
在 FreeRTOSConfig.h 中,定义这样的宏,对比逻辑分析仪的效果:
从下面的对比图可以知道:
9.8.6 对比效果: 空闲任务让步
在 FreeRTOSConfig.h 中,定义这样的宏,对比逻辑分析仪的效果:
从下面的对比图可以知道:
到此这篇互斥锁和条件变量机制的工作流程(互斥锁和条件变量怎么用)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/jszy-gxgz/44800.html