FreeRTOS实时操作系统多任务管理基础知识

目录
  • 什么是多任务系统?
  • FreeRTOS  任务与协程
    • 1.任务(Task) 的特性
    • 2.协程(Co-routine)的特性
  • 任务状态
    • 运行态
    • 就绪态
    • 阻塞态
    • 挂起态
  • 任务优先级
    • 任务实现
  • 任务控制块
  • 任务堆栈

RTOS 系统的核心就是任务管理,FreeRTOS 也不例外,而且大多数学习 RTOS 系统的工程师或者学生主要就是为了使用 RTOS 的多任务处理功能,初步上手 RTOS 系统首先必须掌握的也是任务的创建、删除、挂起和恢复等操作,由此可见任务管理的重要性。

什么是多任务系统?

回想一下我们以前在使用 51、AVR、STM32 单片机裸机(未使用系统)的时候一般都是在main 函数里面用 while(1)做一个大循环来完成所有的处理,即应用程序是一个无限的循环,循环中调用相应的函数完成所需的处理。有时候我们也需要中断中完成一些处理。相对于多任务系统而言,这个就是单任务系统,也称作前后台系统,中断服务函数作为前台程序,大循环while(1)作为后台程序

前后台系统的实时性差,前后台系统各个任务(应用程序)都是排队等着轮流执行,不管你这个程序现在有多紧急,没轮到你就只能等着!相当于所有任务(应用程序)的优先级都是一样的。但是前后台系统简单啊,资源消耗也少啊!在稍微大一点的嵌入式应用中前后台系统就明显力不从心了,此时就需要多任务系统出马了。

多任务系统会把一个大问题(应用)“分而治之”,把大问题划分成很多个小问题,逐步的把小问题解决掉,大问题也就随之解决了,这些小问题可以单独的作为一个小任务来处理。这些小任务是并发处理的,注意,并不是说同一时刻一起执行很多个任务,而是由于每个任务执行的时间很短,导致看起来像是同一时刻执行了很多个任务一样。多个任务带来了一个新的问题,究竟哪个任务先运行,哪个任务后运行呢?完成这个功能的东西在 RTOS 系统中叫做任务调度器。不同的系统其任务调度器的实现方法也不同,比如 FreeRTOS 是一个抢占式的实时多任务系统,那么其任务调度器也是抢占式的。

高优先级的任务可以打断低优先级任务的运行而取得 CPU 的使用权,这样就保证了那些紧急任务的运行。这样我们就可以为那些对实时性要求高的任务设置一个很高的优先级,比如自动驾驶中的障碍物检测任务等。高优先级的任务执行完成以后重新把 CPU 的使用权归还给低优先级的任务,这个就是抢占式多任务系统的基本原理。

FreeRTOS  任务与协程

FreeRTOS 中应用既可以使用任务,也可以使用协程(Co-Routine),或者两者混合使用。但是任务和协程使用不同的API函数,因此不能通过队列(或信号量)将数据从任务发送给协程,反之亦然。协程是为那些资源很少的 MCU 准备的,其开销很小,但是 FreeRTOS 官方已经不打算再更新协程了,所以本教程只讲解任务。

1.任务(Task) 的特性

在使用 RTOS 的时候一个实时应用可以作为一个独立的任务。每个任务都有自己的运行环境,不依赖于系统中其他的任务或者 RTOS 调度器。任何一个时间点只能有一个任务运行,具体运行哪个任务是由 RTOS 调度器来决定的,RTOS 调度器因此就会重复的开启、关闭每个任务。任务不需要了解 RTOS 调度器的具体行为,RTOS 调度器的职责是确保当一个任务开始执行的时候其上下文环境(寄存器值,堆栈内容等)和任务上一次退出的时候相同。为了做到这一点,每个任务都必须有个堆栈,当任务切换的时候将上下文环境保存在堆栈中,这样当任务再次执行的时候就可以从堆栈中取出上下文环境,任务恢复运行。

任务特性: 简单。没有使用限制。支持抢占支持优先级每个任务都拥有堆栈导致了 RAM 使用量增大。如果使用抢占的话的必须仔细的考虑重入的问题。

2.协程(Co-routine)的特性

协程是为那些资源很少的 MCU 而做的,但是随着 MCU 的飞速发展,性能越来越强大,现
在协程几乎很少用到了!但是 FreeRTOS 目前还没有把协程移除的计划,但是 FreeRTOS 是绝对
不会再更新和维护协程了,因此协程大家了解一下就行了。在概念上协程和任务是相似的,但
是有如下根本上的不同:

  • 堆栈使用:所有的协程使用同一个堆栈(如果是任务的话每个任务都有自己的堆栈),这样就比使用任务消耗更少的 RAM。
  • 调度器和优先级:协程使用合作式的调度器,但是可以在使用抢占式的调度器中使用协程。
  • 宏实现:协程是通过宏定义来实现的。
  • 使用限制:为了降低对 RAM 的消耗做了很多的限制。

任务状态

FreeRTOS 中的任务永远处于下面几个状态中的某一个:

运行态

当一个任务正在运行时,那么就说这个任务处于运行态,处于运行态的任务就是当前正在使用处理器的任务。如果使用的是单核处理器的话那么不管在任何时刻永远都只有一个任务处于运行态。

就绪态

处于就绪态的任务是那些已经准备就绪(这些任务没有被阻塞或者挂起),可以运行的任务,但是处于就绪态的任务还没有运行,因为有一个同优先级或者更高优先级的任务正在运行!

阻塞态

如果一个任务当前正在等待某个外部事件的话就说它处于阻塞态,比如说如果某个任务调用了函数 vTaskDelay()的话就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个超时时间任务就会退出阻塞态,即使所等待的事件还没有来临!

挂起态

像阻塞态一样,任务进入挂起态以后也不能被调度器调用进入运行态,但是进入挂起态的任务没有超时时间。任务进入和退出挂起态通过调用函数 vTaskSuspend()和 xTaskResume()。

任务优先级

每 个 任 务 都 可 以 分 配 一 个 从 0~(configMAX_PRIORITIES-1) 的 优 先 级 ,configMAX_PRIORITIES 在文件 FreeRTOSConfig.h 中有定义,前面我们讲解 FreeRTOS 系统配置的时候已经讲过了。如果所使用的硬件平台支持类似计算前导零这样的指令(可以通过该指令选 择 下 一 个 要 运 行 的 任 务 , Cortex-M 处 理 器 是 支 持 该 指 令 的 ) , 并 且 configUSE_PORT_OPTIMISED_TASK_SELECTION 也 设 置 为 了 1 , 那 么 宏configMAX_PRIORITIES 不能超过 32!也就是优先级不能超过 32 级。其他情况下宏configMAX_PRIORITIES 可以为任意值,但是考虑到 RAM 的消耗,宏 configMAX_PRIORITIES最好设置为一个满足应用的最小值。

优先级数字越低表示任务的优先级越低,0 的优先级最低,configMAX_PRIORITIES-1 的优先级最高。空闲任务的优先级最低,为 0。

FreeRTOS 调度器确保处于就绪态或运行态的高优先级的任务获取处理器使用权,换句话说就是处于就绪态的最高优先级的任务才会运行。当宏 configUSE_TIME_SLICING 定义为 1 的时候多个任务可以共用一个优先级,数量不限。默认情况下宏configUSE_TIME_SLICING 在文件FreeRTOS.h 中已经定义为 1。此时处于就绪态的优先级相同的任务就会使用时间片轮转调度器获取运行时间。

任务实现

在使用 FreeRTOS 的过程中,我们要使用函数 xTaskCreate()或 xTaskCreateStatic()来创建任务,这两个函数的第一个参数pxTaskCode,就是这个任务的任务函数。什么是任务函数?任务函数就是完成本任务工作的函数。我这个任务要干嘛?要做什么?要完成什么样的功能都是在这个任务函数中实现的。 比如我要做个任务,这个任务要点个流水灯,那么这个流水灯的程序就是任务函数中实现的。FreeRTOS 官方给出的任务函数模板如下:

void vATaskFunction(void *pvParameters)
{
    for( ; ; )
    {
        //--任务应用程序--
        vTaskDelay();
        /*此处不一定要用延时函数,其他只要能让 FreeRTOS 发生任务切换的 API 函数都可以,
        比如请求信号量、队列等,甚至直接调用任务调度器。只不过最常用的就是 FreeRTOS 的延时函数。*/
    }
    /*不能从任务函数中返回或者退出,从任务函数中返回或退出的话就会调用
    configASSERT(),前提是你定义了 configASSERT()。如果一定要从任务函数中退出的话那一定
    要调用函数 vTaskDelete(NULL)来删除此任务。*/
    //vTaskDelete(NULL);  (5)
}

任务控制块

FreeRTOS 的每个任务都有一些属性需要存储,FreeRTOS 把这些属性集合到一起用一个结构体来表示,这个结构体叫做任务控制块:TCB_t,在使用函数 xTaskCreate()创建任务的时候就会自动的给每个任务分配一个任务控制块。在老版本的 FreeRTOS 中任务控制块叫做 tskTCB,新版本重命名为 TCB_t,但是本质上还是 tskTCB,本教程后面提到任务控制块的话均用 TCB_t表示,此结构体在文件 tasks.c 中有定义。 FreeRTOS 的任务控制块中的成员变量相比 UCOSIII 要少很多,而且大多数与裁剪有关,当不使用某些功能的时候与其相关的变量就不参与编译,任务控制块大小就会进一步的减小。

typedef struct tskTaskControlBlock{    volatile StackType_t  *pxTopOfStack;  //任务堆栈栈顶    #if ( portUSING_MPU_WRAPPERS == 1 )        xMPU_SETTINGS xMPUSettings;  //MPU 相关设置    #endif    ListItem_t  xStateListItem;  //状态列表项    ListItem_t  xEventListItem;  //事件列表项    UBaseType_t uxPriority;  //任务优先级    StackType_t *pxStack;  //任务堆栈起始地址    char pcTaskName[ configMAX_TASK_NAME_LEN ];//任务名字    #if ( portSTACK_GROWTH > 0 )        StackType_t *pxEndOfStack;  //任务堆栈栈底    #endif    #if ( portCRITICAL_NESTING_IN_TCB == 1 )        UBaseType_t uxCriticalNesting; //临界区嵌套深度    #endif    #if ( configUSE_TRACE_FACILITY == 1 ) //trace 或到 debug 的时候用到        UBaseType_t uxTCBNumber;         UBaseType_t uxTaskNumber;     #endif    #if ( configUSE_MUTEXES == 1 )        UBaseType_t uxBasePriority;  //任务基础优先级,优先级反转的时候用到        UBaseType_t uxMutexesHeld;  //任务获取到的互斥信号量个数    #endif    #if ( configUSE_APPLICATION_TASK_TAG == 1 )        TaskHookFunction_t pxTaskTag;    #endif    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) //与本地存储有关        void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];    #endif    #if( configGENERATE_RUN_TIME_STATS == 1 )        uint32_t ulRunTimeCounter;  //用来记录任务运行总时间    #endif    #if ( configUSE_NEWLIB_REENTRANT == 1 )        struct  _reent xNewLib_reent; //定义一个 newlib 结构体变量    #endif    #if( configUSE_TASK_NOTIFICATIONS == 1 ) //任务通知相关变量        volatile uint32_t ulNotifiedValue; //任务通知值        volatile uint8_t ucNotifyState;  //任务通知状态    #endif    #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )        //用来标记任务是动态创建的还是静态创建的,如果是静态创建的此变量就为 pdTURE,        //如果是动态创建的就为 pdFALSE         uint8_t  ucStaticallyAllocated;    #endif    #if( INCLUDE_xTaskAbortDelay == 1 )        uint8_t ucDelayAborted;    #endif} tskTCB;//新版本的 FreeRTOS 任务控制块重命名为 TCB_t,但是本质上还是 tskTCB,主要是为了兼容//旧版本的应用。typedef tskTCB TCB_t;typedef struct tskTaskControlBlock
{
    volatile StackType_t  *pxTopOfStack;  //任务堆栈栈顶
    #if ( portUSING_MPU_WRAPPERS == 1 )
        xMPU_SETTINGS xMPUSettings;  //MPU 相关设置
    #endif
    ListItem_t  xStateListItem;  //状态列表项
    ListItem_t  xEventListItem;  //事件列表项
    UBaseType_t uxPriority;  //任务优先级
    StackType_t *pxStack;  //任务堆栈起始地址
    char pcTaskName[ configMAX_TASK_NAME_LEN ];//任务名字
    #if ( portSTACK_GROWTH > 0 )
        StackType_t *pxEndOfStack;  //任务堆栈栈底
    #endif
    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t uxCriticalNesting; //临界区嵌套深度
    #endif
    #if ( configUSE_TRACE_FACILITY == 1 ) //trace 或到 debug 的时候用到
        UBaseType_t uxTCBNumber;
        UBaseType_t uxTaskNumber;
    #endif
    #if ( configUSE_MUTEXES == 1 )
        UBaseType_t uxBasePriority;  //任务基础优先级,优先级反转的时候用到
        UBaseType_t uxMutexesHeld;  //任务获取到的互斥信号量个数
    #endif
    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
        TaskHookFunction_t pxTaskTag;
    #endif
    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) //与本地存储有关
        void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
    #endif
    #if( configGENERATE_RUN_TIME_STATS == 1 )
        uint32_t ulRunTimeCounter;  //用来记录任务运行总时间
    #endif
    #if ( configUSE_NEWLIB_REENTRANT == 1 )
        struct  _reent xNewLib_reent; //定义一个 newlib 结构体变量
    #endif
    #if( configUSE_TASK_NOTIFICATIONS == 1 ) //任务通知相关变量
        volatile uint32_t ulNotifiedValue; //任务通知值
        volatile uint8_t ucNotifyState;  //任务通知状态
    #endif
    #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
        //用来标记任务是动态创建的还是静态创建的,如果是静态创建的此变量就为 pdTURE,
        //如果是动态创建的就为 pdFALSE
        uint8_t  ucStaticallyAllocated;
    #endif
    #if( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif
} tskTCB;
//新版本的 FreeRTOS 任务控制块重命名为 TCB_t,但是本质上还是 tskTCB,主要是为了兼容
//旧版本的应用。
typedef tskTCB TCB_t;

任务堆栈

FreeRTOS 之所以能正确的恢复一个任务的运行就是因为有任务堆栈在保驾护航,任务调度器在进行任务切换的时候会将当前任务的现场(CPU 寄存器值等)保存在此任务的任务堆栈中,等到此任务下次运行的时候就会先用堆栈中保存的值来恢复现场,恢复现场以后任务就会接着从上次中断的地方开始运行。

创建任务的时候需要给任务指定堆栈,如果使用的函数 xTaskCreate()创建任务(动态方法)的话那么任务堆栈就会由函数 xTaskCreate()自动创建,后面分析 xTaskCreate()的时候会讲解。如果使用函数 xTaskCreateStatic()创建任务(静态方法)的话就需要程序员自行定义任务堆栈,然后堆栈首地址作为函数的参数 puxStackBuffer 传递给函数。

任务堆栈的数据类型为 StackType_t,StackType_t 本质上是 uint32_t,在 portmacro.h 中有定义。所以 StackType_t 类型的变量为 4 个字节,那么任务的实际堆栈大小就应该是我们所定义的 4 倍。

以上就是FreeRTOS实时操作系统多任务管理基础知识的详细内容,更多关于FreeRTOS实时操作系统多任务管理的资料请关注我们其它相关文章!

(0)

相关推荐

  • FreeRTOS实时操作系统移植操作示例指南

    目录 1.添加FreeRTOS源码 2.向工程分组中添加文件 附上delay.c和delay.h的代码 1.添加FreeRTOS源码 在基础工程中新建一个名为 FreeRTOS 的文件夹,将 FreeRTOS 的源码(source文件夹下的内容)添加到这个文件夹中 portable文件夹中,只需留下 keil.MemMang 和 RVDS这三个文件夹,其他的都可以删除掉. 2.向工程分组中添加文件 打开基础工程,新建分组 FreeRTOS_CORE 和 FreeRTOS_PORTABLE,然后向

  • freertos实时操作系统空闲任务阻塞延时示例解析

    阻塞态:如果一个任务当前正在等待某个外部事件,则称它处于阻塞态. rtos中的延时叫阻塞延时,即任务需要延时的时候,会放弃CPU的使用权,进入阻塞状态.在任务阻塞的这段时间,CPU可以去执行其它的任务(如果其它的任务也在延时状态,那么 CPU 就将运行空闲任务),当任务延时时间到,重新获取 CPU 使用权,任务继续运行. 空闲任务:处理器空闲的时候,运行的任务.当系统中没有其他就绪任务时,空闲任务开始运行,空闲任务的优先级是最低的. 空闲任务 定义空闲任务: #define portSTACK_

  • FreeRTOS操作系统的配置示例解析

    目录 1. FreeRTOSConfig.h 文件 2.  “INCLUDE_” 开始的宏 3.“config”开始的宏 FreeRTOS 的系统配置文件为 FreeRTOSConfig.h,在此配置文件中可以完成 FreeRTOS 的裁剪和配置. 1. FreeRTOSConfig.h 文件 FreeRTOS 的配置基本是通过在 FreeRTOSConfig.h 中使用“#define”这样的语句来定义宏定义实现的.在 FreeRTOS 的官方 demo 中,每个工程都有一个 FreeRTOS

  • FreeRTOS实时操作系统结构示例

    目录 1.查找相关文档页 2.获取RTOS源代码 3.FreeRTOS源码目录结构 移植层目录举例: 演示例程目录举例: 4.编译工程 5.运行演示例程 FreeRTOS可以被移植到很多不同架构的处理器和编译器.每一个RTOS移植都附带一个已经配置好的演示例程,可以方便快速启动开发.更好的是,每个演示例程都附带一个说明网页,提供如何定位RTOS演示工程源代码.如何编译演示例程.如何配置硬件平台的全部信息. 演示例程说明网页还提供基本的RTOS移植细节信息,包括如何编写FreeRTOS兼容的中断服

  • FreeRTOS实时操作系统特点介绍

    目录 1.什么是FreeRTOS? 2.为什么择 选择 FreeRTOS ? 3.FreeRTOS 特点 FreeRTOS资料与源码下载 FreeRTOS源码文件介绍 1.什么是FreeRTOS? Free 即免费的,RTOS 全称是 Real Time Operating System,中文就是实时操作系统.注意,RTOS 不是指某一个确定的系统,而是指一类系统.比如 uC/OS,FreeRTOS,RTX,RT-Thread 等这些都是 RTOS 类操作系统. 操作系统允许多个任务同时运行,这

  • FreeRTOS实时操作系统临界段保护场合示例

    目录 临界段保护场合 非中断场合 中断场合 临界段保护场合 FreeRTOS中临界段保护有2种场合,中断和非中断,通过关中断(或者关部分中断)来实现临界保护. 非中断场合 task.h 中 #define taskENTER_CRITICAL()portENTER_CRITICAL() #define taskEXIT_CRITICAL()portEXIT_CRITICAL() portmacro.h中 #define portENTER_CRITICAL()vPortEnterCritical

  • FreeRTOS实时操作系统多任务管理基础知识

    目录 什么是多任务系统? FreeRTOS  任务与协程 1.任务(Task) 的特性 2.协程(Co-routine)的特性 任务状态 运行态 就绪态 阻塞态 挂起态 任务优先级 任务实现 任务控制块 任务堆栈 RTOS 系统的核心就是任务管理,FreeRTOS 也不例外,而且大多数学习 RTOS 系统的工程师或者学生主要就是为了使用 RTOS 的多任务处理功能,初步上手 RTOS 系统首先必须掌握的也是任务的创建.删除.挂起和恢复等操作,由此可见任务管理的重要性. 什么是多任务系统? 回想一

  • FreeRTOS实时操作系统信号量基础

    目录 前言 1.信号量简介 2.二进制信号量 3.计数信号量 4.互斥量 5.递归互斥量 前言 本文介绍信号量的基础知识,详细源码分析见<FreeRTOS进阶FreeRTOS信号量分析> 1.信号量简介 FreeRTOS的信号量包括二进制信号量.计数信号量.互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量). 我们可以把互斥量和递归互斥量看成特殊的信号量.互斥量和信号量在用法上不同: 信号量用于同步,任务间或者任务和中断间同步:互斥量用于互锁,用于保护同时只能有一个任务访问的资

  • FreeRTOS实时操作系统队列基础

    目录 本文介绍队列的基本知识,详细源码分析见<FreeRTOS高级篇5---FreeRTOS队列分析> 1.FreeRTOS队列 队列是主要的任务间通讯方式.可以在任务与任务间.中断和任务间传送信息.大多数情况下,队列用于具有线程保护的FIFO(先进先出)缓冲区:新数据放在队列的后面.当然,数据也可以放在队列的前面,在下一篇讲队列API函数时,会涉及到数据的存放位置. 图1-1:读写队列 图1-1所示的队列中,最多能保存5个项目,并且假设队列永远不会满.任务A使用API函数xQueueSend

  • freertos实时操作系统临界段保护开关中断及进入退出

    目录 中断的基础知识 嵌套: 优先级: 中断的悬起与解悬: 咬尾中断Tail‐Chaining: 晚到的高优先级异常: 进入临界段和退出临界段 中断的基础知识 嵌套: 嵌套向量中断控制器 NVIC(Nested Vectored Interrupt Controller与内核是紧耦合的.提供如下的功能:可嵌套中断支持.向量中断支持.动态优先级调整支持.中断延迟大大缩短. 中断可屏蔽. 所有的外部中断和绝大多数系统异常均支持可嵌套中断.异常都可以被赋予不同的优先级,当前优先级被存储在 xPSR的专

  • FreeRTOS实时操作系统Cortex-M内核使用注意事项

    前言 在阅读本文之前,有两个定义在FreeRTOSConfig.h中的宏,你必须先明白它们是什么意思,<FreeRTOS内核配置说明>一文中,讲解了这两个宏: configKERNEL_INTERRUPT_PRIORITY configMAX_SYSCALL_INTERRUPT_PRIORITY FreeRTOS与Cortex-M内核可谓是绝配,以至于让移植和使用FreeRTOS都变得更简单起来.根据FreeRTOS官方反馈,在Cortex-M内核上使用FreeRTOS大多数的问题点是由不正确

  • FreeRTOS实时操作系统空闲任务的阻塞延时实现

    目录 什么是阻塞延时.为什么需要空闲任务 空闲任务的实现 阻塞延时的实现 xTicksToDelay 递减 SysTick初始化 仿真 什么是阻塞延时.为什么需要空闲任务 RTOS中的延时叫阻塞延时,即任务需要延时时,任务会放弃cpu使用权,cpu转而去做其他的事,当任务延时时间到后,任务重新请求获得cpu使用权.但当所有的任务都处于阻塞后,为了不让cpu空闲没事干就需要一个空闲任务让cpu干活. 空闲任务的实现 空闲任务实现和创建普通任务没区别,空闲任务在调用vTaskStartSchedul

  • FreeRTOS实时操作系统之可视化追踪调试

    目录 前言 1.使能可视化追踪和运行时间统计功能 2.获取任务信息并格式化 3.添加到命令解释列表 前言 用RTOS编程,为每个任务分配多大的堆栈空间就成了一项技术活:分配多了浪费系统资源,分配少了又恐怕会发生堆栈溢出.由于中断和抢占式调度器的存在,我们要估算出一个任务需要多少堆栈是非常困难的,今天我们就介绍一种方法,来获取每个任务的剩余堆栈空间.本文以NXP LPC177x_8x系列微控制器为例. 我们将这个功能做成一个命令,添加到FreeRTOS使用任务通知实现命令行解释器一文介绍的命令解释

随机推荐