FreeRTOS笔记(四)初识任务

文章目录

  • 01 - 单任务与多任务
      • 1.1 - 单任务系统
      • 1.2 - 多任务系统
  • 02 - 创建和删除任务
      • 2.1 - FreeRTOS_API
      • 2.2 - 任务函数
      • 2.3 - 创建任务
      • 2.4 - 删除任务
  • 03 - 堆栈空间
  • 04 - 任务优先级
  • 05 - 任务的基础测试
      • 5.1 - 创建和删除测试
      • 5.2 - 堆栈大小测试
      • 5.3 - 任务的优先级测试
  • 06 - 总结

上一文链接:FreeRTOS笔记(三)配置文件FreeRTOSConfig.h


01 - 单任务与多任务

1.1 - 单任务系统

  单片机裸机编程的时候,都会用一个while(1)去包裹所有应用程序,换言之,裸机编程中的应用程序就运行在一个死循环while(1)中,而且各个应用程序比如串口、LED、显示屏等都是排队轮流执行的(中断除外),只要前面的程序没有完成,后面的程序就必须等待。

FreeRTOS笔记(四)初识任务_第1张图片

  这种裸机编程非常容易实现,对于顺序执行的逻辑可以控制得很好,但是当外部资源庞大,程序逻辑复杂,需要突然程序跳转的设置,某些应用程序会在某个不可预测的时间上需要实时性执行,此时裸机编程就无能为力了,裸机编程的系统一般称为单任务系统,显然现在需要多任务系统。

1.2 - 多任务系统

  多任务系统假若在单核版上运行(嵌入式中往往是单核),那么意味着每个时刻只有一个任务在进行,下一个时刻到底选择哪个任务去执行?需要一个掌控全局任务执行时间安排的角色:调度器。调度器的基本功能是使得任务可以切出和切入,在某个任务需要马上执行的时候立刻打断当前正在执行的任务,这样就使得系统具有实时性。调度器的存在也使得任务具有多种特性,比如优先级、堆栈空间等等。

FreeRTOS笔记(四)初识任务_第2张图片

02 - 创建和删除任务

2.1 - FreeRTOS_API

函数 描述
xTaskCreate() 堆栈由FreeRTOS动态分配
xTaskCreateStatic() 堆栈由用户指定分配
xTaskCreateRestricted() 堆栈由FreeRTOS动态分配,使用MPU(内存保护单元)进行限制
xTaskDelete() 删除任务

  根据FreeRTOS提供的API,能够知道任务具有的基本特性,这些特性需要在创建的时候确定。

FreeRTOS笔记(四)初识任务_第3张图片

2.2 - 任务函数

  首先了解怎样创建和删除一个任务,上述提到,任务应该具有一个死循环函数,除了函数,任务还有优先级、堆栈空间等特性,而这个函数称为任务函数,它告诉任务需要执行的内容,任务函数既然是死循环,就不需要返回值,于是返回值就是void,那么参数呢?FreeRTOS规定任务函数的参数是一个指针,基类型是void(void*指针可以指向任一个类型的指针,理解为包罗万象的盒子,任何东西都能放进去),任务函数的原型如下:

void ATaskFunction( void *pvParameters );

2.3 - 创建任务

  这个任务函数需要绑定在任务中才能被执行,而任务需要创建,FreeRTOS提供任务相关的API,创建任务的API有3种,常用的是xTaskCreate(),不同的API用途在上表查看,xTaskCreate()的原型及需要的头文件如下:

#include “FreeRTOS.h”
#include “task.h”

aseType_t xTaskCreate( TaskFunction_t pvTaskCode,			//任务函数
                        const char * const pcName,		//任务名称
                        unsigned short usStackDepth,		//堆栈大小
                        void *pvParameters,				//任务函数的参数
                        UBaseType_t uxPriority,			//任务优先级
                        TaskHandle_t *pxCreatedTask );	//任务句柄

  参数和返回值可以在官方手册中查看,目前暂时理解部分。值得注意的是,任务的创建可以在调度器启动前,也可以是调度器启动后。

2.4 - 删除任务

  任务函数运行在死循环之内,不能以任何方式返回,如果任务函数实在不需要继续执行,那么可以使用vTaskDelete()API函数主动或者被动删除任务。任务删除后就不再运行,变成了“僵尸”,等待着内核收尸vTaskDelete()的原型及需要的头文件如下:

#include “FreeRTOS.h”
#include “task.h”

void vTaskDelete( TaskHandle_t pxTask );	//任务句柄

03 - 堆栈空间

  FreeRTOS多任务系统需要调度器进行任务的切换,而被切出的任务下一次可以被完整地继续运行的原因在于,每个任务都有自己的堆栈,调度器进行任务切换的时候保护切出任务当前的堆栈现场,当下一次切入的时候再恢复现场,这样任务中的数据就不会被破坏。

FreeRTOS笔记(四)初识任务_第4张图片
  在创建任务的时候需要指出堆栈的大小,这对于一般的开发者而言很难估计,目前只需要写入一个中等大的值即可,如果嵌入式单板的RAM资源短缺,就需要非常仔细地计算每个任务的堆栈大小。任务堆栈的大小由参赛 unsigned short usStackDepth指定,单位是字,不是字节。

04 - 任务优先级

  调度器选择任务执行的依据是任务的优先级,FreeRTOS的任务优先级是0~(configMAX_PRIORITIES-1)configMAX_PRIORITIES在FreeRTOSConfig.h(可以看上一文)中定义,0是最低优先级,数值越大优先级越高。
  调度器选择任务需要有一个调度算法,调度器总是选择优先级最高的任务去执行,如果任务队伍中的优先级一样,就采用时间片的方法法轮流执行,每个任务执行一段时间,随后切换同级的其它任务。

05 - 任务的基础测试

  接下来进行任务的一些基础测试,先测试一些任务的基础特性,真实感受FreeRTOS的任务执行,比如创建删除、堆栈大小和优先级,配合官方的一些API进行使用,任务的创建仅仅测试xTaskCreate()函数。

5.1 - 创建和删除测试

  • 1、创建1个任务,串口间隔输出running,同时输出当前的任务数
#define START_TASK_PRIO                 1       //优先级
#define STATR_STACK_SIZE                128     //堆栈大小
TaskHandle_t StartTask_Handler;                 //任务句柄

//任务函数
void start_task(void *pParam)
{
    for(;;)
    {
        printf("task running, task number: %ld\r\n",uxTaskGetNumberOfTasks());
        vTaskDelay(1000);
    }
}

void main(void)
{
	printf("before create task : %ld\r\n",uxTaskGetNumberOfTasks());
	//创建任务
	xTaskCreate( (TaskFunction_t)start_task,
	             (const char*)"start_task",
	             (uint16_t)STATR_STACK_SIZE,
	             (void*)NULL,
	             (UBaseType_t)START_TASK_PRIO,
	             (TaskHandle_t*)&StartTask_Handler
	);          
	printf("after create task : %ld\r\n",uxTaskGetNumberOfTasks());
	//开启调度器
	vTaskStartScheduler();
}
代码
FreeRTOS笔记(四)初识任务_第5张图片
运行结果

  这里产生疑问,为什么有3个任务?从输出可知,调度器启动前只有1个任务,启动后有3个,意味着调度器创建了2个任务,这2个是什么任务?打开启动调度器函数vTaskStartScheduler()查看源码:

void vTaskStartScheduler( void )
{
    #if( configSUPPORT_STATIC_ALLOCATION == 1 )
    {
        ……
        xIdleTaskHandle = xTaskCreateStatic(); 
        ……
    }
    #else
    {
        ……
        xReturn = xTaskCreate(); 
        ……
    }
    #endif 
    ……
    #if ( configUSE_TIMERS == 1 )
    {
        if( xReturn == pdPASS )
        {
            xReturn = xTimerCreateTimerTask();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif 
    ……
}

  问题解决了,这2个任务,一个是IDLE空闲任务,一个是定时器任务,空闲任务是一定会创建的,因为if…else…都有创建任务的函数,而定时器任务是根据宏configUSE_TIMERS来创建的,这个宏在FreeRTOSConfig.h中配置,上一篇文章中配置了这个宏,所以就会有3个任务。

  • 2、创建2个任务A和B,A任务串口输出running和当前的任务数,然后删除自己,B任务间隔输出running,同时输出当前的任务数,并检查任务A的状态
void A_task(void *pParam)
{
    printf("A task running, task number: %ld\r\n",uxTaskGetNumberOfTasks());
    
    vTaskDelete(NULL);
}   

void B_task(void *pParam)
{
    eTaskState state;
    for(;;)
    {
        printf("B task running, task number: %ld\r\n",uxTaskGetNumberOfTasks());
        state = eTaskGetState(ATask_Handler);
        if(state == eDeleted)
        {
            printf("B task running, A is delete\r\n");
        }
        else
        {
            printf("B task running, A is other\r\n");   
        }
        vTaskDelay(1000);
    }
}   
代码
FreeRTOS笔记(四)初识任务_第6张图片
运行结果

  能够看到,调度器开启后串口输出混乱,这是资源访问的问题,暂时不理会。还是能够观测到,任务A输出了信息,此时还有4个任务(A、B、空闲任务和定时器任务),当A删除后,只有B在运行,并显示A任务已经删除,目前只有3个任务。

5.2 - 堆栈大小测试

  • 创建1个任务,堆栈大小从1往上设置,串口间隔输出running和变量值
#define START_TASK_PRIO					1		//优先级
#define STATR_STACK_SIZE				16			//堆栈大小
TaskHandle_t StartTask_Handler;					//任务句柄
void start_task(void *pParam)
{
	for(;;)
	{
		printf("task running, task number: %ld\r\n",uxTaskGetNumberOfTasks());
		vTaskDelay(1000);
	}
}
代码
FreeRTOS笔记(四)初识任务_第7张图片
运行结果

  当任务堆栈不足够的时候,任务是无法运行的,此时的处理取决于你有没有使用FreeRTOS提供的栈溢出检测,如果在FreeRTOSConfig.h配置文件中定义configCHECK_FOR_STACK_OVERFLOW不为0,那么就会使用vApplicationStackOverflowHook()进行处理,如果没有使用栈溢出检测,那么后果将不可预料,有可能系统会奔溃,有可能会正常运行,有可能会打乱系统运作。

5.3 - 任务的优先级测试

  • 创建2个任务A和B,A优先级比B高,都在串口间隔输出running
/* A 任务 */
#define A_TASK_PRIO					2		//优先级
#define A_STACK_SIZE				128			//堆栈大小
TaskHandle_t ATask_Handler;					//任务句柄


/* B 任务 */
#define B_TASK_PRIO					1		//优先级
#define B_STACK_SIZE				128			//堆栈大小
TaskHandle_t BTask_Handler;					//任务句柄

void A_task(void *pParam)
{
	for(;;)
	{
		printf("A task running, task number: %ld\r\n",uxTaskGetNumberOfTasks());
	}
}	

void B_task(void *pParam)
{
	for(;;)
	{
		printf("B task running, task number: %ld\r\n",uxTaskGetNumberOfTasks());
	}
}	
代码
注意,这里不能使用vTaskDelay()延迟函数,因为vTaskDelay()会使得任务阻塞,那么其它任务就可以执行
FreeRTOS笔记(四)初识任务_第8张图片
运行结果

  在前面任务创建与删除的测试中,A、B两个任务的优先级是相同的,于是调度器采用时间片调度法,任务轮流执行,时间片是多少?可以通过FreeRTOSConfig.h文件去配置,默认情况下我们可知,A、B任务还没有完整输出串口的时候就被切换了,所以出现了乱码。但是现在任务A永远是可以执行的最高优先级任务,任务B则永远得不到执行

06 - 总结

  • 单任务系统不能处理实时性的任务需要
  • 多任务系统中任务的切换需要调度器的存在
  • 调度器的基本功能是任务的切出和切入
  • 任务是FreeRTOS的基本执行单位
  • 任务具有任务函数、优先级、堆栈空间等特性

你可能感兴趣的:(#,FreeRTOS)