第3章 内核结构.docx
《第3章 内核结构.docx》由会员分享,可在线阅读,更多相关《第3章 内核结构.docx(27页珍藏版)》请在冰豆网上搜索。
第3章内核结构
第3章内核结构1
3.0临界段(CriticalSections)1
3.1任务1
3.2任务状态3
3.3任务控制块(TaskControlBlocks,OS_TCBs)4
3.4就绪表(ReadyList)7
3.5任务调度(TaskScheduling)10
3.6给调度器上锁和开锁(LockingandUnLockingtheScheduler)11
3.7空闲任务(IdleTask)12
3.8统计任务13
3.9μC/OS中的中断处理16
3.10时钟节拍20
3.11μC/OS-Ⅱ初始化23
3.12μC/OS-Ⅱ的启动24
3.13获取当前μC/OS-Ⅱ的版本号27
第3章内核结构
本章给出μC/OS-Ⅱ的主要结构概貌。
读者将学习以下一些内容;
●μC/OS-Ⅱ是怎样处理临界段代码的;
●什么是任务,怎样把用户的任务交给μC/OS-Ⅱ;
●任务是怎样调度的;
●μC/OS-Ⅱ是怎样知道应用程序CPU的利用率的;
●怎样写中断服务子程序;
●什么是时钟节拍,μC/OS-Ⅱ是怎样处理时钟节拍的;
●μC/OS-Ⅱ是怎样初始化的;
●怎样启动多任务;
本章还描述以下函数,这些服务于应用程序:
●OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL(),
●OSInit(),
●OSStart(),
●OSIntEnter()和OSIntExit(),
●OSSchedLock()和OSSchedUnlock(),以及
●OSVersion().
3.1临界段(CriticalSections)
μC/OS-Ⅱ定义两个宏(macros)来关中断和开中断。
分别是:
OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。
在文件OS_CPU.H中。
每种微处理器都有自己的OS_CPU.H文件。
3.2任务
一个任务通常是一个无限的循环[L3.1
(2)],如程序清单3.1所示。
程序清单L3.1任务是一个无限循环
voidYourTask(void*pdata)
(1)
{
for(;;){
(2)
/*用户代码*/
调用uC/OS-II的某种系统服务:
OSMboxPend();
OSQPend();
OSSemPend();
OSTaskDel(OS_PRIO_SELF);
OSTaskSuspend(OS_PRIO_SELF);
OSTimeDly();
OSTimeDlyHMSM();
/*用户代码*/
}
}
当任务完成以后,任务可以自我删除,如清单L3.2所示。
注意任务代码并非真的删除了,μC/OS-Ⅱ只是简单地不再理会这个任务了,这个任务的代码也不会再运行。
程序清单L3.2.任务完成后自我删除
voidYourTask(void*pdata)
{
/*用户代码*/
OSTaskDel(OS_PRIO_SELF);
}
3.3任务状态
睡眠态(DORMANT):
指任务驻留在程序空间之中,还没有交给μC/OS-Ⅱ管理。
就绪态:
当任务一旦建立,这个任务就进入就绪态准备运行。
运行态:
调用OSStart()可以启动多任务。
等待状态
中断状态
图3.1任务的状态
3.4任务控制块(TaskControlBlocks,OS_TCBs)
一旦任务建立了,任务控制块OS_TCBs将被赋值(程序清单3.3)。
任务建立的时候,OS_TCBs就被初始化。
程序清单L3.3µC/OS-II任务控制块.
typedefstructos_tcb{
OS_STK*OSTCBStkPtr;
#ifOS_TASK_CREATE_EXT_EN
void*OSTCBExtPtr;
OS_STK*OSTCBStkBottom;
INT32UOSTCBStkSize;
INT16UOSTCBOpt;
INT16UOSTCBId;
#endif
structos_tcb*OSTCBNext;
structos_tcb*OSTCBPrev;
#if(OS_Q_EN&&(OS_MAX_QS>=2))||OS_MBOX_EN||OS_SEM_EN
OS_EVENT*OSTCBEventPtr;
#endif
#if(OS_Q_EN&&(OS_MAX_QS>=2))||OS_MBOX_EN
void*OSTCBMsg;
#endif
INT16UOSTCBDly;
INT8UOSTCBStat;
INT8UOSTCBPrio;
INT8UOSTCBX;
INT8UOSTCBY;
INT8UOSTCBBitX;
INT8UOSTCBBitY;
#ifOS_TASK_DEL_EN
BOOLEANOSTCBDelReq;
#endif
}OS_TCB;
.OSTCBX,.OSTCBY,.OSTCBBitX和.OSTCBBitY用于加速任务进入就绪态的过程或进入等待事件发生状态的过程(避免在运行中去计算这些值)。
这些值是在任务建立时算好的,或者是在改变任务优先级时算出的。
这些值的算法见程序清单L3.4。
程序清单L3.4任务控制块OS_TCB中几个成员的算法
OSTCBY
=priority>>3;
OSTCBBitY
=OSMapTbl[priority>>3];
OSTCBX
=priority&0x07;
OSTCBBitX
=OSMapTbl[priority&0x07];
应用程序中可以有的最多任务数(OS_MAX_TASKS)是在文件OS_CFG.H中定义的。
这个最多任务数也是μC/OS-Ⅱ分配给用户程序的最多任务控制块OS_TCBs的数目。
在μC/OS-Ⅱ初始化的时候,如图3.2所示,所有任务控制块OS_TCBs被链接成单向空任务链表。
当任务一旦建立,空任务控制块指针OSTCBFreeList指向的任务控制块便赋给了该任务,然后OSTCBFreeList的值调整为指向下链表中下一个空的任务控制块。
一旦任务被删除,任务控制块就还给空任务链表。
图3.2空任务列表
3.5就绪表(ReadyList)
每个任务的就绪态标志都放入就绪表中的,就绪表中有两个变量OSRdyGrp和OSRdyTbl[]。
OSRdyGrp和OSRdyTbl[]之间的关系见图3.3,是按以下规则给出的:
当OSRdyTbl[0]中的任何一位是1时,OSRdyGrp的第0位置1,
当OSRdyTbl[1]中的任何一位是1时,OSRdyGrp的第1位置1,
当OSRdyTbl[2]中的任何一位是1时,OSRdyGrp的第2位置1,
当OSRdyTbl[3]中的任何一位是1时,OSRdyGrp的第3位置1,
当OSRdyTbl[4]中的任何一位是1时,OSRdyGrp的第4位置1,
当OSRdyTbl[5]中的任何一位是1时,OSRdyGrp的第5位置1,
当OSRdyTbl[6]中的任何一位是1时,OSRdyGrp的第6位置1,
当OSRdyTbl[7]中的任何一位是1时,OSRdyGrp的第7位置1,
图3.3μC/OS-Ⅱ就绪表
程序清单3.5中的代码用于将任务放入就绪表。
Prio是任务的优先级。
程序清单L3.5使任务进入就绪态
OSRdyGrp|=OSMapTbl[prio>>3];
OSRdyTbl[prio>>3]|=OSMapTbl[prio&0x07];
表T3.1OSMapTbl[]的值
Index
BitMask(Binary)
0
00000001
1
00000010
2
00000100
3
00001000
4
00010000
5
00100000
6
01000000
7
10000000
如果一个任务被删除了,则用程序清单3.6中的代码做求反处理。
程序清单L3.6从就绪表中删除一个任务
if((OSRdyTbl[prio>>3]&=~OSMapTbl[prio&0x07])==0)
OSRdyGrp&=~OSMapTbl[prio>>3];
为了找到那个进入就绪态的优先级最高的任务,并不需要从OSRdyTbl[0]开始扫描整个就绪任务表,只需要查另外一张表,即优先级判定表OSUnMapTbl[256]。
INT8UconstOSUnMapTbl[]={
0,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0x00to0x0F*/
4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0x10to0x1F*/
5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0x20to0x2F*/
4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0x30to0x3F*/
6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0x40to0x4F*/
4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0x50to0x5F*/
5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0x60to0x6F*/
4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0x70to0x7F*/
7,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0x80to0x8F*/
4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0x90to0x9F*/
5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0xA0to0xAF*/
4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0xB0to0xBF*/
6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0xC0to0xCF*/
4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0xD0to0xDF*/
5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,/*0xE0to0xEF*/
4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0/*0xF0to0xFF*/
};
程序清单L3.7找出进入就绪态的优先级最高的任务
y=OSUnMapTbl[OSRdyGrp];
x=OSUnMapTbl[OSRdyTbl[y]];
prio=(y<<3)+x;
例如:
如果OSRdyGrp的值为二进制01101000,查OSUnMapTbl[OSRdyGrp]得到的值是3;
如果OSRdyTbl[3]的值是二进制11100100,则OSUnMapTbl[OSRdyTbl[3]]的值是2;
优先级Prio就等于26(3*8+2)。
3.6任务调度(TaskScheduling)
μC/OS-Ⅱ总是运行进入就绪态任务中优先级最高的那一个。
确定哪个任务优先级最高,下面该哪个任务运行了的工作是由调度器(Scheduler)完成的。
任务级的调度是由函数OSSched()完成的。
中断级的调度是由另一个函数OSIntExt()完成的,这个函数将在以后描述。
OSSched()的代码如程序清单L3.8所示。
程序清单L3.8任务调度器(theTaskScheduler)
voidOSSched(void)
{
INT8Uy;
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();
}
任务切换由以下两步完成:
(1)将被挂起任务的微处理器寄存器推入堆栈。
(2)然后将较高优先级的任务的寄存器值从栈中恢复到寄存器中。
3.7给调度器上锁和开锁(LockingandUnLockingtheScheduler)
用于禁止任务调度。
程序清单L3.9给调度器上锁
voidOSSchedLock(void)
{
if(OSRunning==TRUE){
OS_ENTER_CRITICAL();
OSLockNesting++;
OS_EXIT_CRITICAL();
}
}
程序清单L3.10给调度器开锁.
voidOSSchedUnlock(void)
{
if(OSRunning==TRUE){
OS_ENTER_CRITICAL();
if(OSLockNesting>0){
OSLockNesting--;
if((OSLockNesting|OSIntNesting)==0){
(1)
OS_EXIT_CRITICAL();
OSSched();
(2)
}else{
OS_EXIT_CRITICAL();
}
}else{
OS_EXIT_CRITICAL();
}
}
}
3.8空闲任务(IdleTask)
μC/OS-Ⅱ总是建立一个空闲任务,在没有其它任务进入就绪态时投入运行。
空闲任务设为最低优先级,即OS_LOWEST_PRI0。
程序清单L3.11μC/OS-Ⅱ的空闲任务.
voidOSTaskIdle(void*pdata)
{
pdata=pdata;
for(;;){
OS_ENTER_CRITICAL();
OSIdleCtr++;
OS_EXIT_CRITICAL();
}
}
3.9统计任务
提供运行时间统计的任务叫做OSTaskStat()。
如果用户将系统定义常数OS_TASK_STAT_EN(见文件OS_CFG.H)设为1,这个任务就会建立。
一旦得到了允许,OSTaskStat()每秒钟运行一次(见文件OS_CORE.C),计算当前的CPU利用率。
并放在有符号8位整数OSCPUsage中。
程序清单L3.12初始化统计任务.
voidmain(void)
{
OSInit();/*初始化uC/OS-II
(1)*/
/*安装uC/OS-II的任务切换向量*/
/*创建用户起始任务(为了方便讨论,这里以TaskStart()作为起始任务)
(2)*/
OSStart();/*开始多任务调度(3)*/
}
voidTaskStart(void*pdata)
{
/*安装并启动uC/OS-II的时钟节拍(4)*/
OSStatInit();/*初始化统计任务(5)*/
/*创建用户应用程序任务*/
for(;;){
/*这里是TaskStart()的代码!
*/
}
}
图F3.4统计任务的初始化
程序清单L3.13统计任务的初始化.
voidOSStatInit(void)
{
OSTimeDly
(2);
OS_ENTER_CRITICAL();
OSIdleCtr=0L;
OS_EXIT_CRITICAL();
OSTimeDly(OS_TICKS_PER_SEC);
OS_ENTER_CRITICAL();
OSIdleCtrMax=OSIdleCtr;
OSStatRdy=TRUE;
OS_EXIT_CRITICAL();
}
程序清单L3.14统计任务
voidOSTaskStat(void*pdata)
{
INT32Urun;
INT8Susage;
pdata=pdata;
while(OSStatRdy==FALSE){
(1)
OSTimeDly(2*OS_TICKS_PER_SEC);
}
for(;;){
OS_ENTER_CRITICAL();
OSIdleCtrRun=OSIdleCtr;
run=OSIdleCtr;
OSIdleCtr=0L;
OS_EXIT_CRITICAL();
if(OSIdleCtrMax>0L){
usage=(INT8S)(100L-100L*run/OSIdleCtrMax);
(2)
if(usage>100){
OSCPUUsage=100;
}elseif(usage<0){
OSCPUUsage=0;
}else{
OSCPUUsage=usage;
}
}else{
OSCPUUsage=0;
}
OSTaskStatHook();(3)
OSTimeDly(OS_TICKS_PER_SEC);
}
}
3.10μC/OS中的中断处理
μC/OS中,中断服务子程序要用汇编语言来写。
程序清单L3.15μC/OS-II中的中断服务子程序.
用户中断服务子程序:
保存全部CPU寄存器;
(1)
调用OSIntEnter或OSIntNesting直接加1;
(2)
执行用户代码做中断服务;(3)
调用OSIntExit();(4)
恢复所有CPU寄存器;(5)
执行中断返回指令;(6)
图3.5中断服务
进入中断函数OSIntEnter()的代码如程序清单L3.16所示,
程序清单L3.16通知μC/OS-Ⅱ,中断服务子程序开始了.
voidOSIntEnter(void)
{
OS_ENTER_CRITICAL();
OSIntNesting++;
OS_EXIT_CRITICAL();
}
程序清单L3.17通知μC/OS-Ⅱ,脱离了中断服务
voidOSIntExit(void)
{
OS_ENTER_CRITICAL();
(1)
if((--OSIntNesting|OSLockNesting)==0){
(2)
OSIntExitY=OSUnMapTbl[OSRdyGrp];(3)
OSPrioHighRdy=(INT8U)((OSIntExitY<<3)+
OSUnMapTbl[OSRdyTbl[OSIntExitY]]);
if(OSPrioHighRdy!
=OSPrioCur){
OSTCBHighRdy=OSTCBPrioTbl[OSPrioHighRdy];
OSCtxSwCtr++;
OSIntCtxSw();(4)
}
}
OS_EXIT_CRITICAL();
}
图3.6中断中的任务切换函数OSIntCtxSw()调整栈结构
3.11时钟节拍
μC/OS需要用户提供周期性信号源,用于实现时间延时和确认超时。
节拍率应在每秒10次到100次之间。
用户必须在多任务系统启动以后再开启时钟节拍器,也就是在调用OSStart()之后。
程序清单L3.19启动时钟就节拍器的不正确做法.
voidmain(void)
{
.
.
OSInit();/*初始化uC/OS-II*/
/*应用程序初始化代码...*/
/*...通过调用OSTaskCreate()创建至少一个任务*/
.
允许时钟节拍(TICKER)中断;/*千万不要在这里允许时钟节拍中断!
!
!
*/
.
OSStart();/*开始多任务调度*/
}
μC/OS-Ⅱ中的时钟节拍服务是通过在中断服务子程序中调用OSTimeTick()实现的。
程序清单L3.20时钟节拍中断服务子程序的示意代码
voidOSTickISR(void)
{
保存处理器寄存器的值;
调用OSIntEnter()或是将OSIntNesting加1;
调用OSTimeTick();
调用OSIntExit();
恢复处理器寄存器的值;
执行中断返回指令;
}
程序清单L3.21时钟节拍函数OSTimtick()的一个节拍服务
voidOSTimeTick(void)
{
OS_TCB*ptcb;
OSTimeTickHook();
(1)
ptcb=OSTCBList;
(2)
while(ptcb->OSTCBPrio!
=OS_IDLE_PRIO){(3)
OS_ENTER_CRITICAL();
if(ptcb->OSTCBDly!
=0){
if(--ptcb->OSTCBDly==0){
if(!
(ptcb->OSTCBStat&OS_STAT_SUSPEND)){(4)
OSRdyGrp|=ptcb->OSTCBBitY;(5)
OSRdyTbl[ptcb->OSTCBY]|=ptcb->OSTCBBitX;
}else{
ptcb->OSTCBDly=1;
}
}
}
ptcb=ptcb->OSTCBNext;
OS_EXIT_CRITICAL