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

news2024/12/25 0:14:15

🐱作者:一只大喵咪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.coloradmin.cn/o/1326105.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

百川大模型AI对话实战——Python开发一个对话机器人

百川大模型开放提供API体验中心,体验不错,有小伙伴也对搭建自己的对话机器人比较兴趣,今天通过Python来简单介绍下,如何调用百川大模型的API来构建自己的小产品。 在开发环境中安装Python,如何安装?参照网…

(附源码)基于Springboot框架的网络投票系统 计算机毕设42855

基于springboot网络投票系统 摘 要 随着全球Internet的迅猛发展和计算机应用的普及,特别是近几年无线网络的广阔覆盖以及无线终端设备的爆炸式增长,使得人们能够随时随地的访问网络,以获取最新信息、参与网络活动、和他人在线互动。为了能及时…

Python Pandas 多重索引DataFrame数据(第19讲)

Python Pandas 多重索引DataFrame数据(第19讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ�…

VScode安装C/C++编译器步骤

一、安装C/C插件 二、安装 MinGW-w64 工具链 使用国内源 git clone https://gitee.com/cuihongxi/ubuntu2-mac.git 下载后进入到VScode文件夹下,点击msys2-x86_64-20231026.exe进行安装 完成后,确保选中“立即运行 MSYS2”框,然后选择“完…

董事长陈小华辞职,上市一年半的快狗打车让奇瑞亏掉3.5亿元

近年来,即时货运行业以高速高效的优势,在头部电商的带动下迎来了新一波的流量红利。然而诞生于“58系”的同城货运平台快狗打车(HK:02246)却起了个大早,赶了个晚集。 12月19日,快狗打车发布公告称&#xf…

本地MinIO存储服务如何创建Buckets并实现公网访问上传文件

文章目录 前言1. 创建Buckets和Access Keys2. Linux 安装Cpolar3. 创建连接MinIO服务公网地址4. 远程调用MinIO服务小结5. 固定连接TCP公网地址6. 固定地址连接测试 前言 MinIO是一款高性能、分布式的对象存储系统,它可以100%的运行在标准硬件上,即X86等…

A股风格因子看板 (2023.12第13期)

该因子看板跟踪A股风格因子,该因子主要解释沪深两市的市场收益、刻画市场风格趋势的系列风格因子,用以分析市场风格切换、组合风格暴 露等。 今日为该因子跟踪第13期,指数组合数据截止日2023-11-30,要点如下 近1年A股风格因子检验…

Actuator内存泄露及利用Swagger未授权自动化测试实现

目录 0x00 前言 0x01 Actuator 泄露及利用 1、Actuator heapdump 内存泄露 2、知道泄露后如何进一步利用 3、如何发现 Actuator 泄露(白盒/黑盒) 0x02 Swagger自动化测试 1、什么是Swagger? 2、PostmanBurpSuiteXray 联动 3、思考 0x…

【昆明*线上同步】最新ChatGPT/GPT4科研实践应用与AI绘图技术及论文高效写作

详情点击查看福利:【昆明*线上同步】最新ChatGPT/GPT4科研实践应用与AI绘图技术及论文高效写作 目标: 1、熟练掌握ChatGPT提示词技巧及各种应用方法,并成为工作中的助手。 2、通过案例掌握ChatGPT撰写、修改论文及工作报告,提供…

SparkSQL的编程模型(DataFrame和DataSet)

1.2 SparkSQL的编程模型(DataFrame和DataSet) 1.2.1 编程模型简介 主要通过两种方式操作SparkSQL,一种就是SQL,另一种为DataFrame和Dataset。 SQL SQL不用多说,就和Hive操作一样,但是需要清楚一点的时候,SQL操作的是…

助老理发,寒冬送暖从头开始

为进一步弘扬尊老、敬老、爱老、助老的中华民族传统美德,解决老年人年龄大、冬季出行不便的问题,2023年12月20日,绿萝志愿服务队在翠堤社区开展了“助老理发”志愿活动。 大雪过后天气格外寒冷,但志愿者们依旧早早的来现场做…

Ethernet/IP 之IO 连接简要记录

IO连接 EIP的IO连接提供了在一个生产者和多个消费者之间的特定的通信路径,以达到IO数据在IO连接下传输。 生产者对象产生IO数据通过生产者IO连接管理者对象将连接ID和数据组帧发送给消费者IO连接管理者对象然后将IO数据发送给消费者对象。 显示消息连接 显式消息传…

Seata中AT模式的实现原理03-二阶段提交

全局事务提交 TM提交全局事务 当业务正常处理完毕后 本地事务全部提交完成,TM会将xid提交给TC,TC会返回当前事务状态,status由TC决定,TM最后会将xid从RootContext中解绑,全局事务结束。 TransactionalTemplate priva…

序列化类的高级用法

1.3.3 模型类序列化器 如果我们想要使用序列化器对应的是Django的模型类,DRF为我们提供了ModelSerializer模型类序列化器来帮助我们快速创建一个Serializer类。 ModelSerializer与常规的Serializer相同,但提供了: 基于模型类自动生成一系列…

ansible的playbook

1、playbook的组成部分 (1)task任务:在目标主机上执行的操作,使用模块定义这些操作,每个任务都是一个模块的调用 (2)variables变量:存储和传递数据(变量可以自定义&…

使用Python将OSS文件免费下载到本地:项目分析和准备工作

大家好,我是水滴~~ 本文将介绍如何使用Python编程语言将OSS(对象存储服务)中的文件免费下载到本地计算机。我们先进行项目分析和准备工作,为后续的编码及实施提供基础。 《Python入门核心技术》专栏总目录・点这里 系列文章 使用…

RocketMQ系统性学习-RocketMQ原理分析之Broker接收消息的处理流程

Broker接收消息的处理流程? 既然要分析 Broker 接收消息,那么如何找到 Broker 接收消息并进行处理的程序入口呢? 那么消息既然是从生产者开始发送,消息是有单条消息和批量消息之分的,那么消息肯定是有一个标识&#…

java中常用的加密算法总结

目前在工作中常用到加密的一些场景,比如密码加密,数据加密,接口参数加密等,故通过本文总结以下常见的加密算法。 1. 对称加密算法 对称加密算法使用相同的密钥进行加密和解密。在Java中,常见的对称加密算法包括&…

活动回顾丨迁飞之路主题艺术墙绘落地大坪大融城

重庆作为鹰飞之城,不仅是数十万猛禽迁飞的必经之路,也是其他珍稀鸟类的家园。守护飞羽精灵,领略迁飞之美,2023年12月19日,传益千里携手重庆工商大学艺术学院党员服务站的志愿者们一起走进大坪大融城开展迁飞之路生态艺…

软件测试工程师,“我“从月10k到月30k进阶自动化测试之路...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 作为手工测试&…