嵌入式4-freertos

电子说

1.4w人已加入

描述

▼关注公众号:27熊熊嵌入式▼

在嵌入式开发领域,RTOS(实时操作系统)应用广泛——无人机飞行控制、汽车电子ECU、医疗设备、智能家居、智能手表,背后都有它的身影。

很多开发者入门RTOS时,会先接触FreeRTOS、RT-Thread等内核,却容易陷入“会用API,不懂原理”的困境:为什么它能保证实时响应?多任务如何“同时”运行?上下文切换到底在做什么?

先搞懂:RTOS内核到底是什么?

核心认知:RTOS的核心是“内核”,它不是完整的操作系统(无图形界面、无复杂文件系统),而是一套轻量级任务调度与资源管理框架——相当于嵌入式系统的“大脑”,负责分配CPU资源、调度任务执行、管理系统资源,核心使命是在严格时间约束下,确定性响应外部事件

和Windows、Linux等通用操作系统相比,RTOS内核有三个核心优势:

轻量性:代码体积通常低于10KB,ROM/RAM占用可控,支持裁剪,适配单片机等资源有限硬件(典型任务栈仅需0.5KB~4KB);

实时性:分为硬实时和软实时,硬实时要求任务必须在截止期限前完成(否则系统失效,如汽车ABS制动),软实时允许偶发超时(如音视频同步);

确定性:任务从就绪到执行的最大延迟(最坏情况响应时间)可计算、可验证,这是通用操作系统无法实现的核心特性。

核心拆解1:任务管理——RTOS的“最小执行单元”

RTOS的核心能力,是将复杂系统功能拆分为多个独立任务,每个任务是可独立运行、被调度的最小单元。

任务的3个核心组成(缺一不可)

每个可运行的RTOS任务,包含三个核心部分:

任务函数:核心执行逻辑,通常为无限循环(for(;;)或while(1)),避免任务执行完毕后进入未知状态,“传感器数据采集”“串口指令处理”等业务逻辑均在此编写;

任务堆栈:每个任务的独立工作空间,用于保存上下文数据、局部变量、函数调用返回地址,记录任务运行状态;

任务控制块(TCB):RTOS管理任务的数据结构,包含任务优先级、运行状态、堆栈指针、任务句柄等信息,内核通过TCB定位和管理任务。

FreeRTOS核心代码分析(任务管理)

FreeRTOS中,任务控制块(TCB)的核心定义代码如下(简化版,去除冗余配置),对应上述任务核心组成:

 

// FreeRTOS任务控制块(TCB)完整定义(来自 tasks.c)typedef struct tskTaskControlBlock{    volatile StackType_t *pxTopOfStack;          // 当前栈顶指针(任务切换时保存/恢复)    ListItem_t xStateListItem;                   // 任务状态链表节点(挂载到就绪/阻塞/挂起链表)    ListItem_t xEventListItem;                   // 事件链表节点(用于信号量、队列等待)    UBaseType_t uxPriority;                      // 任务优先级(数值越大优先级越高)    StackType_t *pxStack;                        // 任务堆栈起始地址    char pcTaskName[ configMAX_TASK_NAME_LEN ];  // 任务名称(便于调试)    // ... 其他可选成员(如栈溢出检测标记、任务通知值等)} tskTCB;

 

创建任务的核心API(xTaskCreate),本质是初始化TCB、分配任务堆栈、将任务加入就绪链表

源码:

 

#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )    BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,                            const char * const pcName,                            const configSTACK_DEPTH_TYPE usStackDepth,                            void * const pvParameters,                            UBaseType_t uxPriority,                            TaskHandle_t * const pxCreatedTask )    {        TCB_t *pxNewTCB;           // 新任务控制块指针        BaseType_t xReturn;        // 返回状态        /* 根据堆栈生长方向,决定先分配TCB还是堆栈,避免堆栈覆盖TCB */        #if ( portSTACK_GROWTH > 0 )   /* 堆栈向上生长:先TCB后堆栈 */            pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );            if( pxNewTCB != NULL )            {                pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );                if( pxNewTCB->pxStack == NULL )                {                    vPortFree( pxNewTCB );                    pxNewTCB = NULL;                }            }        #else /* portSTACK_GROWTH <= 0 — 堆栈向下生长:先堆栈后TCB */            {                StackType_t *pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );                if( pxStack != NULL )                {                    pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );                    if( pxNewTCB != NULL )                    {                        pxNewTCB->pxStack = pxStack;   // 将堆栈地址存入TCB                    }                    else                    {                        vPortFreeStack( pxStack );                    }                }                else                {                    pxNewTCB = NULL;                }            }        #endif /* portSTACK_GROWTH */        if( pxNewTCB != NULL )        {            /* 标记任务为动态分配(便于后续删除时释放内存) */            #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )                pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;            #endif            /* 初始化任务TCB:任务名、优先级、堆栈布局、入口函数等 */            prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth,                                  pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );            /* 将任务加入就绪链表(根据优先级挂载到对应的 pxReadyTasksLists[ ]) */            prvAddNewTaskToReadyList( pxNewTCB );            xReturn = pdPASS;        }        else        {            xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;        }        return xReturn;    }#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

 

关键说明:FreeRTOS中,所有就绪任务按优先级挂载到对应就绪链表,调度器从该链表选择最高优先级任务执行,这是抢占式调度的基础。

任务的4种核心状态(动态切换)

任务并非一直处于运行状态,而是在4种状态间动态切换,这是理解调度器的基础:

运行态:任务占用CPU,执行核心逻辑(单核CPU同一时刻,仅一个任务处于运行态);

就绪态:任务准备就绪,具备运行条件,但CPU被更高优先级任务占用,无法运行;

阻塞态:任务因等待事件(延时、信号量、消息队列)暂停,即使CPU空闲也无法运行(如任务延时1秒,期间处于阻塞态);

挂起态:任务被手动暂停(通过RTOS API调用),即使等待事件满足,也无法进入就绪态,需手动唤醒。

示例:温湿度采集任务每隔1秒采集一次数据——采集完成后进入阻塞态(等待1秒);1秒延时结束后进入就绪态;无更高优先级任务时,调度器让其进入运行态,开始下一次采集。

核心拆解2:调度器

调度器负责决定“哪个任务优先占用CPU”“何时切换任务”,核心目标是保证高优先级任务优先执行,确保系统实时性

主流RTOS(FreeRTOS、RT-Thread)采用“抢占式+时间片轮转”混合调度策略,兼顾实时性和公平性:

1. 抢占式调度

这是RTOS实现实时性的关键——更高优先级任务从阻塞态变为就绪态时,调度器立即暂停当前运行的低优先级任务,切换到高优先级任务执行,无需等待低优先级任务完成。

示例:故障报警任务(高优先级)和日志记录任务(低优先级)——日志任务正常运行,检测到故障后,报警任务进入就绪态,调度器立即暂停日志任务,优先执行报警任务,确保故障及时响应。

2. 时间片轮转调度(核心:同优先级任务公平执行)

多个任务优先级相同时,调度器给每个任务分配时间片(如10ms),任务执行完时间片后,主动放弃CPU,切换到下一个同优先级任务,实现“看似同时运行”的效果。

示例:两个同优先级的数据流显示任务和串口接收任务,时间片设为10ms——两任务轮流占用CPU,各执行10ms,从用户角度看,两个功能同步进行。

调度器的调度时机固定,主要在两个场景:一是任务主动放弃CPU(延时、等待事件);二是中断服务程序执行完毕后,调度器检查是否有更高优先级任务就绪,有则触发任务切换。

FreeRTOS核心代码分析(调度器)

FreeRTOS调度器核心函数是vTaskSwitchContext(),作用是“选择下一个执行任务”,核心逻辑是从就绪链表找到最高优先级任务

 


 

 

void vTaskSwitchContext( void ){    if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )    {        /* 调度器挂起,暂不切换,仅标记需要挂起的任务切换 */        xYieldPending = pdTRUE;    }    else    {        xYieldPending = pdFALSE;        traceTASK_SWITCHED_OUT();        #if ( configGENERATE_RUN_TIME_STATS == 1 )            /* 记录当前任务的运行时间统计(略) */            // ......        #endif        /* 栈溢出检测(若使能) */        taskCHECK_FOR_STACK_OVERFLOW();        /* 选择下一个最高优先级的就绪任务 */        taskSELECT_HIGHEST_PRIORITY_TASK();   /* 核心宏:遍历就绪链表,更新 pxCurrentTCB */        traceTASK_SWITCHED_IN();        /* 更新 Newlib 可重入结构(若使能) */        #if ( configUSE_NEWLIB_REENTRANT == 1 )            _impure_ptr = &( pxCurrentTCB->xNewLib_reent );        #endif    }}

 

补充说明:FreeRTOS中,时间片轮转依赖系统滴答定时器(SysTick)中断,每次中断检查当前任务时间片是否用完,用完则触发调度器切换任务,核心代码在xPortSysTickHandler()中,简化如下:

 

void xPortSysTickHandler( void ){    /* 进入临界区:提高 BASEPRI 以屏蔽部分中断 */    vPortRaiseBASEPRI();    {        /* 增加系统节拍计数,并检查是否需要触发 PendSV 进行任务切换 */        if( xTaskIncrementTick() != pdFALSE )        {            /* 触发 PendSV 异常,在最低优先级执行实际上下文切换 */            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;        }    }    vPortClearBASEPRIFromISR();}BaseType_t xTaskIncrementTick( void ){    TCB_t * pxTCB;    TickType_t xItemValue;    BaseType_t xSwitchRequired = pdFALSE;    /* 如果调度器未挂起,正常处理节拍 */    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )    {        const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;        xTickCount = xConstTickCount;        /* 节拍溢出处理:交换延迟链表和溢出延迟链表 */        if( xConstTickCount == ( TickType_t ) 0U )        {            taskSWITCH_DELAYED_LISTS();        }        /* 检查是否有任务因延时或超时而解除阻塞 */        if( xConstTickCount >= xNextTaskUnblockTime )        {            for( ;; )            {                if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )                {                    xNextTaskUnblockTime = portMAX_DELAY;                    break;                }                else                {                    pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );                    xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );                    if( xConstTickCount < xItemValue )                    {                        xNextTaskUnblockTime = xItemValue;                        break;                    }                    /* 将任务从延迟链表中移除,并加入就绪链表 */                    listREMOVE_ITEM( &( pxTCB->xStateListItem ) );                    if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )                    {                        listREMOVE_ITEM( &( pxTCB->xEventListItem ) );                    }                    prvAddTaskToReadyList( pxTCB );                    #if ( configUSE_PREEMPTION == 1 )                        /* 如果解除阻塞的任务优先级 >= 当前任务,则标记需要切换 */                        if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )                        {                            xSwitchRequired = pdTRUE;                        }                    #endif                }            }        }        /* 时间片轮转:同优先级就绪任务数 > 1 则触发切换 */        #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )            if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )            {                xSwitchRequired = pdTRUE;            }        #endif        /* 处理挂起的任务切换请求 */        #if ( configUSE_PREEMPTION == 1 )            if( xYieldPending != pdFALSE )            {                xSwitchRequired = pdTRUE;            }        #endif    }    else    {        /* 调度器挂起时,累加挂起的节拍数,稍后补处理 */        ++xPendedTicks;    }    return xSwitchRequired;}

 

调度器切换任务时,关键操作是上下文切换

上下文,即任务运行时的全部状态,包括CPU寄存器(PC、SP、通用寄存器)、任务堆栈数据等——即任务“当前运行到哪一步”的所有信息。

上下文切换过程分两步,由内核自动完成,无需开发者干预:

保存上下文:任务被暂停时,内核将其上下文信息(寄存器、堆栈数据)保存到自身任务堆栈,即“保存工作进度”;

恢复上下文:任务再次被调度时,内核从其堆栈中恢复上下文信息到CPU寄存器,即“恢复工作进度”,任务从暂停处继续执行。

FreeRTOS核心代码分析(上下文切换)

FreeRTOS上下文切换分“保存当前任务上下文”和“恢复下一个任务上下文”,核心代码为汇编实现,简化后如下:

 

__asm void xPortPendSVHandler( void ){    extern uxCriticalNesting;    extern pxCurrentTCB;    extern vTaskSwitchContext;    PRESERVE8    /* 获取当前任务的进程栈指针 (PSP) */    mrs r0, psp    isb    /* 获取当前任务控制块的指针 */    ldr r3, =pxCurrentTCB    ldr r2, [r3]    /* 如果使用了 FPU,保存 FPU 高寄存器 */    tst r14, #0x10    it eq    vstmdbeq r0!, {s16-s31}    /* 保存核心寄存器 (r4-r11 以及 r14) */    stmdb r0!, {r4-r11, r14}    /* 将新的栈顶指针保存到 TCB 的第一个成员 */    str r0, [r2]    /* 屏蔽中断,调用调度器选择下一个任务 */    stmdb sp!, {r0, r3}    mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY    msr basepri, r0    dsb    isb    bl vTaskSwitchContext    mov r0, #0    msr basepri, r0    ldmia sp!, {r0, r3}    /* 获取新任务的 TCB 和栈顶指针 */    ldr r1, [r3]    ldr r0, [r1]    /* 恢复新任务的寄存器 */    ldmia r0!, {r4-r11, r14}    /* 恢复 FPU 寄存器(如果之前保存过) */    tst r14, #0x10    it eq    vldmiaeq r0!, {s16-s31}    /* 更新 PSP 并异常返回 */    msr psp, r0    isb    bx r14}

 

补充:不同RTOS内核(FreeRTOS、RT-Thread、uC/OS)实现细节有差异(如优先级数值规则:FreeRTOS数值越大优先级越高,uC/OS则相反),但核心原理一致——掌握这些逻辑,切换任何RTOS都能快速上手。

  文章参考:

FreeRTOS源码仓库(可下载完整源码,对照本文代码分析学习):https://github.com/FreeRTOS/FreeRTOS-Kernel

FreeRTOS中文官方文档(适配国内开发者,简化易懂):https://www.freertos.org/(含内核原理、任务管理、调度器等核心模块详解)

审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分