Ucos中任务调度过程中任务切换分析
Ucos中任务调度过程中任务切换分析
μC/OS-Ⅱ总是运行进入就绪态任务中优先级最高的那一个。确定哪个任务优先级最高,下面该哪个任务运行了的工作是由调度器(Scheduler)完成的。任务级的调度是由函数OSSched()完成的。中断级的调度是由另一个函数OSIntExt()完成的,这个函数将在以后描述。OSSched()的代码如程序清单L3.8所示。
程序清单 L3.8 任务调度器(the Task Scheduler)
void OS_Sched (void)
{
INT8U y;
OS_ENTER_CRITICAL();
if ((OSLockNesting | OSIntNesting) == 0) //写得太浪了
{ (1)
y = OSUnMapTbl[OSRdyGrp]; (2)
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]); (2)
if (OSPrioHighRdy != OSPrioCur) { (3)
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; (4)
OSCtxSwCtr++; (5)
OS_TASK_SW(); (6)
}
}
OS_EXIT_CRITICAL();
}
在上面的代码中如果满足一些条件能进行任务切换时则会利用宏调用OS_TASK_SW()来完成实际上的任务切换[L3.8(6)]。在ucos_II.h中:
#define OS_STK_GROWTH 1
#define uCOS 0x80
#define OS_TASK_SW() asm INT uCOS
实事上也就是人为的进行了一次中断,就是把8086CPU中的中断0x80号中断向量重写,而在主函数中又一段代码:
PC_VectSet(uCOS, OSCtxSw);
当然此函数的实现为:
void PC_VectSet (INT8U vect, void (*isr)(void))//一个是向量号,另一个是向量对应函数入口地址
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
INT16U *pvect;
pvect = (INT16U *)MK_FP(0x0000, vect * 4);
OS_ENTER_CRITICAL();
*pvect++ = (INT16U)FP_OFF(isr);
*pvect = (INT16U)FP_SEG(isr);
OS_EXIT_CRITICAL();
}
则PC_VectSet(uCOS, OSCtxSw)就是把0x80的中断向量的中断向量服务函数入口地址设置为OSCtxSw函数的地址.换句话说一旦进行OS_TASK_SW()则就会进行此中断向量所指向的中断服务函数中进行处理.
在看OSCtxSw前需要说明一点,当程序执行到OS_TASK_SW()时, OSTCBHighRdy已经确定下来了,准备好任务切换了.
下面就看看OSCtxSw的实现,它是用汇编写的:
(1)_OSCtxSw PROC FAR
(2) PUSHA ; Save current task's context
(3) PUSH ES ;
(4) PUSH DS ;
MOV AX, SEG _OSTCBCur ; Reload DS in case it was altered
MOV DS, AX ;
(5) LES BX, DWORD PTR DS:_OSTCBCur; OSTCBCur->OSTCBStkPtr = SS:SP
MOV ES:[BX+2], SS ;
MOV ES:[BX+0], SP ;
CALL FAR PTR _OSTaskSwHook ; Call user defined task switch hook
MOV AX, WORD PTR DS:_OSTCBHighRdy+2 ; OSTCBCur = OSTCBHighRdy
MOV DX, WORD PTR DS:_OSTCBHighRdy ;
MOV WORD PTR DS:_OSTCBCur+2, AX ;
MOV WORD PTR DS:_OSTCBCur, DX ;
MOV AL, BYTE PTR DS:_OSPrioHighRdy ; OSPrioCur = OSPrioHighRdy
MOV BYTE PTR DS:_OSPrioCur, AL ;
(6) LES BX, DWORD PTR DS:_OSTCBHighRdy ;
;SS:SP = OSTCBHighRdy->OSTCBStkPtr
MOV SS, ES:[BX+2] ;
MOV SP, ES:[BX] ;
(7) POP DS ; Load new task's context
(8) POP ES ;
(9) POPA ;
(10) IRET ; Return to new task
_OSCtxSw ENDP
PAGE
在80x86处理器上,任务调用OS_TASK_SW()执行软中断指令后,先向堆栈中压入返回地址(段地址和偏移量),然后是状态字寄存器SW(注这些操作都是由CPU自动完成的并且与当前任务本身无关)。紧接着用PUSHA, PUSH ES,和 PUSH DS保存任务运行环境(注意:这些与当前任务有关的寄存器中的数据存储在此任务开始时所建立的栈中)。最后用OSCtxSw()在任务OS_TCB中(注意不是栈了)保存SS和SP寄存器中的内容。
任务环境保存完后,将调用用户定义的对外接口函数OSTaskSwHook(),它用来将
请注意,此时OSTCBCur指向当前任务OS_TCB,OSTCBHighRdy指向新任务的OS_TCB。在OSTaskSwHook()中,用户可以访问这两个任务的OS_TCB。在这里我们不用去关心它.刚提到过因为OSTCBHighRdy这个数据已经指向将要执行的任务的TCB了.
则接下来一方面把OSTCBHighRdy里的数据放到OSTCBCur里,同时它们的指针OSPrioCur也要改为指向OSPrioHighRdy.这样对于整个系统来说当前任务的TCB里的数据就是将要被执行的那个任务的TCB的数据信息了.
但仅仅是这个还不够,因为当前的栈所保存的数据指的数据仍然是被替换的那个任务的数据,一旦把栈里内容弹出后果将不可预测,因为当前任务的TCB和它的数据信息不对应.那怎么办呢?对了就是改SS:SP,在弹出前将CPU的SS:SP指向目标任务的栈.因此我们可以从将要运行的任务的TCB中取出保存栈信息的数据,然后修改寄存器SS:SP,最后再利用出栈,就会使另一个任务开始执行.
Ucos中任务调度过程中任务切换分析
μC/OS-Ⅱ总是运行进入就绪态任务中优先级最高的那一个。确定哪个任务优先级最高,下面该哪个任务运行了的工作是由调度器(Scheduler)完成的。任务级的调度是由函数OSSched()完成的。中断级的调度是由另一个函数OSIntExt()完成的,这个函数将在以后描述。OSSched()的代码如程序清单L3.8所示。
程序清单 L3.8 任务调度器(the Task Scheduler)
void OS_Sched (void)
{
INT8U y;
OS_ENTER_CRITICAL();
if ((OSLockNesting | OSIntNesting) == 0) //写得太浪了
{ (1)
y = OSUnMapTbl[OSRdyGrp]; (2)
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]); (2)
if (OSPrioHighRdy != OSPrioCur) { (3)
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; (4)
OSCtxSwCtr++; (5)
OS_TASK_SW(); (6)
}
}
OS_EXIT_CRITICAL();
}
在上面的代码中如果满足一些条件能进行任务切换时则会利用宏调用OS_TASK_SW()来完成实际上的任务切换[L3.8(6)]。在ucos_II.h中:
#define OS_STK_GROWTH 1
#define uCOS 0x80
#define OS_TASK_SW() asm INT uCOS
实事上也就是人为的进行了一次中断,就是把8086CPU中的中断0x80号中断向量重写,而在主函数中又一段代码:
PC_VectSet(uCOS, OSCtxSw);
当然此函数的实现为:
void PC_VectSet (INT8U vect, void (*isr)(void))//一个是向量号,另一个是向量对应函数入口地址
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
INT16U *pvect;
pvect = (INT16U *)MK_FP(0x0000, vect * 4);
OS_ENTER_CRITICAL();
*pvect++ = (INT16U)FP_OFF(isr);
*pvect = (INT16U)FP_SEG(isr);
OS_EXIT_CRITICAL();
}
则PC_VectSet(uCOS, OSCtxSw)就是把0x80的中断向量的中断向量服务函数入口地址设置为OSCtxSw函数的地址.换句话说一旦进行OS_TASK_SW()则就会进行此中断向量所指向的中断服务函数中进行处理.
在看OSCtxSw前需要说明一点,当程序执行到OS_TASK_SW()时, OSTCBHighRdy已经确定下来了,准备好任务切换了.
下面就看看OSCtxSw的实现,它是用汇编写的:
(1)_OSCtxSw PROC FAR
(2) PUSHA ; Save current task's context
(3) PUSH ES ;
(4) PUSH DS ;
MOV AX, SEG _OSTCBCur ; Reload DS in case it was altered
MOV DS, AX ;
(5) LES BX, DWORD PTR DS:_OSTCBCur; OSTCBCur->OSTCBStkPtr = SS:SP
MOV ES:[BX+2], SS ;
MOV ES:[BX+0], SP ;
CALL FAR PTR _OSTaskSwHook ; Call user defined task switch hook
MOV AX, WORD PTR DS:_OSTCBHighRdy+2 ; OSTCBCur = OSTCBHighRdy
MOV DX, WORD PTR DS:_OSTCBHighRdy ;
MOV WORD PTR DS:_OSTCBCur+2, AX ;
MOV WORD PTR DS:_OSTCBCur, DX ;
MOV AL, BYTE PTR DS:_OSPrioHighRdy ; OSPrioCur = OSPrioHighRdy
MOV BYTE PTR DS:_OSPrioCur, AL ;
(6) LES BX, DWORD PTR DS:_OSTCBHighRdy ;
;SS:SP = OSTCBHighRdy->OSTCBStkPtr
MOV SS, ES:[BX+2] ;
MOV SP, ES:[BX] ;
(7) POP DS ; Load new task's context
(8) POP ES ;
(9) POPA ;
(10) IRET ; Return to new task
_OSCtxSw ENDP
PAGE
在80x86处理器上,任务调用OS_TASK_SW()执行软中断指令后,先向堆栈中压入返回地址(段地址和偏移量),然后是状态字寄存器SW(注这些操作都是由CPU自动完成的并且与当前任务本身无关)。紧接着用PUSHA, PUSH ES,和 PUSH DS保存任务运行环境(注意:这些与当前任务有关的寄存器中的数据存储在此任务开始时所建立的栈中)。最后用OSCtxSw()在任务OS_TCB中(注意不是栈了)保存SS和SP寄存器中的内容。
任务环境保存完后,将调用用户定义的对外接口函数OSTaskSwHook(),它用来将
请注意,此时OSTCBCur指向当前任务OS_TCB,OSTCBHighRdy指向新任务的OS_TCB。在OSTaskSwHook()中,用户可以访问这两个任务的OS_TCB。在这里我们不用去关心它.刚提到过因为OSTCBHighRdy这个数据已经指向将要执行的任务的TCB了.
则接下来一方面把OSTCBHighRdy里的数据放到OSTCBCur里,同时它们的指针OSPrioCur也要改为指向OSPrioHighRdy.这样对于整个系统来说当前任务的TCB里的数据就是将要被执行的那个任务的TCB的数据信息了.
但仅仅是这个还不够,因为当前的栈所保存的数据指的数据仍然是被替换的那个任务的数据,一旦把栈里内容弹出后果将不可预测,因为当前任务的TCB和它的数据信息不对应.那怎么办呢?对了就是改SS:SP,在弹出前将CPU的SS:SP指向目标任务的栈.因此我们可以从将要运行的任务的TCB中取出保存栈信息的数据,然后修改寄存器SS:SP,最后再利用出栈,就会使另一个任务开始执行.