FreeRTOS 基础系列文章
基本对象
FreeRTOS——任务
FreeRTOS——队列
FreeRTOS——信号量
FreeRTOS——互斥量
FreeRTOS——任务通知
FreeRTOS——流和消息缓冲区
FreeRTOS——软件定时器
FreeRTOS——事件组
内存管理
FreeRTOS——静态与动态内存分配
FreeRTOS——堆内存管理
FreeRTOS——栈溢出保护
代码组织
FreeRTOS——源代码组织
FreeRTOS——创建新的项目
FreeRTOS——配置文件
任务特征
简单来说:使用RTOS的实时应用程序可以被构造成一组独立的任务。每个任务都在它自己的上下文中执行,与系统或RTOS调度程序本身中的其他任务没有暗合的依赖关系。在任一时间点应用程序中只有一个任务可以执行,实时RTOS调度器负责决定该执行哪个任务。因此,RTOS调度器可以在应用程序执行时反复启动和停止每个任务(切换每个任务的进出)。由于一个任务不知道RTOS调度器的活动,所以实时RTOS调度器有责任确保当一个任务被切换进来时的处理器上下文(寄存器值,栈内容等)与同样的任务被切换出去时完全一样。为了实现这一点,每个任务都有自己的堆栈。 当任务被交换出去时,执行上下文被保存到该任务的堆栈中,因此当同一任务稍后被切换回来时,它也可以被精确地恢复。
任务状态
任务可以处于以下状态之一:
-
Running(运行)
当一个任务实际执行时,它被称为处于运行状态。它目前正在使用处理器。如果运行 RTOS 的处理器只有一个内核,那么在任何给定时间只能有一个处于运行状态的任务。 -
Ready(就绪)
就绪任务是那些能够执行(它们不处于阻塞或挂起状态)但当前未执行的任务,因为具有相同或更高优先级的不同任务已经处于运行状态。 -
Blocked(阻塞)
如果任务当前正在等待时间或外部事件,则称该任务处于阻塞状态。如果一个任务调用vTaskDelay(),它将阻塞(被置于阻塞状态),直到延时时间过期 —— 一个时间事件。任务也可以阻塞等待队列、信号量、事件组、通知或信号量事件。处于阻塞状态的任务通常有一个“超时”时间,在此时间之后任务将超时,并且被解除阻塞,即使任务正在等待的事件没有发生。处于阻塞状态的任务不占用任何处理时间,不能被选择进入运行状态。
-
Suspended(挂起)
和处于阻塞状态的任务一样,处于挂起状态的任务不能被选择进入运行状态,但处于挂起状态的任务没有超时。相反,只有在分别通过vTaskSuspend()
和xTaskResume()
API 调用,明确命令任务进入或退出挂起状态时,任务才进入或退出挂起状态。
任务优先级
每个任务都分配了从0
到( configMAX_PRIORITIES - 1 )
的优先级,其中configMAX_PRIORITIES
在FreeRTOSConfig.h
中定义。
如果正在使用的端口实现了使用“计数前导零”类型指令(用于在单个指令中选择任务)的端口优化任务选择机制,并且在FreeRTOSConfig.h
中将configUSE_PORT_OPTIMISED_TASK_SELECTION
设置为1
,则configMAX_PRIORITIES
不能高于32
。在所有其他情况下,configMAX_PRIORITIES
可以在合理范围内取任何值 —— 但出于 RAM 使用效率的原因,应将其保持在实际需要的最小值。
低优先级数字表示低优先级任务。空闲任务优先级为零(tskIDLE_PRIORITY
)。
FreeRTOS调度器确保始终为处于就绪或运行状态的高优先级任务分配处理器(CPU)时间,优先于处于就绪状态的低优先级任务。换句话说,置于运行状态的任务始终是能够运行的最高优先级任务。
任意数量的任务可以共享相同的优先级。如果configUSE_TIME_SLICING
没有定义,或者configUSE_TIME_SLICING
设置为1
,那么相同优先级的就绪状态任务将使用时间切片轮询调度方案共享可用的处理时间。
任务实现
一个任务应该有以下结构:
void vATaskFunction( void *pvParameters )
{
for( ;; )
{
-- 这里是任务应用程序代码。 --
}
/* 任务不能试图从其实现函数返回或其他方式退出。在较新的 FreeRTOS
端口中尝试这样做将导致configASSERT()被调,如果它已定义。如果任务
需要退出,那么让任务调用vTaskDelete(NULL)以确保它的退出是干净的。
*/
vTaskDelete( NULL );
}
类型TaskFunction_t
被定义为一个返回void
的函数,并接受一个void
指针作为其唯一形参。所有实现任务的函数都应该是这种类型。该参数可用于向任务传递任何类型的信息。
任务函数永远不应该返回,所以通常作为一个死循环实现。
通过调用xTaskCreate()
或xTaskCreateStatic()
来创建任务,通过调用vTaskDelete()
来删除任务。
任务启动
一个典型的main()函数有如下结构:
int main( void )
{
/* 设置微控制器硬件。 */
prvSetupHardware();
/* 创建应用程序任务,例如: */
vCreateFlashTasks();
vCreatePollQTasks();
vCreateComTestTasks();
等等。
/* 创建定义在main.c本身内的任何任务 */
xTaskCreate( vCheckTask, "check", STACK_SIZE, NULL, TASK_PRIORITY, NULL );
等等。
/* 启动RTOS调度程序,此函数不会返回,因为它将
导致执行上下文从main()更改为已创建的任务之一
*/
vTaskStartScheduler();
/* 永远不会执行到这里! */
return 0;
}