FreeRTOS任务基础知识
单任务和多任务系统
单任务系统
单任务系统的编程方式,即裸机的编程方式,这种编程方式的框架一般都是在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 运行时,任务的状态一定是这四种状态中的一种,下面就分别来介绍一下这四种任务状态。
运行态
如果一个任务得到CPU的使用权,即任务被实际执行时,那么这个任务处于运行态。如果 运行 RTOS 的 MCU 只有一个处理器核心,那么在任务时刻,都只能有一个任务处于运行态 运行
就绪态
如果一个任务已经能够被执行(不处于阻塞态和挂起态),但当前还未被执行(具有相同优 先级或更高优先级的任务正持有 CPU 使用权),那么这个任务就处于就绪态。
阻塞态
如果一个任务因延时一段时间或等待外部事件发生,那么这个任务就处理阻塞态。例如任 务调用了函数 vTaskDelay(),进行一段时间的延时,那么在延时超时之前,这个任务就处理阻塞 态。任务也可以处于阻塞态以等待队列、信号量、事件组、通知或信号量等外部事件。通常情 况下,处于阻塞态的任务都有一个阻塞的超时时间,在任务阻塞达到或超过这个超时时间后, 即使任务等待的外部事件还没有发生,任务的阻塞态也会被解除。
要注意的是,处于阻塞态的任务是无法被运行的。
在日常生活的例子中,母亲在电脑前跟同事沟通时,如果同事一直没回复,那么母亲的工作就被卡住了、被堵住了、处于阻塞状态(Blocked)。重点在于:母亲在等待。
在实际产品中,我们不会让一个任务一直运行,而是使用"事件驱动"的方法让它运行:
任务要等待某个事件,事件发生后它才能运行
在等待事件过程中,它不消耗CPU资源
在等待事件的过程中,这个任务就处于阻塞状态(Blocked)
在阻塞态的任务可以等待两种类型的事件:
时间相关的事件
可以等待一段时间:我等2分钟
也可以一直等待,直到某个绝对时间:我等到下午3点
同步事件:这事件由别的任务,或者是中断程序产生
例子1:任务A等待任务B给他发送数据
例子2:任务A等待用户按下按键
同步事件的来源有很多
队列(queue)
二进制信号量(binary semaphores)
计数信号量(counting semaphores)
互斥量(mutexes)
递归互斥量(recursive mutexes)
事件组(event groups)
任务通知(task notifcations)
挂起态
任务一般通过函数 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相关设置 */#endifListItem_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_ttypedef portSTACK_TYPE StackType_t因此,不论是使用动态还是静态方式创建任务,任务的任务栈大小都应该为ulStackDepth*sizeof(uint32_t)字节,即ulStackDepth字。
相关文章:
FreeRTOS任务基础知识
单任务和多任务系统单任务系统单任务系统的编程方式,即裸机的编程方式,这种编程方式的框架一般都是在main()函数中使用一个大循环,在循环中顺序的执行相应的函数以处理相应的事务,这个大循环的部分可以视为…...
JDBC-API详解、SQL注入演示、连接池
文章目录JDBC1,JDBC概述1.1 JDBC概念1.2 JDBC本质1.3 JDBC好处2,JDBC快速入门2.1 编写代码步骤2.2 具体操作3,JDBC API详解3.1 DriverManager3.2 Connection (事务归我管)3.2.1 获取执行对象3.2.2 事务管理3.3 Stateme…...
C 学习笔记 —— 动态分配内存(malloc)
文章目录分配内存malloccallocrealloc创建数组方式free的重要性举例常见动态分配内存错误忘记检查所请求的内存对NULL指针进行解引用对分配的内存越界访问释放一块内存后,继续使用释放一块内存的一部分是不允许的内存泄漏分配内存 当一个数组声明时,需要…...
RK3588通用布线设计指南
(1)走线长度应包含过孔和封装。(2)由于表贴器件的焊盘会导致阻抗降低,为减小阻抗突变的影响,建议在表贴焊盘的正下方按焊盘大小挖去一层参考层。常用的表贴器件有:电容、 ESD、共模抑制电感、连…...
ChatGPT也懂如何设计开发板!?
到底应该如何设计一款开发板?我们问了一下最近风很大的ChatGPT,得出了这样的回答: 或者这样的回答: 显而易见,RK3568开发板是一款功能丰富,性能优异,易于开发的高性能开发板,适用于各…...
去了字节跳动,才知道年薪40W的测试居然有这么多?
今年大环境不好,内卷的厉害,薪资待遇好的工作机会更是难得。最近脉脉职言区有一条讨论火了: 哪家互联网公司薪资最‘厉害’? 下面的评论多为字节跳动,还炸出了很多年薪40W的测试工程师 我只想问一句,现在的…...
2023前端面试知识点总结
原型 JavaScript中的对象都有一个特殊的 prototype 内置属性,其实就是对其他对象的引用 几乎所有的对象在创建时 prototype 属性都会被赋予一个非空的值,我们可以把这个属性当作一个备用的仓库 当试图引用对象的属性时会出发get操作,第一步时…...
FL StudioV21电脑版水果编曲音乐编辑软件
这是一款功能十分丰富和强大的音乐编辑软件,能够帮助用户进行编曲、剪辑、录音、混音等操作,让用户能够全面地调整音频。FL水果最新版是一款专业级别的音乐编曲软件,集合更多的编曲功能为一身,可以进行录音、编辑、制作、混音、调…...
【数据结构初阶】实现顺序表的简单功能
目录一.线性表和顺序表的概念二.顺序表的实现1.动态顺序表的创建2.初始化顺序表3.打印顺序表4.销毁顺序表5.检查容量6.头插 尾插7.头删 尾删三.使用下标插入删除1.删除指定位置2.向指定位置插入指定数一.线性表和顺序表的概念 线性表是n个具有相同特性的数据元素的有限序列。 线…...
华为OD机试题,用 Java 解【停车场车辆统计】问题
最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…...
Linux中使用Docker部署Mysql数据库
前言 和朋友一起搞一个项目,分了一下工作,但是mysql迟迟安装不上,程序都在一个环境里确实容易出现很多问题,浪费时间和经历在这些配置上,好在有docker了,就在docker里搭建一个Mysql数据库使用吧࿰…...
JPDA(远程调试)使用步骤
JPDA(Java Plateform Debugger Architecture) 更改启动脚本 vi catalina.sh 127行 CATALINA_OPTS “-Xdebug -Xrunjdwp:transportdt_socket,servery,suspendn,address5888” 指定端口,默认是8000 377行以jpda方式启动tomcat ./catalina.sh jpda start tomcat以这个…...
磷脂-聚乙二醇-丙烯酸酯;DSPE-PEG-AC试剂说明;DSPE-PEG-Acrylate科研用
中文名称:磷脂-聚乙二醇-丙烯酸酯 丙烯酸酯-聚乙二醇-磷脂 简称:DSPE-PEG-AC;DSPE-PEG-Acrylate 溶剂:溶于部分常规有机溶剂 PEG分子量:1000;2000;3400;5000等等 注意事项:避免…...
C++入门:异常处理
异常是程序在执行期间产生的问题。C 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。异常提供了一种转移程序控制权的方式。C 异常处理涉及到三个关键字:try、catch、throw。throw: 当问题出现时,程序会抛出一个异常。这是通过使…...
C/C++每日一练(20230225)
目录 1. 工龄问题求解 ★ 2. 字符图形输出 ★★ 3. LRU 缓存机制 ★★★ 1. 工龄问题求解 给定公司N名员工的工龄,要求按工龄增序输出每个工龄段有多少员工。输入首先给出正整数N,即员工总人数; 随后给出N个整数,即每个员工…...
nyist最终淘汰赛第一场
我出的题喜欢吗 我要水题解所以每一篇题解都分一个博客 A 题解链接: Atcoder abc257 E_霾まる的博客-CSDN博客 构造贪心题 在本次淘汰赛中较难 B 题解链接: atcoder abc217 D_霾まる的博客-CSDN博客 STL二分题, 当然你可以数组二分, 相对麻烦一点 在本次淘汰赛中较简单…...
《零成本实现Web自动化测试--基于Selenium》 Selenium-RC
一. 简介 Selenium-RC可以适应更复杂的自动化测试需求,而不仅仅是简单的浏览器操作和线性执行。Selenium-RC能够充分利用编程语言来构建更复杂的自动化测试案例,例如读写文件、查询数据库和E-mail邮寄测试报告。 当测试案例遇到selenium-IDE不支持的逻辑…...
来阿里我的收获是什么?(未完待续)
不知不觉来阿里两年多了,每天都过的很充实,感觉这段时间没有学到什么东西,但是又觉得收获满满,恰好又好久没有动笔写过些什么了,所以有了这个动笔念头。 之前技术方面记录的比较多,这次就记录一些比较磨心的…...
golang net/http库的学习
net/http 是 Golang 标准库中用来构建 HTTP 服务器和客户端的包,它提供了很多功能强大的方法和接口,可以让您方便地构建和处理 HTTP 请求和响应。下面是一些学习 net/http 的建议: 了解 HTTP 协议。在学习 net/http 之前,建议先了…...
Spring(AOP)
目录 1. 预备知识-动态代理 1.1 什么是动态代理1.2 动态代理的优势1.3 基于JDK动态代理实现2. AOP 2.1 基本概念2.2 AOP带来的好处3. Spring AOP 3.1 前置通知3.2 后置通知3.3 环绕通知3.4 异常通知3.5 适配器 1. 预备知识-动态代理 1.1 什么是动态代理 动态代理利用Java的反…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
