ucos笔记.docx
- 文档编号:29675683
- 上传时间:2023-07-26
- 格式:DOCX
- 页数:18
- 大小:250.25KB
ucos笔记.docx
《ucos笔记.docx》由会员分享,可在线阅读,更多相关《ucos笔记.docx(18页珍藏版)》请在冰豆网上搜索。
ucos笔记
1.ucos体系结构以及任务的创建与运行1
2.cpu利用率的计算6
3.Ucos中常见类型的宏定8
4.任务优先级8
5邮箱,信号量11
有些是在网上抄的,有些是自己写的。
只希望把好的东西分享给大家。
1.ucos体系结构以及任务的创建与运行
UCOS体系结构图:
ucos最主要的功能是多任务,看其如何实现:
uC/OSii是通过中断(软)来实现任务现场的切换,做到任务的切换。
程序在创建任务之前首先通过调用OSInit();;初始化系统相关变量与数据结构。
通过函数OSTaskCreate()创建任务。
它的定义如下:
OSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,INTUprio),该函数包括4个参数:
task:
是指向任务代码的指针;pdata:
是任务开始执行时,传递给任务的参数的指针;ptos:
是分配给任务的堆栈的栈顶指针;prio是分配给任务的优先级。
OSTaskCreate()函数中的关键代码如下:
psp=OSTaskStkInit(task,p_arg,ptos,0u);
err=OS_TCBInit(prio,psp,(OS_STK*)0,0u,0u,(void*)0,0u);
if(err==OS_ERR_NONE){
if(OSRunning==OS_TRUE){/*Findhighestprioritytaskifmultitaskinghasstarted*/
OS_Sched();
}
OSTaskCreate()函数通过调用OSTaskStkInit()初始化任务的栈结构,任务的堆栈看起来就像刚发生了中断一样(所以的寄存器按一定的次序都保存在堆栈中,为什么要这样做?
因为任务间的切换就是任务堆栈中保存的CPU现场的切换,所以任务被创建时,要初始化它的堆栈,为后面的切换做准备),堆栈初始化之后,如果系统已经在运行了,则有可能执行任务切换。
函数OS_Sched()主要的作用是进行任务切换,进入OS_Sched()查看相关代码,它最后调用的是OS_TASK_SW()进行任务切换。
OS_TASK_SW()是在OS_CPU_A.ASM实现,它主要是用汇编写的。
主要功能是进行任务级上下文切换。
当任务创建之后,ucos通过函数OSStart()实现多任务启动,调用这个函数之后,任务才真正开始运行。
OSStart()会调用OSStartHighRdy()与OS_SchedNew();
OSStart()会调用OSStartHighRdy()执行最高任务优先级,并不在返回OSSart
OSStartHighRdy()的功能主要使就绪态任务中优先级最高的任务开始运行,OSStartHighRdy()也是通过汇编实现的,值得一提的是,里面有一条重要指令:
CPSIE I
//开总中断【一定要在这里才开总中断,如果在此之前任何一个地方开中断,系统一旦响应中断,很有可能造成系统因没有做出足够的准备而出错(比如如果在初始化systicks时就开启了中断,uC/OS此时还处于未知阶段,就会崩溃),所在在这里开启总中断才是安全的。
它的作用主要是开中断,据说在这里开中断才是最安全的。
看到这里有些同学可能奇怪怎么OSCtxSw()和OSIntCtxSw()完全一样,事实上,这两个函数的意义是不一样的,OSCtxSw()做的是任务之间的切换,如任务A因为等待某个资源或是做延时切换到任务B,而OSIntCtxSw()则是中断退出时,由中断状态切换到另一个任务。
由中断切换到任务时,CPU寄存器入栈的工作已经做完了,所以无需做第二次了(参考邵老师书的3.10节)。
这里只不过由于CM3的特殊机制导致了在这两个函数中只要做触发PendSV中断即可,具体切换由PendSV中断来处理。
前面已经说过真正的任务切换是在PendSV中断处理函数里做的,由于CM3在中断时会有一半的寄存器自动保存到任务堆栈里,所以在PendSV中断处理函数中只需保存R4-R11并调节堆栈指针即可。
当任务创建之后如何进行任务的切换,任务的实现,就是要实现CPU在不同的任务之间切换。
按照uCos作者的话说:
“就是不断的保存,恢复CPU的那些寄存器”。
大家都知道创建的任务都是一个while
(1)的死循环,据说这样的写法是因为函数返回的时候,函数的入口地址会找不到,所以写成死循环。
如果是裸机,当有几个任务时候,肯定会在某个任务函数中出不来。
但ucos不会。
ucos中主要通过OSTimeDly()进行任务的切换。
OSTimeDly()的作用主要是延时,释放cpu,同时,OSTimeDly()会调用OS_Sched()进行一次任务调度,切换任务,执行最高任务。
任务调用OSTimeDly()后,一旦规定的时间期满或者有其它的任务通过调OSTimeDlyResume()取消了延时,它就会马上进入就绪状态。
OSTimeDly
(1)的延时是多少,它的延时主要是一个时钟节拍。
一个时钟节拍是S_TICKS_PER_SEC,它在os_cfg.h中定义,所以一个时钟的节拍为(1/S_TICKS_PER_SEC),它的单位一般是ms,若精确度更高,当然也可以,在战舰stm32中,S_TICKS_PER_SEC为200,即5ms。
OSTimeDly(INT32Uticks)会进行如下赋值,OSTCBCur->OSTCBDly=ticks;将要药延时的基数赋值给OSTCBDly,OSTCBDly主要在structos_tcb 中定义,当延时到了之后就会唤醒任务,让它变成就绪状态,那它怎么知道延时到了没有,所以必须有个函数操作OSTCBDly,让它减一。
此函数就是OSTimeTick(),OSTimeTick()是在SysTickHandler(void)中断中调用的,S_TICKS_PER_SEC为200,即5ms,所以每过5ms就会调用SysTickHandler(void)中断函数,它的实现如下:
voidSysTickHandler(void)
{
OSIntEnter();
OSTimeTick();
OSIntExit();
}
事实上,任何中断服务函数,我们都应该加上OSIntEnter和OSIntExit函数,这是因为UCOSII是一个可剥夺型的内核,中断服务子程序运行之后,系统会根据情况进行一次任务调度去运行优先级别最高的就绪任务,而并不一定接着运行被中断的任务!
void OSIntEnter(void)
{
if(OSRunning==OS_TRUE){
if(OSIntNesting<255u){
OSIntNesting++;
}
}
}
这个函数很简单,他是uCos的内核函数,主要是给中断嵌套计数器OSIntNesting加一,因为uCos内核需要实时的判断程序的当前执行是不是在中断里,要知道,大部分的处理器是可以中断嵌套的
调用脱离中断函数OSIntExit()[L3.15(4)]标志着中断服务子程序的终结,OSIntExit()
将中断嵌套层数计数器减1。
当嵌套计数器减到零时,所有中断,包括嵌套的中断就都完成了,此时μC/OS-Ⅱ要判定有没有优先级较高的任务被中断服务子程序(或任一嵌套的中断)唤醒了。
如果有优先级高的任务进入了就绪态,μC/OS-Ⅱ就返回到那个高优先级的任务,OSIntExit()返回到调用点[L3.15(5)]。
保存的寄存器的值是在这时恢复的,然后是执行中断返回指令[L3.16(6)]。
注意,如果调度被禁止了(OSIntNesting>0),μC/OS-Ⅱ将被返回,OSTimeTick是系统时钟节拍服务函数,在每个时钟节拍了解每个任务的延时状态,使已经到达延时时限的非挂起任务进入就绪状态;
OSTimeTick(); 函数中有一句 if(--ptcb->OSTCBDly==0)主要就是对OSTCBDly进行减1,当减到为0,任务变为就绪态,修改全局变量OSPrioHighRdy,任务的切换有可能在OSIntExit中实现,它的代码如下:
void OSIntExit(void)
{
#ifOS_CRITICAL_METHOD==3
OS_CPU_SR cpu_sr=0;
#endif
if(OSRunning==OS_TRUE){ /* 如果uCos还没有启动运行,则不进行任务调度,因为这时候uCos的初始化还没完成 */
OS_ENTER_CRITICAL();
if(OSIntNesting>0){ /* 将中断嵌套计数器减一 */
OSIntNesting--;
}
if(OSIntNesting==0){ /* 只有在中断嵌套为0,也就是即将退出中断时才进行任务调度 */
if(OSLockNesting==0){
OS_SchedNew();/* 找出当前就绪表中的最高优先级任务,并更新全局变量OSPrioHighRdy*/
if(OSPrioHighRdy!
=OSPrioCur){/*如果发现当前最高优先级任务不是正在运行的任务,就要进行任务切换*/
OSTCBHighRdy =OSTCBPrioTbl[OSPrioHighRdy];
#ifOS_TASK_PROFILE_EN>0
OSTCBHighRdy->OSTCBCtxSwCtr++;
#endif
OSCtxSwCtr++;
OSIntCtxSw();/*调用专门用于中断服务里的上下文切换函数,进行山下文的切换 */
}
}
}
OS_EXIT_CRITICAL();
}
}
到最后,OSIntExit()中主要调用OSIntCtxSw(),进行任务的切换。
OSIntCtxSw()的实现也是在OS_CPU_A.ASM实现,也是用汇编写的。
OSIntExit() 中OS_SchedNew()的作用就是找出当前就绪表中的最高优先级任务,并更新全局变量OSPrioHighRdy。
它里面的算法比较复杂。
我知道调用它就行了。
调用中断切换函数OSIntCtxSw()而不调用任务切换函数OS_TASK_SW(),有两个原因,
第一个原因中断服务程序已经将cpu寄存器存入到中断了的任务堆栈中,而无需做第二次,一已经做完了。
第二个原因是,在中断服务子程序中调用OSIntExit()时,将返回地址推入了堆栈。
OSIntExit()中的进入临界段函数OS_ENTER_CRITICAL()或许将
CPU的状态字也推入了堆栈。
这取决于中断是怎么被关掉的(见第8章移植μC/OS-Ⅱ)。
最后,调用OSIntCtxSw()时的返回地址又被推入了堆栈[L3.17(4)和F3.1(4)],除了栈中不相关的部分,当任务挂起时,栈结构应该与μC/OS-Ⅱ所规定的完全一致。
OSIntCtxSw()只需要对栈指针做简单的调整,uCos的任务切换过程就分析跟踪完了,这里要注意几点,任务的切换并不只会发生在系统时钟滴答的中断服务里,调用发送信号量,发送消息等这些系统函数时也会引起任务切换,但大致的原理都差不多,这里我们只对程序的原理和框架做了说明,实际还是有些细节需要处理的。
OSTimeDlyHMSM()同样也可以实现对cpu的释放,OSTimeDlyHMSM()主要对时分秒,毫秒的的精确延时,阻塞OQpusd()也可以实现对cpu的释放。
2.cpu利用率的计算
voidOSStatInit(void)
{
#ifOS_CRITICAL_METHOD==3u/*AllocatestorageforCPUstatusregister*/
OS_CPU_SRcpu_sr=0u;
#endif
OSTimeDly(2u);/*Synchronizewithclocktick*/
OS_ENTER_CRITICAL();
OSIdleCtr=0uL;/*Clearidlecounter*/
OS_EXIT_CRITICAL();
OSTimeDly(OS_TICKS_PER_SEC/10u);/*DetermineMAX.idlecountervaluefor1/10second*/
OS_ENTER_CRITICAL();
OSIdleCtrMax=OSIdleCtr;/*Storemaximumidlecountercountin1/10second*/
OSStatRdy=OS_TRUE;
OS_EXIT_CRITICAL();
}
函数中刚开始延时两个时钟节拍,是为了进行时钟同步,第一,在就绪表中删除掉当前任务的就绪标志,这个当前任务也就是调用OSStatInt()的用户编写的TaskStart()任务,这是用户创建的优先级最高的任务;如果用户应用程序打算使用统计任务,用户必须在初始化时建立一个唯一的任务,在这个任务中调用OSStatInit()。
换句话说,在调用系统启动函数OSStart()之前,用户初始代码必须先建立一个任务,在这个任务中调用系统统计初始化函数OSStatInit(),然后再建立应用程序中的其它任务。
因为用户的应用程序必须先建立一个起始任务[TaskStart()],当主程序main()调用系统启动函数OSInit()的时候,μC/OS-Ⅱ只有3个要管理的任务:
TaskStart()、OSTaskIdle()和OS_TaskStat()。
请注意,任务TaskStart()的名称是无所谓的,叫什么名字都可以。
因为μC/OS-Ⅱ已经将空闲任务的优先级设为最低,即OS_LOWEST_PR10,统计任务的优先级设为次低,OS_LOWEST_PR10减1。
启动任务TaskStart()总是优先级最高的任务。
进入多任务的条件准备好了以后,调用系统启动函数OSStart()。
这个函数将使TaskStart()开始执行,因为TaskStart()是优先级最高的任务。
OSCtxSwCtr—一秒钟内任务切换次数的全局变量
OSTimeDlyHMSM(0,0,1,0)—TaskStart()任务自身挂起1s,uc/os开始任务调度
TaskStart()负责初始化和启动时钟节拍。
在这里启动时钟节拍是必要的,因为用户不会希望在多任务还没有开始时就接收到时钟节拍中断。
接下去TaskStart()调用统计初始化函数OSStatInit()。
统计初始化函数OSStatInit()决定在没有其它应用任务运行时,空闲计数器(OSIdleCtr)的计数有多快。
奔腾II微处理器以333MHz运行时,加1操作可以使该计数器的值达到每秒15,000,000次。
OSIdleCtr的值离32位计数器的溢出极限值4,294,967,296还差得远。
微处理器越来越快,用户要注意这里可能会是将来的一个潜在问题。
系统统计初始化任务函数OSStatInit()调用延迟函数OSTimeDly()将自身延时2个时钟节拍以停止自身的运行。
这是为了使OSStatInit()与时钟节拍同步。
μC/OS-Ⅱ然后选下一个优先级最高的进入就绪态的任务运行,这恰好是统计任务OSTaskStat()。
读者会在后面读到OSTaskStat()的代码,但粗看一下,OSTaskStat()所要做的第一件事就是查看统计任务就绪标志是否为“假”,如果是的话,也要延时两个时钟节拍。
一定会是这样,因为标志OSStatRdy已被OSInit()函数初始化为“假”,所以实际上DSTaskStat也将自己推入休眠态(Sleep)两个时钟节拍。
于是任务切换到空闲任务,OSTaskIdle()开始运行,这是唯一一个就绪态任务了。
CPU处在空闲任务OSTaskIdle中,直到TaskStart()的延迟两个时钟节拍完成。
两个时钟节拍之后,TaskStart()恢复运行。
在执行OSStartInit()时,空闲计数OSIdleCtr被清零。
然后,OSStatInit()将自身延时整整一秒。
因为没有其它进入就绪态的任务,OSTaskIdle()又获得了CPU的控制权。
一秒钟以后,TaskStart()继续运行,还是在OSStatInit()中,空闲计数器将1秒钟内计数的值存入空闲计数器最大OSIdleCtrMax中。
OSStarInit()将统计任务就绪标志OSStatRdy设为“真”,以此来允许两个时钟节拍以后OSTaskStat()开始计算CPU的利用率。
TaskStart()任务重新就绪,获得cpu使用权,就执行OSIdleCtrMax=OSIdleCtr; OSStatRdy =TRUE;此后程序结束。
至于,启动多任务之后,统计任务如何计算cpu利用率的,书上说的统计任务每秒执行一次(ucos是实时操作系统,可以做到),大家可以想一想,统计任务执行到最后时是OSTaskStat()调用任务统计外界接入函数OSTaskStatHook()[L3.14(3)],这是一个用户可定义的函数,这个函数能使统计任务得到扩展。
这样,用户可以计算并显示所有任务总的执行时间,每个任务执行时间的百分比以及其它息。
OSTimeDly(OS_TICKS_PER_SEC);这样一个函数。
表示延迟一秒。
而只有统计任务延时时空闲任务才有可能运行,所以每当统计任务运行时OSIdleCtr的值都是在可能的1s内记的数。
因为在统计任务在赋完值之后清零了。
所以,用统计任务可以统计cpu利用率。
3.Ucos中常见类型的宏定
typedefunsignedintOS_STK;
typedefunsignedintOS_CPU_SR;
#ifOS_CRITICAL_METHOD==3
#defineOS_ENTER_CRITICAL(){cpu_sr=OS_CPU_SR_Save();}
#defineOS_EXIT_CRITICAL(){OS_CPU_SR_Restore(cpu_sr);}
#endif
UCOSII提供OS_ENTER_CRITICAL和OS_EXIT_CRITICAL两个宏来实现,这两个宏需要我们在移植UCOSII的时候实现。
本章我们采用方法3(即OS_CRITICAL_METHOD为3)来实现这两个宏。
因为临界段代码不能被中断打断,将严重影响系统的实时性,所以临界段代码越短越好!
OSIdleCtr:
空闲计数器。
当所有任务都没就绪怎么办?
这时就需要空闲任务了,我们把它设为优先级最低的任务。
WFE指令为休眠指令,当来中断时,退出休眠,然后看看有没有已就绪的任务,有则调度之,否则继续休眠,这样可以减小功耗哦。
#defineOS_MAX_TASKS10u//OS_CFG.h中定义
#defineOS_TICKS_PER_SEC200u
4.任务优先级
任务数少于64位的时候,优先级的计算方式:
OSRdyGrp|=OSMapTbl[OS_PRIO>>3] //记录组就绪标志
//OS_PRIO的yyy有2^3种取法,记录组号TBL[yyy](就绪表第几行)
OSRdyTbl[prio>>3]|= OSMapTbl[OS_PRIO&0x07]
//在TBL[yyy]上记录列就绪标志
y =OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy=(INT8U)((y<<3u)+OSUnMapTbl[OSRdyTbl[y]]);
用OSUnMapTbl[OSRdyGrp]确定最高就绪组Y。
OSRdyGrp 比如最高就绪组是0组,那么OSRdyGrp的可能是00000001,00000011,00000101,00001001......11111111.一共2^7种。
我们通过判断置1的最小就绪位是0位就可以知道最高就绪组为0组。
计算机(人视觉的并行处理比计算机强多了)也要一位一位判断。
先看第零位是不是1,是就是第零组,不是判断第2位。
那前几位还好说,后面几位的计算量是相当大的。
不想算干脆用个已经算好的答案来映射吧。
OSUnMapTbl就是这样一个对答案数组(有效减少计算量)。
OSUnMapTbl[OSRdyGrp]的值计算好后保存在Y中,OSRdyTbl[y]记录的就是最高就绪组中的就绪情况。
OSUnMapTbl[OSRdyTbl[y]]用答案表算出这组中最高就绪任务X。
(y<<3u)这个就是他在第几组,一组8个任务,Y<<3相当于Y*8 OSPrioHighRdy就等于Y*8+X
把优先级值右移动三位(prio>>3),就能得到该优先级在OSRdyTbl[]数组中的第几组。
而取优先级值的后三位(prio&0x07),就能得到该优先级在OSRdyTbl[]数组中某一组的第几位。
要在就绪表删除指定优先级任务,首先
1、在OSRdyTbl数组中把该优先级所在的位清0(把1改为0),
2、然后判断其所在的优先级组是否为0,为0则说明该优先级组中的优先级全都没有被设置就绪。
此时就可以把该优先级组与OSRdyGrp相对应的位清0。
否则不需要更改OSrdyGrp。
过程:
取OSRdyGrp中被置1的最低位y,得到最高优先级所在的任务组(即在OSRdyTbl[]的y元素),再取这个任务组中被置1的最低位x,得到最高优先级在OSRdyTbl[]所对于的位,再通过y与x的组合就得到了最高优先级。
y=OSUnMapTal[OSRdyGrp];
//通过查表取得OSRdyGrp最低有效位(被置1的位),即取得最高优先级所在的任务组。
x=OSUnMapTal[OSRdyTbl[y]];
//通过查表取得该任务组中最低有效位,即取得最高优先级所对应的OSRdyTbl[y]位。
prio=(y<<3)+x;
OSTCBBitX=OSMapTbl[OSTCBX];
OSTCBBitY=OSMapTbl[OSTCBY];
我们最初求解的等式可以表达为;
OSRdyGrp=OSMapTbl[OSTCBY];
OSRdyTbl[prio>>3]=OSMapTb
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- ucos 笔记