uCOSII源码详解.docx
- 文档编号:24350061
- 上传时间:2023-05-26
- 格式:DOCX
- 页数:21
- 大小:94.06KB
uCOSII源码详解.docx
《uCOSII源码详解.docx》由会员分享,可在线阅读,更多相关《uCOSII源码详解.docx(21页珍藏版)》请在冰豆网上搜索。
uCOSII源码详解
uC/OS-II源码分析
uC/OS-II源码分析(总体思路一)
首先从main函数开始,下面是uC/OS-IImain函数的大致流程:
main()
{
OSInit();
TaskCreate(...);
OSStart();
}
首先是调用OSInit进行初始化,然后使用TaskCreate创建几个进程/Task,最后调用OSStart,操作系统就开始运行了。
最先看看OSInit完成哪些初始化:
voidOSInit(void)
{
#ifOS_VERSION>=204
OSInitHookBegin();
#endif
OS_InitMisc();
OS_InitRdyList();
OS_InitTCBList();
OS_InitEventList();
#if(OS_VERSION>=251)&&(OS_FLAG_EN>0)&&(OS_MAX_FLAGS>0)
OS_FlagInit();
#endif
#if(OS_MEM_EN>0)&&(OS_MAX_MEM_PART>0)
OS_MemInit();
#endif
#if(OS_Q_EN>0)&&(OS_MAX_QS>0)
OS_QInit();
#endif
OS_InitTaskIdle();
#ifOS_TASK_STAT_EN>0
OS_InitTaskStat();
#endif
#ifOS_VERSION>=204
OSInitHookEnd();
#endif
#ifOS_VERSION>=270&&OS_DEBUG_EN>0
OSDebugInit();
#endif
}
OS_InitMisc()完成的是一些其其他他的变量的初始化:
OSIntNesting=0;
OSLockNesting=0;
OSTaskCtr=0;
OSRunning=FALSE;
OSCtxSwCtr=0;
OSIdleCtr=0L;
staticvoidOS_InitRdyList(void)
{
INT8Ui;
INT8U*prdytbl;
OSRdyGrp=0x00;
prdytbl=&OSRdyTbl[0];
for(i=0;i { *prdytbl++=0x00; } OSPrioCur=0; OSPrioHighRdy=0; OSTCBHighRdy=(OS_TCB*)0; OSTCBCur=(OS_TCB*)0; } 其中包括: 中断嵌套标志OSIntNesting,调度锁定标志OSLockNesting,OS标志OSRunning等。 OSRunning在这里设置为FALSE,在后面OSStartHighRdy中会被设置为TRUE表示OS开始工作。 OS_InitRdyList()初始化就绪Task列表: 首先将OSRdyTbl[]数组中全部初始化0,同时将OSPrioCur/OSTCBCur初始化为0,OSPrioHighRdy/OSTCBHighRdy也初始化为0,这几个变量将在第一个OSSchedule中被赋予正确的值。 OS_InitTCBList()这个函数看名称我们就知道是初始化TCB列表。 staticvoidOS_InitTCBList(void) { INT8Ui; OS_TCB*ptcb1; OS_TCB*ptcb2; OS_MemClr((INT8U*)&OSTCBTbl[0],sizeof(OSTCBTbl)); OS_MemClr((INT8U*)&OSTCBPrioTbl[0],sizeof(OSTCBPrioTbl)); ptcb1=&OSTCBTbl[0]; ptcb2=&OSTCBTbl[1]; for(i=0;i<(OS_MAX_TASKS+OS_N_SYS_TASKS-1);i++) { ptcb1->OSTCBNext=ptcb2; #ifOS_TASK_NAME_SIZE>1 ptcb1->OSTCBTaskName[0]='? '; ptcb1->OSTCBTaskName[1]=OS_ASCII_NUL; #endif ptcb1++; ptcb2++; } ptcb1->OSTCBNext=(OS_TCB*)0; #ifOS_TASK_NAME_SIZE>1 ptcb1->OSTCBTaskName[0]='? '; ptcb1->OSTCBTaskName[1]=OS_ASCII_NUL; #endif OSTCBList=(OS_TCB*)0; OSTCBFreeList=&OSTCBTbl[0]; } 这里完成的工作很简单,首先把整个数组使用OSTCBNext指针连接成链表链起来,然后将OSTCBList初始化为0,也就是还没有TCB,因为还没有Task产生,OSTCBFreeList指向OSTCBTbl[]数组的第一个表示所有TCB都处于Free状态。 OS_InitEventList()初始化Event列表。 staticvoidOS_InitEventList(void) { #ifOS_EVENT_EN&&(OS_MAX_EVENTS>0) #if(OS_MAX_EVENTS>1) INT16Ui; OS_EVENT*pevent1; OS_EVENT*pevent2; OS_MemClr((INT8U*)&OSEventTbl[0],sizeof(OSEventTbl)); pevent1=&OSEventTbl[0]; pevent2=&OSEventTbl[1]; for(i=0;i<(OS_MAX_EVENTS-1);i++) { pevent1->OSEventType=OS_EVENT_TYPE_UNUSED; pevent1->OSEventPtr=pevent2; #ifOS_EVENT_NAME_SIZE>1 pevent1->OSEventName[0]='? '; pevent1->OSEventName[1]=OS_ASCII_NUL; #endif pevent1++; pevent2++; } pevent1->OSEventType=OS_EVENT_TYPE_UNUSED; pevent1->OSEventPtr=(OS_EVENT*)0; #ifOS_EVENT_NAME_SIZE>1 pevent1->OSEventName[0]='? '; pevent1->OSEventName[1]=OS_ASCII_NUL; #endif OSEventFreeList=&OSEventTbl[0]; #else OSEventFreeList=&OSEventTbl[0]; OSEventFreeList->OSEventType=OS_EVENT_TYPE_UNUSED; OSEventFreeList->OSEventPtr=(OS_EVENT*)0; #ifOS_EVENT_NAME_SIZE>1 OSEventFreeList->OSEventName[0]='? '; OSEventFreeList->OSEventName[1]=OS_ASCII_NUL; #endif #endif #endif } 同样将EventTbl[]数组中的OSEventType都初始化为OS_EVENT_TYPE_UNUSED。 OS_InitTaskIdle(),中间我们跳过其他的如Mem等的初始化,看看IdleTask的初始化。 (void)OSTaskCreateExt(OS_TaskIdle, (void*)0, &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE-1], OS_IDLE_PRIO, OS_TASK_IDLE_ID, &OSTaskIdleStk[0], OS_TASK_IDLE_STK_SIZE, (void*)0, OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR); 其实IdleTask的初始化很简单就是调用OSTaskCreate系列的函数创建一个Task,OSTaskCreate我们后面再做进一步分析。 初始化StateTask也是类似调用OSTaskCreate系列函数创建StatTask。 这里只是创建了该Task的各个结构还没有真正运行该Task,直到OSStart中才依据优先级调度运行。 OK,到这里OSInit算高一个段落了,我们接着回到main往下看 uC/OS-II源码分析(总体思路二) OSTaskCreate负责创建Task所需的数据结构,该函数原形如下所示: INT8UOSTaskCreate(void(*task)(void*pd),void*p_arg,OS_STK*ptos,INT8Uprio) 其中task是一个函数指针,指向该Task所开始的函数,当这个Task第一次被调度运行时将会从task处开始运行。 p_arg是传给task的参数指针; ptos是堆栈指针,指向栈顶(堆栈从上往下)或栈底(堆栈从下往上); prio是进程的优先级,uC/OS-II共支持最大64个优先级,其中最低的两个优先级给Idle和Stat进程,并且各个Task的优先级必须不同。 接下来,我们看看这个函数的执行流程: INT8UOSTaskCreate(void(*task)(void*pd),void*p_arg,OS_STK*ptos,INT8Uprio) { void*psp; INT8Uerr; #ifOS_ARG_CHK_EN>0 if(prio>OS_LOWEST_PRIO) { return(OS_PRIO_INVALID); } #endif OS_ENTER_CRITICAL(); if(OSIntNesting>0) { OS_EXIT_CRITICAL(); return(OS_ERR_TASK_CREATE_ISR); } if(OSTCBPrioTbl[prio]==(OS_TCB*)0) { OSTCBPrioTbl[prio]=(OS_TCB*)1; OS_EXIT_CRITICAL(); psp=(OS_STK*)OSTaskStkInit(task,p_arg,ptos,0); err=OS_TCBInit(prio,psp,(OS_STK*)0,0,0,(void*)0,0); if(err==OS_NO_ERR) { if(OSRunning==TRUE) { OS_Sched(); } } else { OS_ENTER_CRITICAL(); OSTCBPrioTbl[prio]=(OS_TCB*)0; OS_EXIT_CRITICAL(); } return(err); } else { 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,INT16Uopt) 和OSTaskCreate类似,task是进程入口地址,pdata是参数地址,ptos是堆栈指针,而opt只是作为一个预留的参数Option而保留。 返回的是调整以后的堆栈指针。 在OSTaskStkInit中,一般是将pdata入栈,flag入栈,task入栈,然后将各寄存器依次入栈。 OS_TCBInit初始化TCB数据结构,下面只提取主要部分来看: INT8UOS_TCBInit(INT8Uprio,OS_STK*ptos,OS_STK*pbos,INT16Uid,INT32Ustk_size,void*pext,INT16Uopt) { OS_TCB*ptcb; OS_ENTER_CRITICAL(); ptcb=OSTCBFreeList; if(ptcb! =(OS_TCB*)0) { OSTCBFreeList=ptcb->OSTCBNext; OS_EXIT_CRITICAL(); ptcb->OSTCBStkPtr=ptos; ptcb->OSTCBPrio=prio; ptcb->OSTCBStat=OS_STAT_RDY; ptcb->OSTCBPendTO=FALSE; ptcb->OSTCBDly=0; #ifOS_TASK_CREATE_EXT_EN>0 ptcb->OSTCBExtPtr=pext; ptcb->OSTCBStkSize=stk_size; ptcb->OSTCBStkBottom=pbos; ptcb->OSTCBOpt=opt; ptcb->OSTCBId=id; #else pext=pext; stk_size=stk_size; pbos=pbos; opt=opt; id=id; #endif #ifOS_TASK_DEL_EN>0 ptcb->OSTCBDelReq=OS_NO_ERR; #endif ptcb->OSTCBY=(INT8U)(prio>>3); ptcb->OSTCBBitY=OSMapTbl[ptcb->OSTCBY]; ptcb->OSTCBX=(INT8U)(prio&0x07); ptcb->OSTCBBitX=OSMapTbl[ptcb->OSTCBX]; #ifOS_EVENT_EN ptcb->OSTCBEventPtr=(OS_EVENT*)0; #endif OSTaskCreateHook(ptcb); OS_ENTER_CRITICAL(); OSTCBPrioTbl[prio]=ptcb; ptcb->OSTCBNext=OSTCBList; ptcb->OSTCBPrev=(OS_TCB*)0; if(OSTCBList! =(OS_TCB*)0) { OSTCBList->OSTCBPrev=ptcb; } OSTCBList=ptcb; OSRdyGrp|=ptcb->OSTCBBitY; OSRdyTbl[ptcb->OSTCBY]|=ptcb->OSTCBBitX; OSTaskCtr++; 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的定义: INT8UconstOSMapTbl[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; INT8UconstOSUnMapTbl[256]= { 从略……………… }; OSMapTbl分别是一个INT8U的八个位,而OSUnMap数组中的值就是从0x00到0xFF的八位中,每一个值所对应的最低位的值。 我们在调度的时候只需将OSRdyGrp的值代入OSUnMapTbl数组中,得到OSUnMapTbl[OSRdyGrp]的值就是哪个优先级最高的Group有Ready进程存在,再使用该Group对应OSRdyTbl[]数组中的值一样带入OSUnMapTbl中就可以得出哪个Task是优先级最高的。 于是我们提前来看看OS_Sched()中获取最高优先级所使用的方法: y=OSUnMapTbl[OSRdyGrp]; OSPrioHighRdy=(INT8U)((y<<3)+OSUnMapTbl[OSRdyTbl[y]]); 显然,先得到的y就是存在最高优先级的Group,然后OSUnMapTbl[OSRdyTbl[y]]就是Group中的偏移,因此OSPrioHighRdy最高优先级就应该是Group<<3再加上这个偏移。 于是乎,我们就可以对上面那一小段很模糊的代码做一下总结: 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其实很短,只有匆匆几句代码: voidOSStart(void) { INT8Uy; INT8Ux; if(OSRunning==FALSE) { y=OSUnMapTbl[OSRdyGrp]; x=OSUnMapTbl[OSRdyTbl[y]]; OSPrioHighRdy=(INT8U)((y<<3)+x); OSPrioCur=OSPrioHighRdy; OSTCBHighRdy=OSTCBPrioTbl[OSPrioHighRdy]; OSTCBCur=OSTCBHighRdy; OSStartHighRdy(); } } 如果OSRunning为TRUE,表示OS已经在运行了,则OSStart不做任何事。 OSRunning为FALSE,则找出最高优先级的Ready的Task,并将该指针赋给OSTCBHighRdy和OSTCBCur。 然后调用OS
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- uCOSII 源码 详解