FreeRTOS 解析

目录

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 中。

typedef struct tskTaskControlBlock
{
    ... ...
    /* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */
    volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    ... ...
} 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() 或者队列/信号量的阻塞操作。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
FreeRTOS是一个开源的实时操作系统内核,适用于嵌入式系统。它提供了线程调度、中断处理、内存管理等核心功能,帮助开发者实现多任务并发、响应时间敏感等要求。 FreeRTOS内核采用基于优先级的抢占式调度算法,通过任务的优先级来确定任务的执行次序。可以通过创建任务来实现多任务并发执行,每个任务都有自己的任务控制块(TCB),用于跟踪任务的状态和信息。在任务创建之后,内核会调用任务的入口函数,任务根据需要进行各种操作,包括延时、等待、同步等。 FreeRTOS使用事件标志组进行任务间的通信和同步。任务可以等待特定的事件标志,当事件发生时,任务会被唤醒继续执行。此外,也可以使用消息队列、信号量、互斥量等同步机制进行任务间的通信和资源共享。 FreeRTOS还提供了软件定时器功能,可以创建周期性的软件定时器,当时间到达时,定时器会触发回调函数的执行。定时器可以用于周期性任务,定时延时等场景。 在中断处理方面,FreeRTOS可以与底层硬件中断机制进行无缝集成。可以将中断处理函数作为独立任务运行,或者将其放入中断服务函数中,通过发送消息或触发事件标志来唤醒相应的任务。 总的来说,FreeRTOS内核提供了丰富的功能和机制,可以帮助嵌入式系统实现多任务并发、实时性、资源共享等需求。通过灵活使用任务、事件标志组、同步机制等功能,可以编写出高效可靠的嵌入式应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值