FreeRTOS 解析
xidianjunnan
分类专栏: 操作系统 文章标签: mcu 物联网 iot
————————————————
版权声明:本文为CSDN博主「xidianjunnan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xidianjunnan/article/details/127898808
目录
Inter-task Communication and Synchronization
Event Bits (or flags) and Event Groups
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() 或者队列/信号量的阻塞操作。
标签:Task,优先级,FreeRTOS,调度,信号量,任务,线程,解析 From: https://www.cnblogs.com/zxdplay/p/17810850.html