首页 > 其他分享 >FreeRTOS 解析

FreeRTOS 解析

时间:2023-11-05 18:23:17浏览次数:42  
标签:Task 优先级 FreeRTOS 调度 信号量 任务 线程 解析

FreeRTOS 解析

xidianjunnan

 

分类专栏: 操作系统 文章标签: mcu 物联网 iot
————————————————
版权声明:本文为CSDN博主「xidianjunnan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xidianjunnan/article/details/127898808

 

目录

Task

Task State

Task Priority

Idle Task

Run Time Statistics

Task Scheduling

Single-core 单核处理器

AMP 非对称多核处理器

SMP 对称多核处理器

Context Switch

Inter-task Communication and Synchronization

Queue 队列

Binary Semaphore 二值信号量

Counting Semaphore 多值信号量

Mutex 互斥锁

Recursive Mutex 循环互斥锁

Task Notifications 任务通知

Stream & Message Buffers

Event Bits (or flags) and Event Groups

Heap_5

Stack

Software Timers


 

FreeRTOS 是免费开源的轻量型实时操作系统,简单易学易用,广泛应用于各种嵌入式平台。

以下简要记录重读官网文档的心得,以备后续系统整理。

task = 任务 = 线程

one port = one specific CPU arch + one specific compiler

 

Task

Task State

Running:一个 CPU 核任意时刻只有一个运行态任务。

Blocked:不被调度器调度,不占用 CPU 时间,等待超时或者外部事件唤醒,例如 vTaskDelay()、等待 queue, semaphore, event group, notification or semaphore event。(注:An event group is a set of binary flags (or bits), to each of which the application writer can assign a meaning.)

Suspended:挂起态,不被调度器调度,不占用 CPU 时间,没有超时和事件唤醒,只能唯一通过执行 vTaskSuspend(),xTaskResume() 进出 Suspended state。任务A 可以挂起自己,也可以被别的运行态任务B 挂起(挂起前,A 处于就绪态、运行态或阻塞态);A 被挂起后,可以被运行态B 或中断函数再恢复到就绪态。挂起调用 vTaskSuspend(),退出调用 xTaskResume()。

状态机:

Task Priority

0 to ( configMAX_PRIORITIES - 1 )

数值越小,优先级越低。

idle 线程优先级 = tskIDLE_PRIORITY = 0

Idle Task

Idle 线程在 OS 调度器开始的时候自动被创建,优先级最低为 0。主要功能是在没有其他高优先级任务运行时,负责回收之前 deleted task 的栈等内存资源。除此之外,还可以执行用户自定义的其他功能,比如 power saving。

configUSE_IDLE_HOOK = 1 时,Idle 线程会回调用户自定义功能,用户需提供回调函数如下,注意回调函数中不能阻塞或死循环不返回。

void vApplicationIdleHook( void );
 

Run Time Statistics

函数 vTaskGetRunTimeStats() 可以统计任务运行花费的 CPU 绝对时间及百分比。前提是使能一个比系统 tick 精度高(越高统计越精确)的 timer 用于统计时间。

 

Task Scheduling

Single-core 单核处理器

固定优先级抢占、相同优先级时间片轮循调度算法。

“固定优先级” 指调度器不会永久改变一个任务的优先级,只在互斥锁优先级继承的时候临时提高低优先级锁持有者任务的优先级。

“抢占” 指高优先级任务可以在任务切换点(tick 中断)抢占低优先级任务;或者低优先级任务在它的时间片内被中断打断,中断之后调度器重新调度高优先级任务,于是低优先级任务在它的时间片内也被高优先级抢占了。

“轮循” 指具有相同优先级的多个任务,按时间片 one by one 轮流进入运行态。

AMP 非对称多核处理器

每个核独立运行一个调度器,调度算法同 single-core。

每个核的架构可以不同,需要一些共享的内存用于多核之前消息传递。

SMP 对称多核处理器

多个核共同运行一个调度器,每个核的架构必须相同。

调度算法同 single-core,不过每个核都可以有一个运行态任务,所以总的运行态任务个数可以跟核的个数一样多!也就意味着,优先级高但不相同的多个任务可以同时运行,而不是像单核那样只最高优先级的一个任务在运行!

Context Switch

说完任务和调度器,来看下任务切换时的上下文切换。当发生中断或者任务主动 yield 时会发生任务上下文切换。所谓上下文 context,就是任务在运行时硬件架构中各寄存器的值;上下文切换,就是将任务 A 的 context 保存到其栈中,调度器选择新任务 B,并将 B 的 context 从其栈中恢复到对应寄存器中,从而完成从 A 到 B 的切换。

1. 任务 A 运行中

 2. RTOS tick 中断发生,首先将 PC 压栈到 A 栈

 3. 中断 ISR

 3.1 portSAVE_CONTEXT() 将 A 的 context 压栈,更新并保存栈指针到 TCB_A 中

 3.2 vTaskIncrementTick() Tick 中断将内核 tick 数++,并检查被 delay() 的任务是否超时了,如果是,任务就变成可运行态。这里假设任务 B 变成可运行态并且优先级高于任务 A。

3.3 vTaskSwitchContext() 查看是否需要上下文切换,本例中任务 B 优先级高于任务 A,所以需要切换到任务 B,将 B 栈指针从 TCB_B 中恢复到系统 SP 寄存器中

 3.4 portRESTORE_CONTEXT() 将 B 的 context 从 B 栈中恢复到相应寄存器(除了 PC)

 3.5 asm volatile ( "reti" ) RETI 指令假设当前栈顶是返回地址即 PC,恢复到 PC 寄存器,至此完成切换到任务 B

 

Inter-task Communication and Synchronization

Queue 队列

用于线程间、线程与中断间通信。

线程安全 FIFO,可以队尾或队首插入。

多个任务读/写阻塞到同一个队列,最高优先级的任务先被 unblock。

Binary Semaphore 二值信号量

多用于同步。类似长度为 1 的队列。

Counting Semaphore 多值信号量

多用于同步。类似长度大于 1 的队列(长度在创建时指定)。

Mutex 互斥锁

多用于互斥操作。不能用在中断函数中,因为中断函数不能阻塞来等待 take mutex,没有 take 也就不会有 give。

优先级继承:低优先级任务A 当前占有锁,高优先级任务B take 时被阻塞,A 的优先级被临时提高到跟 B 一样,减少高优先级B 的阻塞时间。

Recursive Mutex 循环互斥锁

可以被一个任务多次 take,但 take 多少次要相应 give 相同次数,否则不能被其他任务 take。

Task Notifications 任务通知

FreeRTOS V8.2.0 版本引入的新功能,用来 线程<—>线程,中断—>线程通信/同步。
优点:

1. 比前面几种同步方式速度更快(45% faster

2. 更省 ram 空间:不需要单独创建用于同步/通信的信号量/队列等结构体变量,而是将通知信息放在线程控制块 TCB 中。

  1.   typedef struct tskTaskControlBlock
  2.   {
  3.   ... ...
  4.   /* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */
  5.   volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
  6.   volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
  7.   ... ...
  8.   } tskTCB;

3. 可以充当轻量型的二值信号量、计数信号量、event group、mailbox 来使用。

缺点:

1. 只能定向通知到具体某一个任务,而不能像信号量那样所有任务和 ISR 都可以 take。如上所说,通知信息是与某个任务捆绑的,所有只能通过任务的 taskHandle 定向通知给该任务。

2. 当用作队列使用时,接收线程可以阻塞等待,但发送线程不能在队列满时阻塞等待而只能返回错误。

Stream & Message Buffers

FreeRTOS V10.0.0 版本引入的新功能,用来 线程<—>线程,中断—>线程,CPU core <—> core 通信。

注意,一个 Stream/Message Buffer 允许被多个 readers/writers 操作,但不能同时进行,某一时刻只能有一个 reader/writer。

Event Bits (or flags) and Event Groups

用于通信/同步,类似于 Task Notifications,不过后者更轻量级。

 

Heap_5

类似于 Heap_4,提供初次匹配(first fit)的内存块申请算法和碎片合并的内存块回收算法。

不同的是,允许整个 heap 由多个不相邻的内存区域组成。在初次使用 heap 前需调用 vPortDefineHeapRegions() 进行初始化。各个内存区域按地址段从低到高的顺序放在数组里,并将该数组作为参数传给上述初始化函数。

 xPortGetFreeHeapSize() 返回当前空闲 heap 大小。

xPortGetMinimumEverFreeHeapSize() 返回系统空闲 heap 的历史最小值。

vPortGetHeapStats() 返回当前 heap 统计信息:

 

Stack

任务栈在任务被切出时因为要压栈,所以这时很有可能发生栈溢出。

在任务栈地址空间连续的前提下,FreeRTOS 提供两种任务切换时检测被切出任务是否发生栈溢出的方法:

configCHECK_FOR_STACK_OVERFLOW = 1 时,检查栈指针是否在栈空间范围内,如果不在就认为发生了栈溢出。该方法无法判断在任务执行过程中是否发生过栈溢出,因为在切换之前栈指针再回到栈空间内的话还是会当作没发生溢出。相比之下,这种情况可以被下面的方法检测到。

configCHECK_FOR_STACK_OVERFLOW = 2 时,任务创建时任务栈被填充已知的固定值,任务切换时检查栈空间末尾的 16 个字节,如果其中任意一个字节被改变就认为发生了栈溢出。

若 configCHECK_FOR_STACK_OVERFLOW != 0,应用上要定义一个如下回调函数当栈溢出时调用。

void vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName );
 

 

Software Timers

定时器 API 将相关操作 cmd 放入 timer command queue,timer service task 从 queue 中取出 cmd 并执行,该任务的优先级一般很高。所以,定时器回调函数中不能有阻塞操作,比如 vTaskDelay(), vTaskDelayUntil() 或者队列/信号量的阻塞操作。

 

标签:Task,优先级,FreeRTOS,调度,信号量,任务,线程,解析
From: https://www.cnblogs.com/zxdplay/p/17810850.html

相关文章

  • linux系统之五 网卡驱动初始化解析
    一、环境说明内核版本:Linux3.10内核源码地址:https://elixir.bootlin.com/linux/v3.10/source(包含各个版本内核源码,且王页可全局搜索函数)网卡:Intel的igb网卡网卡驱动源码目录:drivers/net/ethernet/intel/igb/二、网卡驱动的加载网卡需要有驱动才能工作,驱动是加载到内核中......
  • is_scoped_enum解析
    限定作用域枚举类型与不限定作用域枚举类型的区别在[1]的条款10中有较为详细的描述,其中一个要点为:限定作用域枚举型仅在枚举类型内可见,他们只能通过强制类型转换以转换到其他的类型,换言之不支持隐式的类型转换,举个例子:enumclassColor:int32_t{//限定作用域枚举black,......
  • FreeRTOS深入教程(任务创建的深入和任务调度机制分析)
    (文章目录)前言本篇文章将带大家深入学习任务的创建和分析任务调度的机制。一、深入理解任务的创建创建任务函数原型:BaseType_txTaskCreate(TaskFunction_tpxTaskCode,constchar*constpcName,/*lint!e971Unqualifiedchartypes......
  • 封装JWT - 生成 jwt 和解析 jwt
    1.ASP.NETCore身份验证和授权验证的功能由Authentication,Authorization中间件提供:app.UseAuthentication(),app.UseAuthorization()。2.控制器类上标注[Authorzie]的控制器则所有的操作方法都会被进行身份验证和授权验证,对于对标注了[Authorize]的控制器类,如果某个方法不想......
  • 深度解析BERT:从理论到Pytorch实战
    本文从BERT的基本概念和架构开始,详细讲解了其预训练和微调机制,并通过Python和PyTorch代码示例展示了如何在实际应用中使用这一模型。我们探讨了BERT的核心特点,包括其强大的注意力机制和与其他Transformer架构的差异。关注TechLead,分享AI全维度知识。作者拥有10+年互联网服务架......
  • 二十六、QT发送http请求并解析返回的json数据
    1.使用的模块和类模块:network类:QNetworkAccessManager、QNetworkRequest、QNetworkReply、QJsonDocument、QJsonObject、QJsonArrayQSslSocket::sslLibraryBuildVersionString();:查看当前使用的ssl版本,访问HTTPS时需要使用、复制libcrypto-1_1-x64.dll和libssl-1_1......
  • 探针探测对sts pod域名解析是否成功的影响
    初始情况apiVersion:v1kind:Servicemetadata:name:nginxspec:ports:-port:80selector:app:nginx---apiVersion:apps/v1kind:StatefulSetmetadata:name:nginxspec:podManagementPolicy:ParallelserviceName:nginxreplicas:2s......
  • 全网最详细4W字Flink全面解析与实践(下)
    本文已收录至GitHub,推荐阅读......
  • C++参数解析参数
    《C/C++参数解析》1.getopt库的使用  getopt用于解析程序命令行输入的参数,可解析短参数和长参数;解析短参数使用getopt()函数,解析长参数可使用getopt_long();1.1getopt()解析短参数  getopt()函数用来解析短参数,例如-a或者-b100这样的参数,不能解析长参数,如--ip198.11.2......
  • 全网最详细4W字Flink全面解析与实践(上)
    本文已收录至GitHub,推荐阅读......