FreeRTOS-任务管理

目录

任务管理

创建任务

创建任务示例1:创建两个同等级的任务

创建任务示例2:使用任务参数

创建任务示例3:静态创建任务

删除任务

删除任务示例:删除任务

挂起任务

任务优先级

优先级实验:修改优先级

Tick

延时函数

延时示例

空闲任务

钩子函数

调度算法


任务管理

创建任务

BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,					// 函数指针,任务函数。退出函数时需调用vTaskDelete(NULL)
						const char * const pcName,					// 任务名称,FreeRTOS内部不使用它,仅起调试作用。长度为configMAX_TASK_NAME_LEN
						const configSTACK_DEPTH_TYPE usStackDepth,	// 栈大小。单位为word(4字节),最大值为uint16_t的最大值。精确确定栈大小的方法是看反汇编码
						void * const pvParameters,					// 调用任务函数时传入的参数,调用pvTaskCode函数指针时用到:pvTaskCode(pvParameters)
						UBaseType_t uxPriority,						// 优先级,范围0~(configMAX_PRIORITIES-1),值超时会调整为最大值。值越小优先级越低。
						TaskHandle_t * const pxCreatedTask );		// 任务句柄。用来保持xTaskCreate输出结果task handle。系统会自动帮任务指向一个任务控制块。
// 句柄:如果想操作这个任务(如修改优先级)时就需要这个,如果不用可以设为NULL。
// 返回值
// 		成功:pdPASS
//		失败:errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY,即-1(失败原因只有内存不足)

TaskHandle_t xTaskCreateStatic(	TaskFunction_t pxTaskCode,					// 函数指针,任务函数。退出函数时需调用vTaskDelete(NULL)
								const char * const pcName,					// 任务名称,FreeRTOS内部不使用它,仅起调试作用。长度为configMAX_TASK_NAME_LEN
								const configSTACK_DEPTH_TYPE usStackDepth,	// 栈大小。单位为word(4字节),最大值为uint16_t的最大值。精确确定栈大小的方法是看反汇编码
								void * const pvParameters,					// 调用任务函数时传入的参数,调用pvTaskCode函数指针时用到:pvTaskCode(pvParameters)
								UBaseType_t uxPriority,						// 优先级,范围0~(configMAX_PRIORITIES-1),值超时会调整为最大值。值越小优先级越低。
								StackType_t * const puxStackBuffer,			// 任务栈起始地址
								StaticTask_T * const pxTaskBuffer);			// 任务控制块指针

静态创建任务:

        需要自己指定堆栈空间、需要自己定义任务控制块。

        优点:不易产生内存碎片。

        缺点:任务如果删除,无法回收内存。

动态创建任务:

        无需指定堆栈空间,只需要传入一个任务句柄,系统会自动分配任务控制块。

        优点:内存分配灵活,节省空间。

        缺点:容易产生内存碎片。

创建任务示例1:创建两个同等级的任务
int main( void )
{
	prvSetupHardware();
	xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
	xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
	
	/* 启动调度器 */
	vTaskStartScheduler();
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

void vTask1( void *pvParameters )
{
	const char *pcTaskName = "T1 run\r\n";
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务1的信息 */
		printf( pcTaskName );
		
		/* 延迟一会(比较简单粗暴) */
		for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
		{}
	}
}

void vTask2( void *pvParameters )
{
	const char *pcTaskName = "T2 run\r\n";
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务1的信息 */
		printf( pcTaskName );
		
		/* 延迟一会(比较简单粗暴) */
		for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
		{}
	}
}

实验现象:task2先运行。

任务开始执行顺序:先看优先级,高等优先级的先执行。同优先级的看任务的创建顺序,后创建的任务先执行。

创建任务示例2:使用任务参数

多个任务可以使用同一个函数,区别是:栈不同,且创建任务时可以传入不同的参数。

static const char *pcTextForTask1 = "T1 run\r\n";
static const char *pcTextForTask2 = "T2 run\r\n";

int main( void )
{
	prvSetupHardware();
	xTaskCreate(vTask, "Task 1", 1000, (void *)pcTextForTask1, 1, NULL);
	xTaskCreate(vTask, "Task 2", 1000, (void *)pcTextForTask2, 1, NULL);
	
	/* 启动调度器 */
	vTaskStartScheduler();
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

void vTask( void *pvParameters )
{
	const char *pcTaskName = pvParameters;
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务1的信息 */
		printf( pcTaskName );
		
		/* 延迟一会(比较简单粗暴) */
		for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
		{}
	}
}

创建任务示例3:静态创建任务

在vTaskStartScheduler()函数定义里,如果需要静态创建任务,则需要把configSUPPORT_STATIC_ALLOCATION宏定义为1。

在FreeRTOSConfig.h文件中添加#define configSUPPORT_STATIC_ALLOCATION 1

除此还需要补全一个函数:vApplicationGetIdleTaskMemory(),该函数是在开启调度器函数中使用的。

StaticTask_t 	IdleTaskTCB;
StackType_t		IdleTaskStack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory (	StaticTask_t **ppxIdleTaskTCBBuffer, 
										StackType_t **ppxIdleTaskStackBuffer, 
										uint32_t *pulIdleTaskStackSize) 
{
	*ppxIdleTaskTCBBuffer   = &IdleTaskTCB;
	*ppxIdleTaskStackBuffer = IdleTaskStack;
	*pulIdleTaskStackSize   = (uint32_t)configMINIMAL_STACK_SIZE;
}
StackTtpe_t 	myTaskStack[128];
StaticTask_t 	myTaskTCB;

int main(void)
{
	xTaskCreateStatic(	myTask,	"myTask", 128, NULL, 2,	myTaskStack, &myTaskTCB);
	
	vTaskStartScheduler();
	
	while(1)
	{
		
	}
}

删除任务

void vTaskDelete( TaskHandle_t xTaskToDelete );
// 参数为任务句柄,也可传入NULL。

常用:

        自杀:vTaskDelete( NULL )。

        被杀:其他任务执行vTaskDelete( pvTaskCode ),pvTaskCode是当前任务的句柄。

        杀人:执行vTaskDelete( pvTaskCode ),pvTaskCode是其他任务的句柄。

删除任务示例:删除任务
int main( void )
{
	prvSetupHardware();
	xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
	
	/* 启动调度器 */
	vTaskStartScheduler();
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

void vTask1( void *pvParameters )
{
	const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );
	BaseType_t ret;
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务的信息 */
		printf("Task1 is running\r\n");
		
		ret = xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &xTask2Handle );
		if (ret != pdPASS)
			printf("Create Task2 Failed\r\n");
		
		// 如果不休眠的话, Idle任务无法得到执行
		// Idel任务会清理任务2使用的内存
		// 如果不休眠则Idle任务无法执行, 最后内存耗尽
		vTaskDelay( xDelay100ms );
	}
}

void vTask2( void *pvParameters )
{
	/* 打印任务的信息 */
	printf("Task2 is running and about to delete itself\r\n");
	
	// 可以直接传入参数NULL, 这里只是为了演示函数用法
	vTaskDelete(xTask2Handle);
}

任务现象:

        Task1 is running

        Task2 is running and about to delete itself

        ...重复上面两行

// 当无法执行延时函数时

        Task1 is running

        Create Task2 Failed

        ...重复上面两行

创建任务1后开始调度,则执行任务1。

在任务1中创建任务2,任务2的优先级高,马上执行,打印并自杀。

任务1继续执行,执行延时函数进入阻塞状态,轮到空闲任务执行,空闲任务释放任务2的内存(TCB、栈)。

延时时间到,任务1继续执行。如此循环。

任务1中如果不调用vTaskDelay,则空闲任务没有机会执行,也就无法释放创建任务2时分配的内存。而任务1不断地创建任务,不断消耗内存,最终会内存耗尽而无法再创建新任务。

挂起任务

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

参数xTaskToSuspend表示要暂停的任务,如果为NULL则表示暂停当前任务。

要退出暂停状态,只能由其他任务或中断程序来操作:

        别的任务调用:vTaskResume

        中断程序调用:xTaskResumeFromISR

实际开发中,暂停状态用得不多。

任务优先级

优先级的取值范围:0~(configMAX_PRIORITIES-1),数值越大优先级越高,值超最大值时默认为最大值。

高优先级的先执行;同优先级则后创建的先执行,然后轮流执行。

FreeRTOS的调度器可以使用2种方法来快速找出优先级最高的、可以运行的任务。使用不同方法时,configMAX_PRIORITIES的取值有所不同。

通用方法:

        使用C函数实现,对所有架构都是同样的代码。对configMAX_PRIORITIES的取值没有限制。

        但configMAX_PRIORITIES的取值最好还是尽量小,因为取值越大越浪费内存,也浪费时间。

        configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为0或未定义时,使用该方法。

架构相关的优化方法:

        架构相关的汇编指令,可以从一个32位的数里快速地找出为1的最高位。使用这些指令,可以快速找出优先级最高、可以运行的任务。

        configMAX_PRIORITIES的取值不能超过32.

        configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为1时,使用该方法。

优先级实验:修改优先级
/* 获取任务的优先级,参数使用时表示指定任务,参数为NULL时表示指定当前任务 */
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );

/* 设置任务的优先级 */
/* 第一个参数:使用时表示指定任务,NULL时表示指定当前任务 */
/* 第一个参数:新优先级,取值范围是0~configMAX_PRIORITIES-1 */
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority );
int main( void )
{
	prvSetupHardware();
	
	/* Task1的优先级更高, Task1先执行 */
	xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );
	xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle );
	
	/* 启动调度器 */
	vTaskStartScheduler();
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

void vTask1( void *pvParameters )
{
	UBaseType_t uxPriority;
	
	/* Task1,Task2都不会进入阻塞或者暂停状态,根据优先级决定谁能运行 */
	
	/* 得到Task1自己的优先级 */
	uxPriority = uxTaskPriorityGet( NULL );
	
	for( ;; )
	{
		printf( "Task 1 is running\r\n" );
		printf( "About to raise the Task 2 priority\r\n" );
		
		/* 提升Task2的优先级高于Task1,Task2会即刻执行 */
		vTaskPrioritySet( xTask2Handle, ( uxPriority + 1 ) );
		
		/* 如果Task1能运行到这里,表示它的优先级比Task2高,那就表示Task2肯定把自己的优先级降低了 */
	}
}

void vTask2( void *pvParameters )
{
	UBaseType_t uxPriority;
	
	/* Task1,Task2都不会进入阻塞或者暂停状态,根据优先级决定谁能运行 */
	
	/* 得到Task2自己的优先级 */
	uxPriority = uxTaskPriorityGet( NULL );
	
	for( ;; )
	{
		/* 能运行到这里表示Task2的优先级高于Task1,Task1提高了Task2的优先级 */
		printf( "Task 2 is running\r\n" );
		printf( "About to lower the Task 2 priority\r\n" );
		
		/* 降低Task2自己的优先级,让它小于Task1,Task1得以运行 */
		vTaskPrioritySet( NULL, ( uxPriority - 2 ) );
	}
}

Tick

同优先级的任务轮流执行,执行时间是tick。tick也称为节拍、心跳、滴答,是使用定时器产生固定间隔的中断。

时间片的长度由configTICK_RATE_HZ决定,假设configTICK_RATE_HZ为100,则时间片就是10ms。

tick操作:

vTaskDelay(2); // 等待2个Tick,假设configTICK_RATE_HZ=100, Tick周期时10ms, 等待20ms

// 还可以使用pdMS_TO_TICKS宏把ms转换为tick
vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms

基于tick实现的延时并不精确,如vTaskDelay(2)的本意是延迟2个Tick周期,有可能经过1个tick多一点就返回了。

使用vTaskDelay函数时,建议以ms为单位,使用pdMS_TO_TICKS把时间转换为tick。这样代码就与configTICK_RATE_HZ无关了,即使配置项configTICK_RATE_HZ改变了,我们也不需要去修改代码。

延时函数

vTaskDelay:至少等待指定个数的Tick interrupt才能变为就绪状态。

vTaskDelayUntil:等待到指定的绝对时刻,才能变为就绪态。

void vTaskDelay( const TickType_t xTicksToDelay ); 	/* xTicksToDelay: 等待多少给Tick */

/* pxPreviousWakeTime: 上一次被唤醒的时间
 * xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement)
 * 单位都是Tick Count
*/
BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement );

延时示例
int main( void )
{
	prvSetupHardware();
	
	/* Task1的优先级更高, Task1先执行 */
	xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );
	xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
	
	/* 启动调度器 */
	vTaskStartScheduler();
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

void vTask1( void *pvParameters )
{
	const TickType_t xDelay50ms = pdMS_TO_TICKS( 50UL );
	TickType_t xLastWakeTime;
	int i;
	
	/* 获得当前的Tick Count */
	xLastWakeTime = xTaskGetTickCount();
	
	for( ;; )
	{
		flag = 1;
		
		/* 故意加入多个循环,让程序运行时间长一点 */
		for (i = 0; i < 5; i++)
			printf( "Task 1 is running\r\n" );
	
##if 1
		vTaskDelay(xDelay50ms);
##else
		vTaskDelayUntil(&xLastWakeTime, xDelay50ms);
##endif
	}
}

void vTask2( void *pvParameters )
{
	for( ;; )
	{
		flag = 0;
		printf( "Task 2 is running\r\n" );
	}
}

空闲任务

在使用vTaskStartScheduler()函数来创建、启动调度器时,函数内部会创建空闲任务:

        空闲任务优先级为0:不能阻碍用户任务运行。

        空闲任务只能处于就绪态或运行态,永远不会阻塞。

注意:如果使用vTaskDelete()删除任务,则需确保空闲任务有机会执行,否则就无法释放被删除任务的内存。

我们可以添加一个空闲任务的钩子函数,空闲任务的循环每执行一次就会调用一次钩子函数。

钩子函数

作用是:

        执行一些低优先级的、后台的、需要连续执行的函数。

        测量系统的空闲时间:空闲时间能被执行就意味着所有的高优先级任务都停止了,所以测量空闲任务占据的时间就可以算出处理器占用率。

        让系统进入省电模式:空闲任务能被执行就意味着没有重要的事情要处理,可进入省电模式降低功耗。

空闲任务的钩子函数的限制:

        不能导致空闲任务进入阻塞状态、暂停状态。

        如果使用vTaskDelete()删除任务,则钩子函数要非常高效地执行。如果空闲任务一直卡在钩子函数里,它就无法释放内存。

使用钩子函数的前提是:在FreeRTOS\Source\tasks.c文件中,更改宏,实现函数。

        configUSE_IDLE_HOOK宏定义为1。

        实现vApplicationIdleHook函数。

调度算法

调度算法:确定某个就绪态任务切换为运行态。

通过配置文件FreeRTOSConfig.h的两个配置项来配置调度算法:configUSE_PREEMPTION、configUSE_TIME_SLICING。第三个配置项为高级选项configUSE_TICKLESS_IDLE,用于关闭tick中断来实现省电,一般为0,即不使用。

配置项可抢占+时间片轮询+空闲任务让步可抢占+时间片轮询+空闲任务不让步可抢占+非时间片轮询+空闲任务让步可抢占+非时间片轮询+空闲任务不让步合作调度
configUSE_PREEMPTION11110
configUSE_TIME_SLICING1100x
configIDLE_SHOULD_YIELD1010x
说明常用很少用很少用很少用几乎不用

  • 23
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值