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