uCOS-II中的任务切换机制(转)

2024-06-17 12:38
文章标签 切换 ii 机制 任务 ucos

本文主要是介绍uCOS-II中的任务切换机制(转),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

初接触UCOS-II,对其任务转换机制的实现总是有点混乱,读了一篇博文,觉得清晰了许多,在这里就转过来mark一下~



【@.1 函数周期与死循环】

 image

一般函数的生命周期很简单,从开始调用函数起,直到函数返回,即结束。这样一来就完成了这个函数的使命,它也就不再需要了。对于一般的函数就是这样,但是回过头想想,对于一个系统、OS、或者工业控制中的一个控制器重的系统个,函数返回是很轻易很随便的就能返回吗?返回就意味着函数结束,死亡,若是想系统这样一个很大的函数,它的返回就意味着系统结束。因此,对于系统的函数返回有些时候我们不希望它返回,返回时是需要好好设计的,像嵌入式中的控制程序我们也并不需要它返回,直接关机就好了。因此,一个系统往往就是一个很大的循环,不停的扫描,而我们编程的时候对于这个死循环是需要好好设计的。考虑以下一个控制要求,

@.按键控制电机启、停、正转反转,并每秒发送CAN报文报告当前情况。

我们可以有多种方法实现这一要求:

image

方法一:每次在循环体重扫描当前按键的电平,从而进入对应的控制电机函数,如果所有电平都没有信号则直接进入下一个循环。发送CAN报文就直接用一个定时中断。这样的好处就是编程简单直白,每次循环进入不同的电机控制函数,坏处很明显,一定要等待到下一个循环才能进入其他的电机控制函数,每次循环的时间不好控制,不管你用函数指针还是if/else来判断,每次循环一定要等待电机动作结束才能进入下一个循环。

image

方法二:改用外部中断来处理按键。仅当按键按下时触发外部中断,从而控制响应的电机进行操作。这样的好处就是循环体简单,可以仅仅就是一个计数器加一,所有控制都等中断来实现。但这样带来的问题也很明显,就是中断嵌套问题。比如当电机正转时按下停止按钮,这时由于是在中断中,停止按钮是否真的能够得到响应?这就涉及到中断嵌套问题,并不见得所有CPU都能支持中断嵌套,我的这一篇文章对中断嵌套问题进行了一个讨论。

image

方法三:采用RTOS的思想,加入任务调度系统。每次任务调度系统就是一个小小的循环,对于各个任务进行轮询,当这次轮询发现某任务是已经就绪的优先级最高的任务,则交给CPU处理,所有中断与任务,任务与任务之间有通讯机制可以交换信息。当然实际上并不仅仅只在任务调度器轮询时才进行任务的切换,实际上的操作比这个复杂一些,我的这篇文章就想以uCOS-II为例讨论RTOS的任务调度系统是怎样执行的。

【@.2 uCOS-II中的任务调度】

回到前面的方法一,二,我们将这种任务称作前后台任务。

image

后台就是指程序的大循环,程序一定要等到前台任务(可以说中断或某个功能函数)返回才能继续运行下去。而在RTOS中每个任务都有自己的控制块指向改任务,由任务调度器来决定这个时候该运行哪个任务。

 image

所有这些任务控制快(TCB)构成一个双向链表,每个TCB中都有一些控制字,比如一个指向堆栈的指针(*sp),一个表明当前任务状态的位(State),指明任务被挂起等待的超时时间(dly),任务的优先级(Prio),指向事件控制块的指针(*Event,事件机制后面会讨论)。一个全局的任务就续表记录了当前任务是否就绪,任务调度器就靠查询任务就绪表寻找到就绪任务中优先级最高的一个任务。

每一个任务都是一个死循环,并且必须在循环内调用系统函数来释放CPU控制权,比如调用系统的延时函数OSTimeDly()延时一段时间,这个时候系统就会知道这个任务被延时了,延时时间记录在TCB中的超时时间dly中,任务调度器将其他任务予以运行。

实际的任务调度有两种机制

1.中断级任务调度:任何中断返回时必须调用一个系统函数OSIntExit(void),进行一次任务调度。比如一般任务调度器通常是一个定时中断,比如1000次每秒,成为OS时钟。每次OS时钟要返回时都会调用OSInitExit()进行任务切换,运行当前就绪的优先级最高任务。一般这个OS时钟的优先级很低,为整个系统优先级倒数第二低(倒数第一低的是Idle Task,空闲任务),因此实际上这个OS时钟最终并不会返回。

2.任务级任务调度:在任务运行中执行一次OS的特定函数,比如前面提到的OSTimeDly(),此函数在返回之前会执行一次任务调度。

因此总的任务调度次数会远远高于OS时钟的轮询频率的。

我们下面举例来说明这两种调度机制:

【@.3 中断级任务调度】

image

当OS时钟的定时中断来临时(比如1000次每秒),会进入OS时钟的服务函数,它会做大致一下工作,递增一个全局的计数器OSTIme,遍历所有OSTCB链表,将其中的dly超时时间递减,若等于0,则在就绪列表中置位。最后这个中断要返回时会调用OSInitExit(),它会找到在任务就绪表中处于就绪状态的最高优先级的任务,并且调用OSInitCtxSw(),即上下文切换,将之前找到的任务放在任务堆栈中的寄存器推入到CPU的R0~R15,CPSR中,最后结束。这里的这个OSInitCtxSw(),上下文切换函数是实际上跟CPU打交道的函数,也是移植uCOS-II需要编写的函数。

任何中断返回时都必须调用这个OSInitExit(),不管是外部中断,定时器中断,串口中断。当然,我们可以在实际编写中将这一中断过程编写一个统一模板,调用中断服务实际函数。这是后话了。

【@.4 任务级任务调度】

image

这种调度方式实际上丰富了任务之间的通讯机制以及任务的控制,也是最需要理解的一种方式。主要的流程跟中断级方式类似,不过是需要在任务中调用一些特定的系统函数。实际的任务切换函数是OSSched(),会调用实际的上下文切换函数OS_TASK_SW()。这个函数往往跟中断级的上下文切换函数是一样的,我们移植时只需要编写一个函数即可。下面以几个具体的函数为例进行讲解:

1.OSTaskResume/ OSTaskSuspend

image

首先是一个比较好理解的OSTaskSuspend()和OSTaskResume(),直接控制任务的挂起和恢复。对于图中的TaskA,在任务运行到某处调用OSTaskSuspend()之后,会挂起就绪表中优先级为prio的任务x(优先级也就是这个函数的传入参数)。之后调用OSSched,进行任务切换。若挂起的是自己,即调用OSTaskSuspend时传入的优先级是自己的,则自身被挂起,函数不返回;若挂起的是别的任务,那么别的任务就直接被挂起,当前函数返回。

另一个任务TaskB中调用OSTaskResume(),将首先在置位表中置位优先级为prio的任务,之后交给OSSched()进行任务切换,之后就是一个优先级的判断问题,若恢复的任务比自己任务高,那么自己被挂起,高优先级任务运行;若被恢复的任务优先级低于自己,则函数自己函数返回,继续运行,直到TaskB运行到其他函数释放CPU控制权。所以像TaskB这样的任务中还需要包含一个其他的函数,比如OSTimeDly来释放CPU。

最后,若恢复的任务是自己,很明显是没有意义的。

2.OSSemPost/OSSemPend

在通讯机制上uCOS-II利用事件管理块(ECB)统一进行处理,其中信号量是最基本的一种方式,可以起到共享资源单独访问和任务间同步的功能。

 image

我们建立一个信号量,类型为OS_EVENT的指针,其内部包含了一个事件等待表,记录着等待该事件(也就是Sem自身)的所有任务,当某一任务等待此信号量时,在它的事件等待表上相应优先级置一。若任务TaskA调用了OSSemPend()等待一个信号量,此函数会调用OS_EventTaskWait,一个内部的事件等待函数。图中表明了此函数的流程,首先会建立任务A的TCB与此信号量Sem之间的链接,即TCB中的Event指针指向Sem。之后清楚任务就绪表中的相应位,再将Sem中的事件等待表置一,OS_EventTaskWait之后会调用OSSched进行实际的上下问切换,此时由于之前的一些列操作将导致这个时候任务会被挂起,只能等待其他任务(或等待超时)将其重新恢复成就绪状态。

调用OSSemPost时的流程如下

 image

OSSemPost会调用OS_EventTaskRdy,图中可以看出这个函数的流程,首先从Sem的等待事件列表中寻找到等待该事件的优先级最高的任务。可能同时有多个任务在等待此信号量,假如其中优先级最高的寻找结果就是前面的TaskA,则接下来将切断此任务的TCB与ECB的联系,并且在任务就续表中置位此任务。OS_EventTaskRdy返回之后将进入OSSched,将根据前面的一些列操作进行任务调度,当然,若发送信号量后接收方(TaskA)的优先级大于发送方(TaskB),则TaskB自身被挂起,运行TaskA;反之,TaskB继续运行。

这种信号量的发送/接收机制适用于任务同步,共享资源的独占,比如:

image

上图中所示,当TaskA调用Pend接收到信号量Sem时执行foo函数,此时TaskB在Pend处,不管怎么调度也不会执行foo函数,直到TaskA运行到Post处时才有可能执行下去。这样就防止了同一时间有两个任务同时处理共享资源(这里是函数foo())。

image

另一种情况如上图示,Post一方由一个任务或一个中断服务函数调用,接受的一方由另一个任务调用Pend。只有当发送方Post这个信号量之后,接收方Pend处才有可能继续往下运行。

注意:这点跟OSTaskResume恢复一个任务很像,但是还是有区别,一旦Resume一个任务这个任务就一直运行下去了,但是Post一个信号量之后含有Pend的任务执行一次,之后到进入任务的下个循环继续Pend,只有等新的一次Post了之后才能继续运行。

3.OSFlagPost/OSFlagPend

信号量集的发送和接受,也是一种多信号的集体通讯方式,这里不详细讨论,只举例简单说明其作用

image

图中所示TaskA和TaskB共同控制TaskTarget,只有当TaskA,B都发送了信号之后,TaskTarget才得以运行。实际上这种信号集的机制还可以有其他的逻辑关系可以配置,比如发送方中任意一个发送任务之后就可以让接收方运行。

【@.5 任务调度总结】

先简单的利用前面的内容,回过头来看文章一开头的任务,搭建一个完整的电机控制系统。这里我们利用uCOS-II中的通讯机制进行设计。

例:带按键去抖的电机控制任务:

image

四个外部中断控制电机的启停,正转反转。外部中断服务函数中Post一个信号量给一个专门的电机调度任务,电机调度任务之后Post一个信号量给按键去抖任务,此任务会利用OSTimeDly函数延时一段时间,比如说20ms,再次判断刚才按键的电平值,若任然有按键值则说明不是抖动,可以控制电机,之后利用Resume,恢复一个电机转动的实际任务。

Post信号量启用一个任务跟Resume恢复一个任务是区别很大的,最大的区别是,Post信号量之后只能让接收方运行一次,必须不停的Post才能不停的运行,而Resume一个任务之后,被恢复的任务将一直运行,只能用Suspend将其再次挂起。

下图表明了任务状态之前切换的关系,调用哪种函数可以进行任务切换。uCOS-II就是通过这些直接或间接调用的系统函数进行任务切换的。

image


这篇关于uCOS-II中的任务切换机制(转)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

AI基础 L9 Local Search II 局部搜索

Local Beam search 对于当前的所有k个状态,生成它们的所有可能后继状态。 检查生成的后继状态中是否有任何状态是解决方案。 如果所有后继状态都不是解决方案,则从所有后继状态中选择k个最佳状态。 当达到预设的迭代次数或满足某个终止条件时,算法停止。 — Choose k successors randomly, biased towards good ones — Close

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

【Tools】大模型中的自注意力机制

摇来摇去摇碎点点的金黄 伸手牵来一片梦的霞光 南方的小巷推开多情的门窗 年轻和我们歌唱 摇来摇去摇着温柔的阳光 轻轻托起一件梦的衣裳 古老的都市每天都改变模样                      🎵 方芳《摇太阳》 自注意力机制(Self-Attention)是一种在Transformer等大模型中经常使用的注意力机制。该机制通过对输入序列中的每个元素计算与其他元素之间的相似性,

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分

从0到1,AI我来了- (7)AI应用-ComfyUI-II(进阶)

上篇comfyUI 入门 ,了解了TA是个啥,这篇,我们通过ComfyUI 及其相关Lora 模型,生成一些更惊艳的图片。这篇主要了解这些内容:         1、哪里获取模型?         2、实践如何画一个美女?         3、附录:               1)相关SD(稳定扩散模型的组成部分)               2)模型放置目录(重要)

【Tools】大模型中的注意力机制

摇来摇去摇碎点点的金黄 伸手牵来一片梦的霞光 南方的小巷推开多情的门窗 年轻和我们歌唱 摇来摇去摇着温柔的阳光 轻轻托起一件梦的衣裳 古老的都市每天都改变模样                      🎵 方芳《摇太阳》 在大模型中,注意力机制是一种重要的技术,它被广泛应用于自然语言处理领域,特别是在机器翻译和语言模型中。 注意力机制的基本思想是通过计算输入序列中各个位置的权重,以确

MyBatis 切换不同的类型数据库方案

下属案例例当前结合SpringBoot 配置进行讲解。 背景: 实现一个工程里面在部署阶段支持切换不同类型数据库支持。 方案一 数据源配置 关键代码(是什么数据库,该怎么配就怎么配) spring:datasource:name: test# 使用druid数据源type: com.alibaba.druid.pool.DruidDataSource# @需要修改 数据库连接及驱动u

学习记录:js算法(二十八):删除排序链表中的重复元素、删除排序链表中的重复元素II

文章目录 删除排序链表中的重复元素我的思路解法一:循环解法二:递归 网上思路 删除排序链表中的重复元素 II我的思路网上思路 总结 删除排序链表中的重复元素 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 图一 图二 示例 1:(图一)输入:head = [1,1,2]输出:[1,2]示例 2:(图