首页 > 技术文章 > 基于μcOS-II实时操作系统源码实现RMS和EDF调度(共享资源)

iterationjia 2020-11-16 17:11 原文

μcOS-II多任务实验报告(RMS、EDF调度)

一、实验概述

  • 实验目的:在ucOS-II上多任务实验,要求考虑到任务间存在共享资源的情况。
  • 本次实验使用到的调度策略为RMS和EDF。
  • 其中RMS为固定优先级的调度,其实就是从就绪态中找到优先级最大的,就是周期最小的让它进入运行态。
  • 而EDF调度起源于EDD算法,EDD算法要求最小化最大延迟,只要将ddl近的设为高优先级就行了,而EDF要求进一步支持任务在任意时间到达,即EDF需要动态的维持任务的优先级。
  • 共享资源利用信号量OSSemCreate(1)实现
  • 备注:在提交的作业中,我把Sched_EDF()注释掉了,默认输出结果是RMS调度(有共享资源)。想看EDF的结果就到oscore.c中,找到OS_SchedNew(),把RMS调度注释掉,清除EDF的注释。想看无共享资源就到main.c,找到对任务resource初始化的代码,注释掉即可。

二、环境搭建

本实验使用的μcOS-II系统,是移植到VS2017上的。在打开解决方案OS2.sln后,发现其使用的Windows SDK版本较旧,因此需要在项目属性中重新指定SDK版本。重新配置后,项目能够正常编译运行。

三、代码分析

在借助资料,研读代码执行流程后,我认识到:

  • 任务在main.c中创建,新建任务后会生成对应的TCB程序控制块(定义在ucos-ii.h中的OS_TCB),并链接到TCB链表中头,链表尾是程序创建的空闲任务。
  • 任务在while中无限循环,cpu利用TimeTick(在os_cpu_c.c中)在这些任务中切换。每隔一段时间中断,中断调用函数OSTickW32,顺序执行OSIntEnter()、OSTimeTick()、OSIntExit()、OSIntCurTaskResume()。
  • 中断主要使用的函数OSTimeTick()在os_core.c中,它会遍历OSTCBList,将所有TCB的等待时间(ptcb->OSTCBDly)减一,如果那个TCB的等待时间变成0了,就把它的优先级添加到任务就绪表。
  • 再看OSIntExit(),也在os_core.c中,通过OSPrioHighRdy,然后去OSTCBPrioTbl数组找到了对应的TCB,赋值给OSTCBHighRdy指针。然后调用了上下文切换函数OSIntCtxSw()函数(用来切换线程,修改了OSTCBCur,OSPrioCur)。
  • 值得注意的是,在OSIntExit()中程序是通过OS_SchedNew()来取得当前就绪态中优先级最高的TCB块的,用OSPrioHighRdy表示。
  • 而OS_SchedNew()(同在os_core.c中),仅仅通过三行关键代码(就在<=63的那个if里)就完成了对RMS的支持。如果想修改调度算法,应该就是改这里了。

四、实验步骤

1 给TCB块添加扩展

如下图所示,定义在ucos_ii.h中的OS_TCB下面,在创建任务时作为TCB扩展传入。OS_TCB的OSTCBExtPtr指针指向的就是这个tcb_ext_info。目的是为执行任务时提供必要的信息

2 创建并执行任务

  • 如下图所示,进行了任务创建(createTasks)与任务执行(startTasks)。都在main.c中。

  • 任务创建要初始化resource,即信号量的事件控制块指针,然后使用OSTaskCreateExt()方法创建任务。其中任务的id号=任务的优先值=任务的周期数。因为任务的优先级和任务的优先值成反比。其中对于RMS调度,任务的优先级与任务的周期成反比,也就是说任务优先值是可以直接用周期表示的。其中对于EDF调度,这个动态优先级调度,也省得我重新为三个任务指定初始优先值,对于这个用例我就默认任务初始优先值等于周期长度。

  • 任务执行先使用while循环来做一个周期的任务,利用时钟中断来切换任务。所以这个周期可能会顺利执行完,也有可能被抢占,执行可能会请求信号量。一个周期做完了,要重置rest_c并记得归还信号量。




3 添加时钟中断对剩余执行时间和剩余周期的操作

在os_core.c中每次timetick,把执行任务的rest_c减去1,并遍历TCB,把所有任务的rest_p减去1,并看哪个任务已经OSTCBDly已经为0,即进入就绪态时再重置任务的rest_p。

4 实现调度

注释掉OS_SchedNew()中的函数,在结尾插入我的新函数Sched_RMS和Sched_EDF()。

4.1 RMS调度

考虑到要实现共享资源,这三行代码还不太利于我操作,尤其是目前我对代码了解还不深的情况下。考虑到RMS较为简单,我就用自己添加的数据结构重新写了RMS调度。重新写好处是,一来EDF完全可以复用它,只要把就绪态中周期最小的改为就绪态中剩余周期最小的就行了;二来,这样方便我过滤掉就绪态中资源被别的任务占用的任务。

主要思路是初始优先级选63,遍历TCB链表(不包括61,62),找到就绪态中可请求资源或无需资源的任务中优先级最高(周期最短)的任务,如果需要请求资源,就给它资源。


4.2 EDF调度

EDF复用RMS的代码,只要把就绪态中周期最小的改为就绪态中剩余周期最小的就行了。


5 输出

两种输出,任务完成与任务抢占

  • 任务完成会调用OSTimeDly(),完成的printf写在该函数里
  • 任务抢占会走OSIntExit(),在该函数里判断一下是不是抢占,如果是就printf抢占的内容。

6 结果展示

6.1 RMS 无共享资源

6.2 EDF 无共享资源

6.3 RMS 有共享资源(最高优先级和最低优先级共享)

6.4 EDF有共享资源(最高优先级和最低优先级共享)

五 值得注意的部分

  • 遍历的时候要注意不单单有优先级为63的空闲任务,我还发现了有优先级为62、61的任务,为了方便输出的观察,我是直接将其过滤掉,不让其影响我的输出。
  • 注意可能存在这种情况,在B任务执行结束时,即rest_c=0时,如果A任务优先级高于B,会发生A先抢占B,然后A执行完之后……然后B任务才能complete。这点给我造成了很大的两点麻烦。
    • 一个是我不希望造成A抢占B,然后某任务再还给B的这种输出,而是应该让B直接完成,然后再让A做其他操作。这个我是通过给任务抢占的判断添加限制条件(在任务抢占输出那一部分),让B能跳过被抢占,直接完成,完成后再转给A。
    • 另一个是我不希望由于A抢占B,B不能立即complete造成资源还在B手里,不能归还,影响其它任务的执行。这点我通过在调度函数最后请求资源时做条件判断,避免了这种情况。
  • 给代码加入信号量部分后,发现代码运行出问题了,导致进入临界区函数OS_ENTER_CRITICAL()没法使用了,导致下面代码运行不了。在找到该函数源码后,依据网上给的思路,我试着换到另一个if判断,并注释掉了一部分代码后,能够正常运行。
  • 还有一个问题是,时钟频率太快导致代码来不及运行,这个我一开始不知道,毫无办法,后来把os_cfg.h文件中#define OS_TICKS_PER_SEC 从100改成10,这样运行就没问题了。

六 源码

github源码

推荐阅读