OS 死锁

资源问题

引起死锁的主要是需要采用互斥访问方法的、不可被抢占的资源

可重用资源和可消耗资源

可重用资源

定义:一种可供用户重复使用多次的资源

性质:

  1. 每个可重用资源中的单元,只能分配给一个进程使用,不允许多个进程共享
  2. 进程若要使用可重用资源,则要按下列步骤:首先,请求资源,若请求资源失败,则进程将会被阻塞或循环等待;然后,使用资源,进程对资源进行操作,如用打印机进行打印;最后,释放资源,当进程使用完资源后自己将其释放
  3. 系统中每类可重用资源中的单元数目是相对固定的,进程在运行期间既不能创建资源,也不能删除资源

对资源的请求和释放利用系统调用实现【设备:request / release;文件:open / close;需要互斥访问的资源:用信号量的 wait / signal】。每次在进程提出资源请求后,系统执行时需要做出一系列的工作

计算机系统中大多数资源属于可重用资源

可消耗资源

别称:临时性资源

定义:在进程运行期间由进程动态创建和消耗的

性质:

  1. 每类可消耗性资源的单元数目在进程运行期间是可以不断变化的,有时有许多,有时为 0
  2. 进程在运行过程中,可以不断地创造可消耗资源的单元,将其放入该资源类的缓冲区中,以增加该资源类的单元数目
  3. 进程在运行过程中可请求若干个可消耗资源单元,用于进程自己消耗,并不再将其返回给该资源类

可消耗资源通常由生产者进程创建、由消费者进程消耗

可抢占资源和不可抢占资源

可抢占资源

定义:某进程在获得这类资源后,这类资源可以再被其他进程或系统抢占

因处理机和内存均属于可抢占资源,故此类资源不会引起死锁

不可抢占资源

定义:一旦系统把这类资源分配给某进程后,就不能将其强行收回,只能在进程用完后等待其自行释放

资源分配图

作用:描述系统死锁

资源分配图是一个有向图,由一组节点 N 和一组边 E 所组成的一个对偶 G=(N,E)

N:两个互斥的子集,一组进程节点 P={P1,P2,...,Pn},一组资源节点 R={R1,R2,...,Rn}N=P∪R

E:e∈E

  • e={Pi,Rj},资源请求边Pi—>Rj,表示进程 Pi 请求一个单位的 Rj 资源
  • e={Rj,Pi},资源分配边Rj—>Pi,表示把一个单位的资源 Rj 分配给进程 Pi

死锁的起因

竞争不可抢占资源引起死锁

竞争可消耗资源引起死锁

前者不会发送死锁,后者会发生死锁

进程推进顺序不当引起死锁

进程推进顺序合法

① ② ③

进程推进顺序非法

④:进入不安全区 D,若两个进程继续向前推进,可能发送死锁

死锁的定义

若一组进程中的每个进程都在等待仅由该组进程中的其他进程才能引发的事件发生,那么该组进程是死锁的

死锁:各进程相互等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象

饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象

死循环:某进程执行过程中一直跳不出某个循环的现象,有时是因为程序逻辑 bug 导致的,有时是程序员故意设计的

共同点

区别

死锁

都是进程无法顺利向前推进的现象(故意设计的死循环除外)

死锁一定是“循环等待对方手里的资源”导致的,故若有死锁现象,则至少有两个或两个以上的进程同时发生死锁。此外,发生死锁的进程一定处于阻塞态

饥饿

可能只有一个进程发生饥饿。发生饥饿的进程可能是阻塞态(长期得不到需要的 I/O 设备),也可能是就绪态(长期得不到处理机)

死循环

可能只有一个进程发生死循环。死循环的进程可以上处理机运行(可以是运行态),只不过无法像期待的那样顺利推进。死锁和饥饿问题是由于操作系统分配资源的策略不合理导致的,而死循环是由代码逻辑的错误导致的。死锁和饥饿是管理者(操作系统)的问题,死循环是被管理者的问题

产生死锁的必要条件

产生死锁必须同时具备下列 4 个必要条件,只要其中任意一个条件不成立,死锁就不会发生

互斥条件

进程对所分配到的资源进行排他性使用,即在一段时间内,某资源只能被一个进程占用。若此时还有其他进程请求该资源,则请求进程只能等待,直至占有该资源的进程用毕释放

请求和保持条件

进程已占有至少一个资源,但又提出新的资源请求,而该被请求的资源已被其他进程占有,此时请求进程被阻塞,同时其对自己已占有的资源保持不放

不可抢占条件

进程已获得的资源在未使用完之前不能被抢占,只能在进程使用完时由其自己释放

循环等待条件

在发生死锁时,必须存在一个“进程 - 资源”循环链,即进程集合{P0,P1,P2,...,Pn}中的 P0 正在等待已被 P1 占用的资源,P1 正在等待已被 P2 占用的资源,……,Pn 正在等待已被 P0 占用的资源

死锁的处理方法

从原理上说,处理死锁有 3 种主要策略:

  • 采用某个协议来预防或避免死锁,确保系统永远不会进入死锁状态 预防死锁和避免死锁
  • 允许系统进入死锁状态,但是会检测它,然后恢复 检测死锁和解除死锁
  • 完全忽略这个问题,并假设系统永远不会出现死锁

预防死锁:一种较简单和直观的事先预防方法,通过设置某些限制条件,去破坏产生死锁的 4 个必要条件中的一个或几个预防死锁,一种较易实现的方法,已被广泛使用

避免死锁:事先预防方法,不需要通过事先采取各种限制措施破坏产生死锁的 4 个必要条件,在资源的动态分配过程中,用某种方法防止系统进入不安全状态

检测死锁:无须事先采取任何限制性措施,允许进程在运行过程中发生死锁。可通过检测机构及时地检测出死锁的发生,然后采取适当措施把进程从死锁中解脱出来

解除死锁:当检测到系统中已发生死锁时采取相应措施,将进程从死锁状态中解脱出来。常采用的措施是撤销一些进程,回收其资源,将回收的资源分配给已处于阻塞状态的进程,使这些进程能够继续运行

死锁预防

互斥条件是非共享设备必须具备的条件,不能改变,应该加以保证

破坏“请求和保持”条件

必须保证做到:当一个进程在请求资源时,不能持有不可抢占资源

第一种协议

内容:

  • 规定所有进程在开始运行之前,必须一次性地申请其在整个运行过程中所需的全部资源。此时,若系统有足够的可分配资源,可把其需要的所有资源分配给它。该进程在整个运行期间不会提出资源要求,从而破坏了“请求”条件
  • 规定系统在分配资源时,只要有一种资源不能满足进程的要求,则即使所需的其他资源都空闲也不分配给该进程,而是让其等待。由于该进程在等待期内未占有任何资源,从而破坏了“保持”条件

优点:简单、易行、安全

缺点:

  1. 资源被严重浪费,严重地降低了资源
  2. 进程经常会发生饥饿现象:因为仅当进程在获得其所需的全部资源后才能开始运行,所以可能由于个别资源长期被其他进程占用,等待该资源的进程迟迟不能开始运行
第二种协议

内容:

规定允许一个进程只获得运行初期所需的资源后,便开始运行。进程运行过程中再逐步释放已分配给自己的、且已用毕的全部资源

破坏“不可抢占”条件

协议:规定当一个已经保持了某些不可抢占资源的进程提出新的资源请求而不能得到满足时,其必须释放已经保持的所有资源,待以后需要时再重新申请【进程已占有的资源会被暂时释放,或者说被抢占,从而破坏“不可抢占”条件】

缺点:实现复杂,需付出很大代价,延长进程的周转时间,增加系统开销,降低系统吞吐量

破坏“循环等待”条件

对系统的所有资源类型进行线性排序,并赋予其不同的序号

预防协议:规定每个进程必须按序号递增的顺序请求资源,若需要多个同类资源单元,则必须一起请求。加入某进程已请求到一些序号较高的资源,后来又想申请序号较低的资源,则必须先释放所有具有相同的和更高序号的资源,然后才能申请序号低的资源。事实上,总有一个进程会占据较高序号的资源,此后其继续申请的资源必然是空闲的,因而进程可以一直向前推进

重点:如何规定每种资源的序号,通常根据大多数进行需要资源的先后顺序确定资源的序号

优点:资源利用率和系统吞吐量明显改善

存在的问题:

  • 为系统中各类资源规定的序号必须相对稳定,这限制了新类型设备的增加
  • 作用使用各类资源的顺序与系统规定的顺序不同,造成对资源的浪费
  • 为了方便用户,系统对用户在编程时所施加的限制条件应尽量少,然而这种按规定次序申请资源的方法必然会限制用户进行简单、自主的编程

死锁避免

在资源动态分配过程中,防止系统进入不安全状态,以避免发生死锁

此方法所施加的限制条件较弱,可能会获得较好的系统性能

避免死锁的实质:使系统在进行资源分配时不进入不安全状态

方法:允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次资源分配的安全性。若此次分配不会导致系统进入不安全状态,则可将资源分配给进程,否则,令进程等待

系统安全状态

当系统处于安全状态时可避免发生死锁,而当系统处于不安全状态时,可能会进入死锁状态

安全状态:系统能按某种进程推进顺序(P1,P2...,Pn),为每个进程 Pi 分配其所需的资源,直至满足每个进程对资源的最大需求,进而使每个进程都可顺利完成的一种系统状态

安全序列:进程推进顺序(P1,P2,...,Pn)

不安全状态:若系统无法找到一个安全序列,则称系统处于不安全状态

并非所有不安全状态都会转为死锁状态,但当系统进入不安全状态后,就可能进入死锁状态

只要系统处于安全状态,其就不会进入死锁状态

若不按照安全序列分配资源,则系统可能会由安全状态进入不安全状态

分析:

  1. 安全序列:(P2,P1,P3)
  2. 最多可再给 P1 分配 1 台磁带机:若给 P1 分配 1 台磁带机,则还剩 2 台,此时的安全序列为(p2,p1,p3)
  3. 最多再给 P2 分配 2 台磁带机
  4. 不能再给 P3 分配磁带机了 利用银行家算法避免死锁

为实现银行家算法,每个新进程在进入系统时,都必须申明在运行过程中可能需要每种资源类型的最大单元数目,该数目不应超过系统所拥有的资源总量。当进程请求一组资源时,系统必须首先确定是否有足够的资源可分配给该进程。若有,则进一步计算在将这些资源分配给该进程后,系统是否会处于不安全状态。若不会,则将资源分配给该进程,否则让该进程等待

银行家算法中的数据结构
  1. 可利用资源向量 Available:一个含有 m 个元素的数组,每个元素代表一类可利用的资源数目,其初值是系统中所配置的该类全部可用资源的数目,该数目会随对应资源的分配和回收而动态改变。Available[j]=K,表示系统中现有 Rj 类资源 K 个
  2. 最大需求矩阵 Max:一个 n×m 的矩阵,定义了系统中 n 个进程中的每个进程对 m 类资源的最大需求。Max[i,j]=K,表示进程 i 需要 Rj 类资源的最大数目为 K
  3. 分配矩阵 Allocation:一个 n×m 的矩阵,定义了系统中每类资源当前已分配给每一进程的资源数。Allocation[i,j]=K,表示进程 i 当前已分得 Rj 类资源的数目为 K
  4. 需求矩阵 Need:一个 n×m 的矩阵,用于表示每个进程尚需的各类资源数。Need[i,j]=K,表示进程 i 还需要 Rj 类资源 K 个方能完成其任务

Need[i,j]=Max[i,j]-Allocation[i,j]

银行家算法

设 Requesti 是进程 Pi 的请求向量,若Requesti[j]=K表示进程 Pi 需要 K 个 Rj 类型的资源。当 Pi 发出资源请求后,系统会按下列步骤进行检查

  1. Requesti[j] ≤ Need[i,j],则转向步骤 2;否则认为出错,因为其所需的资源数已超过它所宣布的最大值
  2. Requesti[j] ≤ Available[j],则转向步骤 3;否则表示尚无足够资源,Pi 须等待
  3. 系统试探着把资源分配给进程 Pi,并修改下列数据结构中的数值:

Available[j] = Available[j] - Requesti[j];

Allocation[i,j] = Allocation[i,j] + Requesti[j];

Need[i,j] = Need[i,j] - Requesti[j];

  1. 系统执行安全性算法,检查此次资源分配后系统是否处于安全状态。若是,则正式将资源分配给进程 Pi,以完成本次分配;否则,将本次的试探分配作废,恢复原来的资源分配状态,让进程 Pi 等待
安全性算法
  1. 设置两个向量
    1. 工作向量 Work:表示系统可提供给进程继续运行所需的各类资源数目,含有 m 个元素,在开始执行安全算法时,Work = Available
    2. 完成向量 Finish:表示系统是否有足够的资源分配给进程,使之运行完成。开始时先令Finish[j] = FALSE;当有足够的资源可分配给进程时,在令Finish[j] = TRUE
  1. 从进程集合中寻找一个能满足下述条件的进程:①Finish[j] = FALSE;②Need[i,j] ≤ Work[j]。若能找到,则执行步骤 3;否则,执行步骤 4
  2. 当进程 Pi 获得资源后,可顺利执行直至完成,并释放分配给它的资源,故应执行:

Work[j] = Work[j] + Allocation[i,j];

Finish[i] = TRUE;

go to step 2;

  1. 若所有进程都满足Finish[i] = TRUE,则表示系统处于安全状态;否则,系统处于不安全状态

死锁的检查

死锁检测算法:用于检测系统状态,以确定系统中是否发生了死锁

为了能对系统中是否已发生死锁进行检测,在系统中必须:①保存有关资源的请求和分配信息 ②嵌入一种算法,使其能够利用这些信息检测系统是否已进入死锁状态

死锁定理

内容

若某时刻系统的资源分配图是不可完全简化的,那么此时系统死锁

化简方法
  1. 在资源分配图中找出一个既不阻塞又非独立的进程节点 Pi 。在顺利的情况下,Pi 可获得所需资源而继续运行,直至运行完毕再释放其所占有的全部资源,相当于消去 Pi 的请求边和分配边,使之成为孤立的节点
  2. Pi 释放资源后,便可使 Pj 获得资源而继续运行,直至 Pj 完成后释放出其所占有的全部资源
  3. 在进行一系列的简化后,若能消去图中的所有边,时所有的进程节点都成为孤立的节点,则称该图是可完全简化的;若不能通过任何过程使该图完全简化,则称该图是不可完全简化的

死锁检测中的数据结构

可利用资源向量 Available:表示 m 类资源中每类资源的可用数目

把不占用资源的进程(向量Allocation=0)记入 L 表,即Li∪ L

从进程集合中找到一个Requesti ≤ Work的进程,做如下处理:

① 将其资源分配图简化,释放出资源,增加工作向量,即令Work = Work + Allocation

② 将其记入 L 表

若不能把所有进程都记入 L 表,则表明系统状态 S 的资源分配图是不可完全简化的,故此系统将发生死锁

死锁的解除

死锁解除算法:当确定系统中发生了死锁时,利用该算法将系统从死锁状态中解脱出来

解除死锁的方法:

  • 抢占资源:从一个或多个进程中抢占足够数量的资源,然后将其分配给死锁进程,以解除死锁状态
  • 终止死锁进程:终止系统中的一个或多个死锁进程,直至打破循环等待,使系统从死锁状态中解脱出来
  • 进程回退法:让一个或多个死锁进程回退到足以避免死锁的地步,要求系统记录进程的历史信息,设置还原点

终止死锁进程的方法

终止所有死锁进程

地位:一种最简单的方法

缺点:付出的代价可能会很大,因为有些进程可能已经运行了很长时间并已接近结束……

逐个终止死锁进程

描述:按照某些顺序逐个终止死锁进程,直至有足够的资源来打破循环等待,把系统从死锁状态中解脱出来

缺点:所付出的代价可能很大,因为每终止一个进程,都需要用死锁检测算法确定系统死锁是否已被解除,若未被解除,则须再终止另一个进程

选择策略的主要依据:为解除死锁而付出的代价最小

选择被终止死锁进程时的考虑因素:

  • 进程的优先级
  • 进程已执行了多少时间,还需要多少时间方能完成
  • 进程在运行中已经使用了多少资源,以后还需要多少资源
  • 进程的性质是交互式的还是批处理式的

付出代价最小的死锁解除算法

一种找到付出代价最小的终止顺序,但成本高的算法
  1. 先从死锁进程组中取出一个,形成第一层终止,若有 n 个死锁进程,则有 n 个第一层
  2. 从 n 个第一层中取出一个,形成第二层终止,每个第一层又有 n-1 个第二层终止
  3. 如此循环,直到解除死锁,计算各层的总代价,得到最小的终止顺序

PS:多重遍历,循环次数为 n!

另一种比较有效的算法
  1. 找到死锁进程组中终止代价最小的,将其从死锁进程组中删去
  2. 从新的死锁进程组中找到终止代价最小的删去
  3. 如此循环,直到解除死锁

PS:找到局部最优,再从局部最优继续找局部最优,n 个进程最多只需要 n 次寻找

代价:

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

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

相关文章

iOS的应用生命周期以及应用界面

在iOS的原生开发中,我们需要特别关注两个东西:AppDelegate和ViewController。我们主要的编码工作就是在AppDelegate和ViewControlle这两个类中进行的。它们的类图如下图所示: AppDelegate是应用程序委托对象,它继承了UIResponder类…

分布式:一文吃透分布式事务和seata事务

目录 一、事务基础概念二、分布式事务概念什么是分布式事务分布式事务场景CAP定理CAP理论理解CAPCAP的应用 BASE定理强一致性和最终一致性BASE理论 分布式事务分类刚性事务柔性事务 三、分布式事务解决方案方案汇总XA规范方案1:2PC第一阶段:准备阶段第二…

【C++的OpenCV】第十四课-OpenCV基础强化(三):单通道Mat元素的访问之data和step属性

🎉🎉🎉 欢迎来到小白 p i a o 的学习空间! \color{red}{欢迎来到小白piao的学习空间!} 欢迎来到小白piao的学习空间!🎉🎉🎉 💖 C\Python所有的入门技术皆在 我…

Python Django 之全局配置 settings 详解

文章目录 1 概述1.1 Django 目录结构 2 常用配置:settings.py2.1 注册 APP:INSTALLED_APPS2.2 模板路径:TEMPLATES2.3 静态文件:STATICFILES_DIRS2.4 数据库:DATABASES2.5 允许访问的主机:ALLOWED_HOSTS 1 …

【Linux】进程的概念

文章目录 1. 基本概念2. 进程的描述3. 进程的一些基本操作3.1 查看进程3.2 结束进程3.3 通过系统调用获取进程标示符3.4 通过系统调用来创建子进程 4. 进程状态4.1 操作系统的进程状态4.2 Linux对于这些状态的处理方式 1. 基本概念 什么是进程? 在回答这个问题之前…

C++标准模板(STL)- 类型支持 (复合类型类别,is_member_pointer,is_reference,is_compound)

类型特性 类型特性定义一个编译时基于模板的结构&#xff0c;以查询或修改类型的属性。 试图特化定义于 <type_traits> 头文件的模板导致未定义行为&#xff0c;除了 std::common_type 可依照其所描述特化。 定义于<type_traits>头文件的模板可以用不完整类型实例…

kafka为什么如此之快?

天下武功&#xff0c;唯快不破。同样的&#xff0c;kafka在消息队列领域&#xff0c;也是非常快的&#xff0c;这里的块指的是kafka在单位时间搬运的数据量大小&#xff0c;也就是吞吐量&#xff0c;下图是搬运网上的一个性能测试结果&#xff0c;在同步发送场景下&#xff0c;…

详解:WMS系统IQC来料检验

WMS系统IQC来料检验是仓库管理系统(WMS)中的一个重要环节,它的目的是对进仓原材料进行抽样检验,以确保入库的原材料符合质量标准。WMS系统通过对仓库的收货、存储、配送、装车和信息管理等过程实现集中化、规范化、标准化、自动化的管理。IQC即进货质量抽查。 IQC来料检验的目的…

Mybatis学习

一、 1.第一个mybatis程序 层层递进&#xff0c;SqlSession用来执行sql语句&#xff0c;SqlSession是与数据库的一次会话。 通过SqlSessionFactory获取SqlSession 通过SqlSessionBuilder的build()方法获取SessionFactory 2.第一个程序就找了30分钟的错&#xff08;悲惨&…

【Redis】redis的过期策略如何实现有关定时器的补充

文章目录 redis的过期策略如何实现关于定时器的补充基于优先级队列/堆实现的定时器基于时间轮实现的定时器 redis的过期策略如何实现 注意&#xff1a;不能直接遍历所有的key来判断当前key是否过期&#xff0c;这样子效率非常低&#xff0c;redis整体策略是&#xff1a;定期删…

Java中String的split函数的详解及应用

文章目录 一、 split函数详解二、应用 一、 split函数详解 split(String regex)为java.lang.String类的方法&#xff0c;其功能通俗的说就是以传入的分隔符参数拆分该字符串 方法具体为&#xff1a; public String[] split(String regex) {return split(regex, 0); }方法内部…

4.2 SSAO算法 屏幕空间环境光遮蔽

一、SSAO介绍 AO 环境光遮蔽&#xff0c;全程Ambient Occlustion&#xff0c;是计算机图形学中的一种着色和渲染技术&#xff0c;模拟光线到达物体能力的粗略的全局方法&#xff0c;描述光线到达物体表面的能力。 SSAO 屏幕空间环境光遮蔽&#xff0c;全程 Screen Space Amb…

MySQL与MongoDB,该如何做技术选型?

hello&#xff0c;大家好&#xff0c;我是张张&#xff0c;「架构精进之路」公号作者。 引言 一般情况下&#xff0c;会考虑到MySQL与MongoDB如何做技术选型的时候&#xff0c;你一定是遇到了类似于非结构化数据JSON的存取难题&#xff0c;否则大家都直接MySQL开始搞起了。 为什…

matlab simulink PMSM_SVPWM PI转速控制

1、内容简介 略 8-可以交流、咨询、答疑 2、内容说明 略PMSM_SVPWM PI转速控制 PMSM SVPWM PI转速控制 3、仿真分析 4、参考论文 略

电脑办公最佳拍档 夸克网盘升级低耗能备份、PDF阅读器等功能

临近年终&#xff0c;上班族不仅要总结过去一年的成绩还要开始制定新规划&#xff0c;在这个过程中整理资料是必不可少的环节。对于经常需要使用文件备份和PDF的用户&#xff0c;推荐大家试一下夸克网盘电脑端&#xff0c;升级后的“低耗能备份”和“PDF阅读器”让备份体验更丝…

面试150题做题记录

面试150题做题记录 题目1: 合并两个有序数组 题目1: 合并两个有序数组 题目&#xff1a;https://leetcode.cn/problems/merge-sorted-array/?envTypestudy-plan-v2&envIdtop-interview-150 最优思路&#xff1a;利用原有数列的单调性质&#xff0c;从右往左遍历&#xff…

MySQL启动后反复重新启动故障

MySQL版本为5.6.45 系统为Ubuntu 20.04 LTS 该服务器重启后&#xff0c;MySQL需要手动执行启动。 运行执行脚本后发现异常&#xff0c;如下图&#xff1a; 提示MySQL服务在不停重复启动。 反复使用ps -ef |grep mysql命令查看&#xff0c;发现mysql进程号一直在变化&#x…

Spring Boot Web MVC

文章目录 一、Spring Boot Web MVC 概念二、状态码三、其他注解四、响应操作 一、Spring Boot Web MVC 概念 Spring Web MVC 是⼀个 Web 框架&#xff0c;一开始就包含在Spring 框架里。 1. MVC 定义 软件⼯程中的⼀种软件架构设计模式&#xff0c;它把软件系统分为模型、视…

redis的分布式锁

分布式锁是一种用在分布式系统中实现同步和互斥访问的机制。 1、分布式锁概念 满足分布式系统或者集群模式下&#xff0c;多进程可见并且互斥的锁。 分布式锁的核心思想就是让分布式集群中的节点都适用同一把锁&#xff0c;只要大家使用的是同一把锁&#xff0c;就能锁住线程…

038-第三代软件开发-简易视频播放器-自定义Slider (二)

第三代软件开发-简易视频播放器-自定义Slider (二) 文章目录 第三代软件开发-简易视频播放器-自定义Slider (二)项目介绍简易视频播放器自定义Slider (二)横向纵向 关键字&#xff1a; Qt、 Qml、 关键字3、 关键字4、 关键字5 项目介绍 欢迎来到我们的 QML & C 项目&…