FreeRTOS任务基础知识

news2024/12/19 3:06:49

单任务和多任务系统

单任务系统

单任务系统的编程方式,即裸机的编程方式,这种编程方式的框架一般都是在main()函数中使用一个大循环,在循环中顺序的执行相应的函数以处理相应的事务,这个大循环的部分可以视为应用程序的后台,而应用程序的前台,则是各种中断的中断服务函数。因此单任务系统也叫做前后台系统,前后台系统的运行示意图如下:

从上图中可以看到,前后台系统的实时性很差,因为大循环中函数处理的事务没有优先级之分,必须是顺序地执行处理,不论待处理事务的紧急程度有多高,没轮到就只能等着,虽然中断可以处理一些紧急的事务,但是在大型嵌入式系统中,这样的单任务系统就会显得力不从心。

多任务系统

多任务系统在处理事务的实时性比单任务系统要好得多,从宏观上来看,多任务系统的多个任务是可以“同时”运行的,因此紧急事务就可以无需等待CPU处理其他事务,再被处理。

要注意的是,多任务系统的多个任务可以“同时”运行,是从宏观的角度而言的,对于单核CPU而言,CPU在同一时刻只能处理一个任务,但是多任务系统的任务调度器可以根据相关的任务调度算法,将CPU的使用权分给任务,在任务获得CPU使用权之后的极短时间(宏观角度)后,任务调度器又会将CPU的使用权分配给其他任务,如此往复,在宏观的角度看来,就像是多个任务同时运行一样。

多任务系统的运行示意图,如下:

从上图中可以看出,相较于单任务系统而言,多任务系统的任务也是具有优先级的,高优先级的任务可以像中断一样抢占,抢占低优先级任务的CPU使用权;优先级相同的任务则各自轮流运行一段极短时间,从而产生“同时”运行的错觉。以上就是抢占式调度和时间片轮转调度的基本原理。

在任务有了优先级的多任务系统中,用户就可以将紧急的事务放在优先级高的任务中进行处理,那么整个系统的实时性就会大大提高。

任务基本概念

对于整个单片机程序,我们称之为application,应用程序。

使用FreeRTOS时,我们可以在application中创建多个任务(task),有些文档把任务也叫做线程(thread)。

以日常生活为例子,比如这个母亲要同时做两件事:

  • 喂饭:这是一个任务

  • 回信息:这是另一个任务

这可以引入很多概念

  • 任务状态(state)

  • 当前正在喂饭,它是running状态;另一个“回信息”就是not running状态

  • not running状态又可以细分为:

  • ready:就绪状态,随时可以运行

  • blocked:阻塞状态,卡住了,母亲在等待同时回复消息

  • suspended:挂起状态,同时废话太多,不管他了

  • 优先级(priority)

  • 我工作生活兼顾:喂饭,回信息优先级一样,轮流做

  • 我忙里偷闲,还有空闲任务,休息一下

  • 厨房着火,什么都别说,先灭火:优先级更高

  • 栈(stack)

  • 喂小孩,我要记得上一口喂了米饭,这口要喂青菜了

  • 回信息时,我要记得刚才聊的是啥

  • 做不同的任务,这些细节不一样

  • 对于人来说,当然是记在脑子里

  • 对于程序来说,是记在栈里

  • 每个任务有自己的栈

  • 事件驱动

  • 孩子吃饭太慢,先休息一会,等他咽下去了,等他提醒我了,再喂下一口

  • 协助式调度(co-operative scheduling)

  • 你给同时回信息:

  • 同事说:好了,你下去给小孩喂一口饭把,你才能离开

  • 同事不放你走,即使孩子哭了你也不能走

  • 你好不容易可以给孩子喂饭了

  • 孩子说:好了,妈妈你去处理一下工作吧,你才能离开

  • 孩子不放你走,即使同事连发信息你也不能走

FreeROTS任务状态

FreeRTOS 中任务存在四种任务状态,分别为运行态、就绪态、阻塞态和挂起态。FreeRTOS 运行时,任务的状态一定是这四种状态中的一种,下面就分别来介绍一下这四种任务状态。

  1. 运行态

如果一个任务得到CPU的使用权,即任务被实际执行时,那么这个任务处于运行态。如果 运行 RTOS 的 MCU 只有一个处理器核心,那么在任务时刻,都只能有一个任务处于运行态 运行

  1. 就绪态

如果一个任务已经能够被执行(不处于阻塞态和挂起态),但当前还未被执行(具有相同优 先级或更高优先级的任务正持有 CPU 使用权),那么这个任务就处于就绪态。

  1. 阻塞态

如果一个任务因延时一段时间或等待外部事件发生,那么这个任务就处理阻塞态。例如任 务调用了函数 vTaskDelay(),进行一段时间的延时,那么在延时超时之前,这个任务就处理阻塞 态。任务也可以处于阻塞态以等待队列、信号量、事件组、通知或信号量等外部事件。通常情 况下,处于阻塞态的任务都有一个阻塞的超时时间,在任务阻塞达到或超过这个超时时间后, 即使任务等待的外部事件还没有发生,任务的阻塞态也会被解除。

要注意的是,处于阻塞态的任务是无法被运行的。

在日常生活的例子中,母亲在电脑前跟同事沟通时,如果同事一直没回复,那么母亲的工作就被卡住了、被堵住了、处于阻塞状态(Blocked)。重点在于:母亲在等待

在实际产品中,我们不会让一个任务一直运行,而是使用"事件驱动"的方法让它运行:

  • 任务要等待某个事件,事件发生后它才能运行

  • 在等待事件过程中,它不消耗CPU资源

  • 在等待事件的过程中,这个任务就处于阻塞状态(Blocked)

在阻塞态的任务可以等待两种类型的事件:

  • 时间相关的事件

  • 可以等待一段时间:我等2分钟

  • 也可以一直等待,直到某个绝对时间:我等到下午3点

  • 同步事件:这事件由别的任务,或者是中断程序产生

  • 例子1:任务A等待任务B给他发送数据

  • 例子2:任务A等待用户按下按键

  • 同步事件的来源有很多

  • 队列(queue)

  • 二进制信号量(binary semaphores)

  • 计数信号量(counting semaphores)

  • 互斥量(mutexes)

  • 递归互斥量(recursive mutexes)

  • 事件组(event groups)

  • 任务通知(task notifcations)

  1. 挂起态

任务一般通过函数 vTaskSuspend()和函数 vTaskResums()进入和退出挂起态,与阻塞态一样, 处于挂起态的任务也无法被运行。

FreeRTOS任务优先级

任务优先级是决定任务调度器如何分配CPU使用权的因素之一。每一个任务都被分配一个0~configMAX_PRIORITIES-1的优先级,宏configMAX_PRIORITIES在FreeRTOSConfig.h文件中定义。

如果在FreeRTOSConfig.h文件中,将宏configUSE_PORT_OPTIMISED_TASK_SELECTION定义为1,那么FreeRTOS则会使用特殊的方法计算下一个要运行的任务,这种特殊方法一般是使用硬件计算前导指令,对于STM32而言,硬件计算器前导零的指令,最大支持32位的数,因此宏configMAX_PRIORITIES的值不能超过32.当然,系统支持的优先级数量越多,系统消耗的资源也就越多,因此,我们应该合理的将宏configMAX_PRIORITIES定义为满足需要的最小值。

FreeRTOS的任务优先级高低与其对应的优先级数值是成正比的,也就是优先级越高,优先级的数值越大,优先级数值为0的任务是优先级最低的任务,configMAX_PRIORITIES-1是优先级最高的任务。

这里和STM32中断的优先级是刚好相反的,中断是优先级越高,对应的优先级数值越小。

FreeRTOS任务调度方式

FreeRTOS一共支持三种调度方式,分别为抢占式调度,时间片轮转调度和协程式调度。其中协程式调度是用于一些资源非常少的设备上的,但是现在已经很少用到了,虽然FreeRTOS源码保留了协程式的代码,但是官方已经不更新该部分代码了。

抢占式调度

抢占式调度主要是针对优先级不同的任务,每个任务都有一个优先级,优先级高的任务可以抢占低优先级的任务,只有当高优先级的任务主动放弃CPU(阻塞,挂起),低优先级任务才可以运行。

时间片轮转调度

时间片调度是主要针对优先级相同的任务,当多个任务的优先级相同时,任务调度器会在每次系统时钟节拍到的时候切换任务,也就是说CPU轮流运行优先级相同的任务,每个任务运行的时间就是一个系统节拍。

FreeRTOS任务控制块

FreeRTOS中的每个已创建的任务都包含一个任务控制块,任务控制块是一个结构体变量,FreeRTOS用任务控制块结构体存储任务的属性(名字,栈,入口函数)等,可以理解为任务控制块就是任务的“身份证”。

任务控制块的代码如下所示:

typedef struct tskTaskControlBlock       /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
    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 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
     /* 指向任务栈栈底的指针*/        
    StackType_t * pxEndOfStack; /*< Points to the highest valid address for the stack. */
    #endif

    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
    /*记录任务独自的临界区嵌套次数*/
        UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
    #endif

    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxTCBNumber;  /*< 由系统分配,每创建一个任务,值增加1,分配任务的值都不同,用于调试 */
        UBaseType_t uxTaskNumber; /*< 由函数vTaskSetTaskNumber()设置,用于调试*/
    #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 )
    /*记录任务处于运行态的时间*/
        configRUN_TIME_COUNTER_TYPE ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
    #endif

    #if ( configUSE_NEWLIB_REENTRANT == 1 )

        /* 用于Newlib */
        struct  _reent xNewLib_reent;
    #endif

    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
    /*任务通知值*/
        volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    /*任务通知状态*/  
      volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    #endif

    /* See the comments in FreeRTOS.h with the definition of
     * tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
    #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */
        uint8_t ucStaticallyAllocated;                     /*< 任务静态创建标志 */
    #endif

    #if ( INCLUDE_xTaskAbortDelay == 1 )
    /*任务被中断延时标志*/
        uint8_t ucDelayAborted;
    #endif

    #if ( configUSE_POSIX_ERRNO == 1 )
    /*用于POSIX*/
        int iTaskErrno;
    #endif
} tskTCB;

从上面的代码中可以看到,FreeRTOS的任务控制块结构体中包含了很多成员变量,但是大部分成员变量都是可以通过FreeRTOSConfig.h配置文件中的配置文件中的配置项宏定义进行裁剪的。

FreeRTOS任务栈

不论是裸机编程还是RTOS编程,栈空间的使用都非常重要。函数中的局部变量、函数调用关系时的现场保护和函数的返回地址等都是存放在栈空间的。

对于FreeRTOS,当使用静态方式创建任务时,需要用户自行分配一块内存,作为任务的栈空间,静态创建任务的函数原型如下:

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
                                    const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                                    const uint32_t ulStackDepth,
                                    void * const pvParameters,
                                    UBaseType_t uxPriority,
                                    StackType_t * const puxStackBuffer,
                                    StaticTask_t * const pxTaskBuffer )
    

其中函数参数ulStackDepth,为任务栈的大小,参数puxStackBuffer,为任务栈的内存空间。FreeRTOS会根据这两个参数,为任务设置好任务的栈。

而使用动态方式创建任务时,系统则会自动从系统堆中分配一块内存,作为任务的栈空间,动态方式创建任务的函数原型如下:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                            const char * const pcName, 
                            const configSTACK_DEPTH_TYPE usStackDepth,
                            void * const pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t * const pxCreatedTask )

其中函数的参数usStackDepth,即为任务栈的大小,FreeRTOS会根据任务栈的大小,从FreeRTOS的系统堆中分配一块内存,作为任务的栈空间。

值得说明的是,参数usStackDepth表示的任务栈大小,实际上是以字为单位,并不是以字节为单位。对于静态方式创建任务的函数xTaskCreateStatic(),参数usStackDepth表示的是作为任务栈且数据类型为StackType_t的数组puxStackBuffer中元素的个数,而对于动态创建的任务的函数xTaskCreate(),参数usStackDepth将被用于申请作为任务栈的内存空间,其内存申请相关代码,如下所示:

pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) )

可以看出,静态和动态创建任务时,任务栈的大小都与数据类型StackType_t有关,对于STM32而言,该数据类型定义为:

#define portSTACK_TYPE uint32_t

typedef portSTACK_TYPE StackType_t

因此,不论是使用动态还是静态方式创建任务,任务的任务栈大小都应该为ulStackDepth*sizeof(uint32_t)字节,即ulStackDepth字。

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

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

相关文章

Linux内核共享内存使用常见陷阱与分析

所谓共享内存就是使得多个进程可以访问同一块内存空间&#xff0c;是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制&#xff0c;如 信号量结合使用&#xff0c;来达到进程间的同步及互斥。其他进程能把同一段共享内存段“连接到”他们自己的…

【华为OD机试模拟题】用 C++ 实现 - 最小叶子节点(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 获得完美走位(2023.Q1) 文章目录 最近更新的博客使用说明最小叶子节点题目输入输出示例一输入输出示例二输入输出Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华…

oracle数据库使用JDBC导入ClickHouse数据

一、背景 需求要把oracle中的数据导入到clickhouse中&#xff0c;使用clickhouse的jdbc表引擎&#xff0c;把oracle11g的数据导入到clickhouse中。 二、方案 通过clickhouse-jdbc-bridge&#xff1a;是clickhouse提供的一个jdbc组件&#xff0c;用于通过JDBC的方式远程访问其他…

[面试直通版]网络协议面试核心之IP,TCP,UDP-TCP与UDP协议的区别

点击->计算机网络复习的文章集<-点击 目录 前言 UDP TCP 区别小总结 前言 TCP和UDP都是在传输层&#xff0c;在程序之间传输数据传输层OSI模型&#xff1a;第四层TCP/IP模型&#xff1a;第三层关键协议&#xff1a;TCP协议、UDP协议传输层属于主机间不同进程的通信传…

Unity Lighting -- 光照入门

识别光源 首先来看一张图&#xff0c;看看我们能在这个场景中找到几个光源。 相信大家能够很容易看出来&#xff0c;四盏路灯模型带有四个光源&#xff0c;右边的红绿蓝三个发光的灯也是光源。场景中还有一个光源&#xff0c;这个光源来自天空&#xff0c;让场景看起来有点日落…

尚医通(二十四)就医提醒和预约统计

目录一、就医提醒1、搭建定时任务模块二、后台管理系统-预约统计功能1、开发每天预约数据接口2、封装远程调用接口4、整合统计功能前端一、就医提醒 我们通过定时任务&#xff0c;每天8点执行&#xff0c;提醒就诊 1、搭建定时任务模块 &#xff08;1&#xff09;添加依赖 &l…

【MySQL】调控 字符集

一、 MySQL 启动选项 & 系统变量 启动选项 是在程序启动时我们程序员传递的一些参数&#xff0c;而 系统变量 是影响服务器程序运行行为的变量 1.1 启动项 MySQL 客户端设置项包括&#xff1a; 允许连入的客户端数量 、 客户端与服务器的通信方式 、 表的默认存储引擎 、…

zookeeper入门到精通

文章目录一、zookeeper入门1. 概述zookeeper的工作机制2.特点3.数据结构4.应用场景4.1.统一命名服务4.2.统一配置管理4.3.统一集群管理4.4.服务器节点动态上下线4.5.软负载均衡5.下载地址二、zookeeper安装1.本地模式安装2.配置参数解读三、zookeeper集群操作1.集群操作1.1 集群…

C++学习笔记-继承

继承的基本概念 类与类之间的关系 has-A&#xff0c;包含关系&#xff0c;用以描述一个类由多个“部件类”构成&#xff0c;实现has-A关系用类的成员属性表示&#xff0c;即一个类的成员属性是另一个已经定义好的类。 use-A&#xff0c;一个类使用另一个类&#xff0c;通过类…

前端面试题整理6-react

React 中 keys 的作用是什么&#xff1f; Keys是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识 在开发过程中&#xff0c;我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中React 会借助元素的 Key 值来判断该元素是新近创建的还…

第五章 Opencv图像的几何变换

目录1.缩放图像1-1.resize()方法2.翻转图像2-1.flip()方法3.仿射变换图像3-1.warpAffine()方法3-2.平移3-3.旋转3-4.倾斜4.透视图像4-1.warpPerspective()方法几何变换是指改变图像的几何结构&#xff0c;例如大小、角度和形状等&#xff0c;从而使图像呈现出缩放、翻转、仿射和…

KUKA机器人外部自动运行模式的相关信号配置

KUKA机器人外部自动运行模式的相关信号配置 通过例如PLC这样的控制器来进行外部自动运行控制时,运行接口向机器人控制系统发出机器人进程的相关信号(例如运行许可、故障确认、程序启动等),机器人向上级控制系统发送有关运行状态和故障状态的信息。 必需的配置:  配置CEL…

Oracle-01-简介篇

&#x1f3c6;一、Oracle的历史和发展 Oracle公司成立于1977年&#xff0c;由拉里埃里森&#xff08;Larry Ellison&#xff09;、鲍勃明特&#xff08;Bob Miner&#xff09;和埃德奥茨&#xff08;Ed Oates&#xff09;共同创立。起初&#xff0c;公司的主要业务是开发和销售…

docker基础用法及镜像和容器的常用命令大全

1.docker 体系架构 Docker 采用了 C / S 架构&#xff0c;包括客户端和服务端。Docker 守护进程作为服务端接受来自客户端的请求&#xff0c;并处理这些请求&#xff08;创建、运行、分发容器&#xff09;。客户端和服务端既可以运行在一个机器上&#xff0c;也可通过 socket 或…

数字IC手撕代码--乐鑫科技(次小值与次小值出现的次数)

前言&#xff1a;本专栏旨在记录高频笔面试手撕代码题&#xff0c;以备数字前端秋招&#xff0c;本专栏所有文章提供原理分析、代码及波形&#xff0c;所有代码均经过本人验证。目录如下&#xff1a;1.数字IC手撕代码-分频器&#xff08;任意偶数分频&#xff09;2.数字IC手撕代…

九龙证券|阿里+鸿蒙+人工智能+元宇宙概念热度爆棚,“会说话的猫”亮了!

近一周组织调研个股数量有240多只&#xff0c;汤姆猫成为调研组织数量最多的股票。 证券时报数据宝统计&#xff0c;近一周组织调研公司数量有240多家。从调研组织类型来看&#xff0c;证券公司调研相对最广泛&#xff0c;调研230多家公司。 “会说话的猫”亮了 汤姆猫成为近…

倒计时3天:现实与虚拟交织,元宇宙警察将如何执法?

在元宇宙、Web3高速发展的时代&#xff0c;欧科云链以科技助警&#xff0c;帮助公安等机构实现对新型犯罪的监管与侦破。 ——摘要元宇宙作为应用场景和生活方式的未来&#xff0c;拥有着巨大的发展潜力。伴随5G网络、云计算、区块链等技术迅速发展&#xff0c;虚拟现实、人机交…

java面试题-JVM类加载机制

类加载的生命周期&#xff1f;1. 加载阶段&#xff08;Loading&#xff09;在Java程序中&#xff0c;当需要使用某个类时&#xff0c;JVM会使用类加载器来查找并加载该类文件。类加载器会首先从文件系统或网络中查找相应的 .class 文件&#xff0c;读取类的二进制数据&#xff…

【JDK8新特性之方法引用-案例实操】

一.JDK8新特性之方法引用-案例实操 之前我们学习了Stream流以及Lambda表达式相关的内容&#xff0c;如果想看的同学可以看一下之前的文章&#xff0c;接下来我们就来学习让Lambda表达式更加简洁的方法引用。 二. 什么是方法引用&#xff1f;为什么要使用方法引用&#xff1f; …

Leetcode Solutions - Part 1

回溯: 字符串的排列 回溯&#xff1a;78. 子集 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1&#xff1a; 输入&#xff1a;nums …