ucos实时操作系统学习笔记——内核结构和任务创建
对于ucos实时操作系统,邵贝贝的那本书已经写得很详细了,我因为之前不深的研究过ucos,所以在这里做一个笔记,写一些个人对该操作系统的理解,仅仅是个人理解,如果有人看到这边随笔有不对的地方,望给我指正。同时,锻炼一下自己组织语言的能力,有时候知道那么个意思,却总也说不出口。
ucos内种中有几个人变量比较重要,被贯穿在ucos内核的设计中。这几个变量中有在PCB中的局部变量,也有在整个系统内核设计中的全局变量。下面将分别介绍一下这几个变量。
首先,从OS_PCB中的局部变量讲起,如果去掉OS_TASK_CREATE_EXT_EN,OS_EVENT_EN等一些可选项的参数,ucos的OS_TCB结构体中主要的变量主要是这几个(简化OS_PCB结构体如下):
typedef struct os_pcb{ OS_STK *OSTCBStkPtr; /* Pointer to current top of stack */ struct os_tcb *OSTCBNext; /* Pointer to next TCB in the TCB list */ struct os_tcb *OSTCBPrev; /* Pointer to previous TCB in the TCB list */ INT32U OSTCBDly; /* Nbr ticks to delay task or, timeout waiting for event */ INT8U OSTCBStat; /* Task status */ INT8U OSTCBStatPend; /* Task PEND status */ INT8U OSTCBPrio; /* Task priority (0 == highest) */ INT8U OSTCBX; /* Bit position in group corresponding to task priority */ INT8U OSTCBY; /* Index into ready table corresponding to task priority */ OS_PRIO OSTCBBitX; /* Bit mask to access bit position in ready table */ OS_PRIO OSTCBBitY; /* Bit mask to access bit position in ready group */ }OS_TCB;
从简化的的OS_PCB结构可看出其结构非常简单,主要包括以上几个部分:栈指针,当前任务所处TCB list的位置,描述任务delay的tick时间,任务状态,任务优先级以及计算当前任务position的四个变量。前面几个变量看一下注释应该可以很容易理解,最后四个变量需要稍作介绍,这也是整个内核结构中比较重要的部分。
接下来介绍一下几个关联比较密切的局部和全局变量,局部变量包括OS_PCB中的OSTCBX, OSTCBY, OSTCBBitX, OSTCBBitY,全局变量包括OSUnMapTbl, OSPrioHighRdy, OSRdyGrp, OSRdyTbl。
在OSTaskCreate的OS_TCBInit函数中有如下一段代码,可以从中看到创建任务的优先级和OS_PCB中四个局部变量的关系。操作系统根据任务优先级把任务分为不同的group,OSTCBY变量中存放的就是group的信息,每个group中有多少个任务则放在OSTCBX中。需要说明的是在ucos的内核设计过程中,通过宏定义将操作系统的可以创建的任务个数(OS_LOWEST_PRIO)分为64和256。在OS_LOWEST_PRIO是64的时候,会将任务分为8个group,每个group中最多会有8个任务,在如下代码中优先级<=63时,prio的最大值是63所以prio最大有6位,可以看到group和每个group任务的个数;在OS_LOWEST_PRIO是256的时候,会将任务分为16个group,每个group最多有16个任务。
通过全局变量OSRdyGrp来记录有属于哪个group的任务处于ready状态等待执行,为了降低操作系统内存的使用,通过Bit位代表每一个group并存储在OSRdyGrp中。OSRdyTbl则是代表属于哪个group中有多少个任务处于ready状态。为了更好的计算,每个操作系统在创建之初会提前计算好自己Bit位值,如下程序中OSTCBBITY和OSTCBBitX(OS_PRIO是根据OS_LOWEST_PRIO来计算定义的)。
#if OS_LOWEST_PRIO <= 63u /* Pre-compute X, Y */ ptcb->OSTCBY = (INT8U)(prio >> 3u); ptcb->OSTCBX = (INT8U)(prio & 0x07u); #else /* Pre-compute X, Y */ ptcb->OSTCBY = (INT8U)((INT8U)(prio >> 4u) & 0xFFu); ptcb->OSTCBX = (INT8U) (prio & 0x0Fu); #endif /* Pre-compute BitX and BitY */ ptcb->OSTCBBitY = (OS_PRIO)(1uL << ptcb->OSTCBY); ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX);
上面介绍的几个全局变量中还有两个OSUnMapTbl, OSPrioHighRdy。OSUnMapTbl是一个匹配数组,数组中存放的是每个index值化成2进制,数值是1的bit位的位置是多少。当输入任务处于ready状态的group值时,可以得到那个group值最小;同理,当输入每个group中任务table时,可以得到这个group中优先级数据最小的任务,当然优先级数值越小代表优先级越高,从而通过取到的值经过计算得到优先级的数值。下面代码很好的诠释了OSPrioHighRdy这个是如何计算的。对于当OS_LOWEST_PRIO是256时,计算会复杂一些,当任务group或者table在8-16这个范围,会通过移位计算,因为OSUnMapTbl是按照8位一组设计的数组。
static void OS_SchedNew (void) { #if OS_LOWEST_PRIO <= 63 /* See if we support up to 64 tasks */ INT8U y; y = OSUnMapTbl[OSRdyGrp]; OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]); #else /* We support up to 256 tasks */ INT8U y; INT16U *ptbl; if ((OSRdyGrp & 0xFF) != 0) { y = OSUnMapTbl[OSRdyGrp & 0xFF]; } else { y = OSUnMapTbl[(OSRdyGrp >> 8) & 0xFF] + 8; } ptbl = &OSRdyTbl[y]; if ((*ptbl & 0xFF) != 0) { OSPrioHighRdy = (INT8U)((y << 4) + OSUnMapTbl[(*ptbl & 0xFF)]); } else { OSPrioHighRdy = (INT8U)((y << 4) + OSUnMapTbl[(*ptbl >> 8) & 0xFF] + 8); } #endif }
其次,介绍一下内核中几个比较重要的全局变量,如下所示:
OS_EXT OS_TCB *OSTCBCur; /* Pointer to currently running TCB */ OS_EXT OS_TCB *OSTCBFreeList; /* Pointer to list of free TCBs */ OS_EXT OS_TCB *OSTCBHighRdy; /* Pointer to highest priority TCB R-to-R */ OS_EXT OS_TCB *OSTCBList; /* Pointer to doubly linked list of TCBs */ OS_EXT OS_TCB *OSTCBPrioTbl[OS_LOWEST_PRIO + 1];/* Table of pointers to created TCBs */ OS_EXT OS_TCB OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS]; /* Table of TCBs
1. OSTCBCur是一个OS_TCB类型的指针,指向当前正在运行的任务的TCB的地址;
2. OSTCBFreeList是一个指向没有使用的TCB列表的地址的指针;
3. OSTCBHighRdy指向当前处于当前处于ready状态的最高优先级的任务TCB地址的指针;
4. OSTCBList指向TCB列表的地址的指针;
5. OSTCBPrioTbl中存储的是对应优先级创建任务的TCB的地址,对于每一个优先级来说,当值是(OS_TCB *)1时,表示当前优先级reservd,当值时(OS_TCB *)0时,表示当前优先级可用;
6. OSTCBTbl是提前为每个优先级分配的TCB Table的内存。
再次,讲一下任务的创建,将通过简化代码,讲一下代码设计内容,此内容为个人理解,语言有可能"个人味"比较重,可以看一下邵老师对这块的分析。简化代码如下:
INT8U OSTaskCreate (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT8U prio) { OS_STK *psp; INT8U err;
OS_ENTER_CRITICAL(); if (OSIntNesting > 0u) { /* 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_RESERVED;/* Reserve the priority to prevent others from doing ... */ /* ... the same thing until task is created. */ OS_EXIT_CRITICAL(); psp = OSTaskStkInit(task, p_arg, ptos, 0u); /* Initialize the task's stack */ err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u); if (err == OS_ERR_NONE) { if (OSRunning == OS_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_ERR_PRIO_EXIST); }
上面已经说过,OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()是成对存在的。任务创建时,会判断当前是否在中断中创建任务,如果是则会开中断,返回一个错误;然后判断当前优先级TCB是否被占用(if (OSTCBPrioTbl[prio] == (OS_TCB *)0) ),如果被占用,则开中断,返回优先级存在错误码;如果没有被占用,则设置当前任务的TCB table为reserved状态,说明当前任务优先级不能再被分配给其他任务,然后开中断;之后,会设置当前任务的堆栈地址和大小,用于切换任务时状态的保存和恢复;然后是对分配得到的TCB进行初始化,对TCB进行初始配置,简化参数主要包括上面提到OS_TCB中的参数配置,在对TCB初始化时,返回OS_ERR_NONE时,说明创建任务成功,这时候会重新进行最高优先级的选择,任务调度OS_Send(任务调度其实就是选择最高优先级的任务,然后将堆栈指针中的数据替换掉然后保存原来的CPU寄存器的过程),如果创建TCB初始化错误即任务创建错误,则会将分配的TCB释放掉,函数返回err。当前函数的临界区开关中断函数成对出现,当进入到子函数时,其实也是成对出现,关系比较清楚。
任务创建相对简单,将代码中一些可选的功能去掉后,代码就变得很简单清楚,需要什么功能,添加什么功能分析,会比较调条理,如果整体的去分析解读会显得比较乱,不利于梳理。
,