【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中的任务通知状态和任务通知值。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

中国激光雷达的2023:倔强的笨小孩

作者 |David 编辑 |王博 现在回头来看,从2007年莱万多夫斯基和大卫霍尔在硅谷骑着摩托车四处兜售激光雷达开始,到2023年仅中国车载市场出货量接近60万,覆盖了市面上40%以上搭载高阶智驾的新车型,激光雷达一直在用有力的数据回应着…

华为atlas300安装教程

1、安装包位置: /data/ai_install_packages 2、添加HwHiAiUser用户: groupadd -g 1000 HwHiAiUser useradd -g HwHiAiUser -u 1000 -d /home/HwHiAiUser -m HwHiAiUser -s /bin/bash 3、安装驱动: ./Ascend-hdk-310p-npu-driver_6.0.0_l…

HashMap扩容是2倍的原因(全网博客几乎都解释错了)

零、前言 最近在写博客时,突然又想起来哪个经常出现在面试题里的问题: HashMap扩容为什么是原来的2倍? 因为看过源码,我觉得这个问题并不难。在我之前的通俗解释equals和hashCode的关系和作用里也说过这个原因。但为了博客的严谨…

DesignDoll使用方法

选择材质球 取消网格线 控制手部动作-设置左右手 - 手部运动 控制身材 控制身高 比例

第三节TypeScript 基础类型

1、typescript的基础类型 如下表: 数据类型 关键字 描述 任意类型 any 生命any的变量可以赋值任意类型的值 数字类型 number 整数或分数 字符串类型 string 使用单引号(‘’)或者双引号(“”)来表示字符串…

RESTful简介与C/C++实现

一、RESTful简介 RESTful,全称为Representational State Transfer,是一种软件架构风格和设计理念,而不是一种标准。它主要用于Web服务的设计和开发,强调资源的状态表示和状态转移。RESTful风格的设计使得Web服务更加简洁、清晰和…

页面菜单,通过get请求一个url后,跳转另外一个页面,+丢失问题

业务场景描述: 在A系统,菜单点击跳B系统这个操作。 A系统菜单是get请求到B系统的一个缓冲页面,然后这个缓冲页面获取到url中的accessToken后,在这个页面中通过post请求后端接口。 问题描述: 当accessToken中包含了…

MongoDB 单机安装部署

文章目录 说明1. 下载安装包2. 安装数据库3. 配置 systemctl4. 创建 root 用户 说明 本篇文章介绍 MongoDB 二进制安装的步骤,整个过程还是比较简单。 1. 下载安装包 进入 MongoDB 官网,获取安装包的下载链接: https://www.mongodb.com/tr…

Leetcode—179.最大数【中等】

2023每日刷题&#xff08;六十五&#xff09; Leetcode—179.最大数 算法思想 实现代码 其中sort的lambda自定义排序策略参考自官方文档 class Solution { public:string largestNumber(vector<int>& nums) {string ans;vector<string> strs;for(auto num: …

mysql创建用户和赋权

1.创建用户 CREATE USER new_userlocalhost IDENTIFIED BY user_password; “localhost"只允许本地连接&#xff0c;而”%"允许所有IP地址都可以连接到服务器。 2.赋权 GRANT ALL PRIVILEGES ON database_name.* TO new_userlocalhost; FLUSH PRIVILEGES; 3.给…

Uniapp + Vue3 封装请求工具挂载全局

新建request.js工具类 const http {// baseUrl 地址baseUrl: http://localhost:8080,// 请求方法request(config) {// config&#xff1a;请求配置对象&#xff0c;具体参照uniapp文档config beforeRequest(config)// 请求地址拼接config.url this.baseUrl config.url// 异…

Linux Centos 配置 Docker 国内镜像加速

在使用 Docker 进行容器化部署时&#xff0c;由于国外的 Docker 镜像源速度较慢&#xff0c;我们可以配置 Docker 使用国内的镜像加速器&#xff0c;以提高下载和部署的效率。本文将介绍如何在 CentOS 系统上配置 Docker 使用国内镜像加速。 步骤一&#xff1a;安装 Docker 首…

最新时报!即将开业的新工厂将推出量产人形机器人

原创 | 文 BFT机器人 世界上第一个工厂批量生产的人形机器人即将在太平洋西北地区开业。 Agility Robotics首席执行官谢尔顿在一次采访中谈到&#xff0c;一旦达到“顶峰”&#xff0c;公司将在其“RoboFab”工厂生产10000个机器人&#xff0c;这就是量产的意思了&#xff0c;…

基础硬件、实施运维工程师与操作系统的介绍

目录 一、实施与运维 1.2 实施运维一般做什么 1.1.1实施工程师 1.1.2运维工程师 1.3 实施运维需要具备哪些技能 三、基础硬件 四、操作系统 4.1 Windows 4.2 Linux 4.3 macOS 4.4 Unix 五、总结 一、实施与运维 1.1 实施运维是干什么的 1、运维工程师负责服务的稳…

java 4.数组

文章目录 4.数组4.1数组的概念4.2 数组的定义4.3 数组的初始化4.4 数组下标的有效范围与常见异常4.5 数组内存分析4.6 二维数组4.6.1 创建二维数组4.6.2 二维数组的赋值4.6.3 多维数组4.6.4 通过二维数组输出不同版式的古诗 4.7 不规则数组4.8 数组的基本操作4.8.1 数组遍历4.8…

Leetcode 55 跳跃游戏

题意理解&#xff1a; 非负整数数组 nums, 最初位于数组的 第一个下标 。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 需要跳到nums最后一个元素即为成功。 目标&#xff1a;是否能够跳到最后一个元素。 解题思路&#xff1a; 使用贪心算法来解题&#xff0c;需要理解…

神经网络:激活函数层知识点

1.激活函数的作用&#xff0c;常用的激活函数有哪些 激活函数的作用 激活函数可以引入非线性因素&#xff0c;提升网络的学习表达能力。 常用的激活函数 Sigmoid 激活函数 函数的定义为&#xff1a; f ( x ) 1 1 e − x f(x) \frac{1}{1 e^{-x}} f(x)1e−x1​ 如下图…

推荐几个好用的开源无代码/低代码开发平台

一、什么是无代码/低代码开发 无代码/低代码开发是一种可视化的应用程序开发方法&#xff0c;使用具有拖放组件和模型驱动逻辑组合的图形界面。无代码/低代码开发试图降低从软件技术平台、产品和服务中提取价值的进入壁垒。低代码开发平台被称为可视化集成开发环境&#xff08…

美颜技术详解:深入了解视频美颜SDK的工作机制

本文将深入探讨视频美颜SDK的工作机制&#xff0c;揭示其背后的科技奥秘和算法原理。 1.引言 视频美颜SDK作为一种集成到应用程序中的技术工具&#xff0c;通过先进的算法和图像处理技术&#xff0c;为用户提供令人印象深刻的实时美颜效果。 2.视频美颜SDK的基本工作原理 首…

node.js mongoose schemaTypes

目录 官方文档 简介 SchemaType 示例 配置SchemaType规则 通用规则 特定schemaType规则 String Number Date Map monggose会根据shcemaType将文档值转换成指定的类型 官方文档 Mongoose v8.0.3: SchemaTypes 简介 SchemaTypes是在使用Mongoose时&#xff0c;用于…