本文主要是介绍为什么说Ucosii是可剥夺型内核?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
为什么说Ucosii是可剥夺型内核?
因为它不仅可以实现高优先级任务可以剥夺低优先级任务的CPU使用权,还可以实现低优先级任务也可以剥夺高优先级任务的CPU使用权。
首先,在ucosii中,任务只有5种状态,分别是睡眠状态、就绪状态、运行状态、等待状态、中断状态。其次,在ucosii中有两种调度器:一种是任务级调度器OS_Sched。在OS_Sched中,会先查找优先级最高的任务,接着调用OSCtxSw实现任务切换,OSCtxSw由汇编语言实现。另一种是中断级调度器OSIntExit。在OSIntExit中,也是会先查找优先级最高的任务,接着调用OSIntCtxSw实现任务切换,OSIntCtxSw通常位于OS_CPU_A.ASM中,由汇编语言实现。
先说高优先级任务可以剥夺低优先级任务的CPU使用权。 它是怎么实现这一点的呢?依靠ucosii的时钟中断来实现。ucosii与大多数计算机系统一样,用硬件定时器产生一个周期为毫秒级的周期性中断来实现系统时钟。两次中断之间相隔的时间就是最小时钟单位,称为时钟节拍。硬件定时器以时钟节拍为周期定时产生中断。系统接收到时钟中断请求后,这时硬件层面的CPU正常情况下都是处于中断允许状态的,系统会通过强行中止当前正在运行的任务并跳转至中断服务子程序OSTickISR(题外话:以51单片机为例,中断响应的过程首先是由硬件自动生成一条长调用指令“LCALL addr16”,紧接着就由CPU执行该指令。这里的addr16就是程序存储区中相应的中断入口地址,对应这里的OSTickISR。),被中止的任务便处于中断状态。在OSTickISR中,首先保存CPU的内容至被中断的任务的任务堆栈,接着调用时钟节拍服务函数OSTimeTick。OSTimeTick不会做任务调度,它只会做两件事:一是给计数器OSTime加1;二是了解每个任务的延时状态,使已经到了延时时限的非挂起的任务进入就绪状态。OSTimeTick完事返回OSTickISR后,接着调用OSIntExit做任务调度(注意:OSIntExit做任务调度时,不一定会做任务切换。),之后中断返回,退出了OSTickISR。这时候,系统却不一定会继续运行之前被中断的任务,而是根据OSIntExit任务调度的结果有可能去运行优先级更高的任务。说白了,这是借助硬件层面的硬件定时器的时钟中断对CPU进行强行剥夺,再通过时钟中断服务子程序OSTickISR来实现软件层面对任务的CPU使用权的剥夺。这种情形可以理解为顺优先级剥夺。
这里需要强调一点:除了时钟中断OSTickISR,ucosii还存在其他类型的中断(比如OSCtxSw和OSIntCtxSw也是中断服务程序,这点要非常重视!),而且中断之间可以相互嵌套,称之为中断嵌套。在编写ucosii的中断服务程序时,按照官方的要求是要用到两个重要的函数OSIntEnter和OSIntExit。其中,OSIntEnter的作用是把全局变量OSIntNesting加1,从而用它来记录中断嵌套的层数。OSIntEnter的调用通常发生在中断服务程序(如OTTickISR)保护了中断任务的断点数据(也就是CPU相关寄存器的内容存入任务堆栈)之后,运行中断服务代码(如OSTimeTick)之前,所以又称之为进入中断服务函数。相对的,OSIntExit被称之为退出中断服务函数。这个函数在中断嵌套层数为0、调度器未被锁定且从任务就绪表中查找到的最高级别就绪任务又不是被中断的任务的情况下需要进行任务切换,否则返回被中断的服务子程序。
接着说低优先级任务也可以剥夺高优先级任务的CPU使用权。ucosii可以通过OSTimeDly将高优先级的任务强行转为等待状态,从而让出了CPU,使得低优先级的任务有机会先于高优先级的任务执行。这时用到的就是任务级调度器OS_Sched了。在OSTimeDly中,会调用OS_Sched。OS_Sched内部会先查找优先级最高的任务,然后有可能调用OS_TASK_SW做任务切换。这种情形可以理解为逆优先级剥夺。
重要细节:任务切换时,断点数据是如何被保存和恢复的?
1.断点数据主要包括PC寄存器、Rn寄存器、PSW寄存器的内容,其中PC寄存器的内容又称为断点指针。
2.无论是OSTickISR,还是OSCtxSw和OSIntCtxSw,本质上都是中断服务程序,且OSTickISR在内部执行过程中调用了OSIntCtxSw,它们都是由汇编语言编写的。它们之间的显著区别在于:OSCtxSw内部既有保护断点数据的功能代码,也有恢复断点数据的功能代码,而OSIntCtxSw只有恢复断点数据的功能代码,因此OSTickISR在内部还需要额外编写保护断点数据的功能代码。
3.先来分析在中断服务程序怎么实现将CPU中非常重要的内容保存至被中断的任务的任务堆栈的?以51单片机为例,CPU在执行LCALL指令响应中断时,会自动将断点指针压入堆栈,也就是说断点指针不需要再额外编写指令去保存。至于剩下的,可以借助push指令,比如push r1,r7 to [SP],将CPU通用寄存器的内容保存到任务堆栈。那为什么push指令能将CPU通用寄存器的内容保存到任务堆栈?因为处于运行状态中的任务的任务堆栈就设置在片内RAM中。至于如何将处于运行状态中的任务的任务堆栈就设置在片内RAM中,则是通过具体的汇编指令实现的。
4.同理,在中断返回时,CPU在执行IRET指令或者有相同功能的指令时,能把断点指针自动推入PC寄存器,剩余的数据则用pop指令来恢复,如 pop r1,r7 from [sp],将Rn弹入CPU通用寄存器,具体的汇编指令实现的。
5.ucosii的每一个任务都有一个私有的任务堆栈,通常这个堆栈是放在RAM中的。但遗憾的是,有些单片机处理器的片内RAM的存储空间极其有限,不可能把应用程序中所有任务的任务堆栈同时都放入片内RAM中。但凑巧的是,由于这种处理器只有一个CPU在某个时刻只能运行一个任务,所以只要保证这个处于运行中的任务的任务堆栈存放在片内RAM即可。于是,对于这种片内RAM较小的系统,可以把所有任务的任务堆栈内容都存放在片外RAM,而把片内RAM当做共用堆栈。每当有一个任务被调度器选中时,则在任务切换时把该任务在片外RAM中存储的任务堆栈内容复制到片内RAM中。为了方便起见,把用来存储任务堆栈内容的片外RAM空间称为任务堆栈,而将共用堆栈称为系统堆栈。
以上便是ucosii实现可剥夺的原理。
最后,想学ucosii的,可以将ucosii移植到pc机上进行研究。需要源代码和相关编译器borland的可以从这里下载。欢迎大家来提问!
这篇关于为什么说Ucosii是可剥夺型内核?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!