首页 > 其他分享 >FreeRTOS-空闲任务prvIdleTask()函数解析

FreeRTOS-空闲任务prvIdleTask()函数解析

时间:2024-08-08 14:27:36浏览次数:8  
标签:优先级 函数 FreeRTOS void 低功耗 任务 prvIdleTask 空闲

目录

以下源码为FreeRTOS v9.0.0版本,不同版本源码可能会有所区别,但实现的逻辑差不多。

需要空闲任务的原因:处理器总是需要代码来执行——所以至少要有一个任务处于运行态。为了保证这一点,当调用vTaskStartScheduler()时,调度器会自动创建一个空闲任务。

空闲任务主要用于处理待删除任务列表低功耗,它拥有最低优先级(优先级 0)以保证其不会妨碍具有更高优先级的应用任务进入运行态——当然,没有任何限制说是不能把应用任务创建在与空闲任务相同的优先级上;如果需要的话,你一样可以和空闲任务一起共享优先级,不过这样系统就会运行该同等优先级任务了,不会去运行空闲任务(除非该同等优先级任务被挂起)。

prvIdleTask()函数

/*
这个宏函数 portTASK_FUNCTION() 用于允许编译器特定的语言扩展。
此函数的等效原型为 void prvIdleTask( void *pvParameters );

它实际长这样
#define portTASK_FUNCTION( vFunction, pvParameters ) void vFunction( void *pvParameters )
*/
static portTASK_FUNCTION(prvIdleTask, pvParameters)
{
    /* 防止编译器警告的 */
    (void)pvParameters;

    /*rtos的空闲任务---当调度程序启动时自动创建 */

    for (;;)
    {
        /* 查看是否有任务删除了自己---如果是,则空闲任务负责释放已删除任务的TCB和堆栈
        在该函数内实现被删除任务内存资源的释放 */
        prvCheckTasksWaitingTermination();
/* 抢占式调度的宏为 0 时需要强制进行任务切换,不然高优先级任务无法及时执行 */
#if (configUSE_PREEMPTION == 0)
        {
            /* 如果没有使用抢占式调度,那么会强制任务切换,看看是否有其他任务可用。
            如果使用了抢占,就不需要这样做,因为任何可用的任务都会自动获得处理器 */
            taskYIELD();
        }
#endif /* configUSE_PREEMPTION */
/* 使用抢占式调度并且空闲任务应该让出时间片的宏为1的时候,空闲任务需要让出时间片给同等优先级的其他就绪任务,
宏 configIDLE_SHOULD_YIELD 用于使能空闲任务可以被同优先级的任务抢占 */
#if ((configUSE_PREEMPTION == 1) && (configIDLE_SHOULD_YIELD == 1))
        {
            /* 抢占式调度时,与空闲任务同等优先级的任务将会抢占空闲任务执行并且沿用空闲任务剩余的时间片
            获取与空闲任务优先级一样的任务数量,当前空闲任务优先级下的就绪列表有任务,那就进行任务切换,执行这些任务 */
            if (listCURRENT_LIST_LENGTH(&(pxReadyTasksLists[tskIDLE_PRIORITY])) > (UBaseType_t)1)
            {
                taskYIELD();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */
/* 空闲任务钩子 */
#if (configUSE_IDLE_HOOK == 1)
        {
            extern void vApplicationIdleHook(void);

            /* 从空闲任务中调用用户定义的函数。这允许应用程序设计人员在不增加单独任务开销的情况下添加后台功能。
            注意:vApplicationIdleHook()在任何情况下都不能调用可能阻塞的函数,因为空闲任务不能被阻塞 */
            vApplicationIdleHook(); // 空闲任务钩子函数
        }
#endif /* configUSE_IDLE_HOOK */

/* 这个条件编译应该使用 != 0 的不等式,而不是等于1。这是为了确保当用户定义的
低功耗模式实现要求将 configUSE_TICKLESS_IDLE 设置为 1 以外的值时调用
portSUPPRESS_TICKS_AND_SLEEP() */
/* 低功耗模式 */
#if (configUSE_TICKLESS_IDLE != 0)
        {
            TickType_t xExpectedIdleTime; // 预期空闲时间

            /* 获取预期空闲时间,即进入低功耗模式多久 */
            xExpectedIdleTime = prvGetExpectedIdleTime();
            /* 只有进入低功耗时间大于等于最短时长 configEXPECTED_IDLE_TIME_BEFORE_SLEEP
            系统才会进入低功耗模式节省资源,此次计算的结果可能不是最终进入低功耗的时长,因为可能受到任务调度的影响 */
            if (xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP)
            {
                vTaskSuspendAll();
                {
                    configASSERT(xNextTaskUnblockTime >= xTickCount);

                    /* 重新计算进入低功耗模式的时间,此时调度器被挂起,所以本次的计算结果
                    就是进入响应低功耗模式的时长 */
                    xExpectedIdleTime = prvGetExpectedIdleTime();
                    /* 只有进入低功耗时间大于等于该宏,系统才会进入低功耗模式节省资源 */
                    if (xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP)
                    {
                        /* 调试用的 */
                        traceLOW_POWER_IDLE_BEGIN();
                        /* 该宏函数就是让MCU进入相应低功耗模式的,传入需要进入低功耗模式的时长 */
                        portSUPPRESS_TICKS_AND_SLEEP(xExpectedIdleTime);
                        traceLOW_POWER_IDLE_END();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                (void)xTaskResumeAll();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
#endif /* configUSE_TICKLESS_IDLE */
    }
}

1.调用 prvCheckTasksWaitingTermination() 函数检查是否有任务删除了自己,如果有则在该函数内进行TCB控制块和任务堆栈内存的释放。

2.判断是否有与空闲任务同等优先级的任务存在,如果有并且启用了相关的宏那就进行任务切换,转之去运行该同等优先级的任务。

3.是否启动空闲任务钩子函数,空闲任务构造函数是在空闲任务执行时被调用的函数(类似与回调函数),这允许开发者在不单独增加任务创建的开销情况下执行一些其他动作,比如检测系统处理裕量。

4.是否可以进入低功耗模式,使用函数 prvGetExpectedIdleTime() 获取进入低功耗模式的预期时间,只有大于进入低功耗时间的最短时长才会允许进入低功耗模式。最后通过使用 portSUPPRESS_TICKS_AND_SLEEP 进入低功耗模式。

prvCheckTasksWaitingTermination() 函数

查看是否有任务删除了自己---如果是,则是空闲任务负责释放已删除任务的TCB和堆栈在该函数内实现被删除任务内存资源的释放。

static void prvCheckTasksWaitingTermination(void)
{

/* 这个函数从rtos的空闲任务中调用 */
/* 只有启用了任务删除的宏才会执行该函数 */
#if (INCLUDE_vTaskDelete == 1)
    {
        BaseType_t xListIsEmpty;

        /* 等待清理的被删除任务大于0,说明有任务被删除,需要空闲任务释放内存 */
        while (uxDeletedTasksWaitingCleanUp > (UBaseType_t)0U)
        {
            /* 挂起调度器,挂起调度器会阻止上下文切换,如果调度器被挂起时,中断请求切换上下文,
            那么请求将会被挂起。而且只有在调度器恢复(取消挂起)时才会执行 */
            vTaskSuspendAll();
            {
                /* 等待删除列表是否为空,不为空说明需要处理 */
                xListIsEmpty = listLIST_IS_EMPTY(&xTasksWaitingTermination);
            }
            (void)xTaskResumeAll();
            /* 待删除任务列表中的任务数量不为 0 */
            if (xListIsEmpty == pdFALSE)
            {
                TCB_t *pxTCB;

                taskENTER_CRITICAL();
                {
                    /* 获取等待删除列表中的首个列表项任务控制块进行任务的资源释放 */
                    pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY((&xTasksWaitingTermination));
                    (void)uxListRemove(&(pxTCB->xStateListItem));
                    --uxCurrentNumberOfTasks;
                    /* 等待清除的删除任务减一 */
                    --uxDeletedTasksWaitingCleanUp;
                }
                taskEXIT_CRITICAL();
                /* 调用删除任务释放堆栈和TCB内存 */
                prvDeleteTCB(pxTCB);
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    }
#endif /* INCLUDE_vTaskDelete */
}

该函数很简单,就是执行被删除任务资源的释放,并更新等待终止列表中已被删除任务的数量。内部通过调用 prvDeleteTCB() 函数进行已删除任务的资源释放。

prvGetExpectedIdleTime() 函数

/* 获取预期空闲时间,即进入低功耗模式多久 */
static TickType_t prvGetExpectedIdleTime(void)
{
    TickType_t xReturn;
    UBaseType_t uxHigherPriorityReadyTasks = pdFALSE;

/* uxHigherPriorityReadyTasks 用于处理 configUSE_PREEMPTION(1使用抢占式内核,0使用协程) 为0的情况,
因为可能有高于空闲优先级任务的任务处于 Ready 状态,即使空闲任务正在运行 */
#if (configUSE_PORT_OPTIMISED_TASK_SELECTION == 0)
    {
        if (uxTopReadyPriority > tskIDLE_PRIORITY)
        {
            uxHigherPriorityReadyTasks = pdTRUE;
        }
    }
#else
    {
        /* 最低有效位 */
        const UBaseType_t uxLeastSignificantBit = (UBaseType_t)0x01;

        /* 是否有大于优先级0的任务就绪了,0x01指bit0被置1了,bit0表示优先级0的任务
        只要后面不管哪个bit被置1都是大于0x01的,即有更高优先级任务就绪了,所以高优先级
        任务标志位设置为 pdTRUE */
        if (uxTopReadyPriority > uxLeastSignificantBit)
        {
            uxHigherPriorityReadyTasks = pdTRUE;
        }
    }
#endif
    /* 如果当前执行的任务优先级大于空闲任务优先级是无法进入低功耗的,低功耗模式是在
    空闲任务执行期间进入的 */
    if (pxCurrentTCB->uxPriority > tskIDLE_PRIORITY)
    {
        xReturn = 0;
    }
    /* 与空闲任务优先级 0 一样的任务数量大于1,也无法进入低功耗,需要转去执行优先级和空闲任务
    一样为0的这个任务 */
    else if (listCURRENT_LIST_LENGTH(&(pxReadyTasksLists[tskIDLE_PRIORITY])) > 1)
    {
        xReturn = 0;
    }
    /* 存在优先级高于空闲任务的就绪任务 */
    else if (uxHigherPriorityReadyTasks != pdFALSE)
    {
        xReturn = 0;
    }
    else
    {
        /* 下一个任务解除阻塞时间 - 当前滴答计数器值 = 进入低功耗的时间
        在排除了前面这么多种情况之后,就只剩空闲任务在运行,可以进入低功耗
        模式了,使用下个任务解除阻塞时间(如50) - 当前计数值(如20) = 30
        即可以进入低功耗模式的时间就是30 */
        xReturn = xNextTaskUnblockTime - xTickCount;
    }

    return xReturn;
}

该函数主要用于获取即将要进入的低功耗时间,如果当前存在优先级大于空闲任务优先级则无法进入低功耗模式。故将 uxHigherPriorityReadyTasks 标志位置1,最终通过返回 xReturn 来确定进入低功耗模式的时长,如果返回的是0则不会进入低功耗模式。

标签:优先级,函数,FreeRTOS,void,低功耗,任务,prvIdleTask,空闲
From: https://www.cnblogs.com/RAM-YAO/p/18348902

相关文章

  • 嵌入式实时操作系统(RT-Thread、FreeRTOS、UCOSIII)
    实时操作系统(RT-Thread、FreeRTOS、UCOSIII)文章目录`实时操作系统(RT-Thread、FreeRTOS、UCOSIII)``专有名词概念``1、什么是嵌入式``嵌入式系统的特点``2、什么是实时``3、什么是操作系统``操作系统主要功能和特性``常见的操作系统类型包括``4、嵌入式实时操作系统``关......
  • 基于STM32F103的FreeRTOS系列(七)·任务创建·列表的使用超详细解析
    目录1. 列表和列表项1.1 列表和列表项简介1.1.1  列表1.1.2  列表项1.1.3  迷你列表项1.1.4 列表与列表项关系图1.2 列表初始化1.3 列表项的初始化1.4 列表项的插入函数1.5 列表项的末尾插入1.6 列表项的删除1.7 列表的遍历1. 列表......
  • FreeRTOS中任务创建函数xTaskCreate()的解析
    目录函数xTaskCreate()函数prvInitialiseNewTask()函数pxPortInitialiseStack()函数prvAddNewTaskToReadyList()总结函数xTaskCreate()此函数用于使用动态的方式创建任务,任务的任务控制块以及任务的栈空间所需的内存,均由FreeRTOS从FreeRTOS管理的堆中分配,若使用此函数,......
  • 将iap的接收升级数据部分移植到freertos系统中
    目录前言二、移植过程1.在任务中添加代码三、遇到的问题1.boot跳转卡死在TIM6的中断使能2.代码进入app后却卡死在boot的.s文件的B.处总结前言        在完成基于TCP服务器的iap裸机程序后得到一个新的任务,该任务让我把iap中通过TCP接收数据的代码移植......
  • 基于vscode搭建freertos环境
    前言目前网上windows仿真freertos的资料都是比较久远的,不太适合现有的开发,因此重新整理了一下资料.目标:使用Vscode进行FreeRTOS开发和仿真.关键词:freertos,vscode,llvm,cmake,windows环境配置编译器目前使用的是llvm-MinGW-msvcrt:Releases·mstorsjo/llvm-mingw(g......
  • FreeRTOS基础知识详细版
    RTOS概念‌‌‌‌‌‌RTOS全称是RealTimeOperatingSystem,中文名就是实时操作系统,提供了任务调度、内存管理、中断处理等功能。‌1.任务调度:裸机编程需要手动调度任务,而RTOS提供自动的任务调度器。2.硬件管理:裸机编程需要开发者手动管理硬件资源,RTOS提供了......
  • STM32F1移植FREERTOS DEMO
    简介STM32F1太常用了,因为官网已经把移植的工作做的很完善了,只要文件放到相应工程里就可以使用,这里只做一个简单的DEMO,记录下FreeRTOS正常启动的流程CUBEMX配置1.新建CUBEMX工程,使用SWD的情况先配置SWD设置,防止第一次烧录后,后续无法使用2.由于FreeRTOS有重新使用到SYSTICK(滴搭......
  • STM32Cubemx在FreeRTOS中使用面向对象的方式使用串口
    文章目录前言一、创建FreeRTOS工程二、创建文件对串口进行封装三、代码编写总结前言本篇文章将带大家来学习使用面向对象的方式在FreeRTOS中使用串口,使用面向对象的方法非常适合编写可移植性强的代码,那么这篇文章就带大家来看一下这个代码要怎么写。一、创建FreeRT......
  • FreeRTOS 列表和列表项
    强烈建议:一定要有数据结构中链表的基础 列表和列表项列表:列表是FreeRTOS中的一个数据结构,概念上和链表有点类似,列表被用来跟踪FreeRTOS中的任务。列表项:列表项就是存放在列表中的项目 列表相当于链表,列表项相当于节点,FreeRTOS中的列表是一个双向环形链表......
  • FreeRTOS学习笔记(二)
    FreeRTOS移植一、获取FreeRTOS源码1.1官网下载进入官网直接下载官网:https://www.freertos.org/zh-cn-cmn-s/1.2正点原子网盘下载正点原子资料v10.4.6例程git:https://gitee.com/yuan-zhenbin/freertos-code-repository.gitFreeRTOS资料网盘:http://www.openedv.c......