关于ucosii操作系统原理---(一)任务调度

 从正点原子资料了解到,ucosii实时操作系统可分为三部分:任务调度、任务间同步与通信、内存管理。

一、任务调度

  ucosii操作系统的任务调度算法分为两部分:一是在任务就绪表中寻找最高优先级的任务;二是完成任务切换。

  我们先来说一下任务切换,后边再说查表寻找最高优先级。关于任务切换,ucosii会给每个任务分配一个优先级,当发生任务切换时,总会切换到就绪的优先级最高的任务。而发生任务切换有两种情况:一是等待资源或者是延时;二是退出中断。

  那具体是如何来发生任务切换的呢?这里我们先回想一下中断的执行过程,程序的执行顺序是由CPU的PC计数器根据地址一步一步往下执行的,假设从02H地址开始执行,这个时候触发了中断条件,那么对应某个中断会有专门的中断向量表,假设此时我触发的是中断0,而中断0对应的中断向量表地址是13H,那么此时CPU会将下一条指令地址03H(这也是后边所提到的断点指针)储存在堆栈里边,并且把断点数据(这是通过堆栈指针)储存在堆栈中,此时PC计数器根据中断向量表PC计数器会被设置成13H,从该处地址开始执行程序,当执行完毕以后,将堆栈中的断点指针(也就是03H地址)赋给PC计数器,让程序从此处开始执行,即可恢复原来的代码执行下去。因此,ucosii也是借助这一思想,模仿刚被中断时,断点数据入栈,然后在中断程序执行完返回恢复断点数据时,此时把另一个任务的堆栈指针来替换掉当前需要返回的断点任务堆栈指针,不返回断点处继续执行,这样就发生了任务切换。ps:这里如果不理解中断的话,我建议去b站搜索爱上半导体这个博主,他讲的很明白。

  既然我们现在知道了基本原理,我们来看ucosii具体是如何实现上述过程的吧(这里推荐去看一下任哲老师的《ucosII 实时操作系 统》一书,正点原子也有该资料)。首先os为了方便管理各个任务,从而定义了一个任务控制块来管理任务(TCB),它是一个结构体,里边定义了任务堆栈指针,当前状态、优先级别等一些与任务有关的成员。TCB就相当于一张身份证,只有拥有了身份证,才会承认你是任务。随后,os将这些TCB连接起来形成两条链表(一条链表是空链表,一条链表是已经登记了任务的链表)方便管理任务。

  接下来,系统在调用OSInit()函数进行初始化时,会创建一个空任务块链表,其中的每个节点都是一个空任务控制块。当你调用OSTaskCreate()函数的时候,此时系统就会将头指针指向的空任务控制块赋给你创建的这个任务。并且为了加快对TCB的访问速度,还把链表中的TCB指针全部保存在OSTCBPrioTB1[]数组中,并且该数组的下标就是各个任务的优先级,同时在该数组中,把正在运行的TCB指针定义为OSTCBCur。如下图所示:

                                                                            

  此时还需要一个任务就绪表来方便查找任务的最高优先级是谁。首先系统需要一个就绪任务登记表,上边登记了哪些是就绪任务。在这个表中,给每一个任务安排了一个二进制位,1代表就绪,否则不就绪。这实际上是用一个U8类型的数组OSRdyTb1[]来充当这个任务就绪表,如下图所示,这是一个可以记录32个任务就绪状态的任务就绪表,这个数组一个元素可以代表8个任务的就绪状态,4个元素就可以代表32个任务的就绪状态。

                                                                                

  接下来,为了方便对于就绪表的查找,此时OS又定义了一个u8类型的变量叫做OSRdyGrp,这是一个8位的变量,它的每一位都对用上述数组的一个元素。因此,可以看到这个变量有8位,每一位对应一个OSRdyTB1[]数组的一个元素,而一个元素又记录8个任务的就绪状态,因此,ucosii最多管理64个任务。

  那如何根据这个任务登记表来查找最高优先级的任务呢?由于优先级是一个单字节数字,其值不会超过63,用二进制表示就是00111111。因此,我们把优先级看作是一个6位的二进制数,用高三位来指明变量OSRdyGrp的具体数据位,并用来确定就绪表数组元素的下标,用低三位来指明数组OSRdyTB1的具体元素。比如一个就绪的任务优先级为30,其二进制为00011110,那么它的低6位为0111110,这个时候看高三位为011,代表着OSRdyGrp的第3位置1,同时对应数组OSRdyTCB1[3]的第6位也置1。这样就能获得一个就绪任务的优先级为多少。那么通过上述方法,找到了最高优先级的任务,就得到了相应的TCB,即是OSTCBPrioTB1[最高优先级],把其赋值给指针变量OSTCBHighRdy。得到了这两个指针变量,就可以在OS_TASK_SW()宏里边切换了,实际上这里这个宏它是汇编写的,主要目的只是为了触发PendSV中断,并不是在这里发生的任务切换,而实际上的任务切换发生在PendSV中断函数里边(这里后边再出章节讲什么是PendSV)。

  接下来再详细说说任务切换的过程,这里联系到一开始所说的断点数据,我们把当前任务被终止的位置叫做断点,而在这个断点出现时保存在cpu寄存器里的数据叫做断点数据。如果我们在恢复断点数据时把另外一个任务的堆栈指针赋给cpu的堆栈指针sp,那么此时另外一个任务堆栈里边的数据因为堆栈指针的改变,数据就会转存到CPU上,因此此时CPU运行的就是新任务,而不是被终止的任务了。任务切换的动作如下图所示:

                                                                     

  因此实际上任务的切换就是CPU堆栈指针的切换,被中止运行的任务堆栈指针要保存在这个中止任务的TCB里边,然后将新任务的堆栈指针从新任务的TCB里边取出来存在CPU的SP(堆栈指针R13)里边,其示意性汇编代码如下:

                                                                              

  而要完成上述这些操作,需要进行如下步骤:

    其中,对于第一步和第七步,这里的断点指针不是堆栈指针,要区别开来。要注意先有断点,才有断点数据,而断点数据不是指一个数据,而是指通用寄存器、PC和PSW各寄存器里边的数据的集合。堆栈指针是为了将断点数据保存到堆栈里(因为堆栈里数据的转移是通过堆栈指针sp的指向来完成的。比如我们知道在CM3中的栈是向下生长的栈,sp先减4,再存入新的数值放入堆栈)。而断点指针是指向一个断点的指针,只有让PC寄存器获得这个断点指针,才能运行新任务。也就是说断点指针是新任务的入口地址,而堆栈指针是用于管理新任务里边的数据。而断点指针想要保存到堆栈里边,不像断点数据那样有入栈出栈指令,断点指针的入栈和出栈只能借助引发中断来达到这个目的了,也就是一开始所说的中断会保存断点在堆栈中,此时这里的断点指针,就类似于一开始所说的03H。那我们以什么来引发中断呢?答案是软中断指令,也就是在中断状态控制寄存器ICSR里边写1,即可触发PendSV中断。

   总结:ucosii中的任务切换实际上是模仿中断将当前运行任务的断点指针和断点数据保存在堆栈中,只不过在中断返回运行中止任务时,用新任务的断点指针和堆栈指针替换掉原来被中止的任务的断点指针和堆栈指针。

   以上就是我对ucosii操作系统任务切换的理解,理解的不一定完全正确,请谨慎参考。

  • 43
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值