转载自 https://blog.csdn.net/zhoutaopower/article/details/107019521

任务管理是操作系统中重中之重,不管什么 OS ,任务的调度管理都是核心,FreeRTOS 也是一样;在深入到 FreeRTOS 任务管理的源码之前,鄙人觉得有必要先去从全局的角度进行把握,从全局到局部,从粗线条,到细节,鄙人觉得这样方可更快的熟悉相关的内部原理;

从全局来看的话,可以先梳理 FreeRTOS 关于任务相关的 APIs,支持的 Feature,以及相关的特性,这样一来,在深入到源码级分析的话,知道使用场景,便知道为何这样设计;

分析基于 FreeRTOS V 10.3.1

首先,FreeRTOS 任务支持如下特性:

1、多任务执行;

2、支持配置任务优先级;

3、支持任务的阻塞,挂起;

4、任务都是自己的栈空间;

5、支持周期性任务;

6、任务抢占;

7、协作式调度;

1、任务状态

在单核处理器上,多任务是宏观并行,微观串行的;每个任务可以处于不同的状态:

几乎所有的 OS 下,任务都分为了 Ready、Blocked、Running、Suspend 等状态;这样划分是根据具体的使用场景进行的;

Running:指的是正在运行的任务,在单核系统中,同一时刻只有一个任务处于 Running;

Ready:指的是可以被调度运行的任务,也就是处于就绪的任务;

Blocked:指的是因为某种原因(等待资源,等待时间)暂时不满足执行条件的任务的状态;

Suspend:指的是被挂起的任务,暂时不参与调度的状态;

2、任务创建

FreeRTOS 中,创建一个任务使用 xTaskCreate 接口:

BaseType_t xTaskCreate(  TaskFunction_t pvTaskCode,
const char * const pcName,
unsigned short usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * pvCreatedTask);

参数的含义如下:

pvTaskCode:任务执行的函数,此函数必须是一个死循环,不会返回。

pcName:任务的名字,是一个字符串;最大长度由宏 configMAX_TASK_NAME_LEN 指定,该宏位于 FreeRTOSConfig.h 文件中。

usStackDepth:任务堆栈的大小,它的单位不是字节,比如在 32-bits 位宽的情况下,这个值设置为 100,那么就是分配了 400 字节大小的堆栈。

pvParameters:传递给任务执行函数的参数。

uxPriority:任务的优先级。

pvCreatedTask:创建任务成功后的任务句柄,后期可以使用这个句柄来调用任务相关的 API。

返回值的含义如下:

Return:如果创建任务成功,返回 pdPASS 否则返回 pdFAIL

比如:

创建了两个任务 vTask1 和 vTask2,堆栈深度为 1000,优先级都为 1,没有入参;

创建完后,两个任务都默认被添加进入了 Ready 状态,调用 vTaskStartScheduler() 开启调度器;

它们的实现都是无限循环,执行的时候,进行打印;

它们在时间线上的执行如图所示:

3、任务优先级

在任务初始化的时候,可以指定任务的优先级,同样在任务运行过程中,也可以通过 API 来改变任务的优先级;

FreeRTOS 支持的最大优先级由 configMAX_PRIORITIES 确定,优先级的值越低,代表优先级越低;0 是最低优先级,所有在 FreeRTOS 中,优先级的范围处于 0 ~ configMAX_PRIORITIES 之间;

因为任务存在优先级不一样,调度器总是选择处于 Ready 状态的优先级最高的任务;

比如:

Task 1 优先级是 1,Task 2 优先级是 2;两个 Task 都是之前的实现的情况下,每次调度器选择任务的时候,因为 Task 2 优先级高,每次都会选择 Task 2,那么 Task 1 就会被饿死:

4、任务阻塞

类似上面的情况,Task 1 被饿死,得不到执行,显然不是我们想要的,归根结底,是因为 Task 2 一直满足执行条件!按照设计来说,Task 2 优先级高,它应该是去做一些急需快速完成的任务,其他任何事情不准阻拦他,但是哪有任务一直都急需被完成呢?所以,正常情况下,Task 2 可能是在等待某个事件(比如,某个中断后)或者某个时间点来完成急需完成的任务,其他时间应该处于一种等待的状态,我们称之为阻塞,也就是 Blocked;

在 FreeRTOS 中,有两种方式可以让任务进入阻塞状态:

1、阻塞在时间上:也就是执行任务的时间未到;

2、等待事件:也就是对应的事件还没有发生;

如果类似上面的代码,在死循环里面调用了 vTaskDelay 函数,这就是会导致任务处于阻塞状态,等时间到了指定的延时后,才再次进入 Ready 状态,被调度器调度;

在这种设计下,不同优先级的任务同时运行的时候,就不会有被饿死的情况,同时当两个任务都没有执行的时候,时间让给 IDLE 线程:

5、任务挂起

被挂起的任务,进入 Suspend 状态,调度器在任务选择的时候,不再调度进入 Suspend 状态的任务,除非再次对此任务调用 Resume,重新进入 Ready 队列,接受调度器的调度;

6、空闲任务

在调度器初始化的时候,会创建一个 Idle 任务,这样可以确保至少有一个任务在运行;此任务优先级最低,为 0;

空闲任务用来在处理被删除的任务的内存,所有删除任务后,一定要确保空闲任务被运行,这样才能够内存回收;

FreeRTOS 支持空闲任务的钩子函数,当开启 configUSE_IDLE_HOOK 宏是 1 的时候,在空闲任务的时候,函数:

void vApplicationIdleHook( void );

被调用,常用的方式是在此实现低功耗相关的处理逻辑;

5、任务调度

5.1、抢占式调度

FreeRTOS 的任务调度基于周期性的 Tick 心跳,调度器从 Ready 状态列表中选择下一个优先级最高的任务投入运行;被阻塞的任务可以通过 event 来临或者阻塞时间到,重新进入 Ready 状态;

软件上,可以通过配置宏,来改变调度算法的行为,这些宏位于 FreeRTOSConfig.h

configUSE_PREEMPTION:配置为 1 则说明支持抢占式调度,否则称之为协作式调度;

注:协作式调度需要任务主动放弃 CPU,下一个才能够被调度;抢占式调度由系统决定调度;

configUSE_TIME_SLICING :配置为 1 的时候,同样优先级的任务会被轮转调度执行;否则优先级相同的任务,不会被轮转执行,只会执行其中一个;

最常用的配置是,上述两个都配置为 1;

configUSE_PREEMPTION = 1

configUSE_TIME_SLICING = 1

比如有 3 个任务:Task 1、Task 2、Task 3 如下所示,他们的优先级也标记出来;

Task 1 处于 blocked 状态,等待 event 满足条件;

Task 2 是周期性任务;

Task 3 是低优先级任务,也处于 blocked 状态,等待它的 event;

t1 时刻,Task 2 执行,Task 1 和 Task 3 的 event 都没满足;

t2 时刻,没有任务执行,Idle 线程执行;

t3 时刻,Task 3 的 event 满足了,被调度执行;

t4 时刻,执行 Idle;

t5 时刻,Task 3 的 event 满足了,被调度执行;

t6 时刻,Task 2 周期性任务来了,优先级高于 Task 3,即便是 Task 3 未执行完,OS 依然调度 Task 2 先执行;

t7 时刻,Task 2 执行完毕,OS 调度 Task 3;

t8 时刻,没有需要调度的任务,进入 Idle;

t9 时刻,Task 2 周期性任务来了,被调度;

t10时刻,最高优先级的 Task 1 的 event 满足,即便是 Task 2 未执行完,OS 依然调度 Task 1 执行;

t11 时刻,Task 1 执行完毕,继续调度尚未执行完毕的 Task 2;此刻低优先级的 Task 3 的 event 虽然也满足,但是优先级低,执行被推后;

t12 时刻,Task 2 执行完毕,Task 3 得以被调度;

t13 时刻,没有任务活动,调度 Idle 线程;

在 configUSE_PREEMPTION 和 configUSE_TIME_SLICING 都配置为 1 的时候,如果线程和 Idle 线程一样优先级,那么他们会被轮转调度:

上面的调度过程不在多说,Task 1 优先级最高,当满足他的 event 的时候,抢占其他任务执行,随后进入 blocked 状态;

在上面的例子中(有任务和 Idle 线程一样连续执行并且优先级一样),还有一个宏 configIDLE_SHOULD_YIELD,可能会导致调度器行为变化:

configIDLE_SHOULD_YIELD = 0,那么行为和上面的一样,也是默认情况;

configIDLE_SHOULD_YIELD = 1,那么 Idle 线程会 loop 一次然后主动让出 CPU,如下所示:

如果 :

configUSE_PREEMPTION = 1

configUSE_TIME_SLICING = 0

这表示调度器支持抢占,但是对于同样优先级的任务,不会去轮转,比如:

可以看到 Task 1 优先级高,可以抢占低优先级;

但是 Task 2 和 Idle 优先级一样,但是得不到轮转的调度;

5.2、协作式调度

当配置如下:

configUSE_PREEMPTION = 0

configUSE_TIME_SLICING = any value

代表调度器会进行协作式调度,什么意思呢?如下所示:

Task 1 优先级最高,Task 2 其次,Task 3 优先级最低;

t1 时刻,Task 3 处于运行,Task 1 和 Task 2 处于阻塞;

t2 时刻,Task 2 满足运行条件进入 Ready 状态,但是由于不支持抢占调度,所以无法执行,Task 3 继续执行;

t3 时刻,Task 1 满足运行条件进入 Ready 状态,但是由于不支持抢占调度,所以无法执行,Task 3 继续执行;

t4 时刻,Task 3 主动调用 taskYIELD() 函数,主动放弃 CPU,此刻调度器选择优先级最高的 Task 1 进行运行;

t5 时刻,Task 1 执行完毕,进入 blocked,调度器调度 Task 2 运行;

t6 时刻,Task 2执行完毕,进入 blocked,调度器调度 Task 3运行;

FreeRTOS --(7)任务管理之入门篇的更多相关文章

  1. Spring Cloud(一):入门篇

    Spring Cloud 简介 Spring Cloud 是一个基于 Spring Boot 实现的微服务架构开发工具,可以快速构建分布式系统中的某些常用模式,如配置管理.服务治理.断路器.智能路由. ...

  2. Membership三步曲之入门篇 - Membership基础示例

    Membership 三步曲之入门篇 - Membership基础示例 Membership三步曲之入门篇 -  Membership基础示例 Membership三步曲之进阶篇 -  深入剖析Pro ...

  3. spring boot(一):入门篇

    构建微服务:Spring boot 入门篇 什么是spring boot Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框 ...

  4. 1. web前端开发分享-css,js入门篇

    关注前端这么多年,没有大的成就,就入门期间积累了不少技巧与心得,跟大家分享一下,不一定都适合每个人,毕竟人与人的教育背景与成长环境心理活动都有差别,但就别人的心得再结合自己的特点,然后探索适合自己的学 ...

  5. 一个App完成入门篇(七)- 完成发现页面

    第七章是入门篇的倒数第二篇文章了,明天整个APP将进入收官. 本节教程主要要教会大家使用二维码扫描和用do_WebView组件加在html页面. 导入项目 do_WebView组件 扫描功能 自定义事 ...

  6. [原创]Linq to xml增删改查Linq 入门篇:分分钟带你遨游Linq to xml的世界

    本文原始作者博客 http://www.cnblogs.com/toutou Linq 入门篇(一):分分钟带你遨游linq to xml的世界 本文原创来自博客园 请叫我头头哥的博客, 请尊重版权, ...

  7. 转:OSGi 入门篇:模块层

    OSGi 入门篇:模块层 1 什么是模块化 模块层是OSGi框架中最基础的一部分,其中Java的模块化特性在这一层得到了很好的实现.但是这种实现与Java本身现有的一些模块化特性又有明显的不同. 本文 ...

  8. 转:OSGi 入门篇:生命周期层

    OSGi 入门篇:生命周期层 前言 生命周期层在OSGi框架中属于模块层上面的一层,它的运作是建立在模块层的功能之上的.生命周期层一个主要的功能就是让你能够从外部管理应用或者建立能够自我管理的应用(或 ...

  9. 【three.js详解之一】入门篇

    [three.js详解之一]入门篇   开场白 webGL可以让我们在canvas上实现3D效果.而three.js是一款webGL框架,由于其易用性被广泛应用.如果你要学习webGL,抛弃那些复杂的 ...

随机推荐

  1. 一个tomcat部署两个springboot服务时启动JMX报错

    一.问题来源 今天在部署开发好的组件的时候,发现无法启动,检查启动日志,报如下错误: 2022-03-17T10:39:41.823+08:00 ERROR vediomanage.vediomana ...

  2. Java 中怎么打印数组?

    你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法来打印数组.由 于数组没有实现 toString() 方法,所以如果将数组传递给 System.ou ...

  3. jQuery--事件案例(鼠标提示)

    1.文字提示 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://ww ...

  4. 什么是 spring 装配?

    当 bean 在 Spring 容器中组合在一起时,它被称为装配或 bean 装配.Spring 容器需要知道需要什么 bean 以及容器应该如何使用依赖注入来将 bean 绑定 在一起,同时装配 b ...

  5. Spark学习摘记 —— RDD转化操作API归纳

    本文参考 在阅读了<Spark快速大数据分析>动物书后,大概了解到了spark常用的api,不过书中并没有给予所有api具体的示例,而且现在spark的最新版本已经上升到了2.4.5,动物 ...

  6. git和github学习笔记

    1. 了解Git和Github 2. 使用Github 3. Git安装和使用 4. Git基本工作流程 5. Git初始化及仓库创建和操作 6. Git管理远程仓库 7. Github Pages ...

  7. Living Documentation

    Living Documentation Living documentation in legacy systems Living documentation, which comes from t ...

  8. 基于canvas和web audio实现低配版MikuTap

    导言 最近发掘了一个特别happy的网页小游戏--MikuTap.打开之后沉迷了一下午,导致开发工作没做完差点就要删库跑路了,还好boss瞥了我一眼就没下文了.于是第二天我就继续沉迷,随着一阵抽搐,这 ...

  9. 微信小程序调研

    小程序入口 微信发现,小程序 公众号主体查看小程序 好友分享,群分享 公众号自定义菜单跳转 APP页面跳转 第三方服务 附近的小程序 扫普通链接二维码打开小程序 需要后台开启功能,开启后,用户在微信& ...

  10. 【Android开发】【布局】各种TabLayout样式

    Demo