【FreeRTOS】空闲任务

空闲任务及其钩子函数

介绍

空闲任务(Idle任务)的作用之一:释放被删除的任务的内存

除了上述目的之外,为什么必须要有空闲任务? 这是一个良好的程序,它的任务都是事件驱动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。在使用vTaskStartScheduler()函数来创建、启动调度器时,这个函数内部会创建空闲任务

  • 空闲任务优先级为0:它不能阻碍用户任务运行
  • 空闲任务要么处于就绪态,要么处于运行态,永远不会阻塞

空闲任务的优先级为0,这意味着一旦某个用户的任务变为就绪态,那么空闲任务马上被切换出去,让这个用户任务运行。在这种情况下,我们说用户任务"抢占"(pre-empt)了空闲任务,这是由调度器实现的。

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

我们可以添加一个空闲任务的钩子函数(Idle Task Hook Functions),空闲任务的循环每执行一次,就会调用一次钩子函数。钩子函数的作用有这些:

  • 执行一些低优先级的、后台的、需要连续执行的函数
  • 测量系统的空闲时间:空闲任务能被执行就意味着所有的高优先级任务都停止了,所以测量空闲任务占据的时间,就可以算出处理器占用率。
  • 让系统进入省电模式:空闲任务能被执行就意味着没有重要的事情要做,当然可以进入省电模式了。
  • 空闲任务的钩子函数的限制:
  • 不能导致空闲任务进入阻塞状态、暂停状态
  • 如果你会使用vTaskDelete()来删除任务,那么钩子函数要非常高效地执行。如果空闲任务移植卡在钩子函数里的话,它就无法释放内存。

使用钩子函数的前提

在FreeRTOS\Source\tasks.c中,可以看到如下代码,所以前提就是:

  • 把这个宏定义为1:configUSE_IDLE_HOOK
  • 实现vApplicationIdleHook函数

实际操作

创建一个任务的时候,传递了一个函数,假设这个函数只执行10次,不是死循环
在这里插入图片描述

如果这里不是死循环,执行完成之后,直接返回,就进入了错误处理的函数

关闭所有中断,进入死循环, 其他所有的任务也不能再次执行

在这里插入图片描述

任务能够调度,都是依赖Tick中断,现在中断都关闭了,那么所有的任务都没有办法再次切换了

如果想退出这个任务,必须自杀

任务如何退出?

任务如何退出?

  1. 自杀 vTaskDelete(NULL)
  2. 他杀 vTaskDelete(handle) //传入任务的句柄

这里复制第九个程序,改成十个程序

修改这一段代码,先注释vTaskDelete(NULL);,然后再取消注释,对比前后的效果

/**********************************************************************
 * 函数名称: Led_Test
 * 功能描述: Led测试程序
 * 输入参数: 无
 * 输出参数: 无
 *            无
 * 返 回 值: 0 - 成功, 其他值 - 失败
 * 修改日期        版本号     修改人        修改内容
 * -----------------------------------------------
 * 2023/08/03        V1.0     韦东山       创建
 ***********************************************************************/
void Led_Test(void)
{
    Led_Init();

    //while (1)
    for (unsigned char i = 0; i < 3; i++)
    {
        Led_Control(LED_GREEN, 1);
        mdelay(500);

        Led_Control(LED_GREEN, 0);
        mdelay(500);
    }
    vTaskDelete(NULL);
}
  • 任务自杀之后,LED就不再闪烁了,全彩LED正常运行

如果是任务A杀死任务B,需要任务A给任务B收尸,如果B自杀,则由空闲任务收尸
收尸就是做一些清除工作,需要释放TCB结构体、释放栈~

如果有很多任务自杀的话,空闲任务根本没有机会执行,没有办法执行,没有办法做清除工作,可能会导致内存不够用

为了让空闲任务有机会运行,我们需要养成良好的编程习惯

  • a 事件驱动
  • b 调用延时函数delay的时候,要主动休眠,不要使用死循环

这样我们可以再次修改代码,使用vTaskDelay,优化代码

void Led_Test(void)
{
    Led_Init();
    //while (1)
    for (unsigned char i = 0; i < 3; i++)
    {
        Led_Control(LED_GREEN, 1);
        // mdelay(500);
        vTaskDelay(500);

        Led_Control(LED_GREEN, 0);
        // mdelay(500);
        vTaskDelay(500);
    }
    vTaskDelete(NULL);
}

我们调用vTaskDelete,会把自己从ReadyList切换到DelayTaskList中,也就是从就绪态切换为阻塞态,不会再参与调度
使用vTaskDelete,进入了阻塞状态,让出了CPU资源,这样空闲任务才有机会运行,帮助自杀的任务回收资源

IDLE函数

看看空闲任务的函数,从main.c里找,启动调度器

osKernelStart();

进入函数,找到

vTaskStartScheduler();

创建了空闲任务,记录人空闲任务 prvIdleTask

xIdleTaskHandle = xTaskCreateStatic(	prvIdleTask,
												configIDLE_TASK_NAME,
												ulIdleTaskStackSize,
												( void * ) NULL, /*lint !e961.  The cast is not redundant for all compilers. */
												( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
												pxIdleTaskStackBuffer,
												pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */

检查自杀的任务,做一些清理工作

prvCheckTasksWaitingTermination();

还会调用HOOK函数,钩子函数,每次都可以执行这个函数,我们可以从中添加一些打印信息,统计程序运行的状态。

vApplicationIdleHook();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北国无红豆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值