FreeRTOS初级篇----任务管理

一、任务状态

再FreeRTOS中,CPU同一时刻只执行一个任务,只不过是所有任务切换的速度特别快,默认1ms切换一次任务,所以宏观上来看就是CPU再同时运行所有任务。
根据任务的执行情况,任务有四种状态。
1、运行态Running
2、就绪态Ready
3、阻塞态Blocked
4、挂起态Suspended
不论什么任务,他的状态肯定是上述的四种之一。
1、运行态Running:运行态就是正在执行的任务所处的状态,同一时刻只有一个任务能处于运行态,此任务拥有CPU的控制权。
2、就绪态Ready:就绪态就是任务目前可以正常运行,但是还没有轮到她,CPU正在被其他任务使用,所以他必须等待CPU控制权被释放出来,然后去获取CPU执行任务。
3、阻塞态Blocked:阻塞态就是任务被堵住了,因为任务正在等待某一事件发生,若事件不发生,则阻塞态的任务就会一直被堵着,得不到执行,直到他所等待的事件发生。比如等待信号量、消息队列等而处于的状态就是阻塞态,另外调用FreeRTOS中的延迟函数也会使任务进入阻塞态。
4、挂起态Suspended:跟阻塞态差不多,任务都得不到执行,必须有特定的条件才能执行。挂起态的任务会退出任务调度系统,调度器看不到此任务。进入挂起态唯一的方法就是使用vTaskSuspend()函数,参数传入NULL就会把自己挂起。

void vTaskSuspend( TaskHandle_t xTaskToSuspend ); 

如果要让任务退出挂起态,只能由别的任务来实现。
使用vTaskResume ()xTaskResumeFromISR()这两个函数来取消别的任务的挂起状态,将任务从挂起态恢复。
任务状态转换图:
在这里插入图片描述
实验:
创建3个任务,在任务1中,程序运行10个tick的时候,把任务3挂起,运行20个tick的时候,把任务3在唤醒。在任务2中,延时10个tick,使自己进行阻塞态。

TaskHandle_t xHandleTask1;
TaskHandle_t xHandleTask2;
TaskHandle_t xHandleTask3;
void Task1(void * param)
{
	TickType_t TickCountOld = xTaskGetTickCount();
	TickType_t TickCountNew;
	while(1)
	{
		TickCountNew = xTaskGetTickCount();
		
		Task1RunningFlag = 1;
		Task2RunningFlag = 0;
		Task3RunningFlag = 0;
		printf("A");
		if(TickCountNew - TickCountOld == 10)
		{
			vTaskSuspend(xHandleTask3);
		}
		if(TickCountNew - TickCountOld == 20)
		{
			vTaskResume(xHandleTask3);
		}	
	}	
}
void Task2(void * param)
{
	while(1)
	{
		Task1RunningFlag = 0;
		Task2RunningFlag = 1;
		Task3RunningFlag = 0;
		printf("a");
		vTaskDelay(10);
	}	
}
StackType_t xTask3Stack[100];
StaticTask_t xTask3Tcb;
void Task3(void * param)
{
	while(1)
	{
		Task1RunningFlag = 0;
		Task2RunningFlag = 0;
		Task3RunningFlag = 1;
		printf("1");
	}	
}
StackType_t xIdleTaskStack[100];
StaticTask_t xIdleTaskTcb;
void vApplicationGetIdleTaskMemory(StaticTask_t * * ppxIdleTaskTCBBuffer, StackType_t * * ppxIdleTaskStackBuffer, uint32_t * pulIdleTaskStackSize)
{
	*ppxIdleTaskTCBBuffer = &xIdleTaskTcb;
	*ppxIdleTaskStackBuffer = xIdleTaskStack;
	*pulIdleTaskStackSize = 100;
}
int main( void )
{
	prvSetupHardware();
	printf("Hello World!\r\n");

	xTaskCreate(Task1,"Task1",100,NULL,1,&xHandleTask1);
	xTaskCreate(Task2,"Task2",100,NULL,1,&xHandleTask2);
	xHandleTask3 = xTaskCreateStatic(Task3,"Task3",100,NULL,1,xTask3Stack,&xTask3Tcb);

	vTaskStartScheduler();
	return 0;
}

输出结果:
在这里插入图片描述
图中两条灰色虚线之间的空隙代表2ms,可以看到,任务2每次停止运行10ms,说明任务2调用vTaskDelay(10);函数使自己进入阻塞态10个tick,任务3在10个tick后,被任务1调用vTaskSuspend(xHandleTask3);函数,使其被挂起,然后又在10个tick后,调用vTaskResume(xHandleTask3);函数,唤醒任务3,任务3重新运行。

二、延时函数

FreeRTOS中提供了两个延时函数。
1、

vTaskDelay(const TickType_t xTicksToDelay );`

此函数的延时时间是任务前一次结束的时间到任务后一次开始的时间是xTicksToDelay ms。不管任务一次的运行时间是长还是短,延时的是一次结束到下次开始的时间。
在这里插入图片描述

2、

BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement ) 

此函数的延时时间是任务前一次开始的时间到任务后一次开始的时间。不管任务一次的运行时间是长还是短,延时的是一次开始到下次开始的时间。这种延时可以让任务周期性的被执行。
在这里插入图片描述

三、空闲任务及其钩子函数

空闲任务里面会执行一些清理工作,清理任务产生的无用内存。
比如在vTaskDelete( TaskHandle_t xTaskToDelete )任务删除函数中,有下面的一段话,大致意思是,如果任务自己删除自己,这样就不会完成一些清理工作,必须由空闲任务完成清理工作,来释放内存。
在这里插入图片描述
实验:
main函数创建任务2,任务2里面不断的创建任务1,任务1自己删除自己。

void Task1(void * param)
{
	while(1)
	{
		printf("A");
		vTaskDelete(NULL);
	}
}
void Task2(void * param)
{
	TaskHandle_t xHandleTask1;
	BaseType_t xReturn;
	while(1)
	{
		printf("a");
		xReturn = xTaskCreate(Task1,"Task1",1024,NULL,2,&xHandleTask1);
		if(xReturn != pdPASS)
		{
			printf("Task1 create fail!\r\n");
		}
	}	
}
int main( void )
{
	prvSetupHardware();
	printf("Hello World!\r\n");

	xTaskCreate(Task2,"Task2",100,NULL,1,NULL);
	
	vTaskStartScheduler();
	return 0;
}

输出结果:
在这里插入图片描述
可以看到,程序正常运行几次之后,在创建任务就失败了,因为任务1自己删除自己产生的无用内存没有被释放,直到所有内存空间都被用完了,任务2在创建任务就创建失败。
这样我们知道了,在空闲任务里面会清理自我删除的任务,那么空闲任务是在哪里被创建的呢?
进入启动任务调度器函数vTaskStartScheduler( )。可以看到空闲任务是在这里面被创建的。
在这里插入图片描述
在这里插入图片描述
在里面,既可以静态创建也可以动态创建。
除了清理自我删除的任务,空闲任务还会做一些其他的事情,比如执行后台需要连续执行的函数、测量系统的空闲时间、进入省电模式等。
用户可以修改空闲任务函数,在里面执行一些自己需要的代码,但是这样就会破坏FreeRTOS的文件,所以FreeRTOS为空闲任务提供了一个钩子函数,类似ST公司HAL库中的一些中断函数中,为用户提供的回调函数。用户可以在钩子函数中填写自己的代码,然后空闲任务内部会调用这个钩子函数,从而去执行用户自己的代码。
进入空闲任务函数,可以看到他在内部会调用钩子函数。
在这里插入图片描述
对于钩子函数,也有一些要求。钩子函数不能导致空闲任务进入阻塞状态或暂停状态。空闲任务被阻塞了,那么可能永远也不会被唤醒。所以空闲任务必须处于运行态和就绪态两种状态之一。而且钩子函数也不能太过复杂,执行的时候要非常高效的执行,不能占用太长的时间。而且钩子函数千万不能和任务函数一样,用死循环结束,否则钩子函数就退不出去了。
实验:
首先定义宏,使能空闲任务钩子函数。

#define configUSE_IDLE_HOOK			1

编写任务1、2和钩子函数。任务1、2的内容跟上面的一样,只不过是加了flag便于观察现象。钩子函数也是之加入了标志位观察现象。

void Task1(void * param)
{
	while(1)
	{
		Task1RunningFlag = 1;
		Task2RunningFlag = 0;
		IdleRunningFlag = 0;
		printf("1");
		vTaskDelete(NULL);
	}
}
void Task2(void * param)
{
	TaskHandle_t xHandleTask1;
	BaseType_t xReturn;
	while(1)
	{
		Task1RunningFlag = 0;
		Task2RunningFlag = 1;
		IdleRunningFlag = 0;
		printf("2");
		xReturn = xTaskCreate(Task1,"Task1",1024,NULL,2,&xHandleTask1);
		if(xReturn != pdPASS)
		{
			printf("Task1 create fail!\r\n");
		}
	}	
}
void vApplicationIdleHook(void)
{
	Task1RunningFlag = 0;
	Task2RunningFlag = 0;
	IdleRunningFlag = 1;
	printf("0");
}
int main( void )
{
	prvSetupHardware();
	printf("Hello World!\r\n");
	
	xTaskCreate(Task2,"Task2",100,NULL,0,NULL);

	vTaskStartScheduler();
	return 0;
}

注意:要把任务2的优先级设置为0,与空闲任务同一个优先级,否则空闲任务得不到执行,同样也不能清理内存。
输出结果:
在这里插入图片描述在这里插入图片描述
可以看到,程序一直在运行,任务2创建任务1也能一直创建成功。任务1执行完,然后删除自己,这样优先级为0的任务2和空闲任务才能运行,二者交替运行,在空闲任务中,会把任务1删除自己产生的内存碎片清理掉,然后任务2继续创建任务1,每次空闲任务都会清理任务1的碎片,这样就会一直由空闲的内存来创建任务1了。

四、任务调度

之前说过,FreeRTOS中,高优先级任务优先执行,同优先级任务交替执行,这其实只是FreeRTOS中的一种调度方式,除此之外,还有别的任务调度方式。
任务调度方式有3部分可以选择,一个是不同优先级任务之间是否可以抢占,另一个是同优先级任务之间的切换方式,最后一个是空闲任务是否礼让。
FreeRTOSConfig.h文件中,通过三个宏是来配置上面所说的任务调度模式,分别是configUSE_PREEMPTIONconfigUSE_TIME_SLICINGconfigIDLE_SHOULD_YIELD
一、 configUSE_PREEMPTION
configUSE_PREEMPTION为1,高优先级的任务优先执行,能抢占低优先级任务的CPU控制权,这也是FreeRTOS默认的任务调度模式。
实验:
创建三个任务,其中任务3优先级最高。

void Task1(void * param)
{
	while(1)
	{
		Task1RunningFlag = 1;
		Task2RunningFlag = 0;
		Task3RunningFlag = 0;
		IdleRunningFlag = 0;
		printf("1");
	}
}
void Task2(void * param)
{
	TaskHandle_t xHandleTask1;
	BaseType_t xReturn;
	while(1)
	{
		Task1RunningFlag = 0;
		Task2RunningFlag = 1;
		Task3RunningFlag = 0;
		IdleRunningFlag = 0;
		printf("2");
	}	
}
void Task3(void * param)
{
	const TickType_t xDelay5ms = pdMS_TO_TICKS(5UL)
	while(1)
	{
		Task1RunningFlag = 0;
		Task2RunningFlag = 0;
		Task3RunningFlag = 1;
		IdleRunningFlag = 0;
		printf("3");
		vTaskDelay(xDelay5ms);
	}	
}
void vApplicationIdleHook(void)
{
	Task1RunningFlag = 0;
	Task2RunningFlag = 0;
	Task3RunningFlag = 0;
	IdleRunningFlag = 1;
	printf("0");
}

int main( void )
{
	prvSetupHardware();
	printf("Hello World!\r\n");

	xTaskCreate(Task1,"Task1",100,NULL,0,NULL);
	xTaskCreate(Task2,"Task2",100,NULL,0,NULL);
	xTaskCreate(Task3,"Task3",100,NULL,2,NULL);

	vTaskStartScheduler();
	return 0;
}

输出结果:
在这里插入图片描述
可以看到,任务3执行的时候,其他任务得不到执行,只有在任务3进入阻塞态后,任务1、2和空闲任务才会交替执行,等任务3退出阻塞态后,立刻执行任务3。

configUSE_PREEMPTION为0,所有优先级的任务都不能抢占CPU,只能等待正在执行的任务把CPU释放出来后,才能获取CPU控制,得到执行。
FreeRTOSConfig.h文件中,把宏 configUSE_PREEMPTION更改为0,然后重新执行上面的任务。
输出结果:
在这里插入图片描述
可以看到,任务3优先级最高,先执行一次,然后进入阻塞态,然后任务1执行,由于任务1自己不进入阻塞态,所以他会一直执行下去,即使更高优先级的任务3退出阻塞态,也得不到执行。
二、configUSE_TIME_SLICING
configUSE_TIME_SLICING用来配置同优先级任务的调度模式。
configUSE_TIME_SLICING 为1,则同优先级任务交替执行,也可以叫时间片轮转,每个任务轮流执行一段时间。此模式也是FreeRTOS默认的模式。从上面的实验可以看到同优先级任务交替执行的现象,这里就不再演示了。
configUSE_TIME_SLICING 为0,时间片不轮转,同优先级任务不会主动切换,而是要等发生一个任务调度,才会被动的切换任务。下面是实验演示,代码还是之前的,只不过是把宏configUSE_TIME_SLICING设置为了0。
输出结果:
在这里插入图片描述
可以看到,任务3休眠期间,只有1个低优先级任务会被执行,只有当任务3退出阻塞态重新执行,也就是发生一次任务调度之后,低优先级任务才会被切换。之前是在任务3一次阻塞期间,其余3个0优先级任务都会交替执行,而这种模式下只会执行1个任务。

三、configIDLE_SHOULD_YIELD
configIDLE_SHOULD_YIELD决定空闲任务是否礼让其他任务。礼让的意思就是空闲任务执行一次就发生一次任务调度,从而让CPU去执行其他的程序。空闲任务实际上内部也是一个while(1)死循环,在循环内部调用钩子函数并且根据宏configIDLE_SHOULD_YIELD选择是循环一次发生调度还是循环多次在发生调度。
configIDLE_SHOULD_YIELD为1,则空闲任务会礼让其他任务,内部while(1)循环执行一次就立刻发生调度,去执行别的任务。
波形图:
在这里插入图片描述
可以看到,空闲任务执行的时间每次都很短,说明空闲任务只执行了很短的时间就不执行了,去执行别的任务。
configIDLE_SHOULD_YIELD为1,则空闲任务不会礼让其他任务,空闲任务和其他的任务有同等的地位,执行时间也都是差不多相同的。
波形图:
在这里插入图片描述
可以看到,此时的空闲任务的执行时间明显变长了很多,他和任务1、2的执行时间都是差不多的。
根据上面三种情况,可以实现不同的任务调度模式。
在这里插入图片描述
通常第一种情况使用的最多,其余四种任务调度方式几乎不怎么用到。也就是configUSE_PREEMPTIONconfigUSE_TIME_SLICINGconfigIDLE_SHOULD_YIELD这三个宏都配置为1。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值