• 【RTOS训练营】队列的读写、休眠和唤醒、常规应用、使用和晚课提问


    一:队列的读写

    对于队列,我们只要理解它就是一个环形缓冲区,然后还可以去休眠、唤醒,就可以了。

    1.写数据的时候,如果没有空间自己就休眠

    2.读数据的时候,如果没有数据自己就休眠

    3.写数据成功之后,如果有其他任务在等待数据,就把它唤醒

    4.读数据成功之后,如果有其他任务在等待空间,就把它唤醒

    比环形缓冲区多了:休眠和唤醒的操作。

    我们再来回顾一下怎么去创建一个队列:

    请添加图片描述

    这里,故意把这个程度改为4,来看看会发生什么事。

    请添加图片描述

    我们要关注的是这个图片里面黄色的那两个变量;

    一个是写位置:pcWriteTo,另一个是读位置:pcReadFrom

    请添加图片描述

    怎么写数据呢?假设初始情况和写了一个数据后,分别如下:

    请添加图片描述

    1.写到哪里去?pcWriteTo

    2.写完之后,pcWriteTo指向下一个位置

    那怎么读数据?假设初始情况和写了一个数据后,分别如下:

    请添加图片描述

    1.pcReadFrom先调整为指向下一个位置

    2.再从pcReadFrom读出一个元素

    读出数据之后,数据还保存在队列里, 但是指针位置变了,所以之前的那些数据并不会再次读出来,也就相当于之前的数据被废弃了。

    读和写的时候,都是使用memcpy,那么复制多长的数据呢?

    创建队列的时候就指定有每一个元素有多长。

    二:队列的休眠和唤醒

    下面我们来看看休眠和唤醒的操作。

    写队列、读队列的操作是很类似的。

    请添加图片描述

    休眠的时间可以设置成:0、portMAX_DELAY、某个值

    0的话就表示不休眠:成就成,不成就拉倒。

    通过返回值来判断是否成功:

    请添加图片描述

    请添加图片描述

    上一次课我们讲了写队列的操作,今天我们来讲读队列。

    其实大家英文好的话,看这个代码,它的注释都很完善:

    请添加图片描述

    他会判断一下时间到了没有。

    时间没到,并且队列是空的,那显然得不到数据,那就休息一下。

    怎么休息呢?

    1.当前任务,要把自己放到队列的某个链表去

    2.当前任务,把自己从ready list放到delay list

    请添加图片描述

    假设想去读数据,但是没有数据,则会被放到队列的xTasksWaitingToReceive上。

    为什么要把当前任务放到队列的xTasksWaitingToReceive链表?

    这里是登记一下,等待的数据来了可以唤醒这个任务。

    这里会涉及三个链表:

    1.我在等待数据,那别人怎么知道你在等待数据?就需要把自己放到队列的xTasksWaitingToReceive链表

    2.我要休眠,怎么休眠?把自己从ready list放到delay list

    请添加图片描述

    再强调一下,超时时间,不影响排队的位置:

    请添加图片描述

    请添加图片描述

    三:队列的常规应用

    队列的常规应用:

    1.写到队列的尾部

    2.从队列的头部读到数据

    就是先写到队列的数据,会被先读出来,FIFO,先进先出,这是常规用法。

    请添加图片描述

    我们还可以覆盖地写:

    1.队列长度是1,也就是里面只会有一个元素

    2.写了第1个数据之后,还可以继续写第2个数据、第3个数据

    本来,如果队列已经满了,是无法再写入新的数据的,但是可以用另外一个函数:

    请添加图片描述

    这函数就是覆盖的写:要注意,你能使用这个函数,前提是队列的长度只有1。

    既然是覆盖的写:那就是原来里面的数据会被覆盖。

    请添加图片描述

    从上面图可以看出来,一般的队列操作时,队列没有满才可以写数据。

    但是如果使用了xQueueOverwrite,即使队列满了也可以写数据。

    我们看到了覆盖:写的时候比较特殊,

    还有一个操作:读的时候比较特殊。

    请添加图片描述

    读的时候,本来应该是这样的,先移动读位置,读到数据:

    请添加图片描述

    对于xQueuePeek,他是这样的:
    请添加图片描述

    这两个特殊的写操作、读操作,组合起来就可以得到一个“邮箱”。

    我觉得这个邮箱取名并不好,也许是因为“橱窗”会更好。

    请添加图片描述

    他的适用场景是这样:

    A、B、C、D 4个学习委员,去买报纸。

    只要有一个人能够买到报纸,全班所有的同学都可以写“实事作文”。

    ABCD这些任务,就可以调用:xQueueOverwrite

    其他同学,就可以调用:xQueuePeek

    在这个场景里面, A买到了报纸,其他同学都可以看到这个报纸,B买到了报纸,其他同学都可以看到这个报纸。

    我们根本不在乎是谁买到了报纸,谁买到都可以,这个报纸是共享的,谁都可以看到。

    四:队列的使用

    我们来看看队列在什么情况下使用。

    请添加图片描述

    这就是队列的使用场景,左边生产数据,右边消费数据。

    请添加图片描述

    在我们的项目里,就可以使用队列,

    我们用环形缓冲区的地方,就可以改成使用队列。

    我们在使用这些函数时,要注意使用的位置。

    请添加图片描述

    这些函数有两个版本:

    1.在任务里面使用

    2.在中断函数里面使用(有FromISR后缀)

    五:晚课学员提问

    1. 问: 老师,多个队列休眠,放入xTaskWaitingToReceive队列里面时候,在队列里面的排序是在哪里?

    答: 在队列里面如果有多个任务都在等待数据,谁排在最前面?

    1.谁先来排队,谁就排在前面

    2.谁的优先级更高,他就可以插队

    请添加图片描述

    列表项,里面有一个xItemValue,会先根据这个词在链表里面找到一个位置,再把它插到链表里去。

    总之在链表里面会根据优先级来排放那些任务,如果优先级相同,就会放到同优先级的任务的后面。

    举个例子:

    请添加图片描述

    在上面的图里,有7个任务在等待数据。

    这时候,有一个优先级为3的任务也来等待数据,它在哪里排队?

    请添加图片描述

    那如果再有一个优先级为2的任务来等待数据,他插在哪里?

    请添加图片描述

    使用这种方法,就可以保证:

    1.优先级高的任务,排前面

    2.优先级相同的任务,按照函数调用时间来排

    2. 问: freeRTOS 如果在同一优先级的任务,是不是有没抢断了?只能链表时间片来轮询?

    答: 没错,同优先级的任务,只能轮询。

    3. 问: 各种不同的输入,输入的数据大小不一样,怎么应对这种情况呢?

    答: 我们创建队列的时候就指定了元素的大小,我们去读写队列时,都是使用memcpy

    请添加图片描述

    所以,假设数据源有A和B。

    A本来只需要写一个字节, B需要写100个字节。

    你偏要使用同一个队列来处理A和B提供的数据,那就只能牺牲一些效率,浪费一些空间。

    对于A,即使只需要写一个字节,也需要那么memcpy 100个字节

    4. 问: 老师,任务的优先级怎么配置比较合适,比如有三个task,task1,task2,task3。task1负责采集数据发送给task2处理,task2处理完数据后发给task3进行对外发送。频率都是100HZ。

    答: 他们有依赖关系,也就是说有前后关系,所以优先级不重要。

    如果:task2出来的过程总,允许task1采集数据,那么,task1的优先级更高。

    在实际的开发过程中:task1 > task2 > task3

    task1优先级最高,确保了数据不会丢失,

    task2 > task3 : task2处理完之后,task3才能处理

    5. 问: xQueueReceive的最开头,有个判断时间到没到,这个是怎么判断的,从哪里算是start的时间?

    答: 不是在开头判断时间,队列中没有数据才判断时间。

    请添加图片描述

    6. 问:

    请添加图片描述

    答:

    请添加图片描述

    中断函数要考虑一点:要尽快执行。

    我们假设在中断里面写队列:

    1.写入了数据

    2.导致一个优先级非常高的任务从阻塞变为了就绪

    3.会马上调度吗?

    4.不会,我的中断都还没执行完呢

    5.怎么做?记录下来:

    请添加图片描述

    6.等中断处理完了,才去触发调度

    为什么要这么做呢?

    我们反过来假设:在中断里面,没处理完中断就要去调度、切换任务。

    1.如果这个中断函数里面有两个循环,第1个循环会去切换任务A,第2个循环要去切换任务B

    2.应该把它汇聚起来,只去执行一次切换:只在最后时刻切换任务B

    你切换任务A,中断高于任务,没有用,还不如等到中断处理完的时候,再去挑出优先级最高的任务B,这样只需要切换一次。

    7. 问: 老师,3个任务的优先级那个,可不可以这样思考:如果读任务的优先级高,那么队列中就只能写入一个数据了,所以一般都要写任务优先级高,这样队列才有可能写满。

    答: 还是要具体分析。

    1.不想让数据丢失,写任务优先级就要高

    2.数据丢失没关系,一旦得到数据,就要全力处理:这个时候写任务的优先级可以调低

    对于第2种情况,其实挺普遍的。

    我们在屏幕上按下某个按钮之后,他就卡死了,实际上是在进行数据的处理。

    在处理数据的过程中,你再去点击屏幕也没有任何作用,处理完数据之后,你之前点击屏幕那些动作也没有任何作用。

    8. 问: 中断可以不能被任务打断,是不是可以因为中断没有TCB(调度器只认识TCB),无法保存现场和恢复现场?但是中断可以被中断打断,是因为中断可以使用主堆栈保存现场和恢复现场(不同中断服务函数之间使用的主堆栈是连续的)?老师可以这样理解吗?

    答: 中断可以被打断,中断不可以阻塞。

    不是这个原因,这是由硬件决定的。

    任务运行的时候,一旦发生了中断, CPU就一定会去执行中断

    在执行中断的过程中,有什么理由暂停中断的处理、去执行任务呢?

    9. 问: 中断里调度会发生什么?

    答:

    中断里调度,只会去设置pxCurrentTCB,并不会运行任务。

    我们反过来假设:在中断里面,没处理完中断就要去调度、切换任务。

    如果这个中断函数里面有3个循环,

    第1个循环会去切换任务A,pxCurrentTCB = task A

    第2个循环要去切换任务B, pxCurrentTCB = task B

    第3个循环要去切换任务C, pxCurrentTCB = task C

    前面两个循环毫无意义,你去设置pxCurrentTCB也没有用,中断没执行完都不会去执行任务。

    请添加图片描述

    所以,在中断里调用xQueueSendToBackFromISR时,只会设置一个变量,表示说“需要调度”

    等中断处理完,再设置pxCurrentTCB等于最高优先级的任务。

  • 相关阅读:
    vscode自动升级后无法打开远程解决方案
    jupyter notebook的插件安装以及快捷键
    LeetCode50天刷题计划(Day 20—— 有效的数独(12.10-13.10)
    【Linux】Vim的使用快捷方式
    算法基础-中国剩余定理
    黄金眼PAAS化数据服务DIFF测试工具的建设实践
    【机器学习】最大期望算法(EM)
    德语B级SampleAcademy
    Go | 函数(包)的使用
    VC6实用工作开发环境整理
  • 原文地址:https://blog.csdn.net/thisway_diy/article/details/125998935