FreeRTOS - 任务

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tq384998430/article/details/87883308

简而言之,基于RTOS的实时应用程序都是由若干个独立任务组成的,每个任务的实体就是一个普通的函数并拥有自己的上下文环境以及栈空间。但是对于单核CPU系统来说在任何一个时间点只能有一个任务在运行,这个正在运行的任务是由RTOS的任务调度器从众多的就绪状态的任务中选取出来的最合适的一个任务。(tqOS、UCOS、FreeRTOS都是这个的原理)

对于FreeRTOS系统来说,每个任务的实体函数原型为:

void ATaskFunction( void *pvParameters );

函数体内部一般都是一个无限循环,并且函数不允许返回,典型的任务实体函数的结构如下:

void ATaskFunction( void *pvParameters )
{
/* Variables can be declared just as per a normal function. Each instance
of a task created using this function will have its own copy of the
iVariableExample variable. This would not be true if the variable was
declared static – in which case only one copy of the variable would exist
and this copy would be shared by each created instance of the task. */
int iVariableExample = 0;
/* A task will normally be implemented as an infinite loop. */
for( ;; )
{
/* The code to implement the task functionality will go here. */
}
/* Should the task implementation ever break out of the above loop
then the task must be deleted before reaching the end of this function.
The NULL parameter passed to the vTaskDelete() function indicates that
the task to be deleted is the calling (this) task. */
vTaskDelete( NULL );
}

每个任务拥有运行 Running未运行 Not Running两个基本状态。由于对于单核系统来说只能同时有一个任务正在占用CPU执行,那么其余的所有任务都处于未运行状态。处于未运行状态的任务又拥有多个子状态:就绪状态、挂起状态、阻塞状态,后面会再介绍这几个状态。

任务从Not Running State转到Running State状态时称为switched in或者swapped in,从Running State转到Not Running State转台称为switched out或者swapped out,在FreeRTOS系统中只有任务调度器(scheduler)才能够执行任务的切换。

创建任务时可以使用 xTaskCreate() API 函数,这个函数是最复杂的API函数同时也是最基本、最重要的API函数,xTaskCreate函数原型以及参数说明如下:

portBASE_TYPE xTaskCreate( 
    pdTASK_CODE pvTaskCode,//任务的函数实体,函数指针的形式传入。
    const signed char * const pcName,//任务名称,用可阅读的形式来表示一个任务。
    unsigned short usStackDepth,//任务栈的深度,以字为单位,Cortex M3为例,一个字是4个字节。
    void *pvParameters,//传入任务函数的参数。
    unsigned portBASE_TYPE uxPriority,//任务的优先级,最小为0,最大值为configMAX_PRIORITIES – 1,优先级数值越大表示任务的优先级越高。
    xTaskHandle *pxCreatedTask//用于获取创建的任务的句柄。
);

一个最简单的任务创建的示例如下:

static void Task1( void *pvParameters );
static void Task2( void *pvParameters );

int main(void)
{
	Initialization();
	
	/* Start the tasks defined within this file/specific to this demo. */
    xTaskCreate( Task1, "task1", 256, NULL, 2, NULL );
	xTaskCreate( Task2, "task2", 256, NULL, 1, NULL );
	
	
	/* Start the scheduler. */
	vTaskStartScheduler();
	
	while(1);
	
}

static void Task1( void *pvParameters )
{
	while(1)
	{
		printf("%s\r\n",__FUNCTION__);
		vTaskDelay(1000);
	}
}

static void Task2( void *pvParameters )
{
	while(1)
	{
		printf("%s\r\n",__FUNCTION__);
		vTaskDelay(1000);
	}
}

在xTaskCreate()函数中,uxPriority参数用于表示任务的优先级,优先级可以在RTOS调度器启动之后使用vTaskPrioritySet() 函数进行修改。优先级的最大值是由预编译参数 configMAX_PRIORITIES 决定的,预编译参数参数都存放在FreeRTOSConfig.h配置文件中,这个文件可以对FreeRTOS系统在预编译阶段进行深度裁剪,使得FreeRTOS系统更适合自己的使用情况。要注意configMAX_PRIORITIES的值越大会导致占用更多的RAM空间,所以一般将这个配置参数尽可能的降低到满足应用需求的最小值。

FreeRTOS系统中任务的优先级可以任意分配,任务的实际运行优先级和任务优先级的数值成正比,也就是说优先级0是最低优先级的任务,并且可使用的任务优先级范围为0到configMAX_PRIORITIES – 1。FreeRTOS的scheduler会保证优先级最高的就绪任务处于Running State。任务也可以共享一个任务优先级,相同优先级的任务可以使用时间片轮转调度算法运行。

前面说到了任务的Not Running State状态可以分为多个子状态,下图为整个任务状态的状态转移图:

图中详细说明了Not Running State状态中的三个子状态与Running状态的转移过程。

Ready State表示任务处于就绪状态,随时可以“上任”并执行。

Blocked State表示的是任务正在等待一个事件(Event),这个事件可以是时间事件或者同步事件。时间事件包括等待一段确定的时间段或者等待到达一个绝对时间点,例如延时10ms的时间或者等待系统时间计数到达10000。同步事件比较复杂,多个任务间的同步、通讯都会使用到同步机制,例如某个任务需要等待一个队列接收到一个数据才能继续运行,在此之前都处于阻塞状态,当队列接收到一个数据时会触发一个同步事件,这个任务就会从阻塞转态转移到就绪状态。

Suspended State表示任务处于挂起状态,处于这个状态的任务完全会被系统的Scheduler忽略,不会参与系统的调度工作。任务处于Running State可以使用vTaskSuspend()函数进入挂起状态,并可以通过vTaskResume()或xTaskResumeFromISR()函数退出挂起状态。

我们知道CPU不可以停下来不工作,所以任何时候都必须有一个就绪的任务可以被调度器Swap in,但是我们创建的任务都有可能处于Block State和Suspend State,如果所有用户创建的任务都处于Not Running State的非Ready状态,那么CPU怎么办呢?其实FreeRTOS中在调用vTaskStartScheduler()函数的时候创建了一个空闲任务称为Idle任务,Idle任务的优先级是0,表示优先级最低,可以被所有其他任务抢占,这个任务的工作就是在所有其他任务都不运行的时候运行,Idle的工作很简单,可以什么事都不做只是一个无限循环,并且都处于Ready State,可以随时运行。

当然了,让Idle任务只做一个无限死循环的话未免有点浪费资源,我们使用RTOS的主要目的就是为了更好的利用CPU,FreeRTOS的作者也想到这个问题了,FreeRTOS提供了一个空闲任务的钩子函数,其实所谓的钩子函数就是一个回调函数,在空闲任务执行的时候会回调这个钩子函数,以完成一些用户操作,实现CPU的最高利用率。

一般来说空闲任务的钩子函数可以做以下事情:1、执行低优先级、后台执行、持续执行的任务。2、统计系统的CPU和内存利用率。3、进入低功耗模式。但是使用空闲任务的钩子函数也有一些局限性,不能在钩子函数中进行Idle任务的阻塞或者挂起操作,也就是无法进行延时或者使用系统API进行同步操作。其次是当应用程序中使用了 vTaskDelete() 函数的话Idle任务的钩子函数必须要在合理的时间内返回,因为使用 vTaskDelete() 函数会有内存释放操作,而内存的释放清理都是在Idle任务中实现的,如果Idle任务一直在钩子函数中执行会导致内存的清理操作无法实施。

 

猜你喜欢

转载自blog.csdn.net/tq384998430/article/details/87883308
今日推荐