FreeRTOS基础入门——FreeRTOS内核控制函数(十)

个人名片:

🎓作者简介:嵌入式领域优质创作者
**🌐个人主页:**妄北y

**📞个人QQ:**2061314755

💌个人邮箱: [mailto:2061314755@qq.com]

📱**个人微信:**Vir2025WBY

🖥️个人公众号: 科技妄北
🖋️本文为妄北y原创佳作,独家首发于CSDN🎊🎊🎊
**💡座右铭:**改造世界固然伟大,但改造自我更为可贵。

专栏导航:

妄北y系列专栏导航:

物联网嵌入式开发项目:大学期间的毕业设计,课程设计,大创项目,各种竞赛项目,全面覆盖了需求分析、方案设计、实施与调试、成果展示以及总结反思等关键环节。📚💼💡

QT基础入门学习:对QT的基础图形化页面设计进行了一个简单的学习与认识,利用QT的基础知识进行了翻金币小游戏的制作。🛠️🔧💭

Linux基础编程:初步认识什么是Linux,为什么学Linux,安装环境,进行基础命令的学习,入门级的shell编程。🍻🎉🖥️

深耕Linux应用开发:分享Linux的基本概念、命令行操作、文件系统、用户和权限管理等,网络编程相关知识,TCP/IP 协议、套接字(Socket)编程等,可以实现网络通信功能。常见开源库的二次开发,如libcurl、OpenSSL、json-c、freetype等💐📝💡

Linux驱动开发:Linux驱动开发是Linux系统不可或缺的组成部分,它专注于编写特殊的程序------驱动程序。这些程序承载着硬件设备的详细信息,并扮演着操作系统与硬件间沟通的桥梁角色。驱动开发的核心使命在于确保硬件设备在Linux系统上顺畅运作,同时实现与操作系统的无缝集成,为用户带来流畅稳定的体验。🚀🔧💻

Linux项目开发:Linux基础知识的实践,做项目是最锻炼能力的一个学习方法,这里我们会学习到一些简单基础的项目开发与应用,而且都是毕业设计级别的哦。🤸🌱🚀

非常期待与您一同在这个广阔的互联网天地里,携手探索知识的海洋,互相学习,共同进步。🌐💫🌱 熠熠星光,照亮我们的成长之路

✨✨欢迎订阅本专栏,对专栏内容任何问题都可以随时联系博主,共同书写属于我们的精彩篇章!✨✨

文章介绍:

📚本篇文章将深入剖析RTOS学习的精髓与奥秘,与您一同分享相关知识!🎉🎉🎉

若您觉得文章尚可入目,期待您能慷慨地送上点赞、收藏与分享的三连支持!您的每一份鼓励,都是我创作路上源源不断的动力。让我们携手并进,共同奔跑,期待在顶峰相见的那一天,共庆辉煌!🚀🚀🚀

🙏衷心感谢大家的点赞👍、收藏⭐和评论✍️,您的支持是我前进的动力!

一、内核控制函数

内核控制函数就是FreeRTOS内核所使用的函数,一般情况下应用层程序不使用这些函数,在FreeRTOS官网可以找到这些函数,如图所示:

这些函数含义如下:

二、内核控制函数详解

2.1 函数task YIELD()

FreeRTOS中的一些**API函数也会调用 taskYIELD() 函数,这些API函数会导致任务切换。**这些API函数和任务切换函数 taskYIELD() 统称为系统调用。实际上,taskYIELD() 函数是一个宏,在文件 task.h 中有如下定义:

换句话说,**系统调用是指调用FreeRTOS系统提供的API函数,这些函数包括任务切换函数 taskYIELD()。**这些API函数会引发任务切换,导致当前任务暂停,切换到另一个任务执行。 taskYIELD() 函数实际上是在文件 task.h中定义的一个宏。

在FreeRTOS中,执行系统调用意味着调用系统提供的相关API函数,这些函数会导致任务切换。一个典型的任务切换函数是 taskYIELD()。实际上, taskYIELD() 是一个宏,在 task.h 文件中有如下定义:

cpp 复制代码
#define taskYIELD() portYIELD()

portYIELD() 也是一个宏,在 portmacro.h 文件中定义:

cpp 复制代码
#define portYIELD() \
    { \
        /* 通过向中断控制和状态寄存器ICSR的bit28写入1挂起PendSV来启动PendSV中断 */ \
        portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
        __DSB(portSY_FULL_READ_WRITE); \
        __ISB(portSY_FULL_READ_WRITE); \
    }

1. 启动PendSV中断:

  • portYIELD() 会向中断控制和状态寄存器(ICSR)的bit28写入1,从而挂起PendSV(Pendable Service Call)中断。
  • 这会触发PendSV中断服务函数的执行,进行任务切换。

2. 内存屏障指令:

  • __DSB(portSY_FULL_READ_WRITE)__ISB(portSY_FULL_READ_WRITE) 是数据同步屏障(DSB)和指令同步屏障(ISB)指令,用于确保内存操作顺序和指令执行顺序。

在中断级别也可以进行任务切换,使用的是 portYIELD_FROM_ISR() 宏。定义如下:

cpp 复制代码
#define portEND_SWITCHING_ISR(xSwitchRequired) \
    if (xSwitchRequired != pdFALSE) { \
        portYIELD(); \
    }

#define portYIELD_FROM_ISR(x) portEND_SWITCHING_ISR(x)

2.2 函数taskENTER_CRITICAL()与taskEXIT_CRITICAL()

在FreeRTOS中,临界区用于保护共享资源,确保在多个任务或中断访问同一资源时不会发生数据竞争。用于进入和退出临界区taskENTER_CRITICAL()taskEXIT_CRITICAL() 是用于任务级的临界代码保护,它们必须成对使用。

在FreeRTOS中,宏 taskENTER_CRITICAL()taskEXIT_CRITICAL() 被定义为调用相应的端口特定函数:

cpp 复制代码
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()

portENTER_CRITICAL()portEXIT_CRITICAL() 宏在 portmacro.h 文件中被定义为调用端口特定的实现函数:

cpp 复制代码
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()

port.c 文件中,vPortEnterCritical()vPortExitCritical() 函数的实现如下:

cpp 复制代码
// 用于记录临界区嵌套次数的全局变量
static UBaseType_t uxCriticalNesting = 0;

void vPortEnterCritical(void)
{
    // 关闭中断
    portDISABLE_INTERRUPTS();
    
    // 增加嵌套计数
    uxCriticalNesting++;
    
    if (uxCriticalNesting == 1)
    {
        // 确保没有嵌套在中断上下文中
        configASSERT((portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK) == 0);
    }
}

void vPortExitCritical(void)
{
    // 确保嵌套计数不为零
    configASSERT(uxCriticalNesting > 0);
    
    // 减少嵌套计数
    uxCriticalNesting--;
    
    if (uxCriticalNesting == 0)
    {
        // 使能中断
        portENABLE_INTERRUPTS();
    }
}

1. 进入临界区 (vPortEnterCritical):

  • 关闭中断,以确保临界区代码不会被中断打断。
  • 递增嵌套计数 uxCriticalNesting
  • 如果嵌套计数为1,确保当前不在中断上下文中执行。
  1. 退出临界区 (vPortExitCritical):
  • 检查嵌套计数,确保其大于0。
  • 递减嵌套计数 uxCriticalNesting
  • 如果嵌套计数为0,重新使能中断。

2.3 函数taskENTER CRITICAL FROM ISR()与taskEXIT CRITICAL FROM ISR()

在FreeRTOS中,**taskENTER_CRITICAL_FROM_ISR()taskEXIT_CRITICAL_FROM_ISR() 用于中断服务程序(ISR)中的临界区保护,进入和退出临界区,用于中断服务函数中。**与任务级的临界区保护类似,这些宏用于确保在处理共享资源时不会受到中断的干扰。

在FreeRTOS中,taskENTER_CRITICAL_FROM_ISR()taskEXIT_CRITICAL_FROM_ISR(x) 被定义为调用端口特定的函数:

cpp 复制代码
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR(x) portCLEAR_INTERRUPT_MASK_FROM_ISR(x)

portSET_INTERRUPT_MASK_FROM_ISR()portCLEAR_INTERRUPT_MASK_FROM_ISR(x)portmacro.h 文件中被定义为调用端口特定的实现函数:

cpp 复制代码
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)

ulPortRaiseBASEPRI() 这个函数用于提升 BASEPRI 寄存器的值,以屏蔽低优先级中断:

cpp 复制代码
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI(void)
{
    uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
    
    // 将当前的BASEPRI值保存到ulReturn中
    asm volatile (
        "mrs %0, basepri \n"        // (1)
        "msr basepri, %1 \n"        // (2)
        "dsb \n"
        "isb \n"
        : "=r" (ulReturn) 
        : "r" (ulNewBASEPRI)
    );
    
    return ulReturn;                // (3)
}

1. 保留当前 BASEPRI : 从 BASEPRI 寄存器中读取当前值并保存在 ulReturn 中。

2. 设置新的 BASEPRI : 将 configMAX_SYSCALL_INTERRUPT_PRIORITY 写入 BASEPRI 寄存器,以屏蔽低优先级中断。

3. 返回原始 BASEPRI: 用于退出临界区时恢复。

中断级临界代码保护方法如下:

cpp 复制代码
// 定时器3中断服务函数
void TIM3_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) // 检查是否为溢出中断
    {
        // 进入临界区,保存原始的BASEPRI值
        uint32_t status_value = taskENTER_CRITICAL_FROM_ISR();
        
        {
            // 临界区代码,保护共享资源 total_num
            total_num += 1;
            printf("total_num 的值为:%d\n", total_num);
        }
        
        // 退出临界区,恢复原始的BASEPRI值
        taskEXIT_CRITICAL_FROM_ISR(status_value);
        
        // 清除中断标志位
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    }
}

2.4 函数taskENABLE INTERRUPTS()与taskENABLE INTERRUPTS()

用于关闭进入中断。

在FreeRTOS中,portDISABLE_INTERRUPTS()portENABLE_INTERRUPTS()宏定义如下:

cpp 复制代码
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)

这些宏在portmacro.h文件中定义,并调用相应的函数来控制中断。

以下是vPortSetBASEPRI()vPortRaiseBASEPRI()函数的实现:

cpp 复制代码
static portFORCE_INLINE void vPortSetBASEPRI(uint32_t ulBASEPRI)
{
    asm volatile (
        "msr basepri, %0\n"
        :
        : "r" (ulBASEPRI)
    );
}

static portFORCE_INLINE void vPortRaiseBASEPRI(void)
{
    uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
    asm volatile (
        "msr basepri, %0\n"
        "dsb\n"
        "isb\n"
        :
        : "r" (ulNewBASEPRI)
    );
}

1. vPortSetBASEPRI(uint32_t ulBASEPRI):

  • 这个函数向BASEPRI寄存器写入一个值,该值作为参数ulBASEPRI传递进来。
  • 当传递的值为0时,表示开启中断,因为0值不会屏蔽任何中断。

2. vPortRaiseBASEPRI():

  • 这个函数向BASEPRI寄存器写入configMAX_SYSCALL_INTERRUPT_PRIORITY的值。
  • 这个值会屏蔽所有优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断。

2.5 函数vTaskStartScheduler()与vTaskEndScheduler()

vTaskStartScheduler()vTaskEndScheduler()函数分别用于启动和关闭任务调度器

vTaskStartScheduler()用于启动FreeRTOS任务调度器。这是一个关键函数,它启动任务调度并将控制权交给任务,通常在主程序的最后调用vTaskStartScheduler()来启动调度。调用此函数后,任务调度器开始运行,任务按其优先级和调度策略被执行。

以下是vTaskStartScheduler()函数的简化实现示意:

cpp 复制代码
void vTaskStartScheduler(void)
{
    // 配置定时器中断,用于实现系统节拍
    prvSetupTimerInterrupt();

    // 启动第一个任务
    portRESTORE_CONTEXT();

    // 此函数通常不会返回,除非调度器停止
    for(;;);
}

vTaskEndScheduler()用于关闭FreeRTOS任务调度器。这个函数在大多数移植中未实现,因为关闭调度器通常不会被使用。

在需要停止调度器时调用此函数。调用后,调度器停止,系统返回到调用vTaskStartScheduler()之前的状态。

以下是vTaskEndScheduler()函数的典型实现:

cpp 复制代码
void vTaskEndScheduler(void)
{
    // 停止系统节拍的生成
    portDISABLE_INTERRUPTS();

    // 恢复到调度器启动前的状态
    // 此部分具体实现取决于硬件和移植层
}

2.6 函数VTaskSuspendAll()与函数xTaskResumeAll()

vTaskSuspendAll()和xTaskResumeAll()函数用于挂起和恢复任务调度器。

在文件tasks.c中,vTaskSuspendAll()函数的定义如下:

cpp 复制代码
void vTaskSuspendAll(void)
{
    ++uxSchedulerSuspended;
}
  • uxSchedulerSuspended是一个挂起嵌套计数器。
  • 每次调用vTaskSuspendAll()uxSchedulerSuspended加一,表示调度器被挂起的次数。
  • 支持嵌套挂起,即可以多次调用vTaskSuspendAll()

在文件tasks.c中,xTaskResumeAll()函数的定义如下:

cpp 复制代码
BaseType_t xTaskResumeAll(void)
{
    TCB_t *pxTCB = NULL;
    BaseType_t xAlreadyYielded = pdFALSE;

    configASSERT(uxSchedulerSuspended);

    taskENTER_CRITICAL();
    {
        --uxSchedulerSuspended;

        if (uxSchedulerSuspended == (UBaseType_t)pdFALSE)
        {
            if (uxCurrentNumberOfTasks > (UBaseType_t)0U)
            {
                while (listLIST_IS_EMPTY(&xPendingReadyList) == pdFALSE)
                {
                    pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(&xPendingReadyList);
                    (void)uxListRemove(&(pxTCB->xEventListItem));
                    (void)uxListRemove(&(pxTCB->xStateListItem));
                    prvAddTaskToReadyList(pxTCB);

                    if (pxTCB->uxPriority > pxCurrentTCB->uxPriority)
                    {
                        xYieldPending = pdTRUE;
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }

                if (xYieldPending != pdFALSE)
                {
#if (configUSE_PREEMPTION != 0)
                    {
                        xAlreadyYielded = pdTRUE;
#endif
                        taskYIELD_IF_USING_PREEMPTION();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    }
    taskEXIT_CRITICAL();

    return xAlreadyYielded;
}

1. 进入临界区

  • taskENTER_CRITICAL();:进入临界区,保护调度器状态。

2. 减少挂起计数器

  • --uxSchedulerSuspended;:减少挂起计数器,表示解除一次挂起。
  1. 检查是否完全恢复
  • if (uxSchedulerSuspended == (UBaseType_t)pdFALSE):如果挂起计数器为0,表示调度器完全恢复。

4.处理挂起的任务

  • while (listLIST_IS_EMPTY(&xPendingReadyList) == pdFALSE):循环处理xPendingReadyList中的任务。
  • pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(&xPendingReadyList);:获取挂起的任务控制块。
  • (void)uxListRemove(&(pxTCB->xEventListItem));:从事件列表中移除任务。
  • (void)uxListRemove(&(pxTCB->xStateListItem));:从状态列表中移除任务。
  • prvAddTaskToReadyList(pxTCB);:将任务添加到就绪列表中。
  • if (pxTCB->uxPriority > pxCurrentTCB->uxPriority):如果任务优先级高于当前任务,设置xYieldPendingpdTRUE

5. 任务切换:

  • if (xYieldPending != pdFALSE):如果需要任务切换。
  • xAlreadyYielded = pdTRUE;:标记在xTaskResumeAll()中进行了任务切换。
  • taskYIELD_IF_USING_PREEMPTION();:进行任务切换。
  1. 退出临界区
  • taskEXIT_CRITICAL();:退出临界区。

vTaskSuspendAll():挂起任务调度器,增加挂起计数器。

xTaskResumeAll():恢复任务调度器,减少挂起计数器,处理挂起的任务,并进行必要的任务切换。

2.7 VTaskStepTick()

FreeRTOS的低功耗tickless模式通过停止系统时钟节拍中断来减少功耗。**在这种模式下,当系统进入空闲状态时,系统时钟中断会被停止,并且在系统从低功耗模式退出时需要补偿这段暂停时间。用于设置系统节拍值,****用于在tickless模式下补偿系统时钟暂停的时间。**补偿的工作由vTaskStepTick()函数完成。

在文件tasks.c中,vTaskStepTick()函数的定义如下:

cpp 复制代码
void vTaskStepTick(const TickType_t xTicksToJump)
{
    // 确保跳过的tick数不会超过下一个任务需要解除阻塞的时间
    configASSERT((xTickCount + xTicksToJump) <= xNextTaskUnblockTime);

    // 更新系统时间
    xTickCount += xTicksToJump;

    // 记录系统时间更新的事件
    traceINCREASE_TICK_COUNT(xTicksToJump);
}

📝大佬觉得本文有所裨益,不妨轻点一下👍给予鼓励吧!

❤️❤️❤️本人虽努力,但能力尚浅,若有不足之处,恳请各位大佬不吝赐教,您的批评指正将是我进步的动力!😊😊😊

💖💖💖若您认为此篇文章对您有所帮助,烦请点赞👍并收藏🌟,您的支持是我前行的最大动力!

🚀🚀🚀任务在默默中完成,价值在悄然间提升。让我们携手共进,一起加油,迎接更美好的未来!🌈🌈🌈

相关推荐
网易独家音乐人Mike Zhou10 分钟前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
zy张起灵16 分钟前
48v72v-100v转12v 10A大功率转换电源方案CSM3100SK
经验分享·嵌入式硬件·硬件工程
lantiandianzi7 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
哔哥哔特商务网7 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件
跟着杰哥学嵌入式7 小时前
单片机进阶硬件部分_day2_项目实践
单片机·嵌入式硬件
电子科技圈8 小时前
IAR与鸿轩科技共同推进汽车未来
科技·嵌入式硬件·mcu·汽车
东芝、铠侠总代136100683939 小时前
浅谈TLP184小型平面光耦
单片机·嵌入式硬件·物联网·平面
lantiandianzi9 小时前
基于单片机中医药柜管理系统的设计
单片机·嵌入式硬件
一条晒干的咸魚10 小时前
【Web前端】实现基于 Promise 的 API:alarm API
开发语言·前端·javascript·api·promise
小A15910 小时前
STM32完全学习——使用SysTick精确延时(阻塞式)
stm32·嵌入式硬件·学习