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

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

继续......

整个UCOSII嵌入式操作系统的任务调度策略便是如此,现在进行一个总结:

  • 某个任务在执行中,每隔一定周期发生滴答时钟中断,在中断中遍历整个任务链表,更新每个任务的延时时间,修改就绪状态。
  • 任务执行完毕后,进入延时函数,在延时函数中会把当前任务挂起(清空当前任务的就绪状态,使其进入未就绪状态),然后根据查表发找到在就绪任务中,优先级最高的那一个任务。
  • 找到新任务以后,人工强制发生一个中断,保存上个任务的堆栈信息,弹出下个任务的堆栈信息,同时更改PC指针,进行任务切换。

经过以上三个步骤,便可以完成任务的调度。

现在回到第一篇提出的那个问题:UCOSII到底是如何保证它的实时性的呢? 

如果任务的调度都是发生在当前任务进入延时之后,似乎操作系统根本无法自身的保障实时性。

比如一个优先级最低的任务由于某些处理非常耗费时间,它一直无法进入延时,导致无法进入任务切换,那么优先级高的任务反而是一只都无法被执行了……

同样在第一篇说过,UCOSII系统除了在当前任务进入延时函数会发生调度之外,还有别的时机会进行任务切换:

  1. 当前任务进入了延时;
  2. 当前任务被挂起;
  3. 当前任务执行时,发生了某些中断;

第1点我们已经全部讲完,第2点非常好理解,我们现在看一个函数:OSTaskSuspend()

这个函数的作用是把某个任务挂起(也就是不进行调度),现在来分析一个实例:

有一个任务调用了这个函数:

void App1_task(void *pdata)
{while(1){if (OS_ERR_NONE != OSTaskSuspend(OS_PRIO_SELF)){Dbg_SendStr("App1_task Suspend Error£¡\r\n");}delay_ms(10);};
}

当前任务执行了红色代码之后,便会把自身挂起来,如果没有再别的地方对它进行激活,这个任务便永远也不会执行下去了。

深入分析OSTaskSuspend函数:

INT8U  OSTaskSuspend (INT8U prio)
{BOOLEAN    self;OS_TCB    *ptcb;INT8U      y;
#if OS_CRITICAL_METHOD == 3u                     /* Allocate storage for CPU status register           */OS_CPU_SR  cpu_sr = 0u;
#endif#if OS_ARG_CHK_EN > 0uif (prio == OS_TASK_IDLE_PRIO) {                            /* Not allowed to suspend idle task    */return (OS_ERR_TASK_SUSPEND_IDLE);}if (prio >= OS_LOWEST_PRIO) {                               /* Task priority valid ?               */if (prio != OS_PRIO_SELF) {return (OS_ERR_PRIO_INVALID);}}
#endifOS_ENTER_CRITICAL();if (prio == OS_PRIO_SELF) {                                 /* See if suspend SELF                 */prio = OSTCBCur->OSTCBPrio;self = OS_TRUE;} else if (prio == OSTCBCur->OSTCBPrio) {                   /* See if suspending self              */self = OS_TRUE;} else {self = OS_FALSE;                                        /* No suspending another task          */}ptcb = OSTCBPrioTbl[prio];if (ptcb == (OS_TCB *)0) {                                  /* Task to suspend must exist          */OS_EXIT_CRITICAL();return (OS_ERR_TASK_SUSPEND_PRIO);}if (ptcb == OS_TCB_RESERVED) {                              /* See if assigned to Mutex            */OS_EXIT_CRITICAL();return (OS_ERR_TASK_NOT_EXIST);}y            = ptcb->OSTCBY;OSRdyTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX;                   /* Make task not ready                 */if (OSRdyTbl[y] == 0u) {OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;}ptcb->OSTCBStat |= OS_STAT_SUSPEND;                         /* Status of task is 'SUSPENDED'       */OS_EXIT_CRITICAL();if (self == OS_TRUE) {                                      /* Context switch only if SELF         */OS_Sched();                                             /* Find new highest priority task      */}return (OS_ERR_NONE);
}

直接从红色代码部分开始看,他首先判断一下我要挂起的任务是不是自己,现在我们传的参数就是OS_PRIO_SELF,所有它应该执行第一个if判断。

在这个if判断中保存了一下需要挂起的任务的优先级,然后用蓝色代码判断一下需要挂起的任务是否存在(由于我们挂起的是自身,自身肯定是存在的,但是这并不表示这个判断多余,因为如果是一个优先级为1的任务调用这个函数去挂起一个优先级为2的任务,那判断一下还是很必要的)。

然后接下来的几句代码就不用再解释了,和任务进入延时函数把自己的就绪状态情况是一毛一样的处理。

直接看ptcb->OSTCBStat |= OS_STAT_SUSPEND这句代码,变量OSTCBStat 很容易理解,它表示当前任务的状态,整句代码的意义就是给当前任务设定一个已经被人工挂起了的状态,免得在任务调度的时候被调度出来(在滴答时钟中断中有这个变量的判断)。

这句代码以后:

    if (self == OS_TRUE) {                                      /* Context switch only if SELF         */OS_Sched();                                             /* Find new highest priority task      */}

这几句代码也已经很熟悉了,中间那个函数就是任务切换,先看看那个判断,如果我要挂起的是当前任务,那么就立即进行切换,如果挂起的是别的任务,那就不用切换,这个理解起来应该不难。

在理解的第一种切换时机的前提下,第二种任务切换的时机很好理解,但是第二种任务切换的时机仍然不能保证任务执行的实时性,如果低优先级的任务既不进入延时,也不挂起,高优先级的任务依然无法执行。

现在来看第三种,当中断发生时,任务切换……

在任务执行期间,发生频繁的中断必然就是滴答时钟中断,现在重新回到以前看过的那个中断服务函数:

void SysTick_Handler(void)
{if(delay_osrunning==1)                      //OS开始跑了,才执行正常的调度处理{OSIntEnter();                           //进入中断OSTimeTick();                           //调用ucos的时钟服务程序OSIntExit();                            //触发任务切换软中断}
}

这一次的重点不再是第二个函数,而是第一个和第三个函数:OSIntEnter,OSIntExit。

这两个函数是成对出现,从函数名便可看出,OSIntEnter是进入中断时候调用,OSIntExit是离开中断时候调用。

由于滴答时钟是周期性调用,因此这两个函数也是周期性被调用。

OSIntEnter的定义如下:

void  OSIntEnter (void)
{if (OSRunning == OS_TRUE) {if (OSIntNesting < 255u) {OSIntNesting++;                      /* Increment ISR nesting level                        */}}
}

入口函数的定义很简单,就是对变量OSIntNesting执行加处理,表示我现在正在执行中断函数,如果发生了中断,或者有中断嵌套,那么这个变量肯定是大于1的,在系统的很多地方,都需要判断这个变量,因为很多地方都不能在中断中执行。

出口函数的定义就有些复杂了:

void  OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3u                               /* Allocate storage for CPU status register */OS_CPU_SR  cpu_sr = 0u;
#endifif (OSRunning == OS_TRUE) {OS_ENTER_CRITICAL();if (OSIntNesting > 0u) {                           /* Prevent OSIntNesting from wrapping       */OSIntNesting--;}if (OSIntNesting == 0u) {                          /* Reschedule only if all ISRs complete ... */if (OSLockNesting == 0u) {                     /* ... and 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++;                          /* Keep track of the number of ctx switches */OSIntCtxSw();                          /* Perform interrupt level ctx switch       */}}}OS_EXIT_CRITICAL();}
}

直接从红色部分开始看,首先判断系统是否在运行,在系统运行的前提下,对变量OSIntNesting进行减处理。

当进入中断以后,调用入口函数,对变量OSIntNesting加1,中断内容处理完以后,对变量OSIntNesting减1,当变量OSIntNesting为0的时候,表示没有进行中断处理,这个时候才可以进行任务切换。

if (OSLockNesting == 0u) {                     /* ... and 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++;                          /* Keep track of the number of ctx switches */OSIntCtxSw();                          /* Perform interrupt level ctx switch       */}}

然后判断一下系统是否上锁,如果上锁了,任然不能进行调度。

当一切条件就绪以后,调用函数OS_SchedNew,这个函数也已经熟悉了,作用就是寻找在就绪任务中,优先级最高的那一个。

把优先级最高的任务保存在OSPrioHighRdy中,如果当前任务不等于优先级最高的任务,那么就调用系统函数OSIntCtxSw进行任务切换……

看到这里,应该能够回答那个问题了:如何保证系统的实时性?

void SysTick_Handler(void)
{if(delay_osrunning==1)                      //OS开始跑了,才执行正常的调度处理{OSIntEnter();                           //进入中断OSTimeTick();                           //调用ucos的时钟服务程序OSIntExit();                            //触发任务切换软中断}
}

在中断服务函数中,第二个函数负责更新任务就绪表,第三个任务负责切换任务,因为滴答中断是周期性发生的,所以任务切换也是周期性发生的。

当有一个优先级低的任务执行时,如果有优先级更高的任务就绪了,那么只要发生了一次滴答中断,任务就能被立即切换过去,延时只有一个滴答时钟的时间,如果定义的时钟周期是1ms,那么低优先级的任务最多也就能运行1ms,然后便会强行剥夺CPU的执行权限,转交给高优先级的任务。

由于存在这种机制,因此便能保证UCOSII系统任务的实时性。

总结

个人认为,对于一个嵌入式操作系统而言,最核心和最重要的,便是任务调度的机制与策略,只要实现了这个功能,那么一个嵌入式操作系统的架构也就搭建了起来,至于其他的消息,邮箱,队列等功能,都是在这个架构上实现增值产品。

只要深入理解了UCOSII系统的任务调度的原理,那么自己手动实现一个简易的操作系统内核,似乎也并不是一件触不可及的事情。

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



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

相关文章

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限流通过控制请求的速率来防止