菜鸟学uC/OS(二)<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
By <?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />Norman
2008-07-07
内核结构分析与理解
又被其他事情耽误了两天,不过也好,天天看心头都看毛了。每天看一点可能消化的好一点。废话不多说,学习先^_^
学习内容:
怎样处理临界段代码
什么是任务
怎样通知用户任务
任务是怎样调度的
怎样写中断
时钟节拍
系统初始化
这是邵贝贝老师译文中的学习任务,看后觉得这样挺模糊的。先走一遍再说。
首先是临界段代码:
两个宏:OS_ENTER_CRITICAL()、OS_EXIT_CRITICAL();
作者给出三种方式处理临界段代码,这三种方法各有其优缺点,具体可以看书。我所关心的是如何编写。宏定义嘛,比较简单,不过对于不同的处理器和编译器,就要去熟悉指令了,否则也无法实现。在学习中,看到作者使用了两个函数:OSCPUSaveSR()、OSCPURestoreSR(),我硬是没有找到在哪里定义的。后来查阅了一些资料,隐约觉得他们应该也是宏定义,定义的是一系列的汇编指令,只不过作者没有使用第三种方法处理中断,所以就没有具体编写了。(这是我看很多移植到文章中写过这样的汇编指令,所以有此想法)可能很弱,但是我确实么有经验啊。先作此想吧。
任务:
这个没有什么好说的,需要注意到是一般不提倡使用0、1、2、3以及OS_LOWEST_PRIO-3、OS_LOWEST_PRIO-2、OS_LOWEST_PRIO-1、OS_LOWEST_PRIO,因为它们或者是内核任务所需,或者是未来版本系统的保留。再有就是有一个就是在任务状态中,有一个中断服务态(ISR running),在从中断服务子程序中返回之前,系统需要判定优先级,记住,是之前。
任务控制块:
任务控制块数据结构的定义可以多熟悉熟悉,尤其是一些重要的成员。比如OSTCBX、OSTCBY、OSTCBBit、OSTCBBitY等。
注意,TCB在初始化之前(uCOS_II初始化的时候)就创建了空的TCB列表,当任务创建的时候,将一个空的TCB的指针付赋值给任务的TCB就完成了初始化,而OSTCBFreeList则移动向下一个空的TCB。由于空任务控制块列表是提前创建的,因此,任务控制块的个数是确定的,(OS_N_SYS_TASKS+OS_MAX_TASKS)。
就绪表:
这个可是一个比较重要的东西。其主要内容是几个变量:OSRdyGrp、OSRdyTbl[]和几个表OSMapTbl[]、OSUnMapTbl[]以及就绪表本身。这些东西实现几个功能:使任务进入优先级、脱离优先级以及找出就绪态优先级最高的任务等。具体算法如下:
进入就绪态:
OSRdyGrp |= OSMapTbl[prio >> 3];/*将OSRdyGrp相应位置1,找到行号*/
OSRdyTbl[prio >> 3] |= OSMapTbl[prio & 0x07];/*将OSRdyTbl[OSRdyGrp对应的行]相应位置1*/
脱离就绪态:
其实是个反操作,不过要注意OSRdyTbl[]全部为0才可以将OSRdyGrp相应位清零。
if((OSRdyTbl[prio >>3 ] &= ~OSMapTbl[prio & 0x07]) == 0)
OSRdyGrp &= ~OSMapTbl[prio >> 3];
这里,用到了常用的置位清零的方法: |= &= ~。
最高优先级定位:
y = OSUnMapTbl[OSRdyGrp];
x = OSUnMapTbl[OSRdyTbl[y]];
prio = (y << 3) + x;
这里涉及到OSUnMapTbl表,其实这个表的基本思想就是找到变量OSRdyGrp和OSrdyTbl[y]的最小位的位置。在网上查找的一段解释很好地印证了我的这一想法。
【UC/OS2要得到目前就绪任务中最高优先级任务需要三个公式和一张表: Y = OSUnMapTbl[OSRdyGrp]; X = OSUnMapTbl[OSRdyTbl[y]]; prio = (Y << 3) + X; OSUnMapTbl[] 公式很简单: Y是求出最高优先级的行; X是求出最高优先级的列; OSUnMapTbl[]只有一个目的,就是得到OSRdyGrp和OSRdyTbl[y]最小位的位置。 如果OSRdyGrp是0x11的话,最小位的位置是0位,查OSUnMapTbl[]就得到Y=0,如果OSRdyGrp是0x12的话,最小位位置是1位,查OSUnMapTbl[]就得到Y=1。同理可以的到X的值。再通过prio = Y*8 + X 就可以得到最高优先级。当然,我们可以通过移位或者逐次相与来得到最高优先级,但都没有查表的速度快。不过可以节约ROM空间。】
任务的调度:
关键词:保存与恢复。是的,任务的调度,确切地说是任务的切换就是两步:将被挂起的任务的处理器寄存器压入堆栈;然后将高优先级任务的寄存器值从堆栈中恢复到寄存器中。这里需要注意,为了做到任务切换,uCOS_II运行OS_TASK_SW(),人为模仿了一次中断——软中断。OS_TASK_SW()是一个宏调用,通常都含有微处理器的软中断指令。
在切换过程中,有两个指针需要注意:OSTCBCur和OSTCBHighRdy。在切换任务之前,OSTCBCur指向当前正在运行的低优先级任务TCB,OSTCBHighRdy指向将要调度运行的就绪的最高优先级任务TCB。CPU的SP指针指向当前运行的任务堆栈栈顶,而OSTCBHighRdy——>OSTCBStkPtr指向的是将要调度运行的任务的堆栈栈顶。调用OS_TASK_SW()之后,CPU寄存器保存到当前运行的低优先级任务堆栈中,然后,堆栈指针被保存到低优先级任务的TCB中,而使得低优先级任务的OSTCBCur——>OSTCBStkPtr指向堆栈的同一位置——栈顶。任务切换的后期阶段,OSTCBHighRdy——>OSTCBStkPtr指向恢复前的任务堆栈栈顶,CPU的SP指针复制该地址,并将该栈中的内容恢复到CPU中继续执行。OSTCBCur和OSTCBHighRdy都会指向高优先级任务的TCB。
任务切换示意:
Void OSCtxSw(void)
{
将寄存器压入当前堆栈;
SOTCBCur——>OSTCBStkPtr = SP;/*SP指向将挂起任务栈顶,然后保存堆栈到TCB中*/
OSTCBCur = OSTCBHighRdy;/*开始切换*/
SP = OSTCBHighRdy——>OSTCBStkPtr;/*恢复堆栈前先要找到调度任务的堆栈栈顶*/
将寄存器弹出;
执行中断返回指令;
}
调度上锁与解锁:
记住一点,调用OSSchedLock()之后,用户应用程序不得调用可能会使当前任务挂起的系统功能函数,否则用户会锁住系统。OSLockNesting变量用于跟踪OSSchedLock()函数被调用的次数。
空闲任务:
空闲任务中,有一个Hook函数,可以在此执行一些功能例如进入低功耗模式等。空闲任务OS_TaskIdle永远是出于就绪态的,不能使用使任务挂起的函数。
统计任务:
这个任务的其他作用目前还不是很清楚,只知道作者在书中用来做CPUUsage统计。统计任务在初始化的时候,应用程序的任务还没有创建,因此,能够运行的就只有空闲和统计任务,因此,计数应该是最大的,而应用程序中的任务创建之后,就没有那么多次来运行空闲任务了,通过这种原理能够得到CPU的利用率。
统计任务初始化的时候,还没有创建应用程序的任务,因此,在系统初始化之后,管理的任务就只有OSStart()、OSTaskStat()和OSTaskIdle()三个任务,这三个任务按照一定的设定规律运行,在初始化之后得到一个OSTaskIdle的最大运行次数作为计算的依据。
uCOS_II的中断:
同之前单片机时代的中断是相似的。
大概过程如下:
用户中断服务子程序:
保存全部CPU寄存器
调用OSIntEnter()或者OSIntNesting直接加1
if(OSIntNesting == 1)
{
OSTCBCur ——>OSTCBStkPtr = SP;
}
清中断源
重新开中断
执行用户代码做中断服务
调用OSIntExit()
恢复所有CPU寄存器
执行中断返回指令
整个过程就差不多这个样子了,不过具体的情况还应该有些差别,例如是否重新调度,是否发生中断嵌套等等。
这里还有一个中断级调度——OSIntCtxSW(),这是因为中断发生已经保存了寄存器,如果调用OS_TASK_SW()就会再做一次,没有必要。
uCOS_II的时钟节拍:
严重注意开启时钟节拍器的时间,必须在多任务系统启动之后,也就是在调用OSStart()之后,再开启时钟节拍器。也就是说,调用OSStart()之后应该做的第一件事就是初始化定时器中断。
在内核程序中,中断服务程序写的比较长,这是由其功能决定的,如果想将其缩短,可以写成调用任务的形式:从中断发送一个消息给创建好的节拍邮箱通知节拍服务任务开始时钟节拍。
【在ucos ii的时钟节拍函数中,需要执行用户定义的时钟节拍外连函数OSTimeTickHook (),以及对任务链表进行扫描并且递减任务的延时。这样就造成了时钟节拍函数OSTimeTick ()有两点不足:
① 在时钟中断中处理额外的任务OSTimeIickHook (),这样增加了中断处理的负担,影响了定时服务的准确性;② 在关中断情况下扫描任务链表,任务越多所需要时间越长,而长时间关中断对中断响应有不利影响,是中断处理应当避免的。】
uCOS_II的初始化和启动:
uCOS_II在调用任何其他服务之前,首先要调用系统初始化函数OSInit()初始化所有的变量和数据结构(OS_CORE.C)。
uCOS_II的启动是通过调用OSStart()实现的,在启动uCOS_II之前,至少应该建立一个应用任务。OSStart()调用高优先级任务启动函数OSStartHighRdy(),这是一个与处理器有关的函数,需要用汇编语言编写,其作用是将任务栈中保存的值弹回到CPU寄存器中,然后执行一条中断返回指令,中断返回指令强制执行该任务代码。
总结:
内核的分析比较困扰我,因为以前没有操作系统相关知识,书上写的东西我也是一知半解的,对照书中所讲,结合源码,对uCOS_II的内核结构有了一定的了解,框架搭起来之后,以后再慢慢完善结构吧。
(待续)
用户611915 2012-9-27 17:01