前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【RTOS训练营】队列的读写、休眠和唤醒、常规应用、使用和晚课提问

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

作者头像
韦东山
发布2022-09-19 14:49:09
1.1K0
发布2022-09-19 14:49:09
举报
文章被收录于专栏:韦东山嵌入式

一:队列的读写

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

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等于最高优先级的任务。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一:队列的读写
  • 二:队列的休眠和唤醒
  • 三:队列的常规应用
  • 四:队列的使用
  • 五:晚课学员提问
    • 1. 问: 老师,多个队列休眠,放入xTaskWaitingToReceive队列里面时候,在队列里面的排序是在哪里?
      • 2. 问: freeRTOS 如果在同一优先级的任务,是不是有没抢断了?只能链表时间片来轮询?
        • 3. 问: 各种不同的输入,输入的数据大小不一样,怎么应对这种情况呢?
          • 4. 问: 老师,任务的优先级怎么配置比较合适,比如有三个task,task1,task2,task3。task1负责采集数据发送给task2处理,task2处理完数据后发给task3进行对外发送。频率都是100HZ。
            • 5. 问: xQueueReceive的最开头,有个判断时间到没到,这个是怎么判断的,从哪里算是start的时间?
              • 6. 问:
                • 7. 问: 老师,3个任务的优先级那个,可不可以这样思考:如果读任务的优先级高,那么队列中就只能写入一个数据了,所以一般都要写任务优先级高,这样队列才有可能写满。
                  • 8. 问: 中断可以不能被任务打断,是不是可以因为中断没有TCB(调度器只认识TCB),无法保存现场和恢复现场?但是中断可以被中断打断,是因为中断可以使用主堆栈保存现场和恢复现场(不同中断服务函数之间使用的主堆栈是连续的)?老师可以这样理解吗?
                    • 9. 问: 中断里调度会发生什么?
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档