接下来的任务管理的几篇文章的主要目的是:
任务是通过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调度程序是唯一可以切换任务的实体。
函数原型:
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
uint16_t usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask );
参数:
void *
类型)。返回值:
任务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( ;; );
}
上一个示例实现了两个任务函数,但这个示例只实现了一个任务函数,通过传入不同的任务参数达到上个示例的目的,输出也与上个示例相同。
Free RTOS官网