uC由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“UC”。
uC/OS-II源码分析
在这里我不想分模块来慢慢分析这个OS的行为,我们从CPU的执行顺序来看吧,这样也许清晰一些,并且我们暂时抛弃那些Event/MailBox、信号量等元素,还是先看看作为一个OS核心的Task Schedule部分内容吧。
首先从main函数开始,下面是uC/OS-II main函数的大致流程: main(){ OSInit();TaskCreate(...);OSStart();} 首先是调用OSInit进行初始化,然后使用TaskCreate创建几个进程/Task,最后调用OSStart,操作系统就开始运行了。
OSInit
最先看看OSInit完成哪些初始化: void OSInit(void){ #if OS_VERSION >= 204
OSInitHookBegin();
/* Call port specific initialization code
*/ #endif
OS_InitMisc();
/* Initialize miscellaneous variables
*/
OS_InitRdyList();
/* Initialize the Ready List
*/
OS_InitTCBList();
/* Initialize the free list of OS_TCBs
*/
OS_InitEventList();
/* Initialize the free list of OS_EVENTs
*/ #if(OS_VERSION >= 251)&&(OS_FLAG_EN > 0)&&(OS_MAX_FLAGS > 0)
OS_FlagInit();
/* Initialize the event flag structures
*/ #endif #if(OS_MEM_EN > 0)&&(OS_MAX_MEM_PART > 0)
OS_MemInit();
/* Initialize the memory manager
*/ #endif #if(OS_Q_EN > 0)&&(OS_MAX_QS > 0)
OS_QInit();
/* Initialize the meage queue structures */ #endif
OS_InitTaskIdle();
/* Create the Idle Task
*/ #if OS_TASK_STAT_EN > 0
OS_InitTaskStat();
/* Create the Statistic Task
*/ #endif #if OS_VERSION >= 204
OSInitHookEnd();
/* Call port specific init.code
*/ #endif #if OS_VERSION >= 270 && OS_DEBUG_EN > 0
OSDebugInit();#endif } OS_InitMisc()完成的是一些其其他他的变量的初始化:
OSIntNesting = 0;
/* Clear the interrupt nesting counter
*/
OSLockNesting = 0;
/* Clear the scheduling lock counter
*/
OSTaskCtr
= 0;
/* Clear the number of tasks
*/
OSRunning
= FALSE;
/* Indicate that multitasking not started
*/
OSCtxSwCtr
= 0;
/* Clear the context switch counter
*/
OSIdleCtr
= 0L;
/* Clear the 32-bit idle counter
*/ 其中包括:中断嵌套标志OSIntNesting,调度锁定标志OSLockNesting,OS标志OSRunning等。OSRunning在这里设置为FALSE,在后面OSStartHighRdy中会被设置为TRUE表示OS开始工作。
OS_InitRdyList()初始化就绪Task列表: static void OS_InitRdyList(void){
INT8U
i;
INT8U
*prdytbl;
OSRdyGrp
= 0x00;
/* Clear the ready list
*/
prdytbl
= &OSRdyTbl[0];
for(i = 0;i
*prdytbl++ = 0x00;
}
OSPrioCur
= 0;
OSPrioHighRdy = 0;
OSTCBHighRdy =(OS_TCB *)0;
OSTCBCur
=(OS_TCB *)0;} 首先将OSRdyTbl[]数组中全部初始化0,同时将OSPrioCur/OSTCBCur初始化为0,OSPrioHighRdy/OSTCBHighRdy也初始化为0,这几个变量将在第一个OSSchedule中被赋予正确的值。
OS_InitTCBList()这个函数看名称我们就知道是初始化TCB列表。static void OS_InitTCBList(void){
INT8U
i;
OS_TCB *ptcb1;
OS_TCB *ptcb2;
OS_MemClr((INT8U *)&OSTCBTbl[0],sizeof(OSTCBTbl));
/* Clear all the TCBs
*/
OS_MemClr((INT8U *)&OSTCBPrioTbl[0], sizeof(OSTCBPrioTbl));/* Clear the priority table
*/
ptcb1 = &OSTCBTbl[0];
ptcb2 = &OSTCBTbl[1];
for(i = 0;i
/* Init.list of free EVENT control blocks */
pevent1->OSEventType
= OS_EVENT_TYPE_UNUSED;
pevent1->OSEventPtr
= pevent2;#if OS_EVENT_NAME_SIZE > 1
pevent1->OSEventName[0] = '?';
/* Unknown name
*/
pevent1->OSEventName[1] = OS_ASCII_NUL;#endif
pevent1++;
pevent2++;
}
pevent1->OSEventType
= OS_EVENT_TYPE_UNUSED;
pevent1->OSEventPtr
=(OS_EVENT *)0;#if OS_EVENT_NAME_SIZE > 1
pevent1->OSEventName[0]
= '?';
pevent1->OSEventName[1]
= OS_ASCII_NUL;#endif
OSEventFreeList
= &OSEventTbl[0];#else
OSEventFreeList
= &OSEventTbl[0];
/* Only have ONE event control block
*/
OSEventFreeList->OSEventType
= OS_EVENT_TYPE_UNUSED;
OSEventFreeList->OSEventPtr
=(OS_EVENT *)0;#if OS_EVENT_NAME_SIZE > 1
OSEventFreeList->OSEventName[0] = '?';
/* Unknown name
*/
OSEventFreeList->OSEventName[1] = OS_ASCII_NUL;#endif #endif #endif } 同样将EventTbl[]数组中的OSEventType都初始化为OS_EVENT_TYPE_UNUSED。OS_InitTaskIdle(),中间我们跳过其他的如Mem等的初始化,看看Idle Task的初始化。
(void)OSTaskCreateExt(OS_TaskIdle,(void *)0,/* No arguments paed to OS_TaskIdle()*/
&OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE-1], /* Set Top-Of-Stack
*/
OS_IDLE_PRIO,/* Lowest priority level
*/
OS_TASK_IDLE_ID,&OSTaskIdleStk[0],/* Set Bottom-Of-Stack
*/
OS_TASK_IDLE_STK_SIZE,(void *)0,/* No TCB extension
*/
OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack */ 其实Idle Task的初始化很简单就是调用OSTaskCrete系列的函数创建一个Task, OSTaskCreate我们后面再做进一步分析。
初始化State Task也是类似调用OSTaskCreate系列函数创建Stat Task。这里只是创建了该Task的各个结构还没有真正运行该Task,直到OSStart中才依据优先级调度运行。OK,到这里OSInit算高一个段落了,我们接着回到main往下看。
OSTaskCreate
OSTaskCreate负责创建Task所需的数据结构,该函数原形如下所示:
INT8U OSTaskCreate(void(*task)(void *pd), void *p_arg, OS_STK *ptos, INT8U prio)其中task是一个函数指针,指向该Task所开始的函数,当这个Task第一次被调度运行时将会从task处开始运行。
p_arg是传给task的参数指针;
ptos是堆栈指针,指向栈顶(堆栈从上往下)或栈底(堆栈从下往上);
prio是进程的优先级,uC/OS-II共支持最大64个优先级,其中最低的两个优先级给Idle和Stat进程,并且各个Task的优先级必须不同。接下来,我们看看这个函数的执行流程:
#if OS_ARG_CHK_EN > 0
if(prio > OS_LOWEST_PRIO){
/* Make sure priority is within allowable range
*/
return(OS_PRIO_INVALID);
} #endif
OS_ENTER_CRITICAL();
if(OSIntNesting > 0){
/* Make sure we don't create the task from within an ISR */
OS_EXIT_CRITICAL();
return(OS_ERR_TASK_CREATE_ISR);
}
if(OSTCBPrioTbl[prio] ==(OS_TCB *)0){ /* Make sure task doesn't already exist at this priority */
OSTCBPrioTbl[prio] =(OS_TCB *)1;
/* Reserve the priority to prevent others from doing...*/
/*...the same thing until task is created.*/
OS_EXIT_CRITICAL();
psp =(OS_STK *)OSTaskStkInit(task, p_arg, ptos, 0);
/* Initialize the task's stack
*/
err = OS_TCBInit(prio, psp,(OS_STK *)0, 0, 0,(void *)0, 0);
if(err == OS_NO_ERR){
if(OSRunning == TRUE){
/* Find highest priority task if multitasking has started */
OS_Sched();
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] =(OS_TCB *)0;/* Make this priority available to others
*/
OS_EXIT_CRITICAL();
}
return(err);
}
OS_EXIT_CRITICAL();
return(OS_PRIO_EXIST);OS_LOWEST_PRIO在ucos-ii.h中被定义为63,表示Task的优先级从0到63,共64级。首先判断prio是否超过最低优先级,如果是,则返回OS_PRIO_INVALID错误。
然后调用OS_ENTER_CRITICAL(),进入临界段,在临界段中的代码执行不允许被中断。这个宏是用户自定义的,一般是进行关中断操作,例如在x86中的CLI等。这个宏和OS_EXIT_CRITICAL()相对应,这个宏表示离开临界段。
OSTaskCreate不允许在中断中调用,因此会判断OSIntNesting是否大于0,如果大于0,表示正在中断嵌套,返回OS_ERR_TASK_CREATE_ISR错误。
接着判断该prio是否已经有Task存在,由于uC/OS-II只支持每一个优先级一个Task,因此如果该prio已经有进程存在,OSTaskCreate会返回OS_PRIO_EXIST错误。
相反,如果该prio先前没有Task存在,则将OSTCBPrioTbl[prio]置1,表示该prio已被占用,然后调用OSTaskStkInit初始化堆栈,调用OS_TCBInit初始化TCB,如果OSRunning为TRUE表示OS正在运行,则调用OS_Sched进行进程调度;否则返回。下面来看看OSTaskStkInit和OS_TCBInit这两个函数。
OSTaskStkInit是一个用户自定义的函数,因为uC/OS-II在设计时无法知道当前处理器在进行进程调度时需要保存那些信息,OSTaskStkInit就是初始化堆栈,让Task看起来就好像刚刚进入中断并保存好寄存器的值一样,当OS_Sched调度到该Task时,只需切换到该堆栈中,将寄存器值Pop出来,然后执行一个中断返回指令IRET即可。OSTaskStkInit的原型如下:
OS_STK *OSTaskStkInit(void(*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)和OSTaskCreate类似,task是进程入口地址,pdata是参数地址,ptos是堆栈指针,而opt只是作为一个预留的参数Option而保留。返回的是调整以后的堆栈指针。
在OSTaskStkInit中,一般是将pdata入栈,flag入栈,task入栈,然后将各寄存器依次入栈。OS_TCBInit初始化TCB数据结构,下面只提取主要部分来看:
INT8U OS_TCBInit(INT8U prio, OS_STK *ptos, OS_STK *pbos, INT16U id, INT32U stk_size, void *pext, INT16U opt){
OS_TCB
*ptcb;
OS_ENTER_CRITICAL();
ptcb = OSTCBFreeList;
free TCB list
*/
if(ptcb!=(OS_TCB *)0){
OSTCBFreeList
= ptcb->OSTCBNext;
TCB list
*/
OS_EXIT_CRITICAL();
ptcb->OSTCBStkPtr
= ptos;
TCB
*/
ptcb->OSTCBPrio
= prio;
TCB
*/
ptcb->OSTCBStat
= OS_STAT_RDY;
*/
ptcb->OSTCBPendTO
= FALSE;
timeout flag
*/
ptcb->OSTCBDly
= 0;
*/ #if OS_TASK_CREATE_EXT_EN > 0
ptcb->OSTCBExtPtr
= pext;
extension
*/
ptcb->OSTCBStkSize
= stk_size;
*/
ptcb->OSTCBStkBottom = pbos;
of stack
*/
ptcb->OSTCBOpt
= opt;
*/
ptcb->OSTCBId
= id;
*/ #else
pext
= pext;
warning if not used
*/
stk_size
= stk_size;
/* Get a free TCB from the
/* Update pointer to free
/* Load Stack pointer in
/* Load task priority into
/* Task is ready to run
/* Clear the Pend
/* Task is not delayed
/* Store pointer to TCB
/* Store stack size
/* Store pointer to bottom
/* Store task options
/* Store task ID
/* Prevent compiler
pbos
= pbos;
opt
= opt;
id
= id;#endif #if OS_TASK_DEL_EN > 0
ptcb->OSTCBDelReq
= OS_NO_ERR;#endif
ptcb->OSTCBY
=(INT8U)(prio >> 3);
/* Pre-compute X, Y, BitX and BitY
*/
ptcb->OSTCBBitY
= OSMapTbl[ptcb->OSTCBY];
ptcb->OSTCBX
=(INT8U)(prio & 0x07);
ptcb->OSTCBBitX
= OSMapTbl[ptcb->OSTCBX];#if OS_EVENT_EN
ptcb->OSTCBEventPtr =(OS_EVENT *)0;
/* Task is not pending on an event
*/ #endif
OSTaskCreateHook(ptcb);
/* Call user defined hook
*/
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = ptcb;
ptcb->OSTCBNext
= OSTCBList;
/* Link into TCB chain
*/
ptcb->OSTCBPrev
=(OS_TCB *)0;
if(OSTCBList!=(OS_TCB *)0){
OSTCBList->OSTCBPrev = ptcb;
}
OSTCBList
= ptcb;
OSRdyGrp
|= ptcb->OSTCBBitY;
/* Make task ready to run
*/
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OSTaskCtr++;
/* Increment the #tasks counter
*/
OS_EXIT_CRITICAL();
return(OS_NO_ERR);
}
OS_EXIT_CRITICAL();
return(OS_NO_MORE_TCB);} 首先调用OS_ENTER_CRITICAL进入临界段,首先从OSTCBFreeList中拿出一个TCB,如果OSTCBFreeList为空,则返回OS_NO_MORE_TCB错误。
然后调用OS_EXIT_CRITICAL离开临界段,接着对该TCB进行初始化:
将OSTCBStkPtr初始化为该Task当前堆栈指针;
OSTCBPrio设置为该Task的prio; OSTCBStat设置为OS_STAT_RDY,表示就绪状态;
OSTCBDly设置为0,当该Task调用OSTimeDly时会初始化这个变量为Delay的时钟数,然后Task转入OS_STAT_状态。这个变量在OSTimeTick中检查,如果大于0表示还需要进行Delay,则进行减1;如果等于零表示无须进行Delay,可以马上运行,转入OS_STAT_RDY状态。
OSTCBBitY和OSTCBBitX的作用我们在等会专门来讨论。紧接着就要将该TCB插入OSTCBList列表中,先调用OS_ENTER_CRITICAL进入临界段,将该TCB插入到OSTCBList成为第一个节点,然后调整OSRdyGrp和OSRdyTbl,(这两个变量一会和OSTCBBitX/OSTCBBitY一起讨论),最后将OSTaskCtr计数器加一,调用OS_EXIT_CRITICAL退出临界段。OSMapTbl和OSUnMapTbl 刚才我们看到TCB数据结构中的OSTCBBitX/OSTCBBitY以及OSRdyGrp/OSRdyTbl的使用,这里专门来讨论讨论这几个变量的用法。
uC/OS-II将64个优先级的进程分为8组,每组8个。刚好可以使用8个INT8U的数据进行表示,于是这就是OSRdyGrp和OSRdyTbl的由来,OSRdyGrp表示组别,从0到7,从前面我们可以看到OSRdyGrp和OSRdyTbl是这么被赋值的:
OSRdyGrp
|= ptcb->OSTCBBitY;
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
也就是OSTCBBitY保存的是组别,OSTCBBitX保存的是组内的偏移。而这两个变量是这么被初始化的:
ptcb->OSTCBY
=(INT8U)(prio >> 3);
ptcb->OSTCBBitY
= OSMapTbl[ptcb->OSTCBY];
ptcb->OSTCBX
=(INT8U)(prio & 0x07);
ptcb->OSTCBBitX
= OSMapTbl[ptcb->OSTCBX];
由于prio不会大于64,prio为6位值,因此OSTCBY为prio高3位,不会大于8,OSTCBX为prio低3位。
这里就涉及到OSMapTbl数组和OSUnMapTbl数组的用法了。我们先看看OSMapTbl和OSUnMapTbl的定义:
INT8U const OSMapTbl[8]
= {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
INT8U const OSUnMapTbl[256] = {
0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0x00 to 0x0F
*/
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0x10 to 0x1F
*/
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0x20 to 0x2F
*/
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0x30 to 0x3F
*/
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0x40 to 0x4F
*/
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0x50 to 0x5F
*/
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0x60 to 0x6F
*/
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0x70 to 0x7F
*/
7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0x80 to 0x8F
*/
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0x90 to 0x9F
*/
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0xA0 to 0xAF
*/
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0xB0 to 0xBF
*/
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0xC0 to 0xCF
*/
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0xD0 to 0xDF
*/
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 0xE0 to 0xEF
*/
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
/* 0xF0 to 0xFF
*/ };OSMapTbl分别是一个INT8U的八个位,而OSUnMap数组中的值就是从0x00到0xFF的八位中,每一个值所对应的最低位的值。我们在调度的时候只需将OSRdyGrp的值代入OSUnMapTbl数组中,得到OSUnMapTbl[OSRdyGrp]的值就是哪个优先级最高的Group有Ready进程存在,再使用该Group对应OSRdyTbl[]数组中的值一样带入OSUnMapTbl中就可以得出哪个Task是优先级最高的。
于是我们提前来看看OS_Sched()中获取最高优先级所使用的方法:
y
= OSUnMapTbl[OSRdyGrp];
/* Get pointer to HPT ready to run
*/
OSPrioHighRdy =(INT8U)((y
于是乎,我们就可以对上面那一小段很模糊的代码做一下总结:
prio只有6位,高3位代表着某一个Group保存在OSTCBY中,OSTCBBitY表示该Group所对应的Bit,将OSRdyGrp的该位置1表示该Group中有进程是Ready的;低3位代表着该Group中的第几个进程,保存在OSTCBX中,OSTCBBitX表示该进程在该Group中所对应的Bit,OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX就等于将该进程所对应的Bit置1了。OSStart OK,接下来我们来看这个开始函数了。OSStart其实很短,只有匆匆几句代码: void OSStart(void){
INT8U y;
INT8U x;
if(OSRunning == FALSE){
y
= OSUnMapTbl[OSRdyGrp];
/* Find highest priority's task priority number
*/
x
= OSUnMapTbl[OSRdyTbl[y]];
OSPrioHighRdy =(INT8U)((y
OSPrioCur
= OSPrioHighRdy;
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];/* Point to highest priority task ready to run
*/
OSTCBCur
= OSTCBHighRdy;
OSStartHighRdy();
/* Execute target specific code to start task
*/
} } 如果OSRunning为TRUE,表示OS已经在运行了,则OSStart不做任何事。OSRunning为FALSE,则找出最高优先级的Ready的Task,并将该指针赋给OSTCBHighRdy和OSTCBCur。然后调用OSStartHighRdy()开始运行该进程。
OSStartHighRdy()为用户自定义函数,在这个函数中,主要功能就是进行堆栈切换并将OSRunning设置为TRUE表示OS已经开始运行,然后将保存的寄存器弹出,最后执行中断返回指令IRET就跳到OSTCBHighRdy的最开始处运行了。
OSTimeDly
在Task中,一般执行一段时间之后调用OSTimeDly推迟一段时间再继续运行,OSTimeDly将本进程从Ready TCBList中删除,然后将Delay的时间设置给OSTCBDly,最后调用OS_Sched进行进程调度。void OSTimeDly(INT16U ticks){
INT8U
y;
if(ticks > 0){
/* 0 means no delay!
*/
OS_ENTER_CRITICAL();
y
= OSTCBCur->OSTCBY;
/* Delay current task
*/
OSRdyTbl[y] &= ~OSTCBCur->OSTCBBitX;
if(OSRdyTbl[y] == 0){
OSRdyGrp &= ~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBDly = ticks;
/* Load ticks in TCB
*/
OS_EXIT_CRITICAL();
OS_Sched();
/* Find next task to run!
*/
} } 如果ticks为零,说明不需延迟,则什么事情都不做。否则,调用OS_ENTER_CRITICAL进入临界段,将本进程从Ready TCBList中删除的代码如下: */
OSRdyTbl[y] &= ~OSTCBCur->OSTCBBitX;
if(OSRdyTbl[y] == 0){
OSRdyGrp &= ~OSTCBCur->OSTCBBitY;
} y为当前进程所在Group,OSRdyTbl[y]为该Group所在字节,&=~则将该字节中本进程所占用的Bit清零。如果OSRdyTbl[y]为0,则说明这个Group中没有进程处于Ready状态,则将OSRdyGrp中该Group所占用的Bit清零。
然后将ticks保存在OSTCBDly中,每次OSTimeTick运行时会将这个值减一直至为零。调用OS_EXIT_CRITICAL离开临界段,紧接着调用OS_Sched进入调度例程。
OS_Sched
OS_Sched是进程调度所使用的函数,在这里面找到最高优先级的进程,然后切换到该进程运行。
void OS_Sched(void){
INT8U
y;
OS_ENTER_CRITICAL();
if(OSIntNesting == 0){
/* Schedule only if all ISRs done
y
= OSTCBCur->OSTCBY;
/* Delay current task
and...*/
if(OSLockNesting == 0){
/*...scheduler is not locked
*/
y
= OSUnMapTbl[OSRdyGrp];
/* Get pointer to HPT ready to run
*/
OSPrioHighRdy =(INT8U)((y
if(OSPrioHighRdy!= OSPrioCur){
/* No Ctx Sw if current task is highest rdy
*/
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
OSCtxSwCtr++;
/* Increment context switch counter
*/
OS_TASK_SW();
/* Perform a context switch
*/
}
}
}
OS_EXIT_CRITICAL();} OS_Sched不允许在中断嵌套中调用,因此先判断是否是中断嵌套,并且是否限制进程调度,这两个条件都满足之后,找到最高优先级的进程,如果这个进程不是当前进程,则将新的进程TCB指针保存到OSTCBHighRdy中,为调度计数器OSCtxSwCtr加一,然后调用宏OS_TASK_SW()进行切换。
OS_TASK_SW()宏也是一个自定义的宏,uC/OS-II推荐使用软中断方式实现。
OSCtxSw是一个中断响应函数,一般我们在初始化时将这个软终端和OSCtxSw挂接好。在OSCtxSw中所需要做的事情就是将当前寄存器的值保存到当前堆栈中,然后切换堆栈到新进程的堆栈,将寄存器的值出栈,然后调用中断返回指令IRET就返回到新进程中断前的地方继续执行了。
定时中断
uC/OS-II的定时中断必须在OSStart之后初始化,而不能在OSStart之前,因为害怕第一个TimeTick发生时第一个进程还没有开始运行,而这时uC/OS是处于不可预期状态,会导致死机。
因此对于定时中断,我一般是放在最高级进程的初始化中进行,然后将定时中断和OSTickISR挂接。
OSTickISR也是一个用户自定义函数,所要完成的功能一个是保存当前的寄存器到当前堆栈将OSIntNesting加一,然后调用uC/OS提供的OSTimeTick函数,然后调用OSIntExit()将OSIntNesting减一,最后将各寄存器值出栈,使用中断返回指令IRET返回。
OSTimeTick在每个时钟中断中被调用一次,在该函数中会更新各个进程TCB所对应的OSTCBDly,如果该OSTCBDly减为0,则对应的TCB就被放入Ready TCBList中。
OS_ENTER_CRITICAL();
/* Update the 32-bit tick counter
*/
OSTime++;
OS_EXIT_CRITICAL();
ptcb = OSTCBList;
/* Point at first TCB in TCB list
*/
while(ptcb->OSTCBPrio!= OS_IDLE_PRIO){
/* Go through all TCBs in TCB list
*/
OS_ENTER_CRITICAL();
if(ptcb->OSTCBDly!= 0){
/* No, Delayed or waiting for event with TO
*/
if(--ptcb->OSTCBDly == 0){
/* Decrement nbr of ticks to end of delay
*/ */
if((ptcb->OSTCBStat & OS_STAT_PEND_ANY)!= OS_STAT_RDY){
ptcb->OSTCBStat
&= ~OS_STAT_PEND_ANY;
/* Check for timeout
/* Yes, Clear status flag
*/
ptcb->OSTCBPendTO /* Indicate PEND timeout
*/
} else {
ptcb->OSTCBPendTO = FALSE;
}
if((ptcb->OSTCBStat & OS_STAT_SUSPEND)== OS_STAT_RDY){ /* Is task suspended?
*/
OSRdyGrp
|= ptcb->OSTCBBitY;
/* No, Make ready
*/
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
}
ptcb = ptcb->OSTCBNext;
/* Point at next TCB in TCB list
*/
OS_EXIT_CRITICAL();
} 首先在临界段将OSTime加一,然后遍历整个非Free的TCBList,如果OSTCBDly不为0,则,将OSTCBDly减一,如果这时OSTCBDly为0,而且TCB对应的进程需要等待任何信号量或Event等,则说明超时时间到了,将当前TCB的State中OS_STAT_PEND_ANY位去掉,然后将OSTCBPendTo设置为TRUE,表示这是PEND的超时,否则设置OSTCBPendTO为FALSE。
如果OSTCBDly减为零,且该进程没有Suspend,则将该进程放入Ready TCBList中,使用方法同TaskCreate中的方法。
然后我们来说说OSIntExit这个函数。该函数代码如下: void OSIntExit(void){
INT8U
y;
if(OSRunning == TRUE){
OS_ENTER_CRITICAL();
if(OSIntNesting > 0){
/* Prevent OSIntNesting from wrapping
*/
OSIntNesting--;
}
if(OSIntNesting == 0){
/* Reschedule only if all ISRs
=
TRUE;
complete...*/
if(OSLockNesting == 0){
/*...and not locked.*/
y
= OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy =(INT8U)((y
if(OSPrioHighRdy!= OSPrioCur){
/* No Ctx Sw if current task is highest rdy */
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
OSCtxSwCtr++;
/* Keep track of the number of ctx switches */
OSIntCtxSw();
/* Perform interrupt level ctx switch
*/
}
}
} OS_EXIT_CRITICAL();
} } 首先判断OSRunning是否为1,也就是OS是否在运行,当然没有运行就什么都不做。然后将OSIntNesting减一,这个是需要在临界段进行的。如果OSIntNesting减为零,并且没有限制进程切换,则找到当前最高优先级的进程(方法同OS_Sched()),然后调用OSIntCtxSw进行进程切换。
OSIntCtxSw()是用户自定义函数,该函数的主要功能与OSCtxSw类似,只是需要对当前的堆栈进行稍微的调整,将OSIntExit和OSIntCtxSw调用所需要的堆栈去掉,然后做的和OSCtxSw一样。
在实际的Porting中发现要去掉OSIntExit和OSIntCtxSw调用所占用的堆栈还是比较麻烦的,因此我就现在OSTickISR刚开始的时候保存好现场之后就将堆栈指针赋给当前进程TCB的OSStkPtr,这样,在OSIntCtxSw中就不需要重新对当前堆栈的值进行保存,只需进行切换就可以了。
OK,到这里应该对uC/OS的运行机制有一点点理解了,我们的分析之旅告个段落。以后如果有兴趣我们再继续对Event、信号量等等之类的分模块进行分析。
UC不再独立:俞永福进入阿里最高决策团 传UC估值约50亿美元2014-06-11导语:一直称要“独立”发展的UC,终于在阿里上市前夕决定赶上这波曲线上市的好机会。经济观察网 记者 杨阳......
打造高绩效移动团队 - UC Demo顺畅沟通:增强保护和控制提高操作灵活性和可靠性提供统一和可扩展通信平台......
学习UC课的心得体会 长山中学 语文教师 秦珊珊我参加了进修校组织的UC课培训,受益非浅,体会颇深。下面就本次学习培训,谈谈自己的体会:在本次培训中我知道了如何利用UC进行现代......
什么是uc营销管家UC营销管家(自动语音互动营销 + 意向客户短信同步)是一款适用于所有具有产品或服务推广需求的企事业单位的在线运营服务平台!是集电话营销、呼叫中心功能,短信......
UC头条:2016年度最佳内涵段子中国「心灵·生活」第一微刊前言:一想到这周要上七天班,本宝宝心就好痛,不如看看段子,让自己高兴一下。点击加载图片gif动态图片01.三胞胎。四年后的......