目的
接下来的任务管理的几篇文章的主要目的是:
- FreeRTOS如何为应用程序中的每个任务分配处理时间。
- FreeRTOS如何在给定时间内选择执行哪个任务。
- 每个任务的相对优先级对系统的影响。
- 任务的几种状态。
- 如何实现一个任务。
- 如何创建一个或多个任务实例。
- 如何使用任务参数。
- 如何更改已创建任务的优先级。
- 如何删除任务。
- 如何定期执行一个任务。
- 何时执行空闲任务以及如何使用执行空闲任务。
任务函数
任务是通过C语言实现的函数,它没有返回值,同时有一个void指针的参数,其原型为:
void ATaskFunction( void *pvParameters );
每个任务本身都是一段小程序,它有一个入口点,通常无限循环的运行,并且不会退出,它的实现如下:
void ATaskFunction(void * pvParameters)
{
/*任务内的变量可以按照普通函数中的变量定义,如果变量声明为static,则这个任务的所有实例都共享这
个变量,其中一个实例改变了该变量,所有实例的变量都会改变,如果没有声明为static,这个任务的每个实
例都会有一个lVariableExample,一个任务实例改变lVariableExample后对其他实例中的
lVariableExample没有影响。*/
int32_t lVariableExample = 0;
/*任务通常为死循环 */
for(;;)
{
/*任务具体实现 */
}
/*如果任务跳出上面的循环,则必须在函数结束前删除该任务,通过调用vTaskDelete(NULL),删除当前任务。 */
vTaskDelete(NULL);
}
单个任务函数可以用于创建多个任务,每个创建的实例都是一个单独执行的实例,具有自己的堆栈和任务内定义的自动变量的副本(如上面的lVariableExample)。
任务状态(简化)
这里只介绍简化后的任务状态,后面补充介绍这里未介绍到的。
应用程序可以包含多个任务,如果处理器是单核的,那么任一时刻只能有一个任务处于运行状态,因此可以将任务的状态表示为运行状态和非运行状态,这里是对其简化后的状态,非运行状态还包含多个子状态。当任务处于运行状态时,处理器正在执行任务的代码。当一个任务处于非运行状态,RTOS会保存器其状态(PC指针,自动变量等等),当任务再次进入运行状态时可以从上次离开运行状态之前执行的指令开始继续执行。从运行到非运行状态或从非运行到运行状态的切换,只能通过FreeRTOS调度程序,即FreeRTOS调度程序是唯一可以切换任务的实体。
创建任务
API函数
函数原型:
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
uint16_t usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask );
参数:
- pvTaskCode
指向实现任务的函数指针。 - pcName
任务的名称,在FreeRTOS中不会使用它,它仅仅作为调试用,可读的名称要比任务句柄简单很多。configMAX_TASK_NAME_LEN定义了它的最大长度,包含字符串的结束符(’\0’),如果大于这个长度,将会直接截断。 - usStackDepth
每个任务都有自己唯一的堆栈,在创建任务时由内核分配给任务,usStackDepth指定了分配的大小,这个大小表示的是字,而不是字节,如usStackDepth为100,栈为32位宽,则将分配400字节的堆栈空间。同时usStackDepth乘以堆栈宽度不能超过uint16_t类型的最大值。空闲任务的堆栈大小由configMINIMAL_STACK_SIZE定义,在demo中此常量是最低建议值。如果任务使用大量堆栈空间,则必须分配更大的值。
没有简单的方法来确定任务所需的堆栈空间。可以计算,但大多数用户只需分配他们认为合理的值,然后使用Free RTOS提供的功能来确保分配的空间足够,并且不会浪费RAM。 - pvParameters
任务函数接收的参数(void *
类型)。 - uxPriority
定义任务执行的优先级。 优先级可以从0(最低优先级)分配到(configMAX_PRIORITIES-1),这是最高优先级。 configMAX_PRIORITIES是用户定义的常量,在后面介绍。
如果传递的值超过(configMAX_PRIORITIES -1),任务的优先级会被限制为优先级的最大合法值。 - pxCreatedTask
通过pxCreatedTask传递一个TaskHandle_t指针,函数执行完后指针指向创建任务的句柄,其他设置(获取)任务信息的API就可以通过这个句柄操作创建的任务。如果应用程序不需要这个任务的句柄可以传递一个NULL指针。
返回值:
- pdPASS
表示该任务创建成功。 - pdFAIL
表示任务尚未创建,因为没有足够的堆内存来分配保存任务的数据结构和堆栈。
任务创建
任务1的任务函数:
void vTask1( void *pvParameters )
{
const char *pcTaskName = "Task 1 is running\r\n";
volatile uint32_t ul; /* volatile 保证ul不会被优化掉 */
for( ;; )
{
/*打印字符串 */
vPrintString( pcTaskName );
/* 简单延时 */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{}
}
}
任务2的任务函数:
void vTask2( void *pvParameters )
{
const char *pcTaskName = "Task 2 is running\r\n";
volatile uint32_t ul;
for( ;; )
{
vPrintString( pcTaskName );
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{}
}
}
主函数:
int main( void )
{
/* 创建任务,实际程序中应检查函数的返回值,判断是否创建成功 */
xTaskCreate( vTask1, /* 任务函数. */
"Task 1",/* 任务名,仅调试用 */
1000, /* 堆栈深度 */
NULL, /* 任务不需要参数 */
1, /* 任务优先级为1 */
NULL ); /* 不需要任务句柄*/
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
/* 启动任务调度器 */
vTaskStartScheduler();
/* 正常情况下程序不会运行到这里,如果运行到这里可能是没有足够的RAM创建空闲任务。 */
for( ;; );
}
此示例演示了创建两个简单任务所需的步骤,然后启动执行的任务。 任务只是定期打印一个字符串。 这两个任务都以相同的优先级创建,除了打印字符串外,其他都是相同的。运行效果如下。
上图中看似两个任务同时执行,但实际上这两个任务都在快速进入和退出运行状态。两个任务以相同优先级运行,因此在同一处理器上轮询的执行,如下图。
在t1内,任务1进入运行状态并执行到t2,在t2内,任务2进入运行状态并执行到t3,
在t3内,Task1重新进入运行状态直达t4,反复循环。图中可看出,当任务1处于运行状态时任务2处于非运行状态,任务2处于运行状态时任务将切换到非运行状态,即任一时刻只有一个任务运行。在开始时,不能确定是任务1先运行还是任务2先运行,这取决于任务调度器。
上面示例是在main
函数中创建两个任务,也可以在任务中创建其他任务,如下例:
void vTask1( void *pvParameters )
{
const char *pcTaskName = "Task 1 is running\r\n";
volatile uint32_t ul;
/* 在任务里创建其他任务 */
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
for( ;; )
{
vPrintString( pcTaskName );
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{}
}
}
创建任务时传递参数
上面的示例中创建了两个任务,两任务只有打印的内容不一样,因此也可以通过一个任务函数创建两个任务实例,然后传入各自的参数实现。
任务函数:
void vTaskFunction( void *pvParameters )
{
char *pcTaskName;
volatile uint32_t ul;
/* 通过强制类型转换将传入的void指针转换为任务需要的类型 */
pcTaskName = ( char * ) pvParameters;
for( ;; )
{
vPrintString( pcTaskName );
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{}
}
}
即使只实现了一个任务函数,也可以创建多个任务实例,每个创建的实例将会在任务调度程序下独立的执行。
主函数:
/* 定义传入任务的字符串 */
static const char *pcTextForTask1 = "Task 1 is running\r\n";
static const char *pcTextForTask2 = "Task 2 is running\r\n";
int main( void )
{
/* 创建任务1 */
xTaskCreate( vTaskFunction,
"Task 1",
1000,
(void*)pcTextForTask1, /*传入任务1要打印的内容的指针 */
1,
NULL );
/* 创建任务2,使用任务1相同的任务函数,不同的时传入的参数,但它们是两个不同的实例 */
xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 1, NULL );
/* 启动任务调度器 */
vTaskStartScheduler();
for( ;; );
}
上一个示例实现了两个任务函数,但这个示例只实现了一个任务函数,通过传入不同的任务参数达到上个示例的目的,输出也与上个示例相同。