menu

安全关键系统设计

markdown写起来太麻烦了, 跳转语雀:

https://www.yuque.com/jiucai-kxrf0/wyvx1y/ag125o















以下为搜索引擎收录用, 因为懒得维护博客, 无视

传统安全关键工业领域, 像是汽车, 航天, 自动化等等方向, 因为安全性是首要关注点, 所以会形成一套固定的开发流程和软件框架。 基于Linux的行业应用, 像是生活随处可见的各种电子广告牌, 点餐机, 监控摄像头, 通信设施 ,还包括各种消费电子等等,因为更多关注可拓展性和功能, 所以在开发上不会形成特别的范式。

在一个新的方向出现的时候(比如说自动驾驶, 家用机器人等领域), 因为行业没有标准, 加上为了快速完成原型, 普遍的团队都是使用Linux行业应用的方式切入。 但这些方向,用功能安全的术语来说,不乏ASIL-D的危害事件, 也就是会造成人员损伤, 因此必须要设计成安全关键系统。为了系统达到高安全性, 还是得结合传统安全关键工业领域的开发经验。 所以我想通过对比两种领域开发方式的不同,为从普通Linux行业应用的开发方式,切换到安全关键系统的开发方式上来,增加一点输入 ——— 但肯定不是说要完全的照搬, 因为对这些方向(自动驾驶, 家用机器人等), 安全性很重要, 但是功能也重要, 更需要探索一个折中的方法, 在不损失灵活性的基础上, 让系统变的更安全。

( PS: 工作经验主要做Linux行业应用为主, 对汽车行业并没有实操的经验, 输入来源于书籍和网络资料, 可能会有比较多的错误)

导图 安全关键系统

实时系统 从安全关键系统的导图上看, 实时系统主要是为了满足安全功能设计中的时序要求。 ( 这里面涉及的东西太理论了, 还没有吸收完, 随便写写,有兴趣可以看看tttech的资料和他们的书《分布式实时系统设计》 (:τ」∠)

实时操作系统 因为实时操作系统包含了框架和系统, 因此广义上我们聊实时操作系统的时候, 对其的要求并不止是说时序上的实时, 也包含对基础软件需求。 以下导图从ISO25000的角度, 随便写写安全关键系统可能的对实时操作系统的需求。 (大部分时候我们工程师差不多也就是从这个视角看问题)

工程范式 这里主要从软件视角上看工程范式,硬件视角的话, 其实也大差不差, Linux行业应用包括消费电子的硬件设计和软件设计是相对分离的, 较少整体性考虑, 更像是软件在一个已经固定下的硬件平台上自由发挥。

V模型里的架构设计是规定性的,以核心的架构师为核心。 敏捷方法中, 架构工作是描述性的, 并且分散到多个自行组织的团队中完成。 V模型在汽车等工业领域中使用的多, 因为安全性是最首要的关注点, 安全功能的架构比较关键。 在其他软件领域里, 敏捷开发更常见, 因为功能的实现是最重要的。 敏捷开发的团队更有自组织性, 有创造力, 但相对的架构工作就变得更难以掌控。

两者的差异其实从文档就可以看出来, 一个是从下到上, 一个是从上到下 就像我们使用yuque这样的工具管理文档, 每个团队会写很多记录, 但你看不到一个整体的架构性文档 但是像汽车工业, 会使用systemweaver这样的软件来统一管理需求/设计数据库

软件技术 个人觉得, 汽车工业上的 流程管理/功能安全等方法论 和 技术上的安全系统/实时系统架构设计 等是可以学习的, 但是软件技术其实不太重点, 条条框框的太多, 是通过牺牲“复杂性”, 从而达到”安全性“, 没有“软件定义”的灵活性。 但是如果你的部分子系统是可以抽离的比较干净的, 那通过复用汽车工业的软件框架, 使这部分子系统来快速达到ASIL-D的安全性等级, 也是可以的; 至于需要“软件定义”的模块, 还是需要重新探索如何更灵活满足安全设计。

设计方法V模型是安全关键行业的一种流程方法, 主要包含设计(Design), 实施(Implementation), 验证(Verification)和确认(Validation)等工作, 相比起敏捷开发, V模型更能带来 “构建稳定的产品所必需的精心设计,开发和文档编制” 。(https://wikichi.icu/wiki/V-Model_(software_development))V模型可以用在整个系统上,也可以用在某个子系统,或者软件/硬件上, 或者“功能安全”,“信息安全” 下图为“信息安全”的V流程范例下图为“功能安全”的V流程范例, 可以看到他是由一个大V+两个2个小V组成的流程比起敏捷软件开发, V模型的一部分核心区别在于V的左侧 —— 设计(Design)。本文尝试探索, 在特定工业领域以外, 更通用的更广泛的适合行业应用的V模型中的设计流程。汽车工业功能安全https://zh.wikipedia.org/wiki/ISO_26262汽车工业最典型的V模型流程就是功能安全了, 这套流程对其他安全关键的系统也很有借鉴意义, 所以我们就来看看功能安全里的设计步骤。流程识别项目(item,特定的车用系统产品),以及定义其最上层的系统功能需求。识别项目的全面风险事件(hazardous events)针对一个风险事件,定义其ASIL(可参考Part 9)针对风险事件决定其安全目标(safety goal),其中也包括风险事件的ASIL。针对汽车层级的功能安全概念(functional safety concept),定义可以确保安全目标的系统架构。将安全目标再拆解为细部的安全需求(safety requirements)(一般而言,每一个安全需求都会使用上层安全需求或安全目标的ASIL。不过因为实际情形的限制,可能会将一个安全需求拆解为数个ASIL较低,但有冗余机能的安全需求,由独立的冗余元件来实现)。安全需求会分配到各架构组件(architectural components),可能是子系统、硬件组件、软件组件(一般而言,每一个组件都需要考虑分配到安全需求的ASIL等级,依其标准和程序进行,若有多个等级,以最高的为准)依分配的安全需求以及机能需求开发架构组件,并且确认符合对应需求。支持流程ISO 26262中有定义一些在安全生命周期中在各阶段都持续进行的支持性流程,这个是整合性的流程,也提供了为了支持通用流程目标需额外考虑的事项。受控制的企业界面,可以下达目标、需求和控制方式给分散式开发中的所有供应商。明确的标示安全需求以及在生命周期中的相关管理方式。工作文档的配置管理,有正式唯一的识别方式,可以重现配置,可以建立各工作文档和配置变更之间的可追踪性正式的变更控制,包括变更影响的管理,目的是要确保识别到的缺陷已移除,在产品变更时不会导入危害。工作文档验证的规划、控制以及报告,验证可以是评审、分析及测试,也包括各来源识别到缺陷的回归分析。计划性的识别及管理在安全生命周期中,所有有助于机能安全以及安全评估持续管理的工程文档(文件)软件工具(计划使用以及实际使用的工具)的合格性审查以往开发的软件及硬件模组,要整合到目前开发ASIL等级的合格性审查用服务历史证据来证实某项目若要用在指定的ASIL等级,已有足够的安全性。相关的软件设施IQ-PMEAhttps://www.fangzhenxiu.com/post/678397/创建的步骤如下:1、使用功能网和故障网进行系统结构建模2、为故障网补充错误探测和错误响应3、确定安全目标,设定相应的控制阀,如SPFM、LFM和PMHF等4、为每一个违反安全目标的问题确定容错时间(FTT)5、为每一个错误探测确定探测时间(FDT),为每一个错误响应确定反应时间(FRT)6、记录FIT值,如在FMEDA表格7、记录具有故障或更佳的DC值并通过错误检测8、在图形编辑器中检查时间特征是否满足目标值9、复核计算基础(FIT和DC),确保完整性10、为每一个安全目标发布故障表SQTRACER标准中文版📎ISO-26262.pdf实操网上找的具体实操的案例, 有兴趣可以看看, 以深入理解安全生命周期一个简单的, 可以快速看看https://www.nxpic.org.cn/article/id-323477一个更完整的, 以理解每个步骤要做什么《基于iso26262的功能安全》https://item.jd.com/13229940.html预期功能安全https://zhuanlan.zhihu.com/p/80649486由此可见,SOTIF(中文直译为预期功能安全)的关注点是:由功能不足、或者由可合理预见的人员误用所导致的危害和风险。例如,传感系统在暴雨、积雪等天气情况下,本身并未发生故障,但是否仍能执行预期的功能。而功能安全关注于与安全相关的失效(failure),信息安全关注于与安全相关的威胁(threat)。预期功能安全的分析方法会和功能安全不太一样, 这个主要是针对算法来说的不过这个没找到什么实操说明, 以后丰富了再研究融入其他工业我尝试找了一下汽车工业以外, 其他安全关键领域的设计方法在个别行业, 用不到太复杂的安全设计, 主要用的是额外加装保护实体的方法在某些行业比如轨道交通、过程工业等,安全防护设备(如SIS)和常规控制设备(如DCS)常常是分开的。也就是说,它们在物理上是独立的实体。从系统自顶向下分解而来的安全功能,基本上都由安全防护设备来承担,而安全防护设备基本上也只承担安全功能。功能安全只针对安全防护设备,以及安全防护设备和常规控制设备的系统集成。这样的系统架构泾渭分明,非常清晰,也比较容易理解。在航天领域,会更升级到“结构化设计方法”, ”形式化建模+验证“等等http://www.cmse.gov.cn/dmt/cbw/zrht/2007n/2007ndsq_459/200808/P020200303352709108285.pdf也会看到猎鹰9这样其实没有套用所谓航天标准的产物https://ubunlog.com/en/spacex-uses-linux-and-x86-processors-on-the-falcon-9/除开技术可能不一样, 但是大体也是和汽车一样的用ISO26262这样的流程可行路径在不牺牲敏捷开发持续迭代的特性下, 较为轻量的引入V流程中的部分阶段 (主要是安全设计完整引入就算了, 在一年一代车+OTA的背景下, 连汽车工业自己都在淘汰通过对iso26262的学习,我觉得一个可行的路径, 就是把”设计“ 和 ”确认/验证“ 都当成一个程序来维护。就好像敏捷开发中的开发是一个圆圈, 那我们的改进, 是可以把设计当一个圆圈 + 开发一个圆圈 + 测试一个圆圈, 通过伪代码/代码,在线文档/管理工具, 让死板的V流程更灵活但在环的流程,就好像敏捷开发依赖于devops, 设计的在环依赖于持续的追踪和监控能力, 不过就不在这里谈论展开了。预期功能安全主要是针对算法的TODO普通功能是沿用敏捷开发 or 引入部分V模型? 规定性的架构设计是否有必要?TODO 功能安全流程总体流程管理传统流程管理工具, teambition/aone等项目定义语雀等在线文档工具, 需要人为系统化组织危险分析和风险评估功能安全概念产品开发 - 硬件设计TODO, 建模/仿真产品开发 - 软件设计TODO, 建模/仿真/软件框架 安全确认TODO, 非本文关心其实这里的难点, 还是在于引入 ”经济上可承受的连续安全分析方法“, 在敏捷开发过程中持续保证安全, 不能简单套用ISO26262, 但我们开始不用想那么多, 先引入FMEA, 能提升一点点安全性也是好的至于软件设计/硬件设计, TODO安全设计涵盖危险分析/风险评估 危险分析和风险评估的第一步是情形分析和危险识别,即通过相关的情况分析将产品 存在的风险识别出来。这就要考虑可能引发危险的操作环境和操作模式,并且要考虑在正确 使用时和可预见的误使用时的情况。基于这样的考虑,我们应该通过大量的技术来系统分析, 注意以下一些方面: 1. 准备一个用来进行评估的操作情况清单2. 系统的确定清单上的危险。主要可以通过诸如:头脑风暴,检查列表,历史记录, FMEA,产品矩阵,以及相关的领域研究等技术手段进行。3. 风险应该用在车辆上可以被观察到的条件或影响来进行定义或描述 4. 在相关操作条件和操作模式下危险事件的影响应该被明确说明。如:车辆电源系统 故障可能导致丧失引擎动力,丧失转向的电动助力以及前大灯照明。 5. 如果在风险识别中识别出的风险超出了 ISO26262 的要求范围,则需给出合适的相应 措施。当然,超出 ISO26262 的风险可以不必分类分级。 完成风险的识别之后,就要对这些风险进行适当的分级,以便设定相应的安全目标,并 按照不同的风险等级来采取合理的措施加以避免。 风险的分类主要是通过 3 个指标来考量,即:危险发生时导致的伤害的严重性、在操作 条件下暴露于危险当中的可能性(危险所在工况的发生概率)、危险的可控性。风险分析对工具的要求就是需要记录所有风险 + 给风险分级(具体怎么分级的逻辑请看ISO-26262)功能安全概念需要能管理以下的组织如何从risk到safery goal到functional safety requirement, 主要通过FMEA/FTA等分析方式所以除了要记录层次结构, 最好还能直接集成FMEA这样的安全分析方法可行路径主要分两个流程一个是使用fema的方法事前分析可能的风险 和 对应的安全处理措施, 使用FTA事后分析出现的问题和对应安全处理措施一个是把fema/fta分析后的结果导入成功能安全的文档FEMA策划与准备:识别项目,了解项目计划,定义FMEA的边界,明确FMEA的分析过程,并填写FMEA的表格的第一步栏。结构分析:首先分析产品项目的过程,针对每个过程划分过程步骤或按工站划分,再对每个步骤识别出4M要素(人、机、料、环)。使用结构树的方式进行。功能分析:可以使用功能网络、功能书、功能矩阵等方法分析。进行功能分析前,需要了解要求、产品特性和过程特性。失效分析:主要从过程步骤开始分析,过程步骤的功能失效就是失效模式;而4M要素的失效,即是失效起因;分析失效模式的发生,产生的后果,就是失效影响。风险分析:基于现有的控制手段,对第四步识别出来的风险点进行评分和判定。从严重度、频度和探测度进行评分。优化改进:基于第五步评分结果和措施优先级,制定改进优化措施。结果文件化,输出FMEA表格FTA (1)对所选定的系统作必要的分析,确切了解系统的组成及各项操作的内容,熟悉其正常的作业图; (2)对系统的故障进行定义,对预计可能发生的故障、过去发生过的故障事例作广泛的调查; (3)仔细分析各种故障的形成原因,如设计、制造、装配、运行、环境条件、人为因素等; (4)收集各故障发生的概率数据; (5)选定系统可能发生的最不希望发生的故障状态作为顶事件,画出故障逻辑图; (6)对敌障树作定性分析,确定系统的故障模式; (7)对故障树进行定量计算,计算出顶事件发生概率、各底事件的结构重要度、概率重要度、关键重要度等可靠性指标。更多见:https://www.eet-china.com/mp/a61397.html因为没有现成软件, 所以只能第一步 fmea我们可以通过excel来做, fta也有一些工具第二步 人工从fmeal翻译到功能安全需求的结构, 来指导软件设计, 如果没有软件设计就先直接跳到开发样例功能安全我们这样相当是从0搭起, 所以实践过程中一定多看ISO-26262, 看看有没有能需要拿来的, 逐渐完善大框架(内部)

本文介绍可以进行形式化验证的基于状态机的程序范式, 比如如果应用程序逻辑不太复杂, 可以直接套用范式, 就可以以较低的成本实现可靠程序。

写一个状态机程序 首先我们虚拟一个现实世界的需求场景, 要做一个多节点时间管理的服务, 比如如下图的一个结构(链接)

大概的需求可以列为以下几条:

  1. 主控节点“AM”, 辅节点”BM“, 子节点“set(SEN)”
  2. 所有节点默认都没有初始时间, 系统初始化时间由“AM”从外部”NET“获取
  3. 主控节点“AM” 可向子节点“set(SEN)” 同步时间, 如果“AM“进入”INVALID”状态, “BM”要负责控制同步时间
  4. 仅在所有节点(MM and BM)进入 “STOP” 状态时候可以进行 “BIG” 时间同步, 其他时间可以进行“LITTLE” 同步, 时间差如果大于1s, 必须进行“BIG” 时间同步

下面状态机去实现这个程序: https://github.com/wzyy2/my-studys/blob/master/0-model-checking/app/main.cpp

上面这段c代码各种状态,各种依赖, 我们也没办法确定这个程序的逻辑完备性, 所以下面做形式化验证来确认。 为状态机程序做形式化验证 流程:

针对我们刚刚实现的状态机程序, 用TLA+做一个建模(VSCODE装TLA+插件就可以执行) https://github.com/wzyy2/my-studys/blob/master/0-model-checking/tla/TimeSync.pluscal

针对这个状态机, 我们加入一些验证规则: 可恢复性:

  • 状态随意切换,最后可以正常退出

时间差:

  • AM节点start, {AM, BM, SEN}时间差小于25个tick
  • AM节点!start 后, {BM, SEN}时间差小于25个tick

因为我们设置的状态机逻辑比较简单, 最后验证没有错误。

如果以后其他状态有更多的逻辑加入, 只要维护模型, 就可以验证新加入逻辑的正确性。

其他办法保证软件逻辑的正确性? 单元测试 形式化验证是个很重的过程,等于是重复编程, 而且最终效果也是取决于验证规则的编写。 在本文的写作过程中, 我感觉到TLA+其实就是基于断言的测试用例, 在实际工程直接针对不同范式准备c++的测试用例模板覆盖就可以, 没必要和用专门的语言形式化验证。 半形式化验证 另外一种实际工业生产中用的比较多的办法, 是半形式化的分析/验证, 比如说功能安全里的流程等, 举个例子: https://zhuanlan.zhihu.com/p/444381688, 这样的一个文档其实也可以保证程序逻辑的相对正确性。 定义: 形式化(formal):在完备数学概念基础上,采用具有确定语义定义并有严格语法的语言表达的规范风格。 半形式化(semiformal):采用具有确定语义定义并有严格语法的语言表达的规范风格。 非形式化(informal):采用自然语言表达的规范风格。

非形式化验证 个人观点: 完全非形式化的文档对于“Build a Safety-critical Real-time System“的帮助非常有限。 像是常见的”XX安全功能需求/定义“这样的文档:

  1. 没描述目标系统的结构 a. 不是简单的架构堆叠, 需要有接口, 而且程序严格按接口实现
  2. 没有描述技术功能实现的逻辑 a. 比如说一个监控报警项, 信号是怎么从目标系统出来的, 是怎么观测的, 针对报警做出反应行为是怎么样工作的 导致只有实际编写代码的人才能弄懂里面的细节, 其他人仅仅阅读文档无法进行分析和验证, 无法进行专业技术上的review。

(半形式化不了可能是因为程序已经走样了~

其他程序范式的验证? TODO: 状态机可以靠穷举状态验证, 那其他程序范式的验证思路?

结束 上面通过可进行形式化验证的程序范式, 保证了程序组件逻辑的确定性, 但这还不足以保证整个程序的安全, 后面我们会再针对程序内调用的外部接口做拆解。

(本篇只是记录自己的想法, 没干货) 感觉最近脑子有点开窍, 稍微领悟到了点要用Complexity Reduction的思维去解决现实复杂问题, 要收敛复杂度。 WHAT 在我们所能查阅的资料文献里, system的面向场景都是被抽象成非常简单的描述, 也可因此“Build a Safety-critical Real-time System”并不复杂, 只需要解决单一问题。 但在实际工业中, 是要先解决场景的问题, 所以往往最重要的目标是完成system需要的功能, “Safety-critical Real-time”是次要目标, 由此把很难说服采用简单化系统来解决问题, 系统问题一定是复杂的。

HOW 解决复杂问题的思路有很多, 我的想法是使用“Separation of concerns”的方法去构建安全关键实时系统。 偷懒拿一张傅立叶变换的图做解释:

我们观测到复杂问题就是最前面的红线, 没有规律。 但是如果按照“Separation of concerns”的思路讲大问题明确为N个小问题, 这步就像是做了傅立叶变换, N个小问题就像提取出的各个频率的曲线, 规律就清晰起来。

Difference 虚的讲了一堆 ———— “Separation of concerns” 用在设计软件上分层分模块可以理解, 但解问题思路到底怎么做? 和流程导向的区别 在我的想法里, 其实就是区别于用”流程分层”的方式 or 其他方式, 还是用”组成分类” 的方法。

比如说分析/验证和测试都是属于“测试”的范畴。 按”流程分层”的思路, 我们会先有个测试团队, 测试团队去研究这个流程要做什么,按照前人的经验, 比如说什么功能安全流程, 然后引入分析/验证和测试。 按”组成分类” 的方法, 我们是先关注要解决组件可靠性这个问题。 然后发现需要对逻辑进行验证, 需要对代码进行测试 。 然后把”针对这一模块的验证/测试“加入”测试的流程“。

和问题导向的区别 我们日常中还有一种开发方式就也是关注问题的, 就是问题导向 : 所谓的不停的测试, 敏捷开发, 有bug解bug, 关注bug率。 关注点分离又和他有什么区别? 区别在于, 关注点分离的对象是“关注点”, 是可以“区分”的特定目标 ———— 这样才能收敛大的问题。

拿一个程序CPU资源不足导致卡顿作为例子:

和正向设计的区别 正向设计就像是一个正确的废话。 实践中我感觉最大的问题在于: ● 找不到足有够开发经验的工程师 ○ 新方向的经验在开发过程中积累 ● 没有成熟的基础模板 ○ 有上下游依赖关系的组件, 只能串行开发而不是并行开发, 这样的开发速度基本是不可接受的

分离关注点虽然也是近似正向设计, 但区别在“拟合”设计, 而不是直接“描述”。

思路 下面用导图记录下来在我关注的技术领域上(完整系统涉及到的知识点太多), 构建安全关键系统的思路, 以后再慢慢深入每个单点拆解。

现在这个导图只是当下能认识到的问题, 希望能尽量在后续的实验中丰富枝条 ———— 不过如果过长的话, 那就证明关注点分离这个方法在这条链上不适用, 需要换一种方法。

< 本文是”探索Build a Safety-critical Real-time System最佳实践“ —— ”编程范式“ 系列文章 >

前言 最近翻了下AUTOSAR-C++的标准, 发现这个标准他不只是一个”Google C++ Style”那样的代码风格Rules, 还包含了一定的功能安全和实时系统编程的思想, 对How to Build a Safety-critical Real-time System有指导作用 —— 每个为安全关键系统开发的软件工程师都应该被普及下安全编程的概念, 就好像写后端要理解MVC设计模式一样 —— 本文借导读AUTOSAR-C++标准, 来做个安全编程范式的初始教育。

资料 AUTOSAR-C++地址: https://www.autosar.org/fileadmin/user_upload/standards/adaptive/18-10/AUTOSAR_RS_CPP14Guidelines.pdf

工具 Most of the AUTOSAR C++ rules are automatically enforceable by static analysis. 应用这个规则很重要的一点是找一个支持这个规则的静态分析工具。 网上找了下, 大部分工具都是收费的, 不过最近正好github官方的静态分析工具支持了AUTOSAR, 可以使用: https://github.com/github/codeql-coding-standards/blob/main/docs/user_manual.md

导读 规则定义 autosar给规则定义了几个属性: ● required和advisory, 也就是强制性和建议性 ● automated/partially automated/non-automated, 是否可以通过静态分析工具覆盖 ● implementation/verification/toolchain/infrastructure, 规则面向的目标

在内容上,AUTOSAR C++编程指南将规则分为包括语言无关(Language independent issues )、通用(General)、词法约定(Lexical conventions)等27个类型。

部分规则解释 语言无关问题 Rule A0-1-1 (required, implementation, automated) A project shall not contain instances of non-volatile variables being given values that are not subsequently used.

非自动化的规则 AUTOSAR C++有部分规则是不能通过静态分析工具自动执行的, 这就依赖我们在Code Review等阶段介入去审查。

/edit < 本文是”探索Build a Safety-critical Real-time System最佳实践“ —— ”实时性影响“ 系列文章 >

”实时“做一个计算机术语出现很高频, 工业标准也都在强调实时的重要,但这些都是在强调怎么做实时,而没人说为什么要做实时。 所以本文主要解答: 实时性到底对系统有什么影响?

分析的思路 首先介绍这个系列文章解释“实时性影响的思路”。

首先我们要限制好关注的具体应用类型。 对实时性有要求的应用非常多, 比如音频播放应用就是一个实时性要求非常高的领域,为了不发散, 我们限定在工业领域,分成 “事件响应” 和 “闭环控制”两种应用类型。 这两种类型的差别在于, 后者的时间变量影响输出, 而前者的输出不受时间影响, 所以两者对实时的需求会不太一样。

还有一种类型是直接使用了”延时“作为变量的类型, 采样周期的波动会影响输出的精度, 比如说imu积分, 这种情况比较容易理解,直接影响输出精度, 就不做过多的说明。 (PS: 不是所有使用deltatime的计算都受实时性影响, 这取决与使用的是测量值还是打戳)

其次明确实时的定义, 本文定义的实时分别为avg(delay), max(delay), 并不是特指 操作系统影响的latency, 也包含计算耗时, 通信耗时等, 因为这些所有的变量都是作用于delay而影响系统。

分析方式按“影响机制”, “影响计算”, “影响优化”展开, 从理论到落地。

什么是闭环控制系统 闭环控制系统的定义如下: 闭环控制系统是控制系统的一种类型。具体内容是指: 把控制系统输出量的一部分或全部,通过一定方法和装置反送回系统的输入端,然后将反馈信息与原输入信息进行比较,再将比较的结果施加于系统进行控制,避免系统偏离预定目标。闭环控制系统利用的是负反馈。 即是由信号正向通路和反馈通路构成闭合回路的自动控制系统,又称反馈控制系统

举个例子, 汽车巡航控制系统是典型的闭环控制系统: 连续处理反馈数据,调整输出, 控制汽车巡航在道路线上。 我在github上找了一个汽车巡航控制的项目: https://github.com/JunshengFu/PID-controller.git 接下来本文会以此为程序为仿真平台进行实验。

影响的机制 实验 首先我们先在上面提到的汽车巡航控制项目上做实验 avg(delay)对闭环控制系统的影响实验 以仿真为例, 在PID控制循环上加入一个time delay // default duration: 80ms std::this_thread::sleep_for(std::chrono::milliseconds(100)); time delay:0 , 车辆能比较平滑的控制

time delay:10ms , 车辆控制没有明显的变化

time delay:100ms, 车辆开始失控

可以看到随着avg(delay)增大, 控制系统越来不能达到预期的控制目标, 也就是汽车会偏离道路行驶。 max(delay)对闭环控制系统的影响实验 面我们再做个仿真实验, 看看低概率的max(delay)对闭环控制系统的影响。 测试参数(参数根据使用软实时计算机系统,比如说Linux可能会出现的情况设置): max(delay) = 500ms,frequency(max(delay)) = 0.2Hz

可以看前行的时候车辆控制很正常, 即使出现大延迟也没有迹象, 但一旦在执行较大的转弯的时候, 车辆会明显的失控。

解释 下面我们对实验里的现象作出解释。 delay如何对闭环控制系统造成影响 Time delay对控制系统的影响: https://www.youtube.com/watch?v=wouhREkqZa0&ab_channel=MATLAB 这些都是基础知识了, 我就直接下结论, delay影响了控制周期和采样周期, 由此影响闭环控制系统。 也因此我们看到实验里avg(delay)和max(delay)都造成了车辆的失控。

控制/采样周期的设置 要解释不同的avg(delay)和max(delay)对车辆影响的不同, 我们要首先看周期数值的设置。 控制周期要大于采样周期, 实践里我们可以把两者混在一起。

从原理上来说: 周期的设置要遵守采样定律 ———— 采样频率fs至少为关心的信号最高频率的2倍。

从经验公式上来说: 控制频率是系统截止频率的5到10倍。 (系统截止频率就是系统响应信号的最小频率)

从项目实践上来说: 控制频率要保证控制变量不能超出边界条件。

(灵魂画图, 中间的曲线是控制变量, 蓝色是预期拟合的对象

这个周期的设置解释了实验中的现象。 100ms的avg(delay)导致行车不稳类似绿色曲线, 延迟影响了拟合的效果, 100ms使控制周期过大偏移了最佳控制周期。 500ms的max(delay)导致转弯冲出车道类似红色曲线, 在车辆过弯的时候边界条件更小, 以及500ms导致控制周期过大, 无法及时调节。

影响的计算 根据上文的机制, 下面以巡航控制系统为例进行具体数值计算。 (本文的计算都很粗糙, 看看思路就好, 实际中具体模块的编写者要进行更精准的时延特性分析)

avg(delay)的计算, 最佳控制频率的确定 根据经验公式: 控制频率是系统截止频率的5到10倍 但巡航控制系统是离散的系统, 我们不能直接用系统带宽, 截止频率这样的概念。 这个建模不太会, 所以我这里只从软件的角度去考虑, 我们把系统的固有延迟fixed(delay)加入进来, 比如说 定位数据频率, 转向响应延迟, 力矩响应延迟, 制动响应延迟等等, 最佳控制频率是fixed(delay), 与fixed(delay)的比例越相近效果越好。

在我们的仿真器中, fixed(delay)为软件函数周期100ms —— 得avg(delay)的最佳值为100ms。 以上面的实验中, 可以容忍10ms的延迟增加, 但是到了100ms的延迟增加就和初始表现出现明显差距。

max(delay)的计算, 边界条件的确定 巡航控制系统的边界条件有两个: ● 纵向: 与前车保持距离 ● 横向: 控制在车道内

纵向上一般是需要和前车保持100米以上距离, 横向上行车道宽为3.5m。 横向的边界条件远远严格于纵向, 所以下文我们以横向来确定max(delay) 。

下面的横向计算过程比较简化, 公式为max(delay) = 车道宽/2 / (车速 * sin(转向角)) 在上文的实验中, 在36km/h的车速下, 500ms的延迟叠加明显的导致了车辆失控, 因为已经接近该车况下的max(delay)。 影响的优化 优化实时性对系统的影响主要是两种方式:

  1. 直接减少delay的数值
  2. 采用更好算法适应延迟 算法优化 算法本身可以通过预测等等方式进行补偿, 解决avg(delay)的问题, 这个超出我所涉猎的范围, 就不做过多描述了 减少延迟 要直接减少延时首先要先对延时的组成进行分离, 然后逐一解决其中的问题。 因为目前作者对如何优化的理解还不够深, 后面会通过其他实践进行细化, 这里就不多描述。

思考 通过上面的机制和计算, 思考一个问题: 现在很多机器人/自动驾驶创业公司, 并没有在实时性上投入很多工作, 项目在场地上跑的看起来也很好 —— Linux + ROS 的软件栈, 可以满足他们avg(delay)/ max(delay)的需求吗? 根据经验数据, Linux + ROS 在合理的优化下, avg(delay)至多带来50ms以内的增加, 但是max(delay)不可避免500ms+的情况出现。 从闭环控制系统角度上来看。这些机器人的运行精度不高, avg(delay)的合适值很高, 50ms带来的影响很小。 如果他们的运行环境的边界条件并不苛刻, 比如说行驶不高, 没有高速转向, 运行空间很大等等, 那max(delay)造成的影响也不会很严重。 所以Linux + ROS是满足需求的。

(但这只是从闭环控制效果的角度上来说, 是满足”能跑“的最低要求)

结束 本篇代码: https://github.com/wzyy2/my-studys/tree/master/2-realtime-control-loop/PID-controller

< 本文是”探索Build a Safety-critical Real-time System最佳实践“ —— ”实时性影响“ 系列文章 >

(PS: 本文的模型分法这只是作者自己分法, 实际工业中一种做法是把所有的组件都看待成”时间触发“, 比如闭环控制和事件响应都可以用”时间触发“描述, 叫”稀疏全局时基“, 这种做法理论更完备, 但依赖买他们全套解决方案~~ 作者认为其实这些都是设计方式又不是数学定理, 只要基于一套自洽的逻辑就可以了,摸索一个现行软件可用的模型更有可行性)

什么是事件响应系统 在控制应用程序中,事件即指事件发生。事件发生可导致一个动作或响应。例如,监视引擎的温度。温度过高时,引擎的运行速度将减慢。对于事件(例如,引擎的温度高于预先设定的值)而言,事件将导致引擎运行速度减慢。另一个范例来自生产制造过程。在生产线上,系统在检测到零件将要进行一道工序(事件)时,将获取读数或对零件执行操作(响应)。如系统在一段时间内没有检测到零件或对零件的出现进行响应,生产线将产生次品零件。 事件响应不要理解成编程范式, 比如自动驾驶系统上的感知/定位/停车路径系统也是事件响应系统, 尽管这些模块可能是data-driven, event-driven, timer-based等等编程方式 , 主要和闭环控制算法区分在于时间不影响输出事件, 只影响响应。或者另外说人话, 就是一个链路如果成环就是闭环控制, 不成环就是事件响应, 以此分类。

除了明确定义, 其次是建立简单与复杂时间响应的分类。 对于简单的事件响应系统我们比较好理解, 就是定义里的及时对事件作出响应即可, “当事件A发生, 作出反应B”。 但本文想分析的主要是复杂事件响应系统, 下面是以自动驾驶系统举例(参数设置和实际运行无关, 仅为随手设置) : 复杂响应系统里的“事件->反应“不止一条, 而且可能出现多对多的情况。

(上面的图加些解释:实线为trigger, 虚线为数据ref, 如果没频率(hz)说明, 表明为触发输入。) 整个系统模拟的代码: <TODO, 不想写了, 后面在建模的文章里补>

影响的机制 事件响应系统的实时性影响, 主要是delay作用于最终的响应。 如果这个系统是“当用户点了个赞, 显示计数加一“, 那实时性可以说对这个事件响应系统无明显影响。 如果这个系统是“当电梯速度过高时, 启动限速器“, 那实时性可对这个事件响应系统的影响, 就可以通过delay量化, 延迟越大, 电梯的刹车距离越大, 系统的性能越差。 限定关注的功能 子链路 ∈ 复杂事件响应系统 要分析复杂事件响应系统的实时性影响, 其实就是要分析多个简单事件响应系统的组合。 拿上文自动驾驶系统来说, 在里面还可以划出以下等等没有环路的链路, 通过累加可得延时: ● 相机检测障碍物, 及时变道/停车 ● 雷达检测障碍物, 及时变道/停车 ● 雷达预测障碍物轨迹, 规划变道绕开障碍物

边界条件的确定 影响 = f(delay) 确定要关注的功能后, 接下来要定义这个链路的延时影响, 求得函数f, 然后确定边界条件

影响的计算 对于事件响应系统来说, avg(delay)和 max(delay)没必要分开去看, 不像闭环控制系统那样可能作用于频域, 事件响应系统因delay造成影响在时域直接可观测。

在上面的实验中, 我们选取“雷达检测障碍物, 及时变道/停车”为例子 车速 障碍物探头距离 响应边界 急刹距离 18km/h 10m 1.4s 3m 36km/h 25m 1.3s 12m 72km/h 50m 1.25s 25m

以下是我们设置的参数计算出来的最大延时:

假设ref的数据不影响功能

延时 = 雷达周期延时(100ms) + 感知计算障碍物耗时(200ms) + 规划决策控制耗时/周期(200ms) = 500ms

如果定位ref有影响

延时定位 = 雷达周期延时(100ms) + 定位计算障碍物耗时(100ms) + 规划决策控制耗时/周期(200ms) = 400ms 延时 = max(延时感知, 延时定位) 实验跑出来的最大延时: <TODO, 不想写了, 后面在建模的文章里补>

由此计算完成, 证明我们系统的设定条件可以在(72km/h, 50m)的场景下可以完成延时需求。 但也可以看到在非实时系统和实时优化下跑这个软件, max(delay)很容易超过边界条件, 还是有安全隐患。

延时计算的规则 <TODO, 后面在建模里再细化>

影响的优化 优化事件响应系统的实时性大部分和闭环控制一样, 就不再重复表述。 不同之处一在于调度策略上, 复杂事件响应系统和闭环控制系统有不同, 后者就是考虑自身,进行优先级配置绑定cpu, 前者可能需要一套动态调节子链路到帕累托最优的调度算法。 还有一点在于事件响应系统可以引入全时算法, 比如我们上面的系统, 如果感知的障碍物识别可以在确定时间内输出基础障碍物, 在剩余执行时间内尽量输出预测轨迹, 那这个障碍物识别算法就是一个全时算法。

结束 就此我们介绍完了实时性对系统影响, 一方面是希望帮助理解实时性的具体影响, 另一方面是建立用于引入实时系统建模设计与编程的共识。

本篇代码地址: https://github.com/wzyy2/my-studys/tree/master/2-realtime-control-loop/PID-controller

< 本文是”探索Build a Safety-critical Real-time System最佳实践“ —— ”实时性影响“ 系列文章 >

前文我们介绍了实时性对系统有什么影响, 并将目标系统分成了闭环控制和事件响应两种, 现在我们以此为基础逻辑, 构造一套模型系统。

Model-Based Development 基于模型的汽车软件开发 在汽车领域,开发高效且无错误的软件是一项巨大的挑战。这些挑战随着对复杂软件算法和维护安全标准的需求而增长。为了克服这些挑战,汽车软件开发公司正在从传统的软件开发方式迁移到基于模型的软件开发 (MBSD)。MBSD是一种解决复杂嵌入式控制系统的可视化方法。 https://www.dongchedi.com/article/7101977303135273505

自动驾驶的模型开发 按照功能安全的约束要求, 理论上作为ASIL-D的L4自动驾驶系统应该是需要使用Semi-formal的建模, 也就是比如汽车软件里常见Simulink进行设计的。 (如下功能安全对设计符号的要求)

但现有的建模语言和工具主要针对传统汽车软件开发, 并不能满足自动驾驶系统软件的设计需要, 所以在自动驾驶系统实际开发中比较少进行Model-Based Development, 还是更像普通Linux行业软件的开发。

对自动驾驶链路进行建模 在自动驾驶系统上, 完全的将传统软件开发方式迁移到基于模型的软件开发, 比如说自动化生成代码, or 完全按照模型开发代码, 对单个团队来说都是不太有能力的。是这不代表不可以引入建模来部分辅助进行设计/仿真/验证等流程。 所以接下来我们从以“优化实时性”为目的出发, 对自动驾驶链路进行建模。

原型资料 以Apollo为例我们先搜集一些资料:

(上来也不用建模的很详细, 就先用这些来建立模型吧)

简化策略 在进行建模前, 我们先了解下简化策略:

在上文的原型资料中, 可以看到Percetion模块拆分后可以再细化成非常复杂的子模块, 所以为了简化建模的复杂度, 我们必须进行关注点分离。 实时性问题并不具有涌现性(多个要素组成系统后,出现了系统组成前单个要素所不具有的性质),是可以进行划分的。所以在关注整体链路时可以暂时先不关注模块内部的实现细节, 有需要再细化。 还有就是抽象, 一个框图里的模块背后的软件是复杂的, 我们尽量把模块属性和其接口抽象简化。

Modeling with Block Diagrams 从上文资料中, 可以看到自动驾驶链路都是有向图描述的, 一开始我想用XML来形式化这个有向图描述, 比如说 GraphML(https://live.yworks.com/demos/view/graphml/index.html)。 但是在找了一圈后, 我发现还是直接用python去描述, 用Block Diagram去可视化比较合适, 这样会有更多的开源工具可以利用:https://github.com/petercorke/bdsim

模型描述: 用bdsim这个工程自带的编辑器来描述模型

Block暂时直接用SUM, 也就是累加, 后面有更多的需求的时候可以自己实现Block的定义。

绘图代码:

基于建模进行实时性分析

解决问题 这个建模, 对实际工业开发有什么帮助嘛? 下面我们举几个例子。 功能验证 假设我们现在在做自动驾驶系统的功能安全, 现在我们设立了一个Safety Goal:车辆并道不发生碰撞 。 针对这个Safety Goal设计的 Safety Function:必须在1s内完成障碍物并作出制动停车 。 来自于需求计算而不是技术的定义, 比如说速度20m/s和, 根据流程, 我们现在要回来确认这个时序在技术上是可以实现的和满足要求的。 以下是功能安全手册里提供的确认方法:

根据上文已经建立的Model, 我们可以清楚的通过动态仿真判断出该功能的WCET(最坏执行时间)满足安全功能定义的时序要求。 如果没有进行Modeling, 我们只能依赖测试来确认时序达标。 考虑到时间指标的灵敏性和偶发性, 这种测试是低效率和容易掩盖问题的。

设计优化 一个全局的建模视角可以让我们进行设计上的帕累托改进。 帕累托最优(Pareto Optimality),也称为帕累托效率(Pareto efficiency),是指资源分配的一种理想状态,假定固有的一群人和可分配的资源,从一种分配状态到另一种状态的变化中,在没有使任何人境况变坏的前提下,使得至少一个人变得更好,这就是帕累托改进或帕累托最优化 如果没有一个这样的建模会怎么样? 那就是各个团队只专注于自己的部分, 比如说, 感知部分的开发者会使劲使用资源压榨耗时以获得更优秀的kpi, 而忽略了其他人需要的资源~ 这样做出的系统只能是”纳什均衡“的状态。 纳什均衡是博弈论中一种解的概念,它是指满足下面性质的策略组合:任何一位玩家在此策略组合下单方面改变自己的策略(其他玩家策略不变)都不会提高自身的收益。

< 本文是”探索Build a Safety-critical Real-time System最佳实践“ —— ”实时性影响“ 系列文章 >

前言 什么是调度器(scheduler)?调度器的作用是什么? 调度器是一个操作系统的核心部分。可以比作是CPU时间的管理员。调度器主要负责选择某些就绪的进程来执行。不同的调度器根据不同的方法挑选出最适合运行的进程。 目前Linux支持的调度器就有RT scheduler、Deadline scheduler、CFS scheduler及Idle scheduler等。我想用一系列文章呈现Linux 调度器的设计原理。

本文并不想重复介绍剖析这些调度器, 而是希望重点分析其延时特征, 以帮助进行实时系统开发的时候更好的配置调度算法。

CFS 算法公式 关于CFS的介绍, 网上的文章有很多, 自己看看即可。 除了这篇文章https://zhuanlan.zhihu.com/p/56430999里把CFS的算法用形式化的方式表达了出来, 我觉得很不错,专门摘抄一下: ● 一方面理解CFS ● 一方面利用算法形式化, 可以帮助我们理解配置参数的意义, 比如说cpu.share/cpu.cfs_quota_us/nice等 ● 另一方面还可以用于我们延时特性计算

摘抄 CFS的模型主要的改进是在round robin采用的真实运行时间片的基础上实现了加权出了一个虚拟运行时间的概念vruntime。每个线程(这里理解为系统调度的最小单位,下同)都有一个vruntime值,而不同的权重即该线程的优先级,具体算法为:

这里的权重比是nice 0 的权重和线程权重的比值,nice 0即为系统默认的线程权限,该权重默认为1024。粗暴的理解为在默认条件下,vruntime就等于该线程的实际运行时间。具体来说可以在cgroup中的键值cpu.shares找到这个值,调整这个值可以改变vruntime的计算权重。

(本文额外补充: nice和cpu.share的转换公式 )

得到了这个vruntime之后,系统将会根据每个线程的vruntime排序(实际上是基于红黑树算法,这里不展开),vruntime最小的线程则会最早获得调度。而一旦vruntime的次序发生变化,系统将尝试触发下一次调度。也就是说调度器尽可能的保证所有线程的vruntime都一致,而权重高的线程vruntime提升的慢,容易被优先调度;权重低,同样的时间上vruntime上升的快,反而容易被轮空。一个进程的vruntime可以通过/proc//sched中的se.vruntime选项查看。 这个时候,有个比较典型的例子就是当系统中存在大量vruntime相似的线程之后,类似多米诺效应,线程调度将会被过于频繁的触发,这明显不合理!于是就如何定义“vruntime触发调度”时,CFS引入了一个阈值,即如果前后两个线程vruntime保持在一个阈值之内,系统不会触发调度。而这个阈值大小就是最小的调度时间片。 vruntime>min(vruntimeothers)+threshold

(本文额外补充:sysctl_sched_min_granularity为threshold, 默认0.75ms

注意sysctl_sched_latency, 他保证了vruntime小的线程在这个时间内被至少调度到一次 我看到有人为此写了个Latency补丁, https://lwn.net/Articles/907645/, 类似可以为任务专门配置这个参数)

此外,CFS本质上是CPU运行时间导向的调度,这就有了另一个规则:对于sleep/IO这类的操作,由于相对来说并不占用过多资源,vruntime并不会被马上结算,仍会保持最初的vruntime。 跟到这里,你可能马上出现了一个头脑实验:一个线程A,初始值小但一直sleep,所以vruntime始终不增长;线程B,vruntime初始较大重始终排队等待,这种策略就变得不可理喻了。 所以系统在该线程被重新唤醒之后会重新计算vruntime,vruntime将取当期线程的vruntime和当前系统内最小vruntime-阈值这两个值中的最大值。也就是说如果当前系统中有两个及以上线程,当sleep/IO线程被唤醒之后,无论如何该线程将无法获得第一优先调度,最好的情况也要等当前最小vruntime接受调度之后再次触发调度。

另外,在cgroup的目录下还有两个跟调度有关的设定:cpu.cfs_quota_us,cpu.cfs_period_us。 这两个值确定的该线程得到调度后CPU时间的占空比。cpu.cfs_period_us是得到调度的周期,而cpu.cfs_quota_us是在这个调度周期内绝对CPU时常,单位都是微秒(us)。

(本文额外补充: CFS bandwidth control是另外的一套流程了, https://sn0rt.github.io/cfs-bandwidth-control/, 其参数的影响机制如图:

这种机制其实不是很适合限制突发性延迟敏感性的任务,我看到有人为此搞了个补丁, 可以把这个周期未使用的时间放到下个周期, https://lwn.net/Articles/844976/)

程序逻辑 理解完了算法公式, 下面我们再讲讲一些具体的程序l逻辑, 以确认代码和算法所描述的逻辑是否有出入。 如果要完整理解程序逻辑, 推荐阅读: https://s3.shizhz.me/linux-sched/cfs-sched/group-data-structure 本文主要挑和延时特征有关的讲。

CFS bandwidth control 和 CFS Weight的关系 从上文我们可以看到CFS有weight和bandwidth control两种控制CPU的方式,两者之间的关系其实相互独立的, 代码可以看https://github.com/torvalds/linux/blob/master/kernel/sched/fair.c

CFS bandwith control的逻辑如下:

dequeue_entity和enqueue_entity就是和CFS Weight进行关联的逻辑, 也就是bandwith throttle一生效, 就从调度list上删去, 如果unthrottle解除, 就加回去。 这个过程vruntime是不变的, 也就是一旦unthrottle之后, 下个时间片就会由该线程获得。

CFS的调度时机 从上文可得, CFS bandwitdh control并不直接影响线程调度, 他主要是在背后通过timer来进行队列的维护。

影响调度时机的主要是CFS Weight这套逻辑: 调度节拍(scheduler_tick) (通过CONFIG_HZ_250等配置设置周期) 定时检查任务是否需要reschedule, 然后在系统调用/异常上下文/中断上下文等切换的时候, 调用schedule进行切换。(理论上scheduler_tick的退出就会调用schedule, 因为这是中断上下文切换) (注: 以上逻辑是Kernel preemption打开的情况下)

CFS的延时特性和优化方法 根据上文的理解, 我们可得CFS的延时特性如下:

$t_{bandwith} = (period_{cfs} - quota_{cfs}) + brust_{others}$ 这个是CFS Bandwith Control带来的延迟增加, Brust是指其他受限制任务在unthroutle后抢走的时间片。

$latency_{cfs} = t_{bandwith} + \Sigma (foreach(max(threshold, min(duration_{cfs}, vruntime - vruntime_{others}))) + duration_{tick} + preempted_{others}$ ($duration_{cfs}$为sysctl_sched_latency, $threshold$为sysctl_sched_min_granularity) Preempted为其他非CFS任务(比如RT任务, 比如中断)的抢占因素, 先近似为零来考虑, 如果考虑的话CFS的Latency为∞。 有了这个延时特性公式, 我们就可以做一个结论: 延时敏感的任务不要设置CFS bandwitdh control, 平白增加延迟。

以上计算里面的$\Sigma vruntime$比较复杂,也可以简化成 $\Sigma vruntime < max(duration_{cfs}, load * thresold)$。 load为CPU运行队列长度, 这个在我们日常开发中经常有碰到类似的概念, 就是load_avg, 只不过load_avg包含了RUNNING和IO两种类型, 以及他表示的是AVG而不是 当前值。 简化后的公式如下: $latency_{cfs} < = t_{bandwith} + max(duration_{cfs}, load * thresold) + duration_{tick} + preempted_{others}$ 这样我们就把延迟计算变成可观察指标load。

但实际开发中, 我们关注的并不是$latency_{cfs}$, 而是关注一个任务的实际最坏执行时间。 $wcet_{cfs} = latency_{cfs} * (1 + \frac{t_{exec}}{weight占比 * duration_{cfs}})$ $t_{exec}$表示任务实际执行开销, 如果$t_{exec}$非常小, 则任务的的$wcet_{cfs} = latency_{cfs}$

如果$t_{exec}$很大, 就不好计算了。要如何在实际应用中优化呢? 我们可以应用一些简化方法, $wcet_{cfs} \propto {load}$ 理想情况是我们能把CPU运行队列大于1的持续时间$t_{load}$ 优化到很低的水平。 因为$when(load<=1), wcet_{cfs} = t_{exec} + latency_{cfs}$ $latency_{cfs} + t_{exec} + t_{load} > wcet_{cfs}$ (当然这种简化是不考虑在多CPU下CFS Load Balance的时间开销。)

如果CPU运行队列永远不为1怎么办? 这就不是调度算法的事情了, 下面我笼统给一个解决思路。

资源隔离是指把不关键的任务隔离开来, 比如通过cfs weight只给他0.75ms的运行空间,只影响调度Latency, 不影响执行时间, 在计算load的时候不考虑进去。 限制任务FPS是什么意思呢? 极端来说如果我的程序都是infinite loops类型的任务, 通过CFS在上一层对他们进行了资源限制, 这样$t_{load}$这个指标就失效了,CPU运行队列永远不为1。 为了简化设计, 我们必须要Avoid infinite loops, 这也是real-time programing都会要求的。

总结 通过上述计算和方法, 我们可以基于CFS调度为任务优化最坏时间了, 通过优化Load指标可以控制99分位的数据, 满足大部分软实时系统的需求。

RT 程序逻辑 RT调度的逻辑非常简单, 谁优先级高谁获得CPU资源。

调度策略 特殊的是调度分sched_rr与sched_fifo两种策略, 其运行逻辑如下: 前提线程A与线程B的总时间大于100ms(一般都是满足的),因为100ms为rt调度器的默认调度时间片(RR_TIMESLICE)。 若两个优先级相同的的rr策略的线程,这个肯定是按照时间片依次执行。 若两个优先级相同的fifo策略线程,一个执行完才能到下一个线程。 若两个优先级相同的fifo与rr共存,谁先来谁先执行,比如rr先来,rr先执行100ms,然后fifo执行直到主动退出,然后才到rr执行完成,也就是说一到fifo拿到了执行权,必须等它执行完才可以。 Real Time Scheduler Throttling sched_rt_period_us, sched_rt_runtime_us “/proc/sys/kernel/”目录下两个参数”sched_rt_period_us” 和”sched_rt_runtime_us” ,默认值分别是1000000和950000,单位是us,因此分别是1s和0.95s,意思是在1s的时间内,实时任务至多可运行0.95s,剩下的0.05s则留给普通任务。这样设计主要是为了防止万一有实时任务失心疯地占着CPU不放,至少还可以通过普通任务的运行,尝试从这种异常中恢复。

延时特性和优化方法 实时调度的时间计算就很清晰(注:计算仅限实时Linux): $latency_{rt} =\Sigma ( texec_{highprio}) + t_{slice} * num_{sameprio}$ $wcet_{rt} <= latency_{rt} + \Sigma texec_{sameprio} + t_{exec}$

只要静态配置RT任务, 优先级, 确定好每个任务的执行时间, 即可确定延迟和执行时间。

总结 通过上述计算和方法, 我们可以基于RT调度为任务优化最坏时间了, 在实时Linux上可以100%限定时序范围, 满足硬实时系统的需求。

实践上建议把RT调度和延迟敏感的CFS调度分开, 因为RT调度会影响CFS被Preempt的概率和时间。( TODO: 不确定其他CPU的trigger_load_balance() 会不会拉取被RT Preempt的CFS 任务, 如果会的话, 可能影响小点, 有待实验)

Deadline 程序逻辑 deadline调度器是一个比较少用到的调度方式, 但其实很有用, 我们这里做下了解。

就不摘抄了, 直接看链接了解: https://s3.shizhz.me/linux-sched/dl-sched/algorithm

以下我再加一些补充说明: Deadline和其他调度器的关系 SCHED_DEADLINE是优先级为0的任务, 而SCHED_RR在1-99之间,可以认为把SCHED_DEADLINE认为一种最高优先级的特殊的实时调度任务, 同样受到sched_rt_runtime_us 和 sched_rt_period_us 和silce的限制。

Deadline的应用事项 Deadline调度器最好和固定cpu频率配合, 不然这会导致runtime时间的不确定性。 还有deadline调度只是简单的调度当前deadline最近的任务, 他并不能很好的处理多任务之间的效率问题,具体的效率依赖使用者的配置。

Behavior of sched_yield 当SCHED_DEADLINE主动调用了sched_yield的时候, 比如说sleep, 他就是放弃了当前runtime的资源, 直到下一个周期, 会被马上唤醒。

延时特性和优化方法 deadline调度就是为延时优化而生, 其计算方式非常简单。 $latency_{rt} = period_{set}$ $wcet_{rt} = deadline_{set}$

实际设置值是否能满足要求, 在观测中改进即可。

总结 deadline调度器相比rt调度器, 更适合复杂实时系统, 因为可以通过在环测试修正来逼近解决问题。 (但是我没有找deadline超时的监控, 只有runtime超时的报警, 如果要做硬实时的话, 针对deadline的超时监控也需要做起来, 可能要放在应用层吧)

结束 CFS的延时优化策略可以用于软实时系统优化和资源分配, RT/DEADLINE可以用于硬实时系统的优化。 另外这些优化策略研究都可以成为实时建模的约束条件。