就绪表及优先级相关计算
任务就绪表由2个变量表示
(
OS_EXT INT8U OSRdyGrp; /* Ready list group */
OS_EXT INT8U OSRdyTbl[OS_RDY_TBL_SIZE]; /* Table of tasks which are ready to run */
)
OSRdyGrp:共8bits,用于分组标志
OSRdyTbl[]:共8个字节,字节的每位表示任务的组内标志(每个字节表示一组)。
在OSRdyGrp中,任务按优先级分组,8个任务为一组。OSRdyGrp中的每一位表示8组任务中每一组中是否有进入就绪态的任务。任务进入就绪态时,就绪表OSRdyTbl[]中的相应元素的相应位也置位。就绪表OSRdyTbl[]数组的大小取决于OS_LOWEST_PR1O(见文件OS_CFG.H)。
任务优先级0-63,可用6bits表示。将其分为两部分:高3bits和低3bits。
其中,高3位用于表示该任务所在的组;低3位用于表示该任务组内的位置。
程序清单L3.5 使任务进入就绪态 |
OSRdyGrp |= OSMapTbl[prio >> 3];//组标志置位 |
OSRdyTbl[prio >> 3] |= OSMapTbl[prio & 0x07];//组内标志置位 |
(prio>>3的值为任务所在的组,prio&0x07的值表示任务所在的位)
其中OSRdyGrp、OSRdyTbl和OSMapTbl的表示如下图。
图3.3μC/OS-Ⅱ就绪表
表 T3.1 OSMapTbl[]={0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
| |
Index | Bit Mask (Binary) |
0 | 00000001 |
1 | 00000010 |
2 | 00000100 |
3 | 00001000 |
4 | 00010000 |
5 | 00100000 |
6 | 01000000 |
7 | 10000000 |
程序清单 L3.6从就绪表中删除一个任务 |
if ((OSRdyTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) == 0) |
OSRdyGrp &= ~OSMapTbl[prio >> 3]; |
以上代码将就绪任务表数组OSRdyTbl[]中相应元素的相应位清零,而对于OSRdyGrp,只有当被删除任务所在任务组中全组任务一个都没有进入就绪态时,才将相应位清零。也就是说OSRdyTbl[prio>>3]所有的位都是零时,OSRdyGrp的相应位才清零。
程序清单 L3.7找出进入就绪态的优先级最高的任务 |
y = OSUnMapTbl[OSRdyGrp];//优先级最高的所在的组 |
x = OSUnMapTbl[OSRdyTbl[y]];//组内优先级最高的位 |
prio = (y << 3) + x;//还原成优先级 |
为了找到那个进入就绪态的优先级最高的任务,并不需要从OSRdyTbl[0]开始扫描整个就绪任务表,只需要查另外一张表,即优先级判定表OSUnMapTbl([256])(见文件OS_CORE.C)。OSRdyTbl[]中每个字节的8位代表这一组的8个任务哪些进入就绪态了,低位的优先级高于高位。利用这个字节为下标来查OSUnMapTbl这张表,返回的字节就是该组任务中就绪态任务中优先级最高的那个任务所在的位置。这个返回值在0到7之间。
OSUnMapTbl中的每个元素表示0x00~0xFF的每个数的二进制表示中最低位1出现的位置。
任务调度
任务级的任务调度器:OS_Sched()->OS_TASK_SW->OSCtxSw()
如果在中断服务子程序中调用OSSched(),此时中断嵌套层数OSIntNesting>0,或者由于用户至少调用了一次给任务调度上锁函数OSSchedLock(),使OSLockNesting>0,任务调度函数OSSched()将退出,不做任务调度。
任务调度函数OS_Sched将找出那个进入就绪态且优先级最高的任务OSPrioHighRdy [L3.8(2)],进入就绪态的任务在就绪任务表中有相应的位置位。一旦找到那个优先级最高的任务,OSSched()检验这个优先级最高的任务是不是当前正在运行的任务,以此来避免不必要的任务调度[L3.8(3)]。为实现任务切换,OSTCBHighRdy必须指向优先级最高的那个任务控制块OS_TCB,这是通过将以OSPrioHighRdy为下标的OSTCBPrioTbl[]数组中的那个元素赋给OSTCBHighRdy来实现的[L3.8(4)]。接着,统计计数器OSCtxSwCtr加1,以跟踪任务切换次数[L3.8(5)]。最后宏调用OS_TASK_SW()来完成实际上的任务切换[L3.8(6)]。
任务切换很简单,由以下两步完成,将被挂起任务的微处理器寄存器推入堆栈,然后将较高优先级的任务的寄存器值从栈中恢复到寄存器中。还需要让当前任务控制块OSTCBCur指向即将被执行的任务。
程序清单 L3.11任务切换的示意代码:OSCtxSw |
voidOSCtxSw(void) { 将R1、R2、R3……等寄存器的值推入当前堆栈; OSTCBCur->OSTCBStkPtr = SP; OSTCBCur = OSTCBHighRdy; SP = OSTCBCur->OSTCBStkPtr; 将R1、R2、R3……等寄存器的值从堆栈弹出; 执行中断返回指令; } |
基于ARM920T的OSCtxSw实现分析:
首先必须了解,在将ucos-II移植到ARM920T时,使用了如下结构的任务栈:
程序清单:基于ARM920T的OSCtxSw实现分析 注意:ARM需手动保存pc和psw |
OSCtxSw ; Special optimised code below: ;1、根据任务栈结构,分别压栈,保存旧任务的现场 stmfd sp!,{lr} ; push pc (lr should be pushed in place of PC) stmfd sp!,{r0-r12,lr} ; push lr & register file mrs r4,cpsr stmfd sp!,{r4} ; push current psr mrs r4,spsr stmfd sp!,{r4} ; push current spsr
; 2、OSPrioCur = OSPrioHighRdy ldr r4,=OSPrioCur ldr r5,=OSPrioHighRdy ldrb r6,[r5] strb r6,[r4]
; 3、Get current task TCB address:r5=OSTCBCur ldr r4,=OSTCBCur; r4 = &OSTCBCur ldr r5,[r4]
;4、OSTCBCur->OSTCBStkPtr = SP; str sp,[r5] ; store sp in preempted tasks's TCB
bl OSTaskSwHook ; call Task Switch Hook
; 5、Get highest priority task TCB address:r6=OSTCBHighRdy ldr r6,=OSTCBHighRdy ldr r6,[r6] ;6、SP = OSTCBHighRdy ->OSTCBStkPtr; ldr sp,[r6] ; get new task's stack pointer ; 7、OSTCBCur = OSTCBHighRdy str r6,[r4] ; set new current task TCB address ;8、根据任务栈结构,分别出栈,恢复新任务的现场 ldmfd sp!,{r4} ; pop new task's spsr msr SPSR_cxsf,r4 ldmfd sp!,{r4} ; pop new task's psr msr CPSR_cxsf,r4
ldmfd sp!,{r0-r12,lr,pc} ; pop new task's r0-r12,lr & pc
|
中断级的任务调度器:OSIntExt()
脱离中断函数OSIntExit()[L3.15(4)]标志着中断服务子程序的终结,OSIntExit()将中断嵌套层数计数器减1。当嵌套计数器减到零时,所有中断,包括嵌套的中断就都完成了,此时μC/OS-Ⅱ要判定有没有优先级较高的任务被中断服务子程序(或任一嵌套的中断)唤醒了。如果有优先级高的任务进入了就绪态,μC/OS-Ⅱ就返回到那个高优先级的任务,OSIntExit()返回到调用点[L3.15(5)]。保存的寄存器的值是在这时恢复的,然后是执行中断返回指令[L3.16(6)]。注意,如果调度被禁止了(OSIntNesting>0),μC/OS-Ⅱ将被返回到被中断了的任务。
OSIntExit()看起来非常像OSSched()。但有三点不同。第一点,OSIntExit()使中断嵌套层数减1[L3.17(2)]而调度函数OSSched()的调度条件是:中断嵌套层数计数器和锁定嵌套计数器(OSLockNesting)二者都必须是零。第二个不同点是,OSRdyTbl[]所需的检索值Y是保存在全程变量OSIntExitY中的[L3.17(3)]。这是为了避免在任务栈中安排局部变量。这个变量在哪儿和中断任务切换函数OSIntCtxSw()有关,(见9.04.03节,中断任务切换函数)。最后一点,如果需要做任务切换,OSIntExit()将调用OSIntCtxSw()[L3.17(4)]而不是调用OS_TASK_SW(),正像在OSSched()函数中那样。
基于ARM920T的OSIntCtxSw()实现分析:
程序清单:基于ARM920T的OSIntCtxSw()实现分析: |
_IntCtxSw ; OSIntCtxSwFlag = 0 mov r1,#0 str r1,[r0]
/* 此时正处于中断模式下,因此sp、spsr等寄存器为中断模式下的相应寄存器,因此 r0-r3,r12,lr这些寄存器在进入中断处理程序后,先被保存在了中断模式下的堆栈中。*/ ;现将这些寄存器的值重新恢复。 ldmfd sp!,{r0-r3,r12,lr} ;将r0-r3重新保存回中断模式下的栈空间 stmfd sp!,{r0-r3} /* 此时中断栈的内容(为被中断任务的r0-r3的值): ----------------à r3,r2,r1,r0 ----------------à栈顶 */
;保存中断模式下的栈顶指针sp到r1:r1 = sp_irq mov r1,sp
add sp,sp,#16; sp_irq += 16; ;计算被中断任务的中断返回地址: r2 = lr– 4 sub r2,lr,#4 /* spsr在中断发生时,保存了中断前即用户模式下的cpsr的值,该过程由硬件自动完成 */ ;取中断前的状态寄存器到r3 mrs r3,spsr
orr r0,r3,#NOINT;屏蔽中断 ;返回用户模式(切换到用户堆栈),以便下面保存中断现场 msr cpsr_cxsf,r0
;ldr r0,=.+8 ;movs pc,r0;自动切换到用户堆栈 /* 此时已是用户模式,因此,sp为用户模式的栈顶指针,即被中断任务的堆栈指针。 */ ;根据任务栈结构,分别压栈,保存旧任务的现场 stmfd sp!,{r2} ; push old task's pc stmfd sp!,{r4-r12,lr} ; push old task's lr,r12-r4 /* 此时任务栈的内容: ----------------à pc,lr,r12,r11,,,,,,r4 ----------------à栈顶 */ ;r1为中断栈的栈顶指针,r4 = r1 mov r4,r1 ; Special optimised code below ;r3为中断前状态寄存器cpsr,r5 = r3 mov r5,r3 ;将保存在中断栈中的r0-r3保存到任务栈中,r4为中断栈的栈顶指针 ldmfd r4!,{r0-r3} stmfd sp!,{r0-r3} ; push old task's r3-r0 ;保存任务的cpsr到任务栈 stmfd sp!,{r5} ; push old task's psr ;保存任务的spsr到任务栈 mrs r4,spsr stmfd sp!,{r4} ; push old task's spsr /* 至此,被中断的任务的现场保存完毕! */
; OSPrioCur = OSPrioHighRdy ldr r4,=OSPrioCur ldr r5,=OSPrioHighRdy ldrb r5,[r5] strb r5,[r4]
; Get current task TCB address:r5=OSTCBCur ldr r4,=OSTCBCur ldr r5,[r4] ;OSTCBCur->OSTCBStkPtr = SP; str sp,[r5] ; store sp in preempted tasks's TCB
bl OSTaskSwHook ; call Task Switch Hook
; Get highest priority task TCB address:r6=OSTCBHighRdy ldr r6,=OSTCBHighRdy ldr r6,[r6] ;SP = OSTCBHighRdy ->OSTCBStkPtr; ldr sp,[r6] ; get new task'sstack pointer
; OSTCBCur = OSTCBHighRdy str r6,[r4] ; set new current task TCB address
;根据任务栈结构,分别出栈,恢复新任务的现场 ldmfd sp!,{r4} ; pop new task's spsr msr SPSR_cxsf,r4 ldmfd sp!,{r4} ; pop new task's psr msr CPSR_cxsf,r4
ldmfd sp!,{r0-r12,lr,pc} ; pop new task's r0-r12,lr & pc |
任务调度注意事项:
调用OSSchedLock()以后,用户的应用程序不得使用任何能将现行任务挂起的系统调用。也就是说,用户程序不得调用OSMboxPend()、OSQPend()、OSSemPend()、OSTaskSuspend(OS_PR1O_SELF)、OSTimeDly()或OSTimeDlyHMSM(),直到OSLockNesting回零为止。因为调度器上了锁,用户就锁住了系统,任何其它任务都不能运行。
空闲任务OSTaskIdle()是永远处于就绪状态的,故不要在OSTaskIdleHook()中调用任何可以使任务挂起的Pend函数、OStimeDly???函数以及OSTaskSuspend()函数。
原文地址:http://blog.csdn.net/yhmhappy2006/article/details/3220765