UCOSIII 内核源码解析——os_core.c
os_core.c是OS的核心函数,里面有 OSInit(),OSSched(),任务级别调度函数
栈的设置:栈是单片机RAM里面的一段连续的内存空间,栈的大小由启动文件里面的代码配置
多任务系统中,每个任务都是独立的,互不干扰的,所以要为每个任务都分配独立的栈空间
多任务系统在一个统一的一个栈空间里分配好一个个独立空间
堆也是一样
注意:在uC/OS-III中,空闲任务的栈最小应该大于128
-
**OSInit()**调用初始化钩子函数,初始化系统全局变量,初始化优先级,就绪队列,任务管理器等相关变量
void OSInit (OS_ERR *p_err)//调用初始化钩子函数 (设置中断使用的堆栈地址)初始化系统全局变量 初始化Prio、ReadList { CPU_STK *p_stk; CPU_STK_SIZE size; #ifdef OS_SAFETY_CRITICAL if (p_err == (OS_ERR *)0) { OS_SAFETY_CRITICAL_EXCEPTION(); return; } #endif OSInitHook(); /* Call port specific initialization code 调用初始化钩子函数代码 */ OSIntNestingCtr = (OS_NESTING_CTR)0; /* Clear the interrupt nesting counter 清除中断嵌套计数器 */ OSRunning = OS_STATE_OS_STOPPED; /* Indicate that multitasking not started 多任务处理关闭 */ OSSchedLockNestingCtr = (OS_NESTING_CTR)0; /* Clear the scheduling lock counter 清除计划锁定计数器 */ OSTCBCurPtr = (OS_TCB *)0; /* Initialize OS_TCB pointers to a known state 将OS_TCB指针初始化为已知状态 */ OSTCBHighRdyPtr = (OS_TCB *)0; OSPrioCur = (OS_PRIO)0; /* Initialize priority variables to a known state 将优先级变量初始化为已知状态 */ OSPrioHighRdy = (OS_PRIO)0; OSPrioSaved = (OS_PRIO)0; #if OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u OSSchedLockTimeBegin = (CPU_TS)0; OSSchedLockTimeMax = (CPU_TS)0; OSSchedLockTimeMaxCur = (CPU_TS)0; #endif #ifdef OS_SAFETY_CRITICAL_IEC61508 OSSafetyCriticalStartFlag = DEF_FALSE; #endif #if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u OSSchedRoundRobinEn = DEF_FALSE; OSSchedRoundRobinDfltTimeQuanta = OSCfg_TickRate_Hz / 10u; #endif if (OSCfg_ISRStkSize > (CPU_STK_SIZE)0) { p_stk = OSCfg_ISRStkBasePtr; /* Clear exception stack for stack checking.清除异常堆栈以进行堆栈检查。 */ if (p_stk != (CPU_STK *)0) { size = OSCfg_ISRStkSize; while (size > (CPU_STK_SIZE)0) { /* 这里是对堆栈初始化,清零 同时 堆栈指针p_stk会指向堆栈数组的尾部*/ size--; *p_stk = (CPU_STK)0; p_stk++; } } } #if OS_CFG_APP_HOOKS_EN > 0u OS_AppTaskCreateHookPtr = (OS_APP_HOOK_TCB )0; /* Clear application hook pointers */ OS_AppTaskDelHookPtr = (OS_APP_HOOK_TCB )0; OS_AppTaskReturnHookPtr = (OS_APP_HOOK_TCB )0; OS_AppIdleTaskHookPtr = (OS_APP_HOOK_VOID)0; OS_AppStatTaskHookPtr = (OS_APP_HOOK_VOID)0; OS_AppTaskSwHookPtr = (OS_APP_HOOK_VOID)0; OS_AppTimeTickHookPtr = (OS_APP_HOOK_VOID)0; #endif #if OS_CFG_TASK_REG_TBL_SIZE > 0u OSTaskRegNextAvailID = (OS_REG_ID)0; #endif OS_PrioInit(); /* Initialize the priority bitmap table 初始化优先级位图表 */ OS_RdyListInit(); /* Initialize the Ready List 初始化就绪列表 */ #if OS_CFG_FLAG_EN > 0u /* Initialize the Event Flag module 初始化事件标志模块 */ OS_FlagInit(p_err); if (*p_err != OS_ERR_NONE) { return; } #endif #if OS_CFG_MEM_EN > 0u /* Initialize the Memory Manager module 初始化内存管理器模块 */ OS_MemInit(p_err); if (*p_err != OS_ERR_NONE) { return; } #endif #if (OS_MSG_EN) > 0u /* Initialize the free list of OS_MSGs */ OS_MsgPoolInit(p_err); if (*p_err != OS_ERR_NONE) { return; } #endif #if OS_CFG_MUTEX_EN > 0u /* Initialize the Mutex Manager module 初始化互斥管理器模块 */ OS_MutexInit(p_err); if (*p_err != OS_ERR_NONE) { return; } #endif #if OS_CFG_Q_EN > 0u OS_QInit(p_err); /* Initialize the Message Queue Manager module 初始化消息队列管理器模块 */ if (*p_err != OS_ERR_NONE) { return; } #endif #if OS_CFG_SEM_EN > 0u /* Initialize the Semaphore Manager module 初始化信号量管理器模块 */ OS_SemInit(p_err); if (*p_err != OS_ERR_NONE) { return; } #endif #if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u) OS_TLS_Init(p_err); /* Initialize Task Local Storage, before creating tasks 在创建任务之前初始化任务本地存储 */ if (*p_err != OS_ERR_NONE) { return; } #endif OS_TaskInit(p_err); /* Initialize the task manager 初始化任务管理器 */ if (*p_err != OS_ERR_NONE) { return; } #if OS_CFG_ISR_POST_DEFERRED_EN > 0u OS_IntQTaskInit(p_err); /* Initialize the Interrupt Queue Handler Task 初始化中断队列处理程序任务 */ if (*p_err != OS_ERR_NONE) { return; } #endif OS_IdleTaskInit(p_err); /* Initialize the Idle Task 初始化空闲任务 */ if (*p_err != OS_ERR_NONE) { return; } OS_TickTaskInit(p_err); /* Initialize the Tick Task 初始化滴答定时器任务 */ if (*p_err != OS_ERR_NONE) { return; } #if OS_CFG_STAT_TASK_EN > 0u /* Initialize the Statistic Task 初始化统计任务 */ OS_StatTaskInit(p_err); if (*p_err != OS_ERR_NONE) { return; } #endif #if OS_CFG_TMR_EN > 0u /* Initialize the Timer Manager module 初始化计时器管理器模块 */ OS_TmrInit(p_err); if (*p_err != OS_ERR_NONE) { return; } #endif #if OS_CFG_DBG_EN > 0u OS_Dbg_Init(); #endif OSCfg_Init(); }
-
OSIntExit()中断级调度器函数,在OS_CPU_SysTickHandler()中用到了,当中断处理完成之后,并且没有中断嵌套,查看是否有新的高优先级任务,如果有启动任务调度
代码意思:如果内核没有工作则退出,如果中断嵌套为零则退出,反则自减一,减后如果中断嵌套计数器依旧大于零,则说明还有中断,调用CPU_INT_EN()进入中断,不需要任务切换,任务调度器依旧被锁,则继续进入中断,判断完后获得最高优先级的就绪列表中的任务,增加任务切换计数器的值并跟踪任务切换总数,最后执行中断级任务切换
/* 如果内核没有工作则退出,如果中断嵌套为零则退出,反则自减一,减后如果中断嵌套计数器依旧大 于零,则说明还有中断,调用CPU_INT_EN()进入中断,不需要任务切换。任务调度器依旧被锁,则 继续进入中断,判断完后获得最高优先级的就绪列表中的任务,增加任务切换计数器的值并跟踪任务 切换总数,最后执行中断级任务切换。 */ void OSIntExit (void) { CPU_SR_ALLOC(); if (OSRunning != OS_STATE_OS_RUNNING) { /* Has the OS started? */ return; /* No */ } CPU_INT_DIS(); if (OSIntNestingCtr == (OS_NESTING_CTR)0) { /* Prevent OSIntNestingCtr from wrapping */ CPU_INT_EN(); return; } OSIntNestingCtr--; if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* ISRs still nested? */ CPU_INT_EN(); /* Yes */ return; } if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Scheduler still locked? 调度程序依旧被锁定 */ CPU_INT_EN(); /* Yes */ return; } OSPrioHighRdy = OS_PrioGetHighest(); /* Find highest priority 寻找最高优先级 */ OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr; /* Get highest priority task ready-to-run 获得最高优先级的任务准备执行 */ if (OSTCBHighRdyPtr == OSTCBCurPtr) { /* Current task still the highest priority? 查看当前任务是否为最高优先级 */ CPU_INT_EN(); /* Yes */ return; } #if OS_CFG_TASK_PROFILE_EN > 0u OSTCBHighRdyPtr->CtxSwCtr++; /* Inc. # of context switches for this new task 增加新任务的上下文切换次数 */ #endif OSTaskCtxSwCtr++; /* Keep track of the total number of ctx switches 跟踪 ctx 开关的总数 */ #if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u) OS_TLS_TaskSw(); #endif OSIntCtxSw(); /* Perform interrupt level ctx switch 执行中断级ctx切换 */ CPU_INT_EN(); }
在中断级调度器中真正完成任务切换的就是中断级任务切换函数OSIntCtxSw(),由于在进入中断前线程就已经保存过了,所以它不像OSCtxSW(任务级调度器)需要先保存现场,只需要调用OSIntEnter()函数其主要功能就是为OSIntNestingCtr自加一,这个变量的意思是中断嵌套计数器,用来纪录中断嵌套的深度
-
OSSched():任务级调度,与中断调度器有所不同,这个函数是在任务中调用,目的是切换到更高优先级的任务。真正起到切换的函数OS_TASK_SW(),OS_TASK_SW()就是函数OSCtxSW(),**OSCtxSW()**是os_cpu_a.asm中用汇编写的一段代码,其主要作用就是将当前任务的CPU寄存器的值保存在任务堆栈中,就是保存现场,之后将新任务的堆栈指针加载到CPU的堆栈指针寄存器中,就实现了任务切换,最后还要从新任务的堆栈中恢复CPU寄存器的值
void OSSched (void)//在任务中调用,切换到更高优先级的任务 { CPU_SR_ALLOC(); if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* ISRs still nested? */ return; /* Yes ... only schedule when no nested ISRs */ } if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Scheduler locked? */ return; /* Yes */ } CPU_INT_DIS(); OSPrioHighRdy = OS_PrioGetHighest(); /* Find the highest priority ready 找到最高优先级的就绪任务 */ OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr; if (OSTCBHighRdyPtr == OSTCBCurPtr) { /* Current task is still highest priority task? 当前任务是否还是最高优先级 */ CPU_INT_EN(); /* Yes ... no need to context switch */ return; } #if OS_CFG_TASK_PROFILE_EN > 0u OSTCBHighRdyPtr->CtxSwCtr++; /* Inc. # of context switches to this task 上下文切换到此任务 */ #endif OSTaskCtxSwCtr++; /* Increment context switch counter 递增上下文切换计数器 */ #if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u) OS_TLS_TaskSw(); #endif OS_TASK_SW(); /* Perform a task level context switch */ CPU_INT_EN(); }
这是任务级调度器,结合前面的中端机调度器我们可以知道有两种切换任务的函数为OSCtxSW(),OSIntCtxSW(),他们才是真正进行任务切换的,从以下代码看出他们两个主要做的事情就是触发PendSV异常,也就是一段汇编代码
/* 这里看起来一样是因为Cortex-m3系列进入异常时自动压栈了R0-R3、R12、LR、PSR、PC寄存器, 并在返回时自动弹出,不需要用汇编来写了。 */ OSCtxSw ;保存现场,并产生PendSV切换任务 LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch) LDR R1, =NVIC_PENDSVSET STR R1, [R0] BX LR OSIntCtxSw 因为是中断级调度,所以之前已经保护过现场了,直接产生PendSV切换任务 LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch) LDR R1, =NVIC_PENDSVSET STR R1, [R0] BX LR
;PendSV_Handler PendSV_Handler CPSID I ; 关所有中断 MRS R0, PSP ; PSP是当前进程堆栈的指针,将PSP赋值给R0 CBZ R0, PendSV_Handler_Nosave ; 如果R0为0时跳转到PendSV_Handler_Nosave ;已经存在任务 保存正在运行的任务 SUBS R0, R0, #0x20 ; 偏移0x20的位置用来保存R4至R11 STM R0, {R4-R11} ;将剩下的R4至R11寄存器保存在此进程的堆栈中 LDR R1, =p_OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP; 即OSTCBCur->OSTCBStkPtr这个保存当前的栈尾,以便下次弹出 LDR R1, [R1] STR R0, [R1] ; R0 is SP of process being switched out ;此时,整个上下文的过程已经被保存 ;—————————切换上下文————————————— ; 实现 OSTCBCurPtr = OSTCBHighRdyPtr ; 把下一个要运行的任务的堆栈OSPrioHighRdy加载到 CPU 寄存器中 PendSV_Handler_Nosave PUSH {R14} ; Save LR exc_return value LDR R0, =OSTaskSwHook ; OSTaskSwHook(); 这里用于用户扩展 BLX R0 POP {R14} ;将最高优先级任务赋给当前优先级 LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy; LDR R1, =OSPrioHighRdy ;将当前优先级变量指向最高优先级 LDRB R2, [R1] STRB R2, [R0] LDR R0, =p_OSTCBCur ; OSTCBCur = OSTCBHighRdy; LDR R1, =p_OSTCBHighRdy ;TCB表也一样 LDR R2, [R1] STR R2, [R0] ;到这里,[R2]保存的是新的进程的堆栈指针SP LDR R0, [R2] ; SP = OSTCBHighRdy->OSTCBStkPtr; LDM R0, {R4-R11} ; 弹出其它寄存器,和前面的是一个逆过程 ADDS R0, R0, #0x20 ;和前面的逆过程对比可知 MSR PSP, R0 ; 将R0中的SP赋值给PSP寄存器 ORR LR, LR, #0x04 ; 确保异常返回时使用进程堆栈 CPSIE I ;开中断 BX LR ; 异常返回将恢复那些自动出栈的剩余寄存器 ;跳到当前任务 开始运行 ;这里是系统默认中断做的事:主要工作有判断是否第一次运行、将最高级任务赋给当前任务,然后将放到cpu寄存器运行,最高优先级任务开始运行。
PenSV异常服务要完成两个工作保存上文和切换下文,PenSV还可以有OSStarHighRdy触发。uCOS操作系统里,各种异常/中断的优先级关系:SYSTICK异常 > 中断 > PendSv异常 ,所以PenSV会自动的延迟上下文切换的请求,直到他的中断都处理完了后才触发,这保证了实时性内核的实时性也保证了操作系统的正常轮转。
PenSV的配置是在OSStartHighRdy()中完成的,这个函数用于启动任务切换,即配置PendSV,触发PendSV,在PendSV异常服务函数中完成任务切换
-
OSStartHighRdy()
;常量说明: NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制及状态寄存器 SCB_ICSR。 NVIC_SYSPRI14 EQU 0xE000ED22 ; 系统优先级寄存器 SCB_SHPR3: ; bit16~23 NVIC_PENDSV_PRI EQU 0xFF ; PendSV 优先级的值(最低)。 NVIC_PENDSVSET EQU 0x10000000 ; 触发 PendSV 异常的值 Bit28: PENDSVSET /* 开始第一次上下文切换 配置 PendSV 异常的优先级为最低 在开始第一次上下文切换之前,设置 psp=0 触发 PendSV 异常,开始上下文切换 */ ;******************************************************************* OSStartHighRdy LDR R0, = NVIC_SYSPRI14 ; 设置 PendSV 异常优先级为最低(1) LDR R1, = NVIC_PENDSV_PRI STRB R1, [R0] MOVS R0, #0 ; 设置 psp 的值为 0,开始第一次上下文切换 (2) MSR PSP, R0 LDR R0, =NVIC_INT_CTRL ; 触发 PendSV 异常(3) LDR R1, =NVIC_PENDSVSET STR R1, [R0] CPSIE I ; 启用总中断, NMI 和 HardFault 除外(4) OSStartHang BOSStartHang ; 程序应永远不会运行到这里
-
配置PenSV的优先级为0xFF,最低。在uC/OS-III中,上下文切换是在PendSV异常服务程序中执行的,配置PendSV的优先级为最低,从而消除了在中断服务程序中执行上下文切换的可能
-
设置PSP的值为0,开始第一个任务切换。在任务中,使用栈指针都是PSP,后面如果判断出PSP为0,则表示第一次任务切换
-
触发PenSV异常,如果中断启用且有编写PenSV异常函数的话,则内核会响应PenSV异常,去执行PendSV异常服务函数
-
开中断,因为有些用户在main()函数开始会关掉中断,等全部初始化完成后,在启动OS的时候才开中断
CPSID I ; PRIMASK=1; //关中断 CPSIE I ; PRIMASK=0; //开中断 CPSID F ; FAULTMASK=1; // 关异常 CPSIE F ; FAULTMASK=0; //开异常
OSStartHighRdy()函数有OSStart()调用,OSStart()函数用来启用多任务,如果多任务开始正常进入OSStartHighRdy()函数触发PendSV异常就会进行任务切换
-
-
**OSStart(OS_ERR *p_err) **: 此函数由于启动多任务,让ucosiii接管任务,在调用OSStart()之前,必须调用OSInit()并且必须至少创建一项申请任务
系统使用全局变量OSRunning来表示当前的系统运行状态,刚开始系统初始化的时候停止状态,进入if首先寻找最高优先级的就绪任务,再OSStartHighRdy()用于启动任务切换,操作系统开始执行最高优先级的任务
void OSStart (OS_ERR *p_err) { #ifdef OS_SAFETY_CRITICAL if (p_err == (OS_ERR *)0) { OS_SAFETY_CRITICAL_EXCEPTION(); return; } #endif if (OSRunning == OS_STATE_OS_STOPPED) { OSPrioHighRdy = OS_PrioGetHighest(); /* Find the highest priority 寻找最高优先级的任务*/ OSPrioCur = OSPrioHighRdy; /*将最高优先级付给当前优先级*/ OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr; /* 从TCB链表中取出最高优先级 赋给 最高级TCB */ OSTCBCurPtr = OSTCBHighRdyPtr;//最高优先级赋给 当前优先级 OSRunning = OS_STATE_OS_RUNNING; //将OSRunning标记为1表示已经开始运行内核 OSStartHighRdy(); //一长段汇编代码 /* Execute target specific code to start task 执行目标特定代码以启动任务*/ *p_err = OS_ERR_FATAL_RETURN; /* OSStart() is not supposed to returnOSStart() 不应该返回*/ } else { *p_err = OS_ERR_OS_RUNNING; /* OS is already running 操作系统已经在运行*/ } }
哪些会发送任务切换(任务调度)
- 使用延时函数OSTimeDly()或者OSTimeDlyHMSM();
- OSStart()启动任务调度器
- 删除任务
- 任务通过调用OSTaskSuspend()将自身挂起
- 任务解挂某个挂起任务
- 用户调用OSSched();
- 释放信号量或者发送消息,也可通过配置相应的参数不发生任务调度
- 任务等待的事情还没有发生(等待信号量,消息队列等)
- 任务取消等待
- 删除一个内核对象
- 任务改变自身的优先级或者其他任务的优先级
- 退出所有的嵌套中断
- 通过OSSchedUnlock()给调度器解锁
- 任务调用OSSchedRoundRobinYield()放弃其执行时间片
-
*OSSchedLock(OS_ERR p_err);
允许这个应用程序防止上下文切换,直到已经准备好允许上下文切换
void OSSchedLock (OS_ERR *p_err) //当任务调度发生时,阻止任务调度,直到调用了 OSSchedUnlock()。主要是 OSSchedLockNestingCtr++; { CPU_SR_ALLOC(); CPU_CRITICAL_ENTER(); OSSchedLockNestingCtr++; /* Increment lock nesting level 递增嵌套级别 */ #if OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u OS_SchedLockTimeMeasStart(); #endif CPU_CRITICAL_EXIT(); *p_err = OS_ERR_NONE; } #if OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u void OS_SchedLockTimeMeasStart (void) { if (OSSchedLockNestingCtr == 1u) { OSSchedLockTimeBegin = CPU_TS_TmrRd(); } }
进入临界区,递增嵌套级别(主要目的),判断是否设置了测量锁定时间的代码,有的话进入OS_SchedLockTimeMeasStart(),这个函数用于测量计划程序锁定的峰值时间,CPU_TS_TmrRd()是一个16位的计时器,不希望锁定调度程序的时间超过65536,这不是个好事,开头的CPU_SR_ALLOC()是定义的一个局部变量,用来存CPU关闭前的中断状态。
-
*OSSchedUnlock(OS_ERR p_err)
void OSSchedUnlock (OS_ERR *p_err) { CPU_CRITICAL_ENTER(); OSSchedLockNestingCtr--; /* Decrement lock nesting level 递减锁嵌套级别 */ if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { CPU_CRITICAL_EXIT(); /* Scheduler is still locked 调度器依旧被锁 */ *p_err = OS_ERR_SCHED_LOCKED; return; } #if OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u OS_SchedLockTimeMeasStop(); #endif CPU_CRITICAL_EXIT(); /* Scheduler should be re-enabled */ OSSched(); /* Run the scheduler */ *p_err = OS_ERR_NONE; } void OS_SchedLockTimeMeasStop (void) { CPU_TS_TMR delta; if (OSSchedLockNestingCtr == (OS_NESTING_CTR)0) { /* Make sure we fully un-nested scheduler lock */ delta = CPU_TS_TmrRd() /* Compute the delta time between begin and end */ - OSSchedLockTimeBegin; if (OSSchedLockTimeMax < delta) { /* Detect peak value */ OSSchedLockTimeMax = delta; } if (OSSchedLockTimeMaxCur < delta) { /* Detect peak value (for resettable value) */ OSSchedLockTimeMaxCur = delta; } } }
进入临界区,递减锁嵌套级别,判断如果还有锁嵌套,异常退出,反之判断是否设置测量锁定时间的代码,有的话进入OS_SchedLockTimeMeasStop函数计算出调度器被锁时间,再判断是否有效,退出临界区,在调度器被锁期间可能有就绪任务所以通过调用OSSched()函数打开调度
-
*OSSchedRoundRobinCfg(CPU_BOOLEAN en,OS_TICK dflt_time_quanta,OS_ERR p_err)
调用该函数改变轮询调度参数,如是否打开时间片轮询,时间片大小的设置。代码的流程是如果开启了时间片轮询,进入临界区判断参数en是否开启了时间片轮询,1开启,0关闭。判断参数dflt_time_quanta是否大于0,大于0则时间大小为dflt__time_quanta,小于0则选取系统默认的时间片大小(OS_TICK)(OSCfg_TickRate_HZ/(OS_RATE_HZ)10)。退出临界区,并正确返回
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u //当启动了相同任务优先级时间片轮询(OS_CFG_SCHED_ROUND_ROBIN_EN==1 ) void OSSchedRoundRobinCfg (CPU_BOOLEAN en, OS_TICK dflt_time_quanta, OS_ERR *p_err) { // 决定是否开启时间片轮询,设置任务的默认时间片时间(多少个SysTick),默认为 OSCfg_TickRate_Hz / 10 CPU_SR_ALLOC(); #ifdef OS_SAFETY_CRITICAL if (p_err == (OS_ERR *)0) { OS_SAFETY_CRITICAL_EXCEPTION(); return; } #endif CPU_CRITICAL_ENTER(); if (en != DEF_ENABLED) { //是否开启时间片轮循 OSSchedRoundRobinEn = DEF_DISABLED; } else { OSSchedRoundRobinEn = DEF_ENABLED; } if (dflt_time_quanta > (OS_TICK)0) { //设置任务的默认时间片,如果未设置就选取默认值 OSSchedRoundRobinDfltTimeQuanta = dflt_time_quanta; } else { OSSchedRoundRobinDfltTimeQuanta = (OS_TICK)(OSCfg_TickRate_Hz / (OS_RATE_HZ)10); } CPU_CRITICAL_EXIT(); *p_err = OS_ERR_NONE; } #endif
-
*OSSchedRoundRobinYield(OS_ERR p_err)
该函数在时间片到期前执行完毕,被调用放弃CPU。进入临界区前需要判断,同优先级的任务数是否大于1,轮询是否启用,调度器是否被锁定,判断是否在中断中。判断完后,将之前任务控制块移至就绪列表尾部,将执行的权利给就绪列表的表头指向的任务,确定要使用的时间片大小,退出临界区
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u void OSSchedRoundRobinYield (OS_ERR *p_err) {// 当一个任务在给定的时间片之前完成了任务,可以调用此函数放弃CPU让给相同优先级的等待运行的任务。即放弃未使用的时间片给同等级的任务 OS_RDY_LIST *p_rdy_list; OS_TCB *p_tcb; CPU_SR_ALLOC(); #ifdef OS_SAFETY_CRITICAL if (p_err == (OS_ERR *)0) { OS_SAFETY_CRITICAL_EXCEPTION(); return; } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* Can't call this function from an ISR */ *p_err = OS_ERR_YIELD_ISR; return; } #endif if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Can't yield if the scheduler is locked */ *p_err = OS_ERR_SCHED_LOCKED; return; } if (OSSchedRoundRobinEn != DEF_TRUE) { /* Make sure round-robin has been enabled */ *p_err = OS_ERR_ROUND_ROBIN_DISABLED; return; } CPU_CRITICAL_ENTER(); p_rdy_list = &OSRdyList[OSPrioCur]; /* Can't yield if it's the only task at that priority 如果这是该优先级队列的唯一任务,则无法让步 */ if (p_rdy_list->NbrEntries < (OS_OBJ_QTY)2) { CPU_CRITICAL_EXIT(); *p_err = OS_ERR_ROUND_ROBIN_1; return; } OS_RdyListMoveHeadToTail(p_rdy_list); /* Move current OS_TCB to the end of the list 将当前 OS_TCB 移动到列表末尾,从而实现任务切换 */ p_tcb = p_rdy_list->HeadPtr; /* Point to new OS_TCB at head of the list 指向列表头部的新 OS_TCB */ if (p_tcb->TimeQuanta == (OS_TICK)0) { /* See if we need to use the default time slice 看看我们是否需要使用默认的时间片*/ p_tcb->TimeQuantaCtr = OSSchedRoundRobinDfltTimeQuanta;//使用默认时间片 } else { p_tcb->TimeQuantaCtr = p_tcb->TimeQuanta; /* Load time slice counter with new time 用新时间加载时间片计数器 */ } CPU_CRITICAL_EXIT(); OSSched(); /* Run new task 执行任务调度 */ *p_err = OS_ERR_NONE;//正确执行 } #endif
再看看OS_RdyListMoveHeadToTail(p_rdy_list)函数时如何实现的。调用此函数可将列表的当前头部移动到列表的尾部。0和1是没有任务和只有一个任务,不需要移动,如果是两个任务则将任务控制块的指针进行交换即可,这里的调换实际就是双向链表的删除插入操作。
void OS_RdyListMoveHeadToTail (OS_RDY_LIST *p_rdy_list) { OS_TCB *p_tcb1; OS_TCB *p_tcb2; OS_TCB *p_tcb3; switch (p_rdy_list->NbrEntries) { case 0: case 1: break; //0和1没有任务和只有一个任务,不需要移动 case 2: /* SWAP the TCBs */ p_tcb1 = p_rdy_list->HeadPtr; /* Point to current head */ p_tcb2 = p_rdy_list->TailPtr; /* Point to current tail */ p_tcb1->PrevPtr = p_tcb2; p_tcb1->NextPtr = (OS_TCB *)0; p_tcb2->PrevPtr = (OS_TCB *)0; p_tcb2->NextPtr = p_tcb1; p_rdy_list->HeadPtr = p_tcb2; p_rdy_list->TailPtr = p_tcb1; break; //前后调换 default: /*仅当列表中有两个以上的OS_TCB时才移动*/ p_tcb1 = p_rdy_list->HeadPtr; /*指向当前头部*/ p_tcb2 = p_rdy_list->TailPtr; /*指向当前尾部*/ p_tcb3 = p_tcb1->NextPtr; /*指向新的列表头*/ p_tcb3->PrevPtr = (OS_TCB *)0; /*调整新列表头的后链接*/ p_tcb1->NextPtr = (OS_TCB *)0; /*调整新列表尾部的前向链接*/ p_tcb1->PrevPtr = p_tcb2; /*调整新列表尾部的后链接*/ p_tcb2->NextPtr = p_tcb1; /*调整旧列表尾部的前向链接*/ p_rdy_list->HeadPtr = p_tcb3; /*调整新列表的头和尾指针*/ p_rdy_list->TailPtr = p_tcb1; break; } }
-
OS_IdleTask(void *p_arg)
并没有做什么,里面的OSIdleTaskHook()函数值得看,是一个空闲任务的钩子函数,在OSIdleTaskHook()中最终调用的是函数App_OS_IdleTaskHook(),也就是如果我们想要在空闲任务的钩子函数中做其他处理就需要将代码写在App_OS_IdleTaskHook()。
void OS_IdleTask (void *p_arg) //空闲任务
{
CPU_SR_ALLOC();
p_arg = p_arg;
/* Prevent compiler warning for not using 'p_arg'防止编译器警告不使用'p_arg'*/
while (DEF_ON) {
CPU_CRITICAL_ENTER();
OSIdleTaskCtr++;
#if OS_CFG_STAT_TASK_EN > 0u//大于0则启用统计任务
OSStatTaskCtr++;
#endif
CPU_CRITICAL_EXIT();
OSIdleTaskHook();
/* Call user definable HOOK */
}
}
void OSIdleTaskHook (void)
{
#if OS_CFG_APP_HOOKS_EN > 0u //OS_CFG_APP_HOOKSEN置1,即允许使用空闲任务的钩子函数,
if (OS_AppIdleTaskHookPtr != (OS_APP_HOOK_VOID)0) {
(*OS_AppIdleTaskHookPtr)();//当使能空闲任务的钩子函数以后每次进入空闲任务就会调用指针OS_AppIdleTaskHookPtr所指向的函数。
}
#endif
}
-
OS_IdleTaskInit(OS_ERR *p_err)
空闲任务的创建即初始化,空闲任务的初始化在OSInit()被调用,意味着在系统还没有启动之前空闲任务就已经创建好,必须创建空闲任务(不需要我们手动创建),因为CPU不能停下来,他有最低的优先级
void OS_IdleTaskInit (OS_ERR *p_err) //空闲任务初始化--创建空闲任务 { #ifdef OS_SAFETY_CRITICAL if (p_err == (OS_ERR *)0) { OS_SAFETY_CRITICAL_EXCEPTION(); return; } #endif OSIdleTaskCtr = (OS_IDLE_CTR)0; /* ---------------- CREATE THE IDLE TASK ---------------- */ OSTaskCreate((OS_TCB *)&OSIdleTaskTCB, (CPU_CHAR *)((void *)"uC/OS-III Idle Task"), (OS_TASK_PTR)OS_IdleTask, (void *)0, (OS_PRIO )(OS_CFG_PRIO_MAX - 1u), //最低优先级 (CPU_STK *)OSCfg_IdleTaskStkBasePtr, (CPU_STK_SIZE)OSCfg_IdleTaskStkLimit, (CPU_STK_SIZE)OSCfg_IdleTaskStkSize, (OS_MSG_QTY )0u, (OS_TICK )0u, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS), (OS_ERR *)p_err); }