在实时操作系统 uC/OS-III 中,OSStart
函数是启动多任务处理的关键步骤。它让 uC/OS-III 管理已经创建的任务。在调用 OSStart
之前,必须先调用 OSInit
并至少创建一个应用任务。本文将详细解析 OSStart
函数的工作原理。
1. OSStart 函数概述
OSStart
函数主要完成以下任务:
- 检查操作系统是否已初始化
- 计算内核任务的数量
- 检查是否有应用任务已创建
- 设置最高优先级任务
- 启动最高优先级任务
- 错误处理
2. OSStart 函数流程图
3. OSStart 函数详细解析
3.1 检查操作系统是否已初始化
在 OSStart
函数中,首先检查操作系统是否已初始化。如果操作系统未初始化,则返回错误。
if (OSInitialized != OS_TRUE) {
*p_err = OS_ERR_OS_NOT_INIT;
return;
}
3.2 计算内核任务的数量并检查是否有应用任务已创建
计算内核任务(如统计任务、定时器任务和空闲任务)的数量,并检查是否有应用任务创建
kernel_task_cnt = 0u;
#if (OS_CFG_STAT_TASK_EN > 0u)
kernel_task_cnt++;
#endif
#if (OS_CFG_TMR_EN > 0u)
kernel_task_cnt++;
#endif
#if (OS_CFG_TASK_IDLE_EN > 0u)
kernel_task_cnt++;
#endif
if (OSTaskQty <= kernel_task_cnt) {
*p_err = OS_ERR_OS_NO_APP_TASK;
return;
}
3.3. 设置最高优先级任务
设置当前最高优先级任务
if (OSRunning == OS_STATE_OS_STOPPED) {
OSPrioHighRdy = OS_PrioGetHighest();
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
OSTCBCurPtr = OSTCBHighRdyPtr;
OSRunning = OS_STATE_OS_RUNNING;
}
3.4 调用OSStartHighRdy启动就绪任务
OSStartHighRdy(); /* Execute target specific code to start task */
*p_err = OS_ERR_FATAL_RETURN;
4. 如何启动第一个就绪任务
调用OSStartHighRdy
启动第一个就绪任务。该函数的主要作用是触发一个 PendSV
异常,从而引发上下文切换,开始执行第一个任务。函数的主要步骤包括设置 PendSV
异常优先级、初始化堆栈指针、获取当前高优先级任务、恢复任务上下文等
4.1 OSStartHighRdy 流程图
4.2 详细解析
4.2.1 禁止中断
通过禁止中断(CPSID I 指令),防止在上下文切换期间发生中断
CPSID I
4.2.2 设置 PendSV 异常优先级
设置 PendSV 异常优先级为最低优先级,确保任务切换不会中断其他更高优先级的中断服务程序
MOV32 R0, NVIC_SYSPRI14
MOV32 R1, NVIC_PENDSV_PRI
STRB R1, [R0]
4.2.3 初始化 PSP
将 PSP(进程堆栈指针)设置为 0,用于初始上下文切换调用
MOVS R0, #0
MSR PSP, R0
4.2.4 初始化 MSP
将 MSP(主堆栈指针/中断堆栈指针)初始化为 OS_CPU_ExceptStkBase
MOV32 R0, OS_CPU_ExceptStkBase
LDR R1, [R0]
MSR MSP, R1
4.2.5 调用 OSTaskSwHook
调用 OSTaskSwHook 函数,用于执行其他操作
BL OSTaskSwHook
4.2.6 设置当前优先级
设置当前优先级(OSPrioCur)为最高优先级(OSPrioHighRdy)
MOV32 R0, OSPrioCur
MOV32 R1, OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
4.2.7 设置当前任务控制块指针
设置当前任务控制块指针(OSTCBCurPtr)为最高优先级任务控制块指针(OSTCBHighRdyPtr)
MOV32 R0, OSTCBCurPtr
MOV32 R1, OSTCBHighRdyPtr
LDR R2, [R1]
STR R2, [R0]
4.2.8 加载 PSP
从最高优先级任务控制块指针(OSTCBHighRdyPtr)中加载新的进程堆栈指针(PSP)
LDR R0, [R2]
MSR PSP, R0
4.2.9 更新 CONTROL 寄存器
更新 CONTROL 寄存器,确保使用 PSP 作为堆栈指针并禁用浮点协处理器。
MRS R0, CONTROL
ORR R0, R0, #2
BIC R0, R0, #4
MSR CONTROL, R0
ISB
4.2.10 恢复任务上下文
从堆栈中恢复任务的寄存器上下文,使能中断,并跳转到任务的程序计数器
LDMFD SP!, {R4-R11, LR}
LDMFD SP!, {R0-R3}
LDMFD SP!, {R12, LR}
LDMFD SP!, {R1, R2}
CPSIE I
BX R1
5. 为什么设置PendSV异常优先级为最低优先级
PendSV通常用于实现上下文切换,即从一个任务切换到另一个任务。以下是设置 PendSV 异常优先级为最低优先级的几个原因:
5.1. 确保任务切换在所有其他中断处理之后进行
PendSV 的主要用途是触发任务切换。通过将 PendSV 的优先级设置为最低,可以确保它在所有其他中断处理完成之后才被执行。以确保系统中更高优先级的中断能够及时处理,不会被任务切换延迟。
5.2. 避免中断嵌套
如果 PendSV 异常的优先级高于某些中断,那么在这些中断处理过程中可能会发生任务切换,从而导致中断嵌套和处理复杂性增加。将 PendSV 的优先级设置为最低,可以避免这种情况,确保任务切换在所有中断处理完成后进行,简化了中断处理逻辑。
5.3. 优化系统实时性
系统的实时性要求某些关键中断必须尽快得到响应。通过将 PendSV 的优先级设置为最低,可以确保这些关键中断优先得到处理,从而提高系统的实时性。
5.4. 避免中断响应时间过长
在嵌入式系统中,某些中断(如定时器中断、外部事件中断等)的响应时间非常关键。如果 PendSV 的优先级高于这些中断,那么在处理任务切换时可能会延迟这些中断的响应,导致系统性能下降或数据丢失。将 PendSV 的优先级设置为最低,可以避免这种情况,确保关键中断得到及时响应。