手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务调度策略(三)

本文主要是介绍手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务调度策略(三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

继续......

if (ticks > 0u) {                            /* 0 means no delay!                                  */OS_ENTER_CRITICAL();y            =  OSTCBCur->OSTCBY;        /* Delay current task                                 */OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;if (OSRdyTbl[y] == 0u) {OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;}OSTCBCur->OSTCBDly = ticks;              /* Load ticks in TCB                                  */OS_EXIT_CRITICAL();OS_Sched();                              /* Find next task to run!                             */}

依然是这一部分,接下来的重点是这个函数:OS_Sched()

这个函数实在是太重要了,因此我不得不慎重。

首先看一下官方的注释:

/*********************************************************************************************************
* SCHEDULER
*
* Description: This function is called by other uC/OS-II services to determine whether a new, high
* priority task has been made ready to run. This function is invoked by TASK level code
* and is not used to reschedule tasks from ISRs (see OSIntExit() for ISR rescheduling).
*
*********************************************************************************************************/

从上面的说明可以看出,这个函数的作用,主要是用来调度当前已经进入了就绪状态的最高优先级任务,然后切换进去。

函数定义如下:

void  OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3u                           /* Allocate storage for CPU status register     */OS_CPU_SR  cpu_sr = 0u;
#endifOS_ENTER_CRITICAL();if (OSIntNesting == 0u) {                          /* Schedule only if all ISRs done and ...       */if (OSLockNesting == 0u) {                     /* ... scheduler is not locked                  */OS_SchedNew();OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy     */
#if OS_TASK_PROFILE_EN > 0uOSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task      */
#endifOSCtxSwCtr++;                          /* Increment context switch counter             */OS_TASK_SW();                          /* Perform a context switch                     */}}}OS_EXIT_CRITICAL();
}

关于任务调度的部分肯定是原子操作,不允许任何中断存在,因此必须要关闭中断。

其次,任务调度不能发生在中断中以及任务调度器上锁的情况,因此必须加以判定。

上面的内容比较简单,去掉那些判断和宏开关,我们需要关注的重点主要在以下的部分:

OS_SchedNew();
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
if (OSPrioHighRdy != OSPrioCur) {         /* No Ctx Sw if current task is highest rdy     */OS_TASK_SW();                          /* Perform a context switch                     */
}

首先看这个函数:OS_SchedNew()

函数定义如下:

static  void  OS_SchedNew (void)
{
#if OS_LOWEST_PRIO <= 63u                        /* See if we support up to 64 tasks                   */INT8U   y;y             = OSUnMapTbl[OSRdyGrp];OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
#else                                            /* We support up to 256 tasks                         */INT8U     y;OS_PRIO  *ptbl;if ((OSRdyGrp & 0xFFu) != 0u) {y = OSUnMapTbl[OSRdyGrp & 0xFFu];} else {y = OSUnMapTbl[(OS_PRIO)(OSRdyGrp >> 8u) & 0xFFu] + 8u;}ptbl = &OSRdyTbl[y];if ((*ptbl & 0xFFu) != 0u) {OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(*ptbl & 0xFFu)]);} else {OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u);}
#endif
}

因为我们的系统最高支持64个任务,所以去掉那些我们不需要关注的地方,函数定义简化如下:

static  void  OS_SchedNew (void)
{INT8U   y;y  = OSUnMapTbl[OSRdyGrp];OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
}

看着好像很简单,整个函数就两句话,但是,只要能把这两句话给弄明白了,关于调度的东西基本上就都没问题了。

关于变量OSRdyGrp与数组OSRdyTbl[]的意义,相信各位都已经十分理解,分别代表组就绪状态和任务就绪状态,那么新出来的这个数组OSUnMapTbl[]又代表什么呢?它和任务就绪表有什么关系?

跟踪OSUnMapTbl数组的定义可以发现,这是一个常数数组,它里面的内容是只读的,定义如下:

INT8U  const  OSUnMapTbl[256] = {0u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x00 to 0x0F                   */4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x10 to 0x1F                   */5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x20 to 0x2F                   */4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x30 to 0x3F                   */6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x40 to 0x4F                   */4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x50 to 0x5F                   */5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x60 to 0x6F                   */4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x70 to 0x7F                   */7u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x80 to 0x8F                   */4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x90 to 0x9F                   */5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xA0 to 0xAF                   */4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xB0 to 0xBF                   */6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xC0 to 0xCF                   */4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xD0 to 0xDF                   */5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xE0 to 0xEF                   */4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u  /* 0xF0 to 0xFF                   */
};

这个表还不小,一眼看去脑袋都大了,它到底是个什么玩意儿?别慌,听我慢慢讲解……

依然举个例子,现在我们的系统只有两个任务(0和12),当前的任务优先级是0,然后这个任务进入了延时,这个时候根据前面了解的东西:

 y            =  OSTCBCur->OSTCBY;        /* Delay current task */                             OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;

这个优先级为0的任务已经被设置为了未就绪状态,也就是把它的就绪表清空了,对应的OSRdyTbl[0]肯定是0,由于只有两个任务,因此对应的OSRdyGrp的最后一个bit位,也肯定是0,。

然后我们还有一个优先级为12的任务已经准备就绪, 那么代码执行到了这里,任务的OSRdyTbl[1]必然等于0x10,组号OSRdyGrp必然等于0x02,把2带进这个常数表,得到的结果是:1

这个“1”有什么意义?

y             = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);

结论:这两句代码真正的功能,就是从系统中,把当前已经就绪了的任务里,优先级最高的那个任务给找出来,而这个任务也就是我接下来要切换进去的那一个。

假设现在我们系统中只有两个任务,一个优先级为0(未就绪),一个优先级为12(就绪),现在我们就来看看,他到底是怎么把12这个数据给找出来的。

上面说了,当一个任务的优先级是确定数的时候,他的组号、组内坐席号,偏移量等都是确定的。

当只有任务12就绪时,这个时候组号OSRdyGrp必然等于0x02,那么把它带入那个常数表中,得到结果y为1。

在把1带入数组OSRdyTbl[1]中,等到结果的结果是0x10(参考上一节),把0x10带入常数表,得到的结果OSUnMapTbl[OSRdyTbl[y]] == 4

这个y等于1,把它向左移动3个bit,得到的结果是8(二进制00001000)……最后的结果8 + 4 = 12。

没想到它真的把我需要的优先级给算出来了,到底是怎么做到的?

其实,UCOSII使用的这种方法被叫查表法,根据一定的规律,直接计算出当前优先级最高的那个任务号。

那个看似莫名其妙的常数表OSUnMapTbl,其实它所代表的意思,是0~255个数字中,1所在的最低位:

比如1,二进制00000001,在它的最低位出现了1,那么带入常数表一查,发现OSUnMapTbl[1] = 0,也就是第0位出现了1。

比如2,二进制00000010,在它的次低位出现了1,那么带入常数表一查,发现OSUnMapTbl[2] = 1,也就是第1位出现了1。

比如3,二进制00000011,在它的最低位出现了1,那么带入常数表一查,发现OSUnMapTbl[3] = 0,也就是第0位出现了1。

  ……

比如12,二进制00001100,在它的第2位出现了1,那么带入常数表一查,发现OSUnMapTbl[12] = 2,也就是第2位出现了1。

比如63,二进制011111111,在它的最低位出现了1,那么带入常数表一查,发现OSUnMapTbl[3] = 0,也就是第0位出现了1。

因为有了这个表,算法上才可能做到无论有多少个任务进入了就绪的状态,我都能轻轻松松地取出优先级最高的那一个,根据变量OSRdyGrp的状态,我可以找到那些组有就绪的任务,

如果第0组和第3组内都有就绪的任务,那么OSRdyGrp肯定等于0x05,但是我根本不用关心第3组的状态,忽略即可,因为第0组明显优先程度更大,我只需要继续前往第0组内寻找便可。

进入第0组内部后,假如优先级为1和优先级为5的任务都就绪了,变量OSRdyTbl[0] == 0x22(二进制00100010),我肯定要执行优先级为1的任务,那么我就直接把这个数据带进那个常数表中去查寻,看看最低位出现1的位置,也就是就绪的任务到底是哪一个,

只要找到了它,别的任务就算是就绪状态,我也不用管了……查表法便是基于这个原理。

y             = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);

再看一下这两句代码,第一句代码的意思是:找到就绪任务中优先级最高的组号,比如1组和3组都就绪了,我需要的结果是:1。

第二句代码中的这句话OSUnMapTbl[OSRdyTbl[y]]的意思是:找到在这个组中,优先级最高的任务的坐席号,也就是偏移量,比如任务12和任务13都就绪了,我需要的结果是:4(代表任务12的偏移)。

整个第二个代码的意思是:把组号和组内坐席号组合起来,形成最后的任务优先级。

如果想不明白的话,可以参考上一章:

ptcb->OSTCBY             = (INT8U)(prio >> 3u);
ptcb->OSTCBX             = (INT8U)(prio & 0x07u);

这两句话的意思,在建立任务的时候,把一个好好的优先级给拆开,现在终于是把它们给重新合上了。

问:现在回过头看,OS_SchedNew这个函数的作用是什么?

答:很明显,它的作用就是寻找到,在当前已经就绪的任务中,优先级最高的那一个任务。

问:题外话,思考一个问题,为什么要用查表法呢?如果是自己来做这个策略,有没有其他的方法?

答:当然有,如果是我来做,或许可以建立一个大表,里面装有所有任务的就绪状态,然后写一个for循环,每次进行任务切换的时候,从低到高依次判断,如果任务状态位bit是1,那么就证明这个任务是就绪了的,立即跳出去进行任务切换,如果任务状态bit是0,那就证明这个任务没有就绪,继续进行下一个判断,我想这样肯定更容易理解一些。

不过这样做有一个问题,如果当前我就绪的任务优先级是0,那么在第一个循环就能找到任务,然后任务切换,时间比查表法块很多,如果我就绪的任务是255呢?那么我可能就需要循环255次才能找到就绪的任务,那么时间肯定会很长。

用这种方法会导致寻找就绪任务需要的时间完全不能确定,有时候短,有时候长,然而这对于一个系统而言,最怕的就是这种不确定因素。

查表法和循环法就完全不同了,它虽然死板一些,但不管当前系统有多少任务,不管当前有多少任务是处于就绪状态,它每次计算出最高优先级的任务的时间是一定的,这种确定性对于系统很重要。

待续......

这篇关于手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务调度策略(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/294308

相关文章

Python 中 requests 与 aiohttp 在实际项目中的选择策略详解

《Python中requests与aiohttp在实际项目中的选择策略详解》本文主要介绍了Python爬虫开发中常用的两个库requests和aiohttp的使用方法及其区别,通过实际项目案... 目录一、requests 库二、aiohttp 库三、requests 和 aiohttp 的比较四、requ

Redis过期键删除策略解读

《Redis过期键删除策略解读》Redis通过惰性删除策略和定期删除策略来管理过期键,惰性删除策略在键被访问时检查是否过期并删除,节省CPU开销但可能导致过期键滞留,定期删除策略定期扫描并删除过期键,... 目录1.Redis使用两种不同的策略来删除过期键,分别是惰性删除策略和定期删除策略1.1惰性删除策略

手把手教你idea中创建一个javaweb(webapp)项目详细图文教程

《手把手教你idea中创建一个javaweb(webapp)项目详细图文教程》:本文主要介绍如何使用IntelliJIDEA创建一个Maven项目,并配置Tomcat服务器进行运行,过程包括创建... 1.启动idea2.创建项目模板点击项目-新建项目-选择maven,显示如下页面输入项目名称,选择

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

缓存策略使用总结

缓存是提高系统性能的最简单方法之一。相对而言,数据库(or NoSQL数据库)的速度比较慢,而速度却又是致胜的关键。 如果使用得当,缓存可以减少相应时间、减少数据库负载以及节省成本。本文罗列了几种缓存策略,选择正确的一种会有很大的不同。缓存策略取决于数据和数据访问模式。换句话说,数据是如何写和读的。例如: 系统是写多读少的吗?(例如基于时间的日志)数据是否是只写入一次并被读取多次?(例如用户配

Flink任务重启策略

概述 Flink支持不同的重启策略,以在故障发生时控制作业如何重启集群在启动时会伴随一个默认的重启策略,在没有定义具体重启策略时会使用该默认策略。如果在工作提交时指定了一个重启策略,该策略会覆盖集群的默认策略默认的重启策略可以通过 Flink 的配置文件 flink-conf.yaml 指定。配置参数 restart-strategy 定义了哪个策略被使用。常用的重启策略: 固定间隔 (Fixe

ispunct函数讲解 <ctype.h>头文件函数

目录 1.头文件函数 2.ispunct函数使用  小心!VS2022不可直接接触,否则..!没有这个必要,方源一把抓住VS2022,顷刻 炼化! 1.头文件函数 以上函数都需要包括头文件<ctype.h> ,其中包括 ispunct 函数 #include<ctype.h> 2.ispunct函数使用 简述: ispunct函数一种判断字符是否为标点符号的函

深度学习速通系列:深度学习算法讲解

深度学习算法是一系列基于人工神经网络的算法,它们通过模拟人脑处理信息的方式来学习和解决复杂问题。这些算法在图像识别、语音识别、自然语言处理、游戏等领域取得了显著的成就。以下是一些流行的深度学习算法及其基本原理: 1. 前馈神经网络(Feedforward Neural Networks, FNN) 原理:FNN 是最基本的神经网络结构,它由输入层、隐藏层和输出层组成。信息从输入层流向隐藏层,最

Java后端微服务架构下的API限流策略:Guava RateLimiter

Java后端微服务架构下的API限流策略:Guava RateLimiter 大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿! 在微服务架构中,API限流是保护服务不受过度使用和拒绝服务攻击的重要手段。Guava RateLimiter是Google开源的Java库中的一个组件,提供了简单易用的限流功能。 API限流概述 API限流通过控制请求的速率来防止