FreeRTOS 任务与协程(Co-routines)
中断服务函数是一种需要特别注意的上下文环境,在这个上下文环境中不能使用任何会挂起或阻塞操作,另外,ISR尽可能保持精简短小,快进快出,一般只标记事件的发生,然后通知任务,让对应任务去执行相关处理。每个任务在自己的上下文中执行,不会碰巧依赖于系统内的其他任务或RTOS调度器本身。在设计的时候对任务的运行时间,逻辑,状态等了如指掌,才能设计出好的系统。每个任务都分配有自己的堆栈。中有可能会导致:从而进
目录
任务
每个任务在自己的上下文中执行,不会碰巧依赖于系统内的其他任务或RTOS调度器本身。在任何时间点,应用程序只能执行一个任务,实时RTOS调度器负责执行那个任务。
每个任务都分配有自己的堆栈。换出任务时,执行上下文被保存到该任务的堆栈中,以便以后再换入时可以准确地恢复其执行上下文。
- 每个任务保留自己的堆栈,可以提高RAM使用率
- 独立stack,使stack使用更可控,从而更高效利用RAM,否则可能要按所有task调用最糟情况分配一个覆盖所有调用路径的超大stack
- 并不是stack越多越省RAM
任务状态
- 运行:任务被执行(任务当前正在使用CPU)
- 准备就绪:因有同等或更高优先级的任务正在运行而等待被执行
- 阻塞:任务正在等待时间或外部事件触发,处于阻塞状态的任务通常有一个超时期,超时后任务将被超时并解除阻塞,解除阻塞后会进入准备就绪或运行状态
- 挂起:与阻塞状态一样,挂起任务不能被选择进入运行状态,但处于挂起状态的任务没有超时,通常通过vTaskSuspend() 和 xTaskResume() 来进入或退出挂起状态
任务调度
FreeRTOS 默认使用固定优先级的抢占式调度策略,对同等优先级的任务执行时间切片轮询调度。
- 固定优先级:调度器不会永久更改任务的优先级,但会因优先级继承而暂时提高任务的优先级;
- 抢占式:调度器始终运行优先级更高且可运行的task(高优先级任务会抢占低优先级任务的CPU)
- configUSE_PREEMPTION: 为0时,抢占关闭
- configUSE_TIME_SLICING:为0时,时间切片轮询调度关闭
但固定优先级的抢占式调度策略在单核(freertos只支持单核)中有可能会导致:从而进入阻塞或挂起状态的高优先级任务将会永久独占CPU,永久性剥夺所有更低优先级任务的任何执行时间。
空闲任务
RTOS 调度器启动时,自动创建空闲任务,以确保始终 存在一个能够运行的任务。它以最低优先级创建, 以确保如果有更高的优先级应用程序任务处于准备就绪状态,则不使用任何 CPU 时间。
- 空闲任务不允许出现阻塞,因为FreeRTOS需要保证系统永远有一个可运行的任务。
- 空闲任务一般负责释放RTOS中已删除任务的内存
- 一个task被删除或运行结束时,在执行删除任务的时候,并不会释放任务的内存空间,只会将任务添加到结束列表中,真正的系统资源回收工作是在空闲任务完成的。
任务设计要点
在设计的时候对任务的运行时间,逻辑,状态等了如指掌,才能设计出好的系统。在设计之初就应该考虑以下几点因素:任务运行的上下文环境,任务的执行时间合理设计。
- 任务避免无阻塞死循环,导致一直占用CPU,更低优先级任务无法执行。
中断服务函数(ISR)
中断服务函数是一种需要特别注意的上下文环境,在这个上下文环境中不能使用任何会挂起或阻塞操作,另外,ISR尽可能保持精简短小,快进快出,一般只标记事件的发生,然后通知任务,让对应任务去执行相关处理。
- ISR优先级高于任何优先级任务,如果处理时间过长,将会导致整个系统任务无法正常运行。所以,设计的时候必须要考虑中断的频率,中断的处理时间
- 比如,若中断频率特别高(比如error 中断), 有可能ISR会一直占用CPU,导致中断的下半部task无法执行(真正响应中断的具体任务);
code示例
/* 任务原型 */
void vATaskFunction( void *pvParameters )
{
for( ;; )
{
-- Task application code here. --
}
/* Tasks must not attempt to return from their implementing
function or otherwise exit. In newer FreeRTOS port
attempting to do so will result in an configASSERT() being
called if it is defined. If it is necessary for a task to
exit then have the task call vTaskDelete( NULL ) to ensure
its exit is clean. */
vTaskDelete( NULL );
}
/* 任务原型也可以以下方式定义 */
portTASK_FUNCTION( vATaskFunction, pvParameters )
{
for( ;; )
{
-- Task application code here. --
}
}
/* 任务创建:*/
/* pvTaskCode: 指向任务入口函数的指针, 如vATaskFunction */
/* pcName: 任务的描述性名称 */
/* uxStackDepth: 要分配用作任务堆栈的字节 */
/* pvParameters: 作为参数传递给所创建任务的值 */
/* uxPriority: 任务优先级 */
/* pxCreatedTask: 创建的task句柄,该值也可设置为NULL */
/* 任务创建成功,返回pdPASS, 否则返回 err*/
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE uxStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask
);
协程
协程是为了在非常小型的设备上使用而实现的,但现在很少在实际情况中使用,这里不多做介绍。协程在概念上与任务类似,但存在以下差异:
- 堆栈使用:应用程序中的所有协程共用同一个堆栈,这样所需的RAM会大大减少,但RAM减少是以一些严格限制协程构造为代价的(对API调用位置有限制,且只能在协程间进行协作操作)。
- 当协程阻塞时,协程的堆栈无法维持,这意味着在堆栈中分配的变量很容易丢失其值。
- 共享堆栈的另一个限制:可以从协程函数本身调用可能导致协程阻塞的API,但不能从协程调用的函数中调用。
- 默认协程实现不允许在switch语句中进行阻塞实现。
- 调度和优先级:协程间使用优先级协同调度,但可以包含在使用抢占式任务的应用程序中。
- 协程通过 vCoRoutineSchedule()来调度协程,其调用的最佳位置为空闲任务钩子
- 由于空闲任务优先级最低,所以,协程与task混用的情况下,协程一定会被task抢占;
- 协程通过 vCoRoutineSchedule()来调度协程,其调用的最佳位置为空闲任务钩子
- 宏实现:协程是通过一组宏实现的。
参考
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)