【RTOS学习】源码分析(信号量和互斥量 事件组 任务通知)

🐱作者:一只大喵咪1201
🐱专栏:《RTOS学习》
🔥格言:你只管努力,剩下的交给时间!
图

目录

  • 🍓信号量和互斥量
    • 🍅创建
    • 🍅Take
    • 🍅Give
  • 🍓事件组
    • 🍅设置事件
    • 🍅等待事件
    • 🍅同步点
  • 🍓任务通知
    • 🍅发通知
    • 🍅等待通知
  • 🍓总结

🍓信号量和互斥量

信号量和互斥量几乎一模一样:

创建:

图
如上图所示,创建时使用的都是xSemaphoreCreateXXX函数,只是后面的XXX不一样,其他都非常类似,而且本质上都是调用的xQueueGenericCreate函数来创建通用队列,只是传入的参数不一样。

Give:

图
如上图所示,Give时,使用的都是xSemaphoreGiveXXX,本质上也是在调用xQueueGenericSend向通用队列中写数据,只是参数不同。

Take:

图
如上图所示,Take时,使用的都是xSemaphoreTakeXXX,本质上也是在调用xQueueSemaphoreTake这个函数,只是参数不同,注意,这里并不是调用xQueueGenericReceive函数,和队列的操作不同。

可以看到,信号量和互斥量在创建,释放,申请信号量时的函数非常类似,所以本喵只需要讲解一种即可,就介绍一下最复杂的递归互斥量吧。

🍅创建

图
如上图代码所示,调用xSemaphoreCreateRecursiveMutex创建递归互斥量,这是一个宏函数,会调用xQueueCreateMutex,在该函数中会调用xQueueGenericCreate创建通用队列。

但在这之前先让创建的队列长度为1,队列中每个数据的大小是0字节,然后再调用xQueueGenericCreate

根据前面创建队列时的分析我们知道,此时只会在堆区上创建一个队列结构体Queue_t,并不会分配环形缓冲区。

然后再调用prvInitialiseMutex函数初始化互斥量:

tu
如上图所示prvInitialiseMutex函数,将Queue_t中联合体中xSemaphore成员的xMutexHolder用来表示锁的所有者设置为NULL,再将uxRecursiveCallCount递归锁计数次数设置为0。

最后再调用xQueueGenericSend向队列中写数据,写入数据是NULL,这里仅仅是让uxMessagesWaiting = 1,好让互斥量有初始值。


图

如上图,所以此时得到的Queue_t队列是这样的,没有存放数据的环形缓冲区,只有一个队列头,其中的成员也被赋予了合适的初始值。

🍅Take

图

如上图所示,使用xSemaphoreTakeRecursive申请一把递归锁,该函数会调用xQueueTakeMutexRecursive函数,在这个函数中,先判断锁的持有者身份xMutexHolder,如果是当前任务pxCurrentTCB,则说明此时是递归上锁,则将递归上锁次数uxRecursiveCallCount加一。

如果持有者身份是NULL或者不是当前任务,调用xQueueSemaphoreTake申请锁,申请成功后让uxRecursiveCallCount加一。

图
如上图xQueueSemaphoreTake函数,先判断可否申请锁,也就是队列中的有效数据是否大于0,如果可以申请则将有效数据uxMessagesWaiting的数值减一,然后通过pvTaskIncrementMutexHeldCount函数记录持锁人身份到pxQueue->u.xSemaphore.xMutexHolder成员中。

图
如上图所示,如果队列中的有效数据小于等于0,说明此时无法申请锁,如果该任务不愿意等待的话就直接错误返回,如果愿意等待,则调用vTaskInternalSetTimeOutState记录当前时刻。

在确认要阻塞后,调用xTaskPriorityInherit函数进行优先级继承,然后将该任务放入等待读取数据的xTasksWaitingToReceive链表中,再主动发起调度,让当前任务阻塞。


图
如上图xTaskPriorityInherit函数,如果被阻塞任务的优先级大于持锁者的优先级,并且持锁者在就绪链表中,则交换双方的位置,也就是将二者的优先级交换,并且放入对应的就绪链表中。如果持锁者不在就绪链表中,则直接将当前阻塞任务的优先级给它即可。

如果被阻塞任务的优先级小于等于持锁者的优先级,则不需要进行优先级继承。


图
如上图所示,当这个申请锁的任务再次被唤醒时,也是有两种情况,如果是有人释放了锁,那么for循环中再次判断操作时会成功申请到锁,成功返回。

如果是超时被唤醒,则会先调用prvGetDisinheritPriorityAfterTimeout将被继承的优先级恢复原样,然后错误返回。

🍅Give

图
如上图所示xSemaphoreGiveRecursive,用来释放递归锁,最后会调用xQueueGiveMutexRecursive函数,在该函数中,首先判断释放锁的是否是锁的持有者,如果是持有者,则先将递归次数减一,当该次数为0时,说明不是递归释放,则向队列中写数据,就是让有效数据uxMessagesWaiting加一。

如果不是持有者,则直接错误返回,并不会阻塞。

  • 在释放锁的过程中,并不会阻塞,如果失败就直接返回。

总的来说,使用锁的过程分为如下几步:

  • 创建互斥锁并进行初始化,让锁有初始值,但是此时持锁者为NULL
  • 申请锁时:
    • 如果不是持有者申请锁,则看有效数据个数uxMessagesWaiting是否大于0,如果大于0说明可以申请,如果小于等于0,说明不可以申请,此时会将申请者放入到锁的管理读取数据的事件链表中。
    • 如果是锁的持有者,对于递归锁则会让递归次数增加,非递归锁和不是持有者的待遇一样。
  • 释放锁时:
    • 如果不是持有者,直接错误返回,因为锁不能被其他任务随意释放。
    • 如果是持有者,对于递归锁,则让递归次数减少,当递归次数减少为0时,则向队列中写数据,让有效数据的个数uxMessagesWaiting重新变为1。

🍓事件组

图

如上图所示事件组结构体EventGroup_t的定义,包含两个成员:

  • uxEventBits:这是一个32位的变量,用来存放事件,只是用低24位,每一个比特位代表一个事件。
  • xTasksWaitingForBits:这是一个链表头,该链表用来存放因等待事件就绪而阻塞的任务TCB。

图
如上图xEventGroupCreate函数所示,在创建事件组时,先在堆区上开辟一块存放EventGroup_t结构体的空间,然后将结构体中表示事件值的uxEventBits清0,再初始化一下链表xTasksWaitingForBits


图
如上图所示,每个任务TCB里的xEventListItem事件链表中,每个链表项中的xItemValue,该32位的变量也可以用来表示事件。

  • 高8位用来表示控制位:比如等待后是否清除事件等等。
  • 低24位用来表示事件:每一位表示一个事件和EventGroup_t中的事件位对应。

🍅设置事件

tu
如上图xEventGroupSetBits函数所示,在该函数中,先获取事件组中的链表里的第一个链表项,这里可能管理着因正在等待事件就绪而处于阻塞状态的任务。

然后将要设置的事件值,使用或等的方式赋值给EventGroup_t中的uxEventBits

图
如上图所示代码,在将事件值设置好以后,需要判断是否有任务可以唤醒,使用while循环遍历链表中的所有任务。

在判断过程中,先拿到事件控制位uxControlBits,也就是在等待事件的任务对事件的态度,如果不是eventWAIT_FOR_ALL_BITS,说明当前遍历的任务不要求所有等待是事件都就绪,只要uxBitsWaitedFor & pxEventBits->uxEventBits != 0,说明只有一个或者多个事件就绪,此时将xMatchFound = pdTRUE,表示可以唤醒链表中等待的任务。

如果控制位表示要等待所有事件就绪,则只有uxBitsWaitedFor & pxEventBits->uxEventBits == uxBitsWaitedFor时,也就是所有等待事件都就绪时,才可以唤醒链表中等待的任务。

接下来如果有任务可以唤醒时,从正在遍历的当前任务中uxControlBits拿到是否要清除已经就绪的事件所在的bit。

之后再调用vTaskRemoveFromUnorderedEventList将要唤醒的任务从事件组的链表中移除,并且设置标志eventUNBLOCKED_DUE_TO_BIT_SET,表示该任务被唤醒是由于等待事件成功。

最后返回事件组中的事件值pxEventBits->uxEventBits


在设置事件值时主要分为三大步:

  • 设置要等待的事件值。
  • 唤醒事件组链表中正在等待事件就绪的任务:
    • 等待事件的任务有要求全部事件就绪时才会被唤醒。
    • 等待事件的任务也有只要求有事件就绪时就可以被唤醒。
  • 在退出函数时,根据控制位决定是否清除事件组中已经就绪的事件。

🍅等待事件

图
如上图xEventGroupWaitBits函数,该函数是由某个任务调用的,用来等待事件组中要等待的事件。

首先就是获取事件组中的事件值uxCurrentEventBits,然后判断其与等待任务要等待的事件uxBitsToWaitFor是否相等,要根据xWaitForAllBits决定是所有事件都就绪才符合等待要求还是有事件就绪就符合等待要求。

当符合等待要求时,根据控制位决定是否在退出该函数前将事件组中已经就绪的事件值清除,如果不符合等待要求,且该任务不愿意等待,则超时返回,如果愿意等待,则将该任务放入到事件组用来维护等待事件的任务链表中。

然后主动发起调度,当前任务就阻塞在这里了。

当该任务再次被唤醒时,有可能是等待的事件就绪了被唤醒,也有可能是因为超时而被唤醒:

图
如上图所示,当等待事件的任务再次被唤醒时,根据eventUNBLOCKED_DUE_TO_BIT_SET判断一下是否因为事件就绪被唤醒,如果不是,则说明是超时,再判断一次事件是否就绪,没有就绪则超时返回。

如果是事件就绪而被唤醒,等待成功返回。


等待过程中注意分为三步:

  • 判断要等待的事件和事件组中的事件值,根据任务指定的控制值决定是所有事件都就绪才算等待成功还是只要有事件就绪就算等待成功。
  • 如果等待成功,则成功返回,等待不成功,则根据是否愿意等待决定是错误返回还是放入事件组的阻塞链表中。
  • 处于阻塞状态再次被唤醒后,根据eventUNBLOCKED_DUE_TO_BIT_SET位判断是事件就绪被唤醒还是超时唤醒。

🍅同步点

tu
如上图xEventGroupSync函数,调用该函数可以实现同步点,在函数内部,首先将要等待的事件设置到事件组中:

  • 如果在设置过程中,其他等待同步点的事件产生,则唤醒其他任务
  • 并且判断自己要等待的事件也全部发生,自己不会被阻塞。

如果自己要等待的事件没有全部就绪,说明其他任务也没有被唤醒,此时将当前任务继续添加到事件组的阻塞等待链表中。

如果当前任务愿意等待,则主动发起调度,阻塞到这里。

再次被唤醒后,如果eventUNBLOCKED_DUE_TO_BIT_SET不为1,说明是超时唤醒,则错误返回,如果该位为1,说明是事件被设置唤醒。

多个等待同步的任务都会从这里被唤醒,开始一起继续执行。

🍓任务通知

图
如上图所示,使用队列、信号量、事件组时,我们都要事先创建对应的结构体,双方通过中间的结构体通信。

图
如上图所示,使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发过来的"通知"。

使用任务通知时,可以明确指定:通知哪个任务。

tu
如上图所示,每个任务都有一个结构体:TCB(Task Control Block),里面有2个成员:

  • 一个是uint8_t类型的ucNotifyState数组,用来表示通知状态。
  • 一个是uint32_t类型ulNotifiedValue数组,用来表示通知值。

数组的大小#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1为1,可以将数组看成一个变量。

通知状态有3种取值:

  • taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
  • taskWAITING_NOTIFICATION:任务在等待通知
  • taskNOTIFICATION_RECEIVED:任务接收到了通知,也被称为pending(有数据了,待处理)

🍅发通知

图

如上图所示xTaskNotify,调用该函数项目标任务发出通知时,会调用xTaskGenericNotify,在该函数中,首先获取被通知任务的状态值ucNotifyState,然后将该值设置为taskNOTIFICATION_RECEIVED,表示已经对该任务发出了通知。

根据eAction对被通知的任务通知值ulNotIfiedValue进行操作,可以赋值,可以加加,可以覆盖,也可以不做任何操作等等。

操作完毕后,判断一下被通知之前目标任务的状态,如果是taskWAITING_NOTIFICATION,说明目标任务在等待通知而处于阻塞状态,所以此时将目标任务放入到就绪链表中,如果被通知任务的优先级更高,则主动发起调度。


向任务发起通知总的来说就三步:

  • 设置通知状态为taskNOTIFICATION_RECEIVED
  • 根据eACction操作通知值ulNotIfiedValue
  • 任务如果原本阻塞,则将其放入就绪链表中。

🍅等待通知

图
如上图xTaskNotifyWait函数,任务调用该函数等待任务通知,最后会调用xTaskGenericNotifyWait函数,在该函数内,首先判断当前任务的任务状态ucNotifyState

  • 如果状态值不是taskNOTIFICATION_RECEIVED,说明没有接到通知:
    • 根据控制位决定是否在入口处清除指定事件。
    • 将当前任务状态设置为taskWAITING_NOTIFICATION,表示正在等待通知。
    • 如果愿意等待,则将当前任务放入到延时链表中,然后发起调度,当前任务阻塞。
  • 被唤醒或者第一次调用就接收到任务通知:
    • 再次判断任务状态,如果不是taskNOTIFICATION_RECEIVED,说明是超时唤醒,直接错误返回。
    • 如果是taskNOTIFICATION_RECEIVED,说明接收到任务通知被唤醒,根据控制位决定是否在出口处清除事件。

🍓总结

虽然互斥量信号量,事件组名字中不包含队列,但其本质上还是使用的通用队列,只是该队列中不存放数据本身,只靠Queue_t中的成员。

对于任务通知,不如说是通知任务,更是没有用于两个任务间通信的结构,直接从一个任务指定通知另一个任务,改变的是TCB中的任务通知状态和任务通知值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/290163.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SpringMVC】SpringMVC的请求与响应

文章目录 0. Tomcat环境的配置1. PostMan工具介绍创建WorkSpace建立新的请求 2. 请求映射路径案例结构与代码案例结构案例代码 案例存在问题解决方案方法方法升级版——配置请求路径前缀注解总结 3. Get请求与Post请求案例结构与案例代码案例结构案例代码 Get请求Post请求接收中…

c++打开网页

1.使用ShellExecute 效果图: 相关代码: void Open_url::on_pushButton_clicked() {QString path1 "explorer.exe";QString urlui->lineEdit->text();ShellExecute(NULL, L"open", path1.toStdWString().c_str(), url.toStdWString().c…

海康威视IP网络对讲广播系统命令执行漏洞(CVE-2023-6895)

漏洞介绍 海康威视IP网络对讲广播系统采用领先的IPAudio™技术,将音频信号以数据包形式在局域网和广域网上进行传送,是一套纯数字传输系统。 Hikvision Intercom Broadcasting System 3.0.3_20201113_RELEASE(HIK)版本存在操作系统命令注入漏洞,该漏洞源于文件/ph…

山景DU561—32位高性能音频处理器(DSP)芯片

音频处理可以更好地捕捉和处理声音和音乐;而DSP音频处理芯片是一种利用数字信号处理技术进行音频处理的专用芯片;可用于多种应用,从音乐拾音到复杂的音频信号处理,和声音增强。 由工采网代理的山景DU561是一款集成多种音效算法高…

【大数据存储与处理】实验一 HBase 的基本操作

一、实验目的: 1. 掌握 Hbase 创建数据库表及删除数据库表 2. 掌握 Hbase 对数据库表数据的增、删、改、查。 二、实验内容: 1、题目 0:进入 hbase shell 2、题目 1:Hbase 创建数据库表 创建数据库表的命令:create 表…

ssm基于Java Web的线上办公管理系统设计与实现论文

摘 要 使用旧方法对线上办公管理系统的信息进行系统化管理已经不再让人们信赖了,把现在的网络信息技术运用在线上办公管理系统的管理上面可以解决许多信息管理上面的难题,比如处理数据时间很长,数据存在错误不能及时纠正等问题。这次开发的线…

计算机网络基础——光模块(Optical Modules)基础知识介绍

一、光模块的工作原理 光模块(Optical Modules)的工作原理是将电信号转换为光信号,或者将光信号转换为电信号,实现光纤通信中的光电转换和电光转换功能。具体来说,光模块主要由光电子器件(光发射器和光接收…

阿里云大模型数据存储解决方案,为 AI 创新提供推动力

云布道师 随着国内首批大模型产品获批名单问世,百“模”大战悄然开启。在这场百“模”大战中,每一款大模型产品的诞生,都离不开数据的支撑。如何有效存储、管理和处理海量多模态数据集,并提升模型训练、推理的效率,保…

scrapy的入门和使用

scrapy的入门使用 学习目标: 掌握 scrapy的安装应用 创建scrapy的项目应用 创建scrapy爬虫应用 运行scrapy爬虫应用 scrapy定位以及提取数据或属性值的方法掌握 response响应对象的常用属性 1 安装scrapy 命令:     sudo apt-get install scrapy 或者&#x…

【保姆级教程】使用tensorflow_hub的预训练模型实现神经风格迁移

目录 一 神经风格迁移 二 安装依赖 三 实践 四 其他 一 神经风格迁移 神经风格迁移是一种优化技术,主要将两个图像(内容图像和风格

[网络安全]在win2000虚拟机上创建隐藏账户

手工创建隐藏账户 1.你需要一台win2000 2.winR->cmd->regedt32 增加HEY_LOACL_MACHINE\SAM\的权限,标头有安全,点击,然后勾选 3.新建账号,例如HiddenAccount$($表示在命令行下不现实此用户) net user HiddenAccount$ 123456…

应用 Strangler 模式将遗留系统分解为微服务

许多来源在一般情况下提供了微服务的解释,但缺乏特定领域的示例。新来者或不确定从哪里开始的人可能会发现掌握如何将遗留系统过渡到微服务架构具有挑战性。本指南主要面向那些正在努力启动迁移工作的个人,它提供了特定于业务的示例来帮助理解该过程。 …