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);
}

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

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

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

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

相关推荐
m0_7393128715 分钟前
【STM32】独立看门狗(IWDG)原理详解及编程实践(下)
stm32·单片机·嵌入式硬件
代码总长两年半15 分钟前
FatFs文件系统的移植---STM32(标准库)
c语言·stm32·单片机·嵌入式硬件
IronmanJay1 小时前
kAFL部署、使用与原理分析
信息安全·内核·系统调用·fuzz测试·漏洞检测·内核漏洞检测工具·kafl
Q8343158192 小时前
JL6107S 7端口管理型MAC(1*RGMII+1*SGMII)千兆以太网交换机芯片P2P替代RTL8367S
网络·嵌入式硬件·网络协议·硬件工程·信息与通信·p2p
梦境虽美,却不长2 小时前
51单片机快速入门之独立按键
单片机·嵌入式硬件·51单片机
梦境虽美,却不长3 小时前
51单片机快速入门之定时器和计数器
单片机·嵌入式硬件·51单片机
营赢盈英3 小时前
How to see if openAI (node js) createModeration response “flagged“ is true
javascript·ai·node.js·openai·api
光子物联单片机4 小时前
零基础国产GD32单片机编程入门(二十五)USB口介绍及CDC类虚拟串口通讯详解及源码
单片机·嵌入式硬件·mcu·gd32
QQ19284999064 小时前
基于单片机的水产养殖饲料自动投喂系统
单片机·嵌入式硬件
Nice__J5 小时前
电机驱动开发之驱动板
驱动开发·嵌入式硬件