rust嵌入式开发之基于await构造应用级临界区

在rust嵌入式开发之await一文中我们讨论了如何用await来实现异步操作的串行化。而并发编程时还有一个更重要的问题需要我们解决:资源竞争。

针对并发时的资源竞争,最简单的办法就是利用系统提供的临界区机制来互斥的使用资源。嵌入式rust提供了critical-section来提供临界区的原语,同时在cortex-m这样的crate中都加以了实现。

嵌入式的临界区有几种实现方式:

  • 单核无系统,关闭中断
  • 多核无系统,关闭中断加核心间的硬件自旋锁
  • ROTS,由系统以库函数/系统调用的方式提供

可以看到,临界区必须在硬件/或控制了硬件的系统【如rust的tock、c的rt-thread等】的支持下实现。如果没有系统,就只能通过关中断来实现互斥访问。

Embassy目前还只是一个有限的运行时,还不是一个ROTS,提供不了系统级的临界区。这就导致在用Embassy开发时,在需要用临界区解决资源竞争时必须快进快出,而无法用在串行化交互这种需要长期持有资源的场景中了,如通过RS485总线同时管理多台设备。

针对这个问题,笔者就考虑如何在应用层面提供不需要关中断就可以实现临界区保护的互斥锁。实质上,就是基于Embassy运行时来实现应用层面的互斥锁。

锁协议

嵌入式的应用场景比较简单,所以直接借鉴java的synchronized语义,即对象级的读写互斥锁,不支持共享读。其实,就嵌入式的应用来说,过于复杂的锁协议也没啥必要,属于过渡设计了。

此外,由于rust稳定版尚不支持异步闭包,所以锁的申请与释放必须分开。当然,对于FnOnce的闭包可以提供with来简化,但由于我们设计互斥锁的目的主要是用于异步串行化的资源长期持有,所以with语句用途有限。

所以呢,可长期持有的互斥锁的锁协议为:

  • 一个数据对象【代表一个资源】用一个可长期持有的锁来提供互斥性的临界区保护
  • 可长期持有的锁,应该有可配置的超时间隔
  • 可长期持有的锁允许竞争性申请,申请到锁的任务方可操作对应的受保护资源
  • 未申请到锁的任务应等待直至超时退出锁的竞争
  • 申请到锁的任务操作完毕后,应主动释放锁
  • 当锁释放时,如果有等待的任务,从中挑选一个授予锁

在锁的持有期内,完全可以执行各种await操作。

实现

由于笔者写的项目为商业项目,无法直接贴出源码,所以我们主要讨论原理并辅以说明性的伪码。

实现原理非常简单:

1、主要依托上篇文章讨论过的await机制,以Embassy运行时为基础来实现锁的超时与竞争调度

2、利用Embassy/嵌入式rust所提供的CriticalSectionRawMutex来保护对锁本身的操作,避免锁操作期间的再入问题

锁对象本身的定义非常简单:

pub struct Lock {//锁的内部数据,主要包括两个部分://1、前篇文章中所提到的用于awake机制的waker等任务调度信息数据//2、竞争锁的排队数据,我是用BTreeMap来管理排队inner: _Lock,//由于锁的申请存在竞争,所以这两类锁的内部数据也是需要保护的,我用了CriticalSectionRawMutex//其可以提供跨线程的保护,也就是可以在中断中一样使用,在我使用的STM32F413芯片中其实就是关中断//所以所有的锁操作必须快进快出,要求尽可能的简短lock: Mutex<CriticalSectionRawMutex, bool>,
}

主要用来提供锁接口并实现对锁对象本身的互斥操作。

_Lock是锁实体,其主要提供对申请锁Future的管理,包括当前持有锁的Future、Future的ID管理以及所有申请锁的请求者队列管理。这些功能都很常规,我们无需赘述。

对_Lock的操作需要用CriticalSectionRawMutex进行保护,以避免再入。

申请锁的Future的poll函数示意如下:

fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {let id = self.id;//首先检查自己能否在竞争中获胜赢得锁if self.lock.check(id) {//竞争获胜Poll::Ready(LockCode::OK(id))}else if !self.polled {//第一次参加竞争,但失败了,需要准备waker,并设置超时。可参考上篇文章self.polled = true;let w: &core::task::Waker = cx.waker();self.waker = Some(w.clone());embassy_time_queue_driver::schedule_wake(self.expires_at.as_ticks(), w);Poll::Pending}else if self.expires_at <= Instant::now() {//超时了self.lock.remove(id);Poll::Ready(LockCode::Timeout)}else{//理论上执行不到,只是总得有个返回值Poll::Pending}
}

我们再看一下锁的check函数的竞争逻辑:

fn check(&self, id: u64) -> bool {//锁对象的操作需要用CriticalSectionRawMutex进行保护以避免再入self.lock(|p|{if p.current == id {//被唤醒并进行检查的Future,就是锁的持有者true}else if p.current == 0 {//锁目前没有人持有,所以立刻将锁变更为自己持有p.current = id;true}else{false}})
}

大家在编写Future的poll函数时必须牢记:一个waker只会执行一次

Waker的wake函数会自动删除自己:

// Don't call `drop` -- the waker will be consumed by `wake`.
crate::mem::forget(self);

所以我在这里所写的poll函数最多有两次执行机会:

  • Future创建后被第一次调度执行poll函数,此时如果锁没有持有者,则本Future将获得锁,此时就执行一次
  • 如果锁已经被其它Future持有,本Future就将被安排等待,这是第一次执行
  • 等待中的Future有两种可能被wake【超时、或锁被释放后自己被选中】,这是第二次执行

大家再看下poll函数,就会发现有一种状态是可以执行第三次的啊,即:check失败 + 已经poll过了 + 未超时。但这种情况我们必须避免出现。因为waker只能执行一次,如果出现这样的情况,这个Future将因为再无法被wake,而永远沉睡在系统任务队列中了。所以我们就需要设法防止这种状态的出现。

因此,在某Future被选中唤醒时,锁管理就会将锁先行授予该Future。即:

if let Some(w) = &n.waker {//这使得被wake后执行poll函数的check时,直接命中【p.current == id】而poll成功pb.current = n.id;w.clone().wake();
}

最后,获得锁后必须显式释放:

//获得锁对象,嵌入式比较简单,可以直接用静态的对象,但由于并发,所获得的锁对象不能是&mut
//这就要求锁的操作都不能是&mut self,而必须是&'self,这就是我们为什么需要外封装的原因
let lo = get_lock_...锁名...();
//竞争锁,10秒超时
let (rc, id, rd) = lo.wait(Duration::from_secs(10)).await;
if rc {if let Some(md) = rd {//在我的实现中,锁和待保护对象进行了泛型化的融合,md就是取到的数据对象...md是&mut的,所以可以进行修改等所有需要的操作...//还可以执行各种异步操作Timer::after_millis(1300).await;//必须显式释放锁,获取失败的id是0,即便调用release也无效lo.release(id);}
}else{//超时,可以在此执行容错处理
}
结语

以上,我们就获得了一个轻便而可靠的应用级的临界区互斥锁。

有了锁,我们就可以根据需要来对静态数据、融入泛型结构中提供数据保护了。

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

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

相关文章

密码学 | 椭圆曲线数字签名方法 ECDSA(上)

目录 1 ECDSA 是什么&#xff1f; 2 理解基础知识 3 为什么使用 ECDSA&#xff1f; 4 基础数学和二进制 5 哈希 6 ECDSA 方程 7 点加法 8 点乘法 9 陷阱门函数&#xff01; ⚠️ 原文&#xff1a;Understanding How ECDSA Protects Your Data. ⚠️ 写在前面…

<Halcon> 变换矩阵求解

变换矩阵求解 hom_mat2d_translate( : : HomMat2D, Tx, Ty : HomMat2DTranslate) hom_mat2d_translate通过向量t (Tx,Ty)向齐次二维变换矩阵HomMat2D添加平移&#xff0c;得到平移矩阵&#xff1b;vector_to_rigid( : : Px, Py, Qx, Qy : HomMat2D) vector_to_rigid近似于刚性…

测出Bug就完了?从4个方面教你Bug根因分析

01 现状及场景 &#x1f3af; 1.缺失bug根因分析环节 工作10年&#xff0c;虽然不是一线城市&#xff0c;也经历过几家公司&#xff0c;规模大的、规模小的都有&#xff0c;针对于测试行业很少有Bug根因环节&#xff0c;主流程基本上都是测试提交bug-开发修改-测试验证-发送报…

rest_framework_mongoengine实现后端的增删改查

rest_framework_mongoengine实现后端增删改查 ‍ 一、增删改查 1. 继承ModelViewSet实现增删改查 父urls.py path("api/testapp/", include("apps.testapp.urls")), # 测试子urls.py # -*- coding: utf-8 -*- from django.urls import path from res…

如何在苹果手机上安装iOS应用的.ipa文件?

哈喽&#xff0c;大家好呀&#xff0c;淼淼又来和大家见面啦&#xff0c;如今移动应用市场不断的发展&#xff0c;许多开发者小伙伴们都选择将他们的应用发布到苹果App Store上&#xff0c;但是&#xff0c;有时候他们可能希望通过直接分享IPA文件来分发他们的App&#xff0c;那…

自定义javax.validation 校验能用 spring

自定义注解 import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*;Target(ElementType.FIELD) Documented Retention(value RetentionPolicy.RUNTIME) Constraint(validatedBy {IdExistMyTestValidator.class }) public in…

STM32标准库+HAL库 | CPU片内FLASH存储器数据掉电读写

一、片内FLASH 在STM32芯片内部有一个FLASH存储器&#xff0c;它主要用于存储代码&#xff0c;我们在电脑上编写好应用程序后&#xff0c;使用下载器把编译后的代码文件烧录到该内部FLASH中&#xff0c; 由于FLASH存储器的内容在掉电后不会丢失&#xff0c;芯片重新上电复位后&…

ArduPilot开源飞控之ROS系统简介

ArduPilot开源飞控之ROS系统简介 1. 源由2. ROS系统3. 安装2.1 安装Docker2.2 安装ROS2 4. 总结5. 补充资料 1. 源由 之前在ArduPilot开源飞控之硬件SBC分析中讨论过&#xff0c;个人角度最推荐其中两个系统是&#xff1a; Rpanion-server【推荐&#xff0c;简单】BlueOS【推…

SAP Fiori开发中的JavaScript基础知识14 - promise, async, await异步编程

1. 前言 本文将介绍JavaScript中异步编程技术&#xff0c;包括promise, sync, await的使用。 2. Promise 2.1 简介 Promise 是 JavaScript 中用于处理异步操作的一种对象。它代表了一个异步操作的最终完成&#xff08;或失败&#xff09;及其结果值。 Promise 对象有三种状…

Unity之Unity面试题(四)

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之Unity面试题&#xff08;四&#xff09; TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取…

什么是并行通信、串行通信?什么是全双工、半双工、单工? 什么是异步通信、同步通信? 什么是RS232、RS485?什么是pwm?

什么是并行通信、串行通信&#xff1f; 嵌入式系统中的通信是指两个或两个以上的主机之间的数据互交&#xff0c;这里的主机可以是计算机也可以是嵌入式主机&#xff0c;甚至可以是芯片。主机间通信的方式一般可以分为两类&#xff1a;并行通信和串行通信。并行通信是指多个比特…

华为配置静态ARP示例

华为配置静态ARP示例 组网图形 图1 配置静态ARP组网图 静态ARP简介配置注意事项组网需求配置思路操作步骤配置文件相关信息 静态ARP简介 静态ARP表项是指网络管理员手工建立IP地址和MAC地址之间固定的映射关系。 正常情况下网络中设备可以通过ARP协议进行ARP表项的动态学习&…

Android10以上MediaProject截屏

起因 在系统升级到Android10以上之后&#xff0c;之前的截屏方式不能用了&#xff0c;而且必须将MediaProject放在forground service里面跑才行。网上搜了一圈&#xff0c;都是语焉不详或者没有完整的一个代码应用。只能自己写一个&#xff0c;记录下 代码实现 新建一个Scre…

IO多路转接之poll

目录 1. poll 的基本认识 2. poll 基于 select 的突破 3. poll() 系统调用 3.1. struct pollfd 结构 4. poll() 的 demo 5. poll 的总结 1. poll 的基本认识 poll 是一种多路转接的方案&#xff0c; 它的核心功能和 select 一模一样&#xff0c;我们知道 IO 等待事件就绪…

WebLogic 数据源连接泄露

编码时,有时会忘记释放使用的数据源连接,造成连接泄露,没有连接资源可用。 现象 java.sql.SQLException: Cannot obtain XAConnectionat weblogic.jdbc.jta.DataSource.refreshXAConnAndEnlist(DataSource.java:1691)at weblogic.jdbc.jta.DataSource.getConnectionIntern…

论文略读:Window Attention is Bugged: How not to Interpolate Position Embeddings

iclr 2024 reviewer 打分 6666 窗口注意力、位置嵌入以及高分辨率微调是现代Transformer X CV 时代的核心概念。论文发现&#xff0c;将这些几乎无处不在的组件简单地结合在一起&#xff0c;可能会对性能产生不利影响问题很简单&#xff1a;在使用窗口注意力时对位置嵌入进行插…

华为再次布局新行业:合作伙伴已超前谋划,该领域将大有可为

华为布局新行业 华为向外界公布了一个重要信息&#xff1a;在过去的三年里&#xff0c;尽管受到美国的制裁&#xff0c;华为仍然成功地完成了超过13000个元器件的国产替代研发&#xff0c;以及4000多块电路板的迭代开发。 不仅在硬件领域取得了显著成就&#xff0c;在软件和生…

oracle 19c数据库W00n进程使用很多PGA内存资源的分析

今天&#xff0c;客户反馈测试环境的数据库PGA资源不足&#xff0c;报错ORA-04036: 实例使用的 PGA 内存超出 PGA_AGGREGATE_LIMIT&#xff1b;分析是多个W00n进程使用大量PGA-触发了BUG&#xff0c;对应解决办法就是打补丁。&#xff08;民间办法就是KILL进程、重启数据库&…

大数据面试高频问题:大数据相关基础组件的维护及调优案例大全

目录 1、 Hadoop HDFS 磁盘空间不足问题 2、 Apache Spark Executor 内存溢出问题

基于云开发和微信小程序的爱宠家系统

基于云开发和微信小程序的爱宠家系统 “Development of PetCare Home System based on Cloud Computing and WeChat Mini Program” 完整下载链接:基于云开发和微信小程序的爱宠家系统 文章目录 基于云开发和微信小程序的爱宠家系统摘要第一章 系统概述1.1 研究背景1.2 研究目…