ucOSii任务调度

HarderHeng Lv5

uCOS任务调度算法思路

这里介绍uCOS最重要的任务调度算法:优先级位图法

首先明确,uCOS中每一个任务都有唯一的优先级,也就是说一个优先级就能直接确定一个任务。

  • 当创建一个新任务时,要给它一个确定的优先级,然后将这个优先级的任务设为就绪。

  • 当任务放弃处理器,调度器介入时,需要在所有的就绪的任务中找到优先级最高的任务。

对于第一个要求,只要有一个数据结构来存储就绪的状态即可,例如数组。

对于第二个要求,对一个实时性比较高的操作系统来说,是不容易实现的。如果使用数组的数据结构,那么寻找最高优先级的就绪任务就需要遍历数组,显然速度很慢。如果任务比较少,可以允许遍历。但是一旦任务数量多起来,甚至说支持256个任务时,显然就不可能遍历了。

这里引入uCOS的优先级位图法,以64个优先级为例。

位图法详解

1. 创建新任务

64个优先级分为两层查找,做一个8*8的数组。

第一层用一个字节表示,也就是OSRdyGrp,这个字节哪个位置上是1,就代表哪一行有就绪任务。

第二层是找到的某一行,也是用一个字节表示,**OSRdyTbl[]**,这个字节哪个位置上是1,就代表这一行的那一个是就绪任务。

现在创建一个优先级为15的任务。因为分了两层,并且第一层的索引是8个,所以15这个数字的最低三位表示这个任务在那一行的位置,次三位表示这个任务所在的行。

1
2
3
int prio = 15;
x = prio & 0x07; //获得在一行的哪一个位置
y = prio >> 3; //获得在哪一行

但是此时只是得到了两个数字,而不是两层索引。现在要把y = 1变成00000010的表示,并且将这个二进制数和OSRdyGrp结合起来,就达到了我们的目的。

uCOS引入了一个查找表,**OSMapTbl[]**。下标为1时,元素为00000010,同样的下标为7时,元素为10000000

1
2
3
4
5
6
int prio = 15;
int x = OSMapTbl[prio & 0x07];
int y = OSMapTbl[prio >> 3];
OSRdyGrp |= y;
OSRdyTbl[y] = x;
//在实际的代码中,就不需要x和y了,直接进行或运算即可

2. 在就绪任务中寻找最高优先级

因为有两层的位图索引,所以只要找到OSRdyGrp中的最低有效位,就能知道是哪一行,然后在对应的行**OSRdyTbl[]**中寻找最低有效位,就能找到是哪一列,结合起来就是最高优先级了。但是这样去直接在这两个字节中寻找,依然需要非常数级别的时间。有没有什么办法减少这个时间呢?

uCOS又引入了一个查找表OSUnMapTbl[]。对于OSRdyGrp或者**OSRdyTbl[]**的每一个可能的数字,其最低有效位都是固定可知的,将这些有限的数据记录成一张查找表,就可以直接找到最低有效位。

1
2
3
y = OSUnMapTbl[OSRdyGrp];
x = OSUnMapTbl[OSRdyTbl[y]];
prio = y * 8 + x;

优先级位图法扩展

可以看到在寻找最低有效位时不能保证实时性,所以引入了优先级位图法的额外查找表。

虽然在两层索引中,寻找最低有效位的时间是有限的,查找两个字节,也就是顶多16次查找,但是对实时操作系统来时可能还是难以容忍。

在TLSF内存管理算法中得到了一点启示,使用CLZ硬件指令得到最高有效位,这个操作是一个硬件电路实现的指令,其复杂度可以认为是常数级别。

硬件实现某种操作,降低复杂度,耦合软件与硬件带来更好的性能,和更低的编程门槛。

虽然说优先级位图法的查找表带来了额外空间,但是对一个任务数量本身就有限的嵌入式操作系统来说,这个查找表的大小也并没有很大,最终认为,软件上的巧妙实现是更加优秀的。

TCB(TaskControlBlock)设计

  • 栈顶指针 OSTCBStkPtr
  • 指向下一个TCB的指针OSTCBNext
  • 指向上一个TCB的指针OSTCBPrev
  • 任务时延 OSTCBDly
  • 任务状态 OSTCBStat
    • OS_STAT_RDY 就绪可运行
    • OS_STAT_SEM 等待信号量
    • OS_STAT_MBOX 等待邮箱信息
    • OS_STAT_Q 等待消息队列
    • OS_STAT_SUSPEND 被挂起
    • OS_STAT_PEND_ANY 等待其他的对象
  • 任务等待状态 OSTCBStatPend
    • 从等待状态退出时是否成功,等待超时,等待被中止等等
  • 任务优先级 OSTCBPrio
    • OSTCBX
    • OSTCBY
    • OSTCBBitX
    • OSTCBBitY
  • EXTENSIONS
    • 扩展数据
    • TLS线程本地存储支持
    • 系统事件指针OS_Eventptr
    • 多事件指针 OSTCBEventMultiPtr
    • 多事件就绪指针OSTCBEventMultiRdy
    • 消息 OSTCBMsg
    • 标志模式OSTCBFlagMode
    • 就绪标志OSTCBFlagsRdy

OSTackCreate

  • 检查prio是否合法
  • 初始化任务栈
    • 向下移动栈指针初始化栈内存,初始化栈内存时使用各种寄存器进行占位,有用的寄存器例如PC和SP
  • 初始化TCB,分配TCB

任务切换

任务在何时进行切换?

首先考虑什么情况下任务不会位于就绪状态:

  • 如果被delay,自然就不能够进入就绪状态,会处在suspend状态
  • 如果正在等待某一个事件,比如mbox,也不会进入就绪状态,处在等在mbox的suspend状态

那么何时对就绪状态进行更新呢?

  • 每次进行Tick时,进入SysTickHandler,将delay的任务的Timedly计时减少。如果Timedly时间到了,就进入了就绪的状态。

  • Title: ucOSii任务调度
  • Author: HarderHeng
  • Created at : 2025-03-05 16:13:30
  • Updated at : 2025-03-10 15:24:30
  • Link: https://harderheng.life/2025/03/05/ucOSii任务调度/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments