Output

AUTOSAR OS模块详解(三) Alarm

本文主要介绍AUTOSAR OS的Alarm,并对基于英飞凌Aurix TC3XX系列芯片的Vector Microsar代码和配置进行部分讲解。

文章目录

  • AUTOSAR OS模块详解(三) Alarm
    • 1 简介
    • 2 功能介绍
      • 2.1 触发原理
      • 2.2 工作类型
      • 2.3 Alarm启动方式
      • 2.4 Alarm配置
      • 2.5 代码分析
        • 2.5.1 主要数据结构
        • 2.5.2 Alarm初始化阶段
        • 2.5.3 Alarm启动阶段
        • 2.5.4 Alarm运行阶段
      • 3 小结

1 简介

在上一篇Autosar OS模块详解中,我们介绍了OS Counter,其作为OS时间Tick管理的重要元素,为OS提供了重要的时间基准,并驱动OS进行时间调度。

而Counter仅为OS注入时间驱动力,却不负责管理OS中各个任务或者其他定时事件,比如我们要设置某个Task的周期为10ms,或者有一些定时事件,比如100ms之后触发某个event,从概念上我们不能直接让Counter管理这些定时业务,毕竟它的职责仅仅是驱动OS,就像发动机仅仅负责动力,而不负责车辆的档位。

因此我们还需要一个中间层,来管理所有的定时业务。在Autosar OS中,这个元素称为Alarm,它就像车辆的变速器,来管理不同时间频率的定时事件。在Autosar OS中,Counter通过累加来驱动Alarm,Alarm根据自身的设定参数周期地激活Task或者触发事件。

在这里插入图片描述

对于不同的周期事件,我们只需要设置多个Alarm,每个Alarm驱动所关联的业务,这样的方式实现了代码的抽象和业务分层,具备良好的架构体系。

2 功能介绍

2.1 触发原理

在Autosar OS中,Alarm顾名思义,就是闹钟,用于实现定时业务。在前面的Counter模块中我们已经介绍了,Counter在每个Tick会累加所有关联的Alarm,然后判断Alarm的状态,如果Alarm到期,则会执行相应的Job,也就是下文会提到的工作类型。

2.2 工作类型

在Autosar OS中,Alarm有4种工作类型,用户可配置不同的类型,并关联不同的Task、Event等。

Os_AlarmActionSetEvent

该工作类型用来设置某个Task中的一个Event,关于Event我们后面会介绍到,它是扩展任务中的关键元素,Event触发后该任务进入就绪状态,并且不同的Event关联不同的Runable。

Os_AlarmActionActivateTask

该工作类型用于激活某个Task,用于基础任务。基础任务是一种不带Event的普通任务,该任务每次运行都需要被激活,运行完成之后会自动Terminate,也就是挂起。

Os_AlarmActionIncrementCounter

前文提到过,并非每个Counter都需要关联到独立的硬件,也可以使用软件Counter。使用软件Counter的时候就是通过其他硬件Counter中的Alarm进行累加,使软件Couter得以产生连续的Tick。

Os_AlarmActionCallback

该工作类型为调用相应的CallBack函数,需要注意的是,这种工作类型只能在SC1下使用。该工作类型使用不多,一般情况下很少会将业务逻辑的执行与OS内部Counter、Alarm等元素进行直接关联,而是通过Task或者Event激活等进行业务逻辑的设置。

2.3 Alarm启动方式

Alarm的启动有两种方式,一种是绝对启动(SetAbsAlarm),另一种是相对启动(SetRelAlarm)。

绝对启动

如果以Counter的值作为一个时间轴的话,绝对启动就是Alarm的触发在Counter的初始时刻,所有通过绝对启动的Alarm的启动时刻相等。在Vecotr Os中,所有的Alarm启动之后下一次Counter就会立即执行一次,因此所有的Alarm会在第一个Tick统一执行。

当存在1ms、5ms、10ms三个Alarm时,如果使用绝对启动,其在Counter Tick的时间轴上的触发关系如下图所示。可以看到当Alarm的最小公倍数Tick数出现时,所有的Alarm都会同时触发,此时系统的瞬态负载较高。

在这里插入图片描述

相对启动

相对启动在设置Alarm启动时,可以添加一个Offset,这个Offset的分辨率是Os的Tick。我们可以通过设置Alarm的Offset将Alarm的触发错开,相应的Task的执行也会因此错开。

同样是1ms、5ms、10ms三个Alarm,我们将10ms的Alarm添加一个2ms的Offset,将5ms的Alarm添加一个1ms的Offset,则其在Counter Tick时间轴上的触发如下图所示,我们可以看到,此时最多两两之间发生碰撞,系统的瞬态峰值负载得到了降低。

在这里插入图片描述

另外值得一提的是,如果Os Tick的分辨率是0.5ms,则可将这三个Alarm通过设置1.5ms、2.5ms的Offset的方式全部错开。但是分辨率增加也意味着Os运行开销的增大,需要根据项目实际情况进行斟酌。

2.4 Alarm配置

其实在Vector工具中,Alarm是不需要配置的,工具会根据Task内部配置的Rbl的周期,自适应生成对应的Alarm,相对启动的Offset也是通过配置Task中Rbl的Offset来实现的。这里我们以一个Bsw任务中的10ms Alarm为示例,介绍Alarm各配置的含义。

在这里插入图片描述

OsAlarmCounterRef

如上一章所述,Counter是Alarm的驱动源,因此每个Alarm都需要关联一个Counter,一般情况下是Alarm所在核内的Counter。

OsAlarmAction

如前所述,Alarm具有四种Action,一般最常使用的是OsAlarmSetEvent和OsAlarmActivateTask。这里由于Bsw Task分配了多个周期的Rbl,属性为Extended Task,因此需要使用SetEvent的形式进行调度。

OsAlarmTask

每个Alarm的动作都需要对应一个Task。

OsAlarmCallbackName

SC1类型的Os中可以使用Os_AlarmActionCallback类型,因此如果是该类型的Alarm这里可以配置Callback函数。

OsAlarmCounter

当Alarm的工作类型为Os_AlarmActionIncrementCounter时,这里就需要配置一个Counter,以实现软件Counter的激活。

OsAlarmEvent

当Alarm的工作类型为Os_AlarmActionSetEvent时,这里配置对应的需要设置的Event即可。

OsAlarmAlarmTime

Alarm的Offset,但是实际上Offset是根据对应Task中分配的Rbl的Offset来计算的,因此这里不用配置。

OsAlarmAutostartType

启动类型,绝对启动还是相对启动,这个一般由工具计算也不需要配置。

OsAlarmCycleTime

Alarm的周期,如果是一次性的Alarm,这里配置为0,但是这项配置其实也是工具自动计算的,不用配置。

OsAlarmAppModeRef

Os的启动是有启动模式的,这里可以配置为:在哪种启动模式下,该Alarm自动启动,即初始化时启动。这项配置一般不用配置,Rte启动时会统一启动所有Alarm。

OsAlarmAccessingApplication

Alarm的访问权限配置,一般情况下Alarm的激活都是由Os来执行,权限也是由Os自动分配,少数比较特别的Alarm用户或者运行时的RTE会操作,因此Os无法识别所有访问源,因此这里有时候需要手动配置权限。如果执行Os服务时报权限访问的错误,一般是这种权限配置的地方有缺失。

在这里插入图片描述

前文提到Alarm可以配置Offset以错开激活时机,在Vector工具中,这是通过Task中实现的。如上图中将所有5ms的Rbl都配置了1ms的Offset,则该5ms的Alarm就会由工具自动计算出Offset。

2.5 代码分析

此处以Rte_Al_TE2_Default_BSW_Async_Task_Core0_1_5ms为例,分析Vector Os中关于Alarm的代码执行逻辑。

2.5.1 主要数据结构

Alarm中的主要数据结构为Os_AlarmSetEventConfigType、Os_AlarmActivateTaskConfigType、Os_AlarmIncrementCounterConfigType和Os_AlarmCallbackConfigType,分别对应Alarm的四种工作类型,此处我们的Alarm工作类型为设置Event,因此我们重点介绍Os_AlarmSetEventConfigType类型。

在Os_Alarm_Lcfg.c文件中我们可以找到配置生成的每个Alarm的数据类型:

/*! Alarm configuration data: Rte_Al_TE2_Default_BSW_Async_Task_Core0_1_5ms */
CONST(Os_AlarmSetEventConfigType, OS_CONST) OsCfg_Alarm_Rte_Al_TE2_Default_BSW_Async_Task_Core0_1_5ms =
{/* .Alarm = */{/* .Job                   = */{/* .Dyn      = */ OS_ALARM_CASTDYN_ALARM_2_JOB(OsCfg_Alarm_Rte_Al_TE2_Default_BSW_Async_Task_Core0_1_5ms_Dyn),/* .Counter  = */ OS_COUNTER_CASTCONFIG_TIMERPFRT_2_COUNTER(OsCfg_Counter_SystemTimer),/* .Callback = */ Os_AlarmActionSetEvent},/* .Autostart             = */{/* .AlarmTime        = */ 0uL, /* 0.0 sec *//* .Cycle            = */ 0uL, /* 0.0 sec *//* .ApplicationModes = */ OS_APPMODE_NONE,/* .AlarmMode        = */ OS_ALARMMODE_ABSOLUTE},/* .AccessingApplications = */ (OS_APPID2MASK(OsApplication_NonTrusted_Core0) | OS_APPID2MASK(OsApplication_NonTrusted_Core1) | OS_APPID2MASK(OsApplication_NonTrusted_Core2) | OS_APPID2MASK(OsApplication_NonTrusted_Core3) | OS_APPID2MASK(SystemApplication_OsCore0) | OS_APPID2MASK(SystemApplication_OsCore1) | OS_APPID2MASK(SystemApplication_OsCore2) | OS_APPID2MASK(SystemApplication_OsCore3)),  /* PRQA S 0410 */ /* MD_MSR_Dir1.1 *//* .OwnerApplication      = */ &OsCfg_App_OsApplication_NonTrusted_Core0},/* .Task  = */ &OsCfg_Task_Default_BSW_Async_Task_Core0,/* .Mask  = */ Rte_Ev_Cyclic2_Default_BSW_Async_Task_Core0_1_5ms
};

我们可以看到,配置生成的数据为OsCfg_Alarm_前缀加上Alarm配置名。 这里我们可以看到所有的配置值都在这个结构体里,我们重点关注其中的.Alarm.job.Dyn,这个指针指向的结构体中有一个ExpirationTimestamp成员,是Alarm下一个需要被激活的Counter Tick值,也就是“闹钟”的设定值。

2.5.2 Alarm初始化阶段

关于Os的初始化,在本系列Counter章节中已经介绍过,这里就不赘述了,与Counter相同,Alarm的初始化也是在Os_AppInit中进行的,其调用栈如下图所示。

在这里插入图片描述

在初始化阶段,仅仅是将Alarm的状态切换为OS_ALARMSTATE_CANCELED,并没有其他操作。

2.5.3 Alarm启动阶段

Alarm的启动是在EcuM的StartupTwo阶段完成的,关于EcuM的启动时序,这里不做过多介绍,后续聊到EcuM模块时再做解读。

StartupTwo运行在Default_Init_Task中,这是一个系统自带的初始化Task,优先级较高。在StartupTwo阶段,有两个地方会启动Alarm,SchM_Init和Rte_Start。SchM_Init中主要初始化EcuM等Bsw模块相关联的Alarm,而Rte_Start则主要初始化用户定义的相关的Rbl的Alarm。其时序图如图所示。

在这里插入图片描述

我们从中可以看到,Os_AlarmSetRelAlarm是最终实施的主要服务函数,我们从该函数解读SetRelAlarm的实现,这里忽略掉其中的错误检查。

OS_FUNC_ATTRIBUTE_DEFINITION(OS_LOCAL_INLINE Os_StatusType, OS_CODE, OS_ALWAYS_INLINE, Os_AlarmSetRelAla
(P2CONST(Os_AlarmConfigType, AUTOMATIC, OS_CONST) Alarm,TickType Increment,TickType Cycle
))
{Os_StatusType status;if(ErrorCheck...){}else{P2VAR(Os_AlarmType, AUTOMATIC, OS_VAR_NOINIT) alarmDyn;alarmDyn = Os_AlarmGetDyn(Alarm);                                                                   /* #20 Set alarm's state to SET. */alarmDyn->State = OS_ALARMSTATE_SET;                                                                /* #30 Set alarm's cycle time to Cycle. */alarmDyn->Cycle = Cycle;                                                                            /* #40 Tell counter to execute alarm's job in Increment ticks. */Os_JobAddRel(&Alarm->Job, Increment);                                                               status = OS_STATUS_OK;}return status;
}

这里alarmDyn的数据指向的是OsCfg_Alarm_Rte_Al_TE2_Default_BSW_Async_Task_Core0_1_5ms_Dyn变量,大家能够很容易注意到其命名是有规律的。

首先,该函数将Alarm的状态切换到OS_ALARMSTATE_SET,表示该Alarm已经启动。

然后设置该Alarm的周期为Cycle,这里我们是5ms的Alarm,周期为5。

然后通过Os_JobAddRel将Alarm添加到其关联的Counter中,这里的Increment就是Offset,如果没有偏移的话Increment为1,表示第一次Counter Tick就会激活该Alarm,这里我们设置了1ms的Offset,因此Increment=2,表示第二次Counter Tick才会激活该Alarm。(Counter的逻辑是先累加再判断,因此第一次工作时Counter Tick值为1)

这里的Os_JobAddRel,也就是Os_CounterAddRelJob,是Counter内部的实现函数,我们观察其代码。

FUNC(void, OS_CODE) Os_CounterAddRelJob
(P2CONST(Os_CounterConfigType, AUTOMATIC, OS_CONST) Counter,P2CONST(Os_JobConfigType, AUTOMATIC, OS_CONST) Job,Os_TickType Offset
)
{Os_TickType now;Os_TickType newExpTime;P2CONST(Os_PriorityQueueConfigType, AUTOMATIC, OS_CONST) jobQueue;jobQueue = &Counter->JobQueue;/* #10 Get the current counter value (now). */now = Os_CounterGetPhysicalValue(Counter);                            /* #20 Set job's expiration time to mod(now + Offset). */newExpTime = Os_TimerAdd(Counter->Characteristics.MaxAllowedValue,Counter->Characteristics.MaxCountingValue,now,Offset);Os_JobSetExpirationTimestamp(Job, newExpTime);                        /* #30 Enqueue the given job in counter's job queue. */Os_PriorityQueueInsert(jobQueue, Job);                                /* #40 If the top element of the queue has changed: */if(Job == Os_PriorityQueueTopGet(jobQueue))                           {/* #50 Update timer's compare value. */Os_CounterSetCompareValue(Counter, newExpTime);                     }
}

首先通过Os_CounterGetPhysicalValue计算出Counter的当前值,一般启动时该值都是0。然后通过Os_TimerAdd结合当前值和Offset,计算“闹钟”的定时时刻ExpirationTimestamp,也就是时间轴上的下一个触发点。这里需要最大值来判断溢出。这里now=0,Offset=2,计算出结果为ExpirationTimestamp=2。

然后通过Os_JobSetExpirationTimestamp函数,将计算出的ExpirationTimestamp设置为超时值,在Counter的中断中,就是通过比较Counter的Tick值和ExpirationTimestamp,来判断该Alarm是否需要激活。

然后通过Os_PriorityQueueInsert来将Alarm插入到Counter的Job队列中,这里使用了完全二叉树进行优先级排序,时间轴上未来最近的一个Alarm具有最高优先级,感兴趣的读者可以自行研究,这里不进行算法层面的探讨。

最后一步是判断是否更新了待激活的Alarm,比如当前Counter待激活的时刻为Tick=5,现在插入的Alarm需要在Tick=2时激活,那么就需要更新Counter的Compare值,因为Counter并不是每一次Timer中断都会工作,而是按需执行。

到这里就完成了Alarm的启动,只需要等待Counter中断去执行了。

2.5.4 Alarm运行阶段

前篇我们提到,在Counter中的Os_CounterWorkJobs函数,会轮询任务队列中的所有Alarm,如果该Alarm的ExpirationTimestamp与Counter Tick的值吻合,则执行该Alarm的Callback,同时将该Alarm从Job队列中取出。这部分逻辑不再赘述,读者如果有疑问可以阅读前篇Counter详解。

这里我们在运行阶段重点介绍Alarm在的Callback中的动作,这是Counter激活Alarm之后由Alarm实施的主要业务。我们的Alarm选取的是5ms的SetEvent,因此其Callback是Os_AlarmActionSetEvent。

在这里插入图片描述

如果是周期Alarm,在Os_CounterReloadJob中,该Alarm会被重载,会根据Counter的当前Tick值加上周期,算出最新的ExpirationTimestamp,以设置下一个周期的触发;如果是一次性Alarm,则在Os_AlarmCancelOrReload中Alarm的状态会被设置为OS_ALARMSTATE_CANCELED,以表示关闭该Alarm。

然后执行Os_EventSetInternal以进行Event的设置,来触发对应Task中的Rbl执行。该部分我们在后面的Event文章内容中进行详细介绍,这里就一概而过。

3 小结

本文承接上篇Counter,介绍了Autosar Os中的Alarm机制,解释了Vector工具中的Alarm配置项,并根据Vector代码详细解读了其初始化、启动、运行等阶段,结合详细的时序图,介绍了其由Counter激活后的执行逻辑。

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

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

相关文章

openharmony应用开发快速入门

开发准备 本文档适用于OpenHarmony应用开发的初学者。通过构建一个简单的具有页面跳转/返回功能的应用(如下图所示),快速了解工程目录的主要文件,熟悉OpenHarmony应用开发流程。 在开始之前,您需要了解有关OpenHarmon…

使用傅里叶变换进行图像边缘检测

使用傅里叶变换进行图像边缘检测 今天我们介绍通过傅里叶变换求得图像的边缘 什么是傅立叶变换? 简单来说,傅里叶变换是将输入的信号分解成指定样式的构造块。例如,首先通过叠加具有不同频率的两个或更多个正弦函数而生成信号f(x…

用户中心项目教程(四)---Vue脚手架完成前端初始化

目录 1.项目的创建 2.使用开发工具打开 3.项目运行方法 4.使用按钮组件 5.全局注册 6.如何进行组件的测试 7.使用组件的效果展示 8.关于这个vue项目内容的说明 1.项目的创建 这个前提你是你完成了我的教程(三)里面的相关配置,不然你可…

《自动驾驶与机器人中的SLAM技术》ch4:基于预积分和图优化的 GINS

前言:预积分图优化的结构 1 预积分的图优化顶点 这里使用 《自动驾驶与机器人中的SLAM技术》ch4:预积分学 中提到的散装的形式来实现预积分的顶点部分,所以每个状态被分为位姿()、速度、陀螺零偏、加计零偏四种顶点&am…

二叉搜索树(TreeMapTreeSet)

文章目录 1.概念2.二叉搜索树的底层代码实现(1)首先构建二叉树(2)实现插入功能;(3)实现查找(4)删除(重点) 3.TreeMap 1.概念 TreeMap&TreeSet都是有序的集合都是基于二叉搜索树来实现的 二叉搜索树:是一种特殊的二叉树 若左子…

【QT用户登录与界面跳转】

【QT用户登录与界面跳转】 1.前言2. 项目设置3.设计登录界面3.1 login.pro参数3.2 界面设置3.2.1 登录界面3.2.2 串口主界面 4. 实现登录逻辑5.串口界面6.测试功能7.总结 1.前言 在Qt应用程序开发中,实现用户登录及界面跳转功能是构建交互式应用的重要步骤之一。下…

基于springboot的口腔管理平台

作者:学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等 文末获取“源码数据库万字文档PPT”,支持远程部署调试、运行安装。 项目包含: 完整源码数据库功能演示视频万字文档PPT 项目编码&#xff1…

4 AXI USER IP

前言 使用AXI Interface封装IP,并使用AXI Interface实现对IP内部寄存器进行读写实现控制LED的demo,这个demo是非常必要的,因为在前面的笔记中基本都需哟PS端与PL端就行通信互相交互,在PL端可以通过中断的形式来告知PS端一些事情&…

实力认证 | 海云安入选《信创安全产品及服务购买决策参考》

近日,国内知名安全调研机构GoUpSec发布了2024年中国网络安全行业《信创安全产品及服务购买决策参考》,报告从产品特点、产品优势、成功案例、安全策略等维度对各厂商信创安全产品及服务进行调研了解。 海云安凭借AI大模型技术在信创安全领域中的创新应用…

二、点灯基础实验

嵌入式基础实验第一个就是点灯,地位相当于编程界的hello world。 如下为LED原理图,要让相应LED发光,需要给I/O口设置输出引脚,低电平,二极管才会导通 2.1 打开初始工程,编写代码 以下会实现BLINKY常亮&…

Amazon MSK 开启 Public 访问 SASL 配置的方法

1. 开启 MSK Public 1.1 配置 MSK 参数 进入 MSK 控制台页面,点击左侧菜单 Cluster configuration。选择已有配置,或者创建新配置。在配置中添加参数 allow.everyone.if.no.acl.foundfalse修改集群配置,选择到新添加的配置。 1.2 开启 Pu…

大模型UI:Gradio全解11——Chatbot:融合大模型的聊天机器人(4)

大模型UI:Gradio全解11——Chatbot:融合大模型的聊天机器人(4) 前言本篇摘要11. Chatbot:融合大模型的多模态聊天机器人11.4 使用Blocks创建自定义聊天机器人11.4.1 简单聊天机器人演示11.4.2 立即响应和流式传输11.4.…

流量分析复现(第十八届信息安全大赛 第二届长城杯 )

zeroshell_1 题目:从数据包中找出攻击者利用漏洞开展攻击的会话(攻击者执行了一条命令),写出该会话中设置的flag, 结果提交形式:flag{xxxxxxxxx} 这里大致的思路还是先看看,流量协议的分级 主要还是以TCP流…

ImportError: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32‘ not found

问题描述:安装MMYOLO或者MMROTATE时,出现的问题: (base) rootautodl-container-78fc438fda-4132d99a:~/autodl-tmp/MMROTATE_PROJECT/mmrotate-1.x# python demo/image_demo.py demo/demo.jpg oriented-rcnn-le90_r50_fpn_1x_dota.py orient…

2024年博客之星年度评选—创作影响力评审入围名单公布

2024年博客之星活动地址https://www.csdn.net/blogstar2024 TOP 300 榜单排名 用户昵称博客主页 身份 认证 评分 原创 博文 评分 平均 质量分评分 互动数据评分 总分排名三掌柜666三掌柜666-CSDN博客1001002001005001wkd_007wkd_007-CSDN博客1001002001005002栗筝ihttps:/…

25/1/15 嵌入式笔记 初学STM32F108

GPIO初始化函数 GPIO_Ini:初始化GPIO引脚的模式,速度和引脚号 GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化GPIOA的引脚0 GPIO输出控制函数 GPIO_SetBits:将指定的GPIO引脚设置为高电平 GPIO_SetBits(GPIOA, GPIO_Pin_0); // 将GPIO…

新星杯-ESP32智能硬件开发--ESP32的I/O组成-系统中断矩阵

本博文内容导读📕🎉🔥 ESP32开发板的中断矩阵、功能描述与实现、相关API和示例程序进行介绍 ESP32中断矩阵将任一外部中断源单独分配到每个CPU的任一外部中断上,提供了强大的灵活性,能适应不同的应用需求。 ESP32中断主…

游戏引擎学习第81天

仓库:https://gitee.com/mrxiao_com/2d_game_2 或许我们应该尝试在地面上添加一些绘图 在这段时间的工作中,讨论了如何改进地面渲染的问题。虽然之前并没有专注于渲染部分,因为当时主要的工作重心不在这里,但在实现过程中,发现地…

【2024年华为OD机试】(C卷,100分)- 悄悄话 (Java JS PythonC/C++)

一、问题描述 题目描述 给定一个二叉树,每个节点上站一个人,节点数字表示父节点到该节点传递悄悄话需要花费的时间。 初始时,根节点所在位置的人有一个悄悄话想要传递给其他人,求二叉树所有节点上的人都接收到悄悄话花费的时间…

【Spring Boot】掌握 Spring 事务:隔离级别与传播机制解读与应用

前言 🌟🌟本期讲解关于spring 事务传播机制介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么废话…