3.FreeRTOS_任务管理

这一次的代码基础是"FreeRTOS_任务创建与删除"这篇文章。博客链接如下:

FreeRTOS_任务创建与删除-CSDN博客

多任务交替执行的原理

原理

任务在FreeRTOS中的存在形式是结构体,这些结构体以链表的形式进行管理。

RTOS中有一个tick中断,这是一个周期性的定时器中断。当中断发生后,会在就绪链表中去搜索下一个需要执行的任务是什么,从而实现交替执行。

具体的简化模型如下:

假设就绪任务链表中有三个任务,当前point指向的是任务1,那么此刻执行的就是任务1的任务函数。过了一段时间,产生了一个tick中断,指针指向了下一个任务,开始指向任务2。又过一段时间,执行任务3。又过一段时间,重新指向了任务1。从而实现了交替执行。

任务切换时间

这个交替的时间tick默认为1ms,这并不是一个固定值,可以通过宏来设置。

具体设置如下:

在上图中,设置的为1000Hz,就是1ms。通过修改这个宏的值,即可修改任务切换的时间。

任务的状态

理论知识

任务的状态有四种:运行态、就绪态、挂起态(暂停态)、阻塞态

  • 运行态:正在运行的任务的状态
  • 就绪态:可以运行,但是还没轮到它的任务的状态
  • 挂起态:该任务已经停止,不会再运行的状态。但可以解挂,解挂之后就可以变成就绪态
  • 阻塞态:正在等待一些事件,不能继续运行的任务的状态

它们之间的转换关系如下图:

  • 1:当任务创建时,进入就绪态
  • 2:当轮到该任务执行时,任务进行运行态开始运行
  • 3:当tick中断产生后,该任务重新变回运行态,这时另一个任务进入运行态
  • 4:当任务运行时遇到了需要等待的事件,就会进入阻塞态
  • 5:当任务运行时,调用了挂起的函数,就进入了挂起态
  • 6:当阻塞的任务接收到了等待的事件,就会再次就绪
  • 7:当阻塞态的任务被其他任务调用挂起函数后,就进入了挂起态
  • 8:当就绪态的任务被其他任务调用挂起函数后,就进入了挂起态
  • 9:当挂起态的任务被其他任务调用解挂函数后,就进入了就绪态

挂起与解挂函数

挂起与解挂函数非常简单,传入相应的任务句柄,即可实现挂起与解挂。声明如下:

/* 挂起 */
void vTaskSuspend( TaskHandle_t xTaskToSuspend );

/* 解挂 */
void vTaskResume( TaskHandle_t xTaskToResume );

挂起函数vTaskSuspend传入NULL时,代表挂起自己

解挂函数不可能由自己调用,必须由其他任务来解挂被挂起的任务。

延时函数

等待函数非常简单,传入相应的值即可实现以阻塞形式进行延时。声明如下:

/* 延时,类似 HAL_Delay() */
void vTaskDelay( const TickType_t xTicksToDelay );

/* 延时,该延时是个宏*/
vTaskDelayUntil( pxPreviousWakeTime, xTimeIncrement );

/* 获取计数值,类似HAL_GetTick() */
TickType_t xTaskGetTickCount( void );

vTaskDelay传入的参数的单位为tick,这与前面“ 任务切换时间 ”这一节叙述的宏有关。默认为1ms

vTaskDelayUntil的第一个参数是基准时刻,第二个参数为延时的时间,单位为tick

这两个函数的区别为:vTaskDelay可以确定延时过程为多少tick,vTaskDelayUntil可以确定结束时刻为多少tick。模型如下:

使用vTaskDelayUntil可实现任务周期性的执行,具体代码实现如下:

void TaskFunction(void* param){
    
    TickType_t tStart = xTaskGetTickCount();/* 获取当前的tick值 */

    while(1){

       /* 功能函数  */
       /* ........... */

       vTaskDelayUntil(&tStart,20);/* 以进入该任务为基准时间,阻塞20tick后,进入就绪态 */
    }
}

具体的模型如下:

这样,就实现了每次任务执行的时间都是tStart到tStart+20这段时间。

空闲任务

空闲任务的创建并不需要手动创建,FreeRTOS在启动调度器函数中已经自动创建好了空闲函数。

空闲任务的作用如下:

  • 回收自杀任务空间
  • 执行一些低优先级、低功耗的功能(这需要使用钩子函数来实现)

回收自杀任务空间

当任务进行自杀时,即:vTaskDelete传入的参数为NULL。空闲任务会进行清理工作。

需要注意的是,空闲任务的优先级为0,就是最低优先级。这意味着当任务自杀后,如果有其他任务的优先级高于0时,就不会进行任务的释放。

下面进行一个实验,实验内容如下:

  • 在main中创建一个任务1,这个任务的优先级是1。
  • 在任务1中,创建任务2,这个任务2的优先级为2,如果创建失败了,就打印一个err。
  • 在任务2中,删除自己。

实验一:空闲任务内存回收失败

具体代码实现如下:

/* Task1任务处理函数 */
void Task1Function(void *param){
	
	TaskHandle_t xHandleTask2;
	int i='a';
	BaseType_t xReturn;
	
	while(1){
		
		/* 创建任务2,传入参数a~z */
		xReturn = xTaskCreate(Task2Function,"Task2",100,(void*)&i,2,&xHandleTask2);
		/* 创建失败,打印err */
		if(xReturn != pdPASS){
			printf("err");
		}
		printf("1");
		i++;
	}
}

/* Task2任务处理函数 */
void Task2Function(void *param){
	
	while(1){
		
		/* 打印创建时传入的参数 */
		printf("%c",*((int*)param));
		/* 删除自己 */
		vTaskDelete(NULL);
	}
}


int main( void )
{
#ifdef DEBUG
  debug();
#endif
	
	TaskHandle_t xHandleTask1;
	
	prvSetupHardware();
	SerialPortInit();
	printf("UART TEST\r\n");

    /* 创建任务1 */
	xTaskCreate(Task1Function,"Task1",100,NULL,1,&xHandleTask1);

	vTaskStartScheduler();
	return 0;
}

代码分析如下:

因为任务2的优先级为2,高于任务1的优先级,所以任务1创建完成任务2后,会直接执行任务2。这时,任务2打印传入的参数并删除自己。之后重复这些操作。

运行结果如下:

结果分析如下:

可以看到,任务2打印了a~z还有一些其他字符,这说明任务2都在执行。但是打印到?之后,任务1就开始打印了err,说明任务2创建失败。

任务2的创建失败的原因是没有足够的内存,这说明任务2在调用vTaskDelete删除自己的时候并未释放内存。释放内存的工作是空闲任务来进行的,这说明空闲任务并未执行。

空闲任务的优先级是0,在任务2删除自己后,依旧有一个任务1在执行,它的优先级是1。因此导致空闲任务无法执行,因此删除后的任务的空间无法释放。

实验2:修改代码使得回收成功

经过上述分析,我们需要让空闲任务可以执行。改变任务1的优先级为0,这样就可以在任务2删除之后,任务1和空闲任务交替执行。

基于上述代码,修改部分为main中的创建任务1函数:

/* 修改前的创建任务1函数 */
xTaskCreate(Task1Function,"Task1",100,NULL,1,&xHandleTask1);

/* 修改后的创建任务1函数 */
xTaskCreate(Task1Function,"Task1",100,NULL,0,&xHandleTask1);

运行结果如下:

结果分析:

打印乱码是因为任务2打印的是字符,字符超过ASCII范围就是乱码。但这不是重点。

可以看到的是任务1已经不再打印err,这说明任务2再不断的创建成功,因此也说明了空闲任务回收了任务2 自杀时的任务空间。

钩子函数

钩子函数是空闲任务处理函数中调用到的一个函数。空闲任务可以执行一些功能,这些功能并不会直接让你直接在空闲任务处理函数中修改,而是钩子函数中修改。

钩子函数相关限制:

  • 禁止编写阻塞、暂停相关的函数,因此钩子函数只能有就绪态和运行态
  • 建议钩子函数中的功能执行越快越好,这样可以不影响空闲任务的执行

使用钩子函数需要进行以下操作:

  • 打开宏开关
  • 修改钩子函数,在钩子函数中实现自己的功能

打开宏开关

在FreeRTOSConfig.h中,置1宏开关。具体操作如下:

修改钩子函数

修改如下:

重点是钩子函数名,功能是任意的,这里只是简单打印0

注意:钩子函数中不能写个while(1)

/* 钩子函数 */
void vApplicationIdleHook( void ){
	printf("0");
}

运行结果:

可以看到,相比与实验2,这里多打印了0,证明了钩子函数已经运行。

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值