目录

uCOS-II 源码详细分析

uC/OS-II 源码详细分析(基于V2.92经典稳定版)

uC/OS-II 是由Jean J. Labrosse开发的可剥夺型硬实时多任务RTOS内核,专为资源受限的嵌入式系统设计,全部源码遵循ANSI C标准编写,仅少量CPU架构相关代码用汇编实现,具备可裁剪、可移植、可固化、调度时间恒定、实时性强的核心特点,广泛应用于工业控制、医疗设备、汽车电子等对确定性和可靠性有严苛要求的领域。

一、源码整体分层架构

uC/OS-II 采用严格的分层模块化设计,彻底解耦硬件相关逻辑与内核通用逻辑,极大提升了可移植性,源码整体分为三大核心层级:

1. 硬件无关层(内核通用核心,无需修改)

该层是uC/OS-II的核心主体,与CPU/硬件平台完全无关,跨平台通用,每个模块对应独立的C文件,职责清晰:

源文件 核心功能
os_core.c 内核中枢,实现初始化、任务调度、中断管理、临界区、TCB/ECB链表管理等核心逻辑
os_task.c 任务管理,实现任务创建/删除/挂起/恢复/优先级修改等专属API
os_time.c 时间管理,实现时钟节拍处理、任务延时、延时恢复等时间相关功能
os_mem.c 内存管理,实现固定大小内存分区管理,解决内存碎片问题
os_sem.c 计数型/二值信号量,实现任务同步与资源互斥访问
os_mutex.c 互斥信号量,基于优先级继承协议解决优先级反转问题
os_mbox.c 消息邮箱,实现单指针消息的任务间通信
os_q.c 消息队列,邮箱的增强版,支持多消息FIFO异步通信
os_flag.c 事件标志组,支持多事件的逻辑与/或组合等待
ucos_ii.h 内核总头文件,定义所有数据结构、宏、枚举、函数声明
ucos_ii.c 内核入口,统一包含所有核心源文件,方便工程管理

2. 硬件相关层(移植层,需根据CPU/编译器修改)

该层是内核与硬件之间的抽象层,所有与CPU架构、编译器相关的代码均集中在此,是移植的核心修改对象:

源文件 核心功能
os_cpu.h CPU相关宏定义、基本数据类型、编译器配置、关/开中断宏、任务切换宏
os_cpu_c.c C语言实现的移植函数,核心是任务栈初始化函数OSTaskStkInit(),以及各类钩子函数
os_cpu_a.asm 汇编实现的底层核心函数,包括任务启动OSStartHighRdy()、任务级切换OSCtxSw()、中断级切换OSIntCtxSw()

3. 用户配置层(可裁剪定制,按需修改)

通过宏开关实现内核功能的精细化裁剪与配置,无需修改内核源码,适配不同资源的硬件平台:

源文件 核心功能
os_cfg.h 内核配置文件,通过宏定义配置最大任务数、最大事件数、是否启用统计任务/模块裁剪、时钟节拍频率等核心参数
includes.h 工程总头文件,统一包含内核头文件、硬件驱动头文件、用户代码头文件,集中管理工程依赖

二、核心数据结构详解

理解uC/OS-II源码的核心是吃透三大基础数据结构,它们是整个内核运行的基石。

1. 任务控制块 OS_TCB(Task Control Block)

每个任务唯一对应一个OS_TCB结构体,是任务的“身份证”,内核通过TCB管理任务的全部状态与属性,所有TCB通过双向链表串联管理。核心源码定义如下:

typedef struct os_tcb {
    OS_STK          *OSTCBStkPtr;           /* 栈顶指针,结构体第一个成员,汇编无需偏移即可访问,提升切换效率 */
    struct os_tcb   *OSTCBNext;             /* TCB双向链表后向指针 */
    struct os_tcb   *OSTCBPrev;             /* TCB双向链表前向指针 */
    OS_EVENT        *OSTCBEventPtr;         /* 指向任务等待的事件控制块ECB */
    void            *OSTCBMsg;              /* 任务接收的消息指针,邮箱/队列专用 */
    INT16U          OSTCBDly;               /* 任务延时节拍数/事件等待超时计数 */
    INT8U           OSTCBStat;               /* 任务状态:就绪/等待/挂起/休眠 */
    INT8U           OSTCBPrio;               /* 任务唯一优先级,0最高,OS_LOWEST_PRIO最低 */
    INT8U           OSTCBX;                  /* 就绪表辅助:优先级低3位,对应OSRdyTbl的位 */
    INT8U           OSTCBY;                  /* 就绪表辅助:优先级高3位,对应OSRdyTbl的行 */
    INT8U           OSTCBBitX;               /* 就绪表位掩码:1<<OSTCBX,预计算加速调度 */
    INT8U           OSTCBBitY;               /* 就绪表位掩码:1<<OSTCBY,预计算加速调度 */
#if OS_TASK_DEL_EN > 0
    INT8U           OSTCBDelReq;             /* 任务删除请求标记,支持安全删除 */
#endif
#if OS_TASK_SUSPEND_EN > 0
    INT8U           OSTCBSuspendCtr;         /* 任务挂起计数,支持嵌套挂起 */
#endif
    /* 其他裁剪相关可选成员,如事件标志组指针、任务名、栈检测参数等 */
} OS_TCB;

核心设计细节

  • 栈顶指针OSTCBStkPtr放在结构体首个地址,汇编语言访问时无需计算结构体偏移,大幅提升任务上下文切换的效率。
  • OSTCBX/Y/BitX/BitY在任务创建时预计算完成,避免调度时重复运算,是实现O(1)调度的关键优化之一。
  • 内核维护两个核心TCB链表:OSTCBFreeList(空闲TCB单向链表,创建任务时分配)、OSTCBList(已创建任务TCB双向链表,内核遍历管理)。

2. 就绪表与优先级位图算法

就绪表是uC/OS-II实现O(1)级抢占式调度的核心,无论系统中有多少任务,找到最高优先级就绪任务的时间恒定,完全满足硬实时系统的确定性要求。

就绪表由两个核心全局变量组成,定义在os_core.c中:

INT8U  OSRdyGrp;                         /* 就绪组:8位,每一位对应OSRdyTbl的一行,该行有任务就绪则对应位为1 */
INT8U  OSRdyTbl[OS_RDY_TBL_SIZE];        /* 就绪表:8个8位元素,每一位对应一个优先级的就绪状态 */

设计原理

  • uC/OS-II默认支持64个优先级(063),用6位二进制即可表示一个优先级,高3位为Y(07),对应OSRdyGrp的位号和OSRdyTbl的行号;低3位为X(0~7),对应OSRdyTbl[Y]的位号。
  • 优先级与位图的映射关系(任务创建时预计算):
    OSTCBY    = prio >> 3;    // 等价于 prio / 8,取高3位
    OSTCBX    = prio & 0x07;  // 等价于 prio % 8,取低3位
    OSTCBBitY = 1 << OSTCBY;  // 行掩码
    OSTCBBitX = 1 << OSTCBX;  // 位掩码
    
  • 任务就绪操作(写入就绪表):
    OSRdyGrp |= OSTCBBitY;               // 标记该行有任务就绪
    OSRdyTbl[OSTCBY] |= OSTCBBitX;       // 标记该优先级任务就绪
    
  • 任务取消就绪(从就绪表移除):
    if ((OSRdyTbl[OSTCBY] &= ~OSTCBBitX) == 0) {
        OSRdyGrp &= ~OSTCBBitY;  // 该行无就绪任务,清除组标记
    }

最高优先级就绪任务快速查找: 通过预定义的OSUnMapTbl映射表,仅需两次查表即可定位最高优先级,彻底避免遍历,实现O(1)调度。

// 256字节映射表,预定义8位数值中最低位为1的位置,例如0x04(00000100)返回2
INT8U  const  OSUnMapTbl[256] = {
    0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
};

// 核心查找逻辑,调度器核心
Y = OSUnMapTbl[OSRdyGrp];                  // 找到有就绪任务的最低行号
X = OSUnMapTbl[OSRdyTbl[Y]];               // 找到该行中就绪的最低位号
OSPrioHighRdy = (INT8U)((Y << 3) + X);     // 拼接得到最高优先级

3. 事件控制块 OS_EVENT(Event Control Block)

uC/OS-II的信号量、互斥锁、邮箱、消息队列全部基于ECB统一管理,实现了内核对象的复用,所有等待事件的任务通过ECB的等待表统一管理,设计逻辑与就绪表完全一致。

typedef struct os_event {
    INT8U   OSEventType;                    /* 事件类型:信号量/互斥锁/邮箱/消息队列 */
    INT16U  OSEventCnt;                     /* 信号量计数器,互斥锁/邮箱/队列不使用 */
    void    *OSEventPtr;                    /* 邮箱消息指针/队列控制块指针 */
    INT8U   OSEventGrp;                     /* 事件等待组,与就绪表OSRdyGrp逻辑一致 */
    INT8U   OSEventTbl[OS_EVENT_TBL_SIZE];  /* 事件等待表,管理等待该事件的任务 */
#if OS_EVENT_NAME_EN > 0
    INT8U   *OSEventName;                   /* 事件名,调试用 */
#endif
} OS_EVENT;

核心设计细节

  • 内核维护OSEventFreeList空闲ECB单向链表,创建内核对象时分配,删除时归还,无动态内存分配,规避内存碎片风险。
  • 事件等待表的操作逻辑与就绪表完全一致,事件发生时,通过OSUnMapTbl快速找到等待该事件的最高优先级任务,将其唤醒并放入就绪表,触发调度。

三、内核核心流程源码分析

1. 内核初始化 OSInit()

源码位于os_core.c,是uC/OS-II运行的第一步,必须在OSStart()之前调用,完成所有内核全局变量、数据结构、空闲链表的初始化,创建系统必备任务。

核心执行流程

  1. 初始化就绪表:OSRdyGrp = 0OSRdyTbl数组全部清零,无任何任务就绪。
  2. 初始化TCB空闲链表:根据OS_MAX_TASKS配置,创建对应数量的TCB,串联成单向空闲链表OSTCBFreeList
  3. 初始化ECB空闲链表:根据OS_MAX_EVENTS配置,创建对应数量的ECB,串联成单向空闲链表OSEventFreeList
  4. 初始化各模块空闲链表:内存管理、事件标志组、定时器等模块的空闲控制块链表(根据裁剪配置执行)。
  5. 创建系统核心任务:
    • 空闲任务OS_TaskIdle():优先级固定为最低OS_LOWEST_PRIO,必须创建,当无其他就绪任务时运行,死循环执行,可通过钩子函数实现低功耗控制。
    • 统计任务OS_TaskStat():可选,通过OS_TASK_STAT_EN宏开启,优先级固定为OS_LOWEST_PRIO-1,用于统计CPU使用率、任务运行时间等系统状态。
  6. 初始化全局变量:OSRunning = OS_FALSE(标记内核未启动)、中断嵌套计数器OSIntNesting = 0、调度器锁计数器OSLockNesting = 0等。
  7. 初始化钩子函数:用户可自定义的任务切换、时钟节拍、任务创建/删除等钩子函数。

2. 内核启动 OSStart()

源码位于os_core.c,在OSInit()执行完成,且至少创建1个用户任务后调用,作用是启动多任务调度,正式将CPU控制权交给uC/OS-II内核,该函数只会执行一次,且不会返回

核心执行流程

  1. 遍历就绪表,通过OSUnMapTbl找到当前就绪的最高优先级任务,赋值给OSPrioHighRdyOSPrioCur
  2. 获取该最高优先级任务的TCB,赋值给OSTCBHighRdyOSTCBCur
  3. 调用汇编函数OSStartHighRdy(),执行第一个任务的上下文切换:
    • 初始化系统时钟节拍定时器。
    • 从最高优先级任务的栈中恢复CPU寄存器上下文。
    • 跳转到任务函数入口,开始执行第一个任务。
  4. 设置OSRunning = OS_TRUE,标记内核正式运行,调度器生效。

3. 任务级调度器 OSSched()

源码位于os_core.c,是uC/OS-II任务切换的核心入口,仅用于任务级调度,在任务主动放弃CPU时调用(如任务延时、等待事件、释放信号量、修改优先级等场景),核心职责是找到最高优先级就绪任务,并触发任务切换。

核心执行流程

void  OSSched (void)
{
    INT8U  y;

    OS_ENTER_CRITICAL();  // 进入临界区,关中断保护
    // 合法性检查:中断中不调度、调度器锁定时不调度
    if ((OSIntNesting == 0) && (OSLockNesting == 0)) {
        y = OSUnMapTbl[OSRdyGrp];
        OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
        // 最高优先级任务不是当前任务,才需要切换
        if (OSPrioHighRdy != OSPrioCur) {
            OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
            OSCtxSwCtr++;  // 任务切换计数,统计用
            OSCtxSw();      // 调用汇编函数,执行任务级上下文切换
        }
    }
    OS_EXIT_CRITICAL();  // 退出临界区,开中断
}

核心规则

  • 调度器执行全程必须在临界区中,避免中断打断调度过程,导致就绪表、TCB等共享数据竞争。
  • 中断上下文、调度器锁定状态下,禁止执行任务切换,保证系统稳定性。
  • 仅当最高优先级就绪任务与当前任务不同时,才会触发上下文切换,避免无意义的性能损耗。

4. 任务上下文切换

任务切换的本质是保存当前任务的CPU寄存器上下文到自身任务栈,再从新任务的任务栈中恢复寄存器上下文到CPU,实现CPU使用权的转移,全部由汇编代码实现,位于os_cpu_a.asm中,分为两种场景。

4.1 任务级切换 OSCtxSw()

OSSched()调用,用于正常任务间的切换,以ARM Cortex-M架构为例,核心执行流程:

  1. 触发PendSV异常(Cortex-M架构专用,可在中断末尾低优先级执行,避免打断其他中断,保证实时性)。
  2. PendSV异常处理中,CPU自动保存xPSR、PC、LR、R12、R3-R0寄存器到当前任务栈。
  3. 手动保存R4-R11通用寄存器到当前任务栈,完成完整上下文保存。
  4. 将当前任务的栈顶指针SP,保存到当前任务TCB的OSTCBStkPtr成员。
  5. 更新全局变量:OSTCBCur = OSTCBHighRdyOSPrioCur = OSPrioHighRdy
  6. 从新任务TCB的OSTCBStkPtr中加载栈顶指针到SP。
  7. 从新任务栈中恢复R4-R11通用寄存器。
  8. 执行异常返回,CPU自动从新任务栈中恢复xPSR、PC等寄存器,新任务正式开始运行。

4.2 中断级切换 OSIntCtxSw()

OSIntExit()调用,用于中断服务结束后触发的任务切换,与任务级切换的核心区别是: 中断发生时,CPU已经自动保存了当前任务的完整/部分上下文到任务栈,因此OSIntCtxSw()无需重复保存上下文,仅需完成后续的TCB更新、新任务上下文恢复流程,大幅减少中断处理的性能开销,提升中断响应速度。

四、核心功能模块源码详解

1. 任务管理模块(os_task.c)

任务是uC/OS-II的最小调度单位,每个任务是一个无限循环的函数,具备唯一的静态优先级,内核通过TCB对任务进行全生命周期管理。

1.1 任务创建 OSTaskCreate()

基础任务创建函数,是任务生命周期的起点,核心源码执行流程:

  1. 合法性校验:优先级是否超出范围、是否已被占用、栈指针是否合法、是否在内核运行后创建空闲任务。
  2. 调用OSTaskStkInit(),初始化任务栈,模拟任务被中断后的栈结构,填充PC(任务函数入口地址)、LR(任务退出钩子函数)、寄存器初始值,返回初始化后的栈顶指针。
  3. OSTCBFreeList中分配一个空闲TCB,若分配失败,返回错误码。
  4. 初始化TCB的所有成员,包括栈顶指针、优先级、预计算的OSTCBX/Y/BitX/BitY、任务状态等。
  5. 将新任务的TCB插入到OSTCBList双向链表的头部,同时将TCB地址存入OSTCBPrioTbl[prio]数组,通过优先级快速索引TCB。
  6. 将新任务设置为就绪态,写入就绪表。
  7. 若内核已运行(OSRunning == OS_TRUE),立即调用OSSched()触发调度,若新任务优先级更高,会立即抢占CPU运行。
  8. 退出临界区,返回创建结果。

1.2 核心任务管理API

API函数 核心功能 调用限制
OSTaskCreateExt() 扩展任务创建,支持栈检测、任务名、用户数据等扩展功能 任务级/启动前
OSTaskDel() 删除任务,将任务从就绪表/等待表移除,TCB归还空闲链表 仅任务级,禁止删除空闲任务
OSTaskSuspend() 强制挂起任务,任务进入挂起态,即使就绪也无法被调度 任务级,支持嵌套挂起
OSTaskResume() 恢复被挂起的任务,解除挂起态,就绪则写入就绪表 任务级
OSTaskChangePrio() 动态修改任务的优先级,同步更新就绪表和TCB预计算参数 任务级,禁止修改空闲任务优先级

2. 时间管理模块(os_time.c)

uC/OS-II的时间管理完全基于时钟节拍(SysTick),时钟节拍是固定频率的硬件定时器中断,是系统的“心跳”,默认频率10~1000Hz,频率越高实时性越好,但系统开销越大。

2.1 时钟节拍处理 OSTimeTick()

必须在时钟节拍中断服务函数中调用,是系统时间驱动的核心,源码执行流程:

  1. 检查内核是否已运行(OSRunning == OS_TRUE),未运行则直接返回。
  2. 调用OSIntEnter(),中断嵌套计数器OSIntNesting加1,标记进入中断上下文。
  3. 遍历OSTCBList双向链表中的所有任务TCB:
    • 若任务的OSTCBDly(延时/超时计数)大于0,将其减1。
    • 若减1后OSTCBDly == 0,且任务无挂起、无等待事件,将该任务从等待态转为就绪态,写入就绪表。
  4. 调用OSTimeTickHook()时钟节拍钩子函数,用户可自定义周期执行的逻辑。
  5. 调用OSIntExit(),中断嵌套计数器减1,若所有中断处理完毕且有更高优先级任务就绪,触发中断级任务切换。

2.2 任务延时 OSTimeDly()

任务主动放弃CPU的核心方式,将任务从就绪表移除,进入延时等待态,直到延时计数归零,源码执行流程:

  1. 合法性校验:禁止在中断中调用、禁止延时空闲任务、延时节拍数不能为0。
  2. 进入临界区,设置当前任务TCB的OSTCBDly = ticks(延时节拍数)。
  3. 将当前任务从就绪表中清除,转为等待态。
  4. 退出临界区,调用OSSched()触发调度,CPU切换到其他最高优先级就绪任务。
  5. 延时结束后,任务被OSTimeTick()置为就绪态,重新获得CPU使用权,函数返回。

2.3 核心时间管理API

API函数 核心功能 调用限制
OSTimeDlyHMSM() 按小时/分钟/秒/毫秒延时,内部转换为节拍数调用OSTimeDly() 仅任务级
OSTimeDlyResume() 强制结束任务的延时,提前将其置为就绪态 仅任务级
OSTimeGet()/OSTimeSet() 获取/设置系统时钟节拍计数器,用于计时 无限制

3. 内存管理模块(os_mem.c)

uC/OS-II采用固定大小内存分区管理机制,彻底解决了嵌入式系统中标准malloc/free带来的内存碎片、分配时间不确定的问题,实现了O(1)级别的内存分配与释放。

3.1 内存控制块 OS_MEM

每个内存分区对应一个OS_MEM结构体,管理一块连续的内存区域,划分为多个固定大小的内存块:

typedef struct os_mem {
    void    *OSMemAddr;         /* 内存分区的起始地址 */
    void    *OSMemFreeList;     /* 空闲内存块单向链表头指针 */
    INT32U  OSMemBlkSize;       /* 每个内存块的字节大小 */
    INT32U  OSMemNBlks;         /* 分区内总内存块数量 */
    INT32U  OSMemNFree;         /* 分区内当前空闲内存块数量 */
#if OS_MEM_NAME_EN > 0
    INT8U   *OSMemName;         /* 内存分区名,调试用 */
#endif
} OS_MEM;

3.2 核心内存管理API源码逻辑

API函数 核心功能 执行核心流程
OSMemCreate() 创建内存分区,将连续内存划分为固定大小的内存块 1. 校验内存地址、块大小、块数量合法性;2. 从OSMemFreeList分配空闲OS_MEM;3. 将内存划分为多个块,串联成空闲链表;4. 初始化OS_MEM所有成员,返回分区指针
OSMemGet() 从分区申请一个内存块,O(1)复杂度 1. 校验分区指针和空闲块数量;2. 关中断,从空闲链表头部取出第一个空闲块;3. 更新空闲链表头指针,空闲计数减1;4. 开中断,返回内存块指针
OSMemPut() 释放内存块到对应分区,O(1)复杂度 1. 校验分区和内存块指针合法性;2. 关中断,将内存块插入空闲链表头部;3. 空闲计数加1;4. 开中断,返回执行结果

4. 同步与通信模块

uC/OS-II提供了完整的任务间同步与通信机制,全部基于ECB统一管理,覆盖信号量、互斥锁、邮箱、消息队列、事件标志组。

4.1 信号量(os_sem.c)

用于任务间同步、共享资源互斥访问、中断与任务同步,核心是计数器机制,核心API:

  • OSSemCreate():创建信号量,初始化计数器,返回ECB指针。
  • OSSemPend():P操作,等待信号量,计数器>0则减1成功返回;否则任务进入等待态,加入事件等待表,设置超时时间。
  • OSSemPost():V操作,释放信号量,有等待任务则唤醒最高优先级任务,无等待任务则计数器加1。
  • OSSemDel():删除信号量,归还ECB到空闲链表。

4.2 互斥信号量(os_mutex.c)

专为共享资源互斥访问设计,基于优先级继承协议,彻底解决普通信号量的优先级反转问题:当低优先级任务持有互斥锁,被高优先级任务等待时,临时将低优先级任务的优先级提升到等待该锁的最高优先级,避免中间优先级任务抢占。核心API:OSMutexCreate()OSMutexPend()OSMutexPost()

4.3 消息邮箱与消息队列

  • 消息邮箱(os_mbox.c):实现单指针消息的传递,OSEventPtr指向消息内容,支持一对一/一对多通信,核心API:OSMboxCreate()OSMboxPend()OSMboxPost()
  • 消息队列(os_q.c):邮箱的增强版,支持多消息FIFO异步通信,基于环形缓冲区实现,支持多生产者-多消费者模型,核心API:OSQCreate()OSQPend()OSQPost()OSQPostFront()

5. 中断管理

uC/OS-II支持中断嵌套,最高嵌套层数由OS_MAX_INT_NESTING定义(默认255层),中断可抢占任务执行,中断服务结束后可触发任务切换,保证高优先级任务的实时响应。

5.1 核心中断管理API

  • OSIntEnter():必须在中断服务函数开头调用,将中断嵌套计数器OSIntNesting加1,标记进入中断上下文,禁止任务级调度。
  • OSIntExit():必须在中断服务函数末尾调用,将OSIntNesting减1,当计数器归零(所有中断处理完毕)时,检查是否有更高优先级任务被唤醒,若有则调用OSIntCtxSw()执行中断级任务切换。

5.2 中断服务函数标准模板

void  XXX_IRQHandler(void)
{
    OSIntEnter();  // 进入中断,标记嵌套层数
    /* 中断核心业务处理 */
    // 1. 清除中断标志位,避免重复触发
    // 2. 读取硬件数据、执行最小化的硬件操作
    // 3. 可调用OSSemPost()/OSQPost()唤醒任务,禁止调用Pend类阻塞函数
    OSIntExit();   // 退出中断,检查并触发任务切换
}

核心规则

  • 中断服务函数中绝对禁止调用阻塞类函数(如OSTimeDly()、OSSemPend()),中断上下文无法被挂起。
  • 中断服务函数应尽可能精简,耗时操作放到任务中执行,中断仅负责唤醒任务,保证中断响应延迟最小化。

五、临界区保护机制

uC/OS-II对就绪表、TCB、ECB等内核共享资源的访问,通过临界区保护,核心是关/开中断,避免多任务/中断并发访问导致的数据竞争与错乱。

核心宏定义在os_cpu.h中:

#define  OS_ENTER_CRITICAL()  // 进入临界区,关中断
#define  OS_EXIT_CRITICAL()   // 退出临界区,开中断

提供三种可配置的实现方式,通过OS_CRITICAL_METHOD宏选择:

  1. 方式1:直接关中断/开中断,实现最简单,但会破坏中断原有状态,不推荐使用。
  2. 方式2:进入临界区前,将中断状态保存到局部变量,关中断;退出时从局部变量恢复中断状态,不会破坏原有中断状态,最常用。
  3. 方式3:通过栈保存中断状态,进入前压栈保存中断状态,关中断;退出时出栈恢复,适用于编译器不支持局部变量保存状态寄存器的场景。

六、源码设计核心亮点与局限

1. 核心设计亮点

  • 硬实时确定性:基于位图的O(1)调度算法,调度时间恒定,与任务数量无关,完全满足硬实时系统要求。
  • 极致可移植性:严格的分层设计,硬件相关代码集中在3个文件,适配几乎所有8/16/32位MCU/MPU,移植门槛极低。
  • 极简高可靠:遵循“简洁即可靠”的设计理念,无动态内存分配,所有内核对象均为静态管理,彻底规避内存碎片、堆分配失败等非确定性风险。
  • 强可裁剪性:通过os_cfg.h的宏开关,可精细化裁剪不需要的模块,最小内核ROM占用可低至几KB,RAM占用几百字节,完美适配资源极度受限的嵌入式平台。
  • 代码规范性极强:命名规范统一(所有API以OS开头),注释详尽,结构清晰,是嵌入式RTOS入门与学习的最佳范本。

2. 核心局限

  • 优先级唯一:不支持同优先级多任务,一个优先级只能对应一个任务,无时间片轮转调度机制(该特性在uC/OS-III中支持)。
  • 任务数量受限:默认最多支持64个任务,最高可扩展至255个,不适合超大规模多任务场景。
  • 无多核支持:仅支持单核处理器,无法适配多核异构平台。
  • 功能相对精简:无进程、虚拟内存、文件系统、网络协议栈等高级组件,需额外搭配第三方组件实现。

七、源码学习建议

  1. 先搭环境跑通demo:先在STM32等主流MCU上完成uC/OS-II的移植,创建2个基础任务,验证任务切换、延时等基础功能,建立感性认知。
  2. 吃透核心数据结构:先深入理解OS_TCB、就绪表、OS_EVENT三大核心数据结构,这是读懂所有源码的基础。
  3. 抓住核心主线:重点攻克“任务创建-调度器-任务切换-时钟节拍”这一核心主线,理解RTOS多任务运行的本质。
  4. 分模块逐个突破:主线打通后,再分模块学习时间管理、内存管理、同步通信机制,逐个拆解源码逻辑。
  5. 结合调试跟踪执行:使用仿真器单步调试,跟踪任务创建、调度、切换的完整执行流程,直观理解内核运行机制。