文章目录
- 队列的核心以及好处
- 队列的核心
- 队列的好处
- 深入源码了解队列机制
- 深入队列读取操作
- 深入队列写入操作
- 读写队列出超时时间
- 信号量
- 深入信号量获取以及释放操作
- 互斥量
- 互斥量和信号量的不同
- 深入源码看优先级继承是怎么操作到的
队列的核心以及好处
队列的核心
队列的核心就是关中断、环形缓冲区、链表。
从而做到互斥访问数据。
如果队列函数就仅仅是这样关中断的话,那我们不就可以自己手写了吗,那队列好像也没有什么厉害的地方,其实不然,使用队列还有很多的好处。
队列的好处
我们想象一个场景:
那这里就显示出了队列的好处了,队列就可以大显身手了!
使用队列的话,判断了一次之后任务就会休眠,这样不就大大减少了时间吗,提升了CPU的运行速度!直到符合条件了,就会主动地去唤醒它。
深入源码了解队列机制
上面说的这么简单,B任务没有读到数据就休眠,休眠内部代码具体是怎么做到的,具体可以看我的上一篇文章:任务的内部机制
那么,发送信息的任务A是怎么知道是去唤醒的是任务B的呢?
其实在创建队列的时候,里面的操作,产生了两个特定的链表,读写队列,都会有写满或者没数据的情况,遇到这两种情况,毋庸置疑,任务都是会休眠的,那到时候别人来唤醒它的时候,怎么找得到它呢?这个时候,这两个链表就发挥了作用!
创建队列的时候生成了一个结构体,上面所说的东西以及其他没有说到的东西都包含在里面:
休眠了,就会把自己的任务的TCB结构体记录在上面这两个链表里面,到时候就会去这两个链表里面找!
假如没数据可读了,那么读任务就会休眠,等到别的任务来唤醒它的时候,就会去其中一个链表里面找,那么另外一种情况,亦是如此!
深入队列读取操作
主要操作:
因为遇到没有数据的情况下,读取数据是可以选择的等待的,想要等待你就乖乖休眠,把你的任务的TCB写入到Delay链表中,然后在队列链表中登记一下,到时候有数据了第一时间叫你,你要是没有耐心等到的话就走吧,下图所示:
成功读取到了数据了,还要干什么吗?
成功读取到了数据了;那么队列就会有空出来的位置了,可能在我没有读取出来之前可能有一个写任务想要写,但是由于没有位置了但是他很有耐心,选择了等待,那么他就会进入休眠状态,并且在List_t xTaskswaitingToSend链表处登记了一下,那我成功了读取了数据腾出空间来了,我就会去那个链表看看你是否还在那里,在的话就优先让你写!
为什么要关闭中断呢?
一个工程中难免会有多个任务一起来读同一个队列,所以每次轮到谁来读取了,肯定要吃独食啊,不然我读一半,你突然调度进来读,那不是很“逆天”了吗?
深入队列写入操作
写过程其实也是类似的!
因为遇到写满的情况下,写入数据是可以选择的等待的,想要等待你就乖乖休眠,把你的任务的TCB写入到Delay链表中,然后在队列链表中登记一下,到时候有位置写数据了第一时间叫你,你要是没有耐心等到的话就走吧。
成功写入了数据了,还要干什么吗?
成功写入到了数据了;那么队列就会有数据进入,可能在我没有写入进来之前可能有一个读任务想要读,但是由于没有数据可读休眠了,但是他很有耐心,选择了等待,那么他就会进入休眠状态,并且在List_t xTaskswaitingToReceive链表处登记了一下,那我成功了写入了数据有数据可读了,我就会去那个链表看看你是否还在那里,在的话就优先让你读走!
为什么要关闭中断呢?
一个工程中难免会有多个任务一起来写同一个队列,所以每次轮到谁来写了,肯定要吃独食啊,不然我写一半,你突然调度进来写,那不是很“人才”了吗?
读写队列出超时时间
读写任务休眠的时候,可以选择休眠,也是可以选择休眠多久的,不可能呆呆一直傻等着,所以等待的时候是可以设定等待时间的,时间一到,我就不等了被唤醒。
信号量
其实信号量就是特殊的队列,用的都是同样一套机制。
信号量就不像队列一样负责传递数据的,他更简单点,就负责“加减”一个计数值,这个计数值在创建的时候是可以自由设定的!
信号量和队列还有什么不同?
就是信号量的释放操作就等同于队列的写入操作,获取操作等同于读取操作!
信号量的释放操作是被设定没有等待时间的,要么释放成功,要么释放失败!
深入信号量获取以及释放操作
获取:
释放:
具体的操作和干了什么事情和队列大差不差,使用的同样的内部机制,了解了队列就相当于了解信号量!
互斥量
互斥量和信号量的不同
其实互斥量就是特殊的信号量,就是比信号量高级了一点,多了一个功能——优先级继承!互斥量依旧是维持一个“计数值”,但是互斥量的打这个计数值是不可以自由设定的,已经被设定死了为1,只能在0-1之间轮转!
那么优先级继承到底是个啥呢?
假设一个场景中使用了信号量:
由于低优先级的任务获取了信号量,使得最高优先级的任务卡住了(休眠),那么不就只能中优先级的任务运行了吗?(中优先级运行途中没有主动放弃CPU资源),那么低优先级的就不能及时释放信号量,就导致了最高优先级的任务一直被卡着!高优先级任务肯定很重要啊,不然怎么被设定为高优先级,所以重要的事一直没有去做,是很坏事的!
那换成使用互斥量,结果会怎么样呢?
显而易见,运行顺序显然不同了,在高优先级的任务被卡住后,低优先级任务居然可以在中优先级的任务前面先运行,太奇怪了,这就是互斥量的高级之处——优先级继承发挥了作用!
当一个任务因为使用互斥量被另一个任务卡住了,被迫休眠,那么内部就会比较一下这两个任务的优先级,假如,被卡住的任务的优先级>卡住别人的任务,那么卡住的任务就会将对方的优先级提高到像自己一样高级别的优先级,让他更早的运行,及时释放互斥量,让我(卡柱的任务)及时运行!在释放互斥量的一瞬间,那个卡住别人的任务就会自主的降低自己的优先级(恢复到到原来模样),所以说是自主降优先级的,不得不说那个卡住别人的任务还是有点识时务的!
深入源码看优先级继承是怎么操作到的
首先要很确切的理解是怎么操作的,要理解一下任务的运行以及调度机制,可以看一下我之前写的一篇文章:任务的内部机制
信号量中当一个任务去获取信号量的时候,失败了就会选择是否愿意等待,等待你就乖乖设定你的等待时间,然后 就去休眠吧,但是互斥量之所以高级,是因为他多做了一步——优先级继承,先去比较一下卡住你的任务和自己的优先级谁高谁低,然后再判断是否提升其优先级,这一步就是下面这一步:提高互斥量持有者的优先级!
那这句核心的代码里面到底做了什么呢?
经过了下面的这简单两步就得以提升了那个卡住别人的任务的优先级!
看不懂的可以看看这个文章:任务的内部机制
那提升了之后也是要恢复的呀,那是由谁来恢复呢?是有卡住别人的任务自己来降低的,释放互斥量一瞬间,就会自主降低自己的优先级!所以说那个恢复优先级的操作就隐藏在释放互斥量的函数里面:
里面有着这一句:(就是这个恢复了优先级)
那他里面到底做了什么操作呢?
原来里面的变量uxBasePriority记录了它原来的优先级,
然后后面的操作不就是提升优先级的时候反过来而已嘛?
这样就实现了所谓的优先级继承!