热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

浅谈嵌入式软件架构设计

关于嵌入

目录:

1、 嵌入式环境下软件设计的特点

2、 设计目标

3、 设计思路

4、 多进程解耦


(一)嵌入式环境下软件设计的特点

要谈嵌入式的软件架构,首先必须了解嵌入式软件设计的特点。下面的这一段摘自http://www.uml.org.cn/embeded/201906123.asp,个人觉得写的非常有道理,应该是出自一个至少有5年嵌入式软件开发经验的高级工程师之手,和大家分享下。

1 和硬件密切相关

嵌入式软件普遍对硬件有着相当的依赖性。这体现在几个方面:

1)一些功能只能通过硬件实现,软件操作硬件,驱动硬件。

2)硬件的差异/变更会对软件产生重大影响。

3)没有硬件或者硬件不完善时,软件无法运行或无法完整运行。

这些特点导致几方面的后果:

1. 软件工程师对硬件的理解和熟练程度会很大程度的决定软件的性能/稳定性等非功能性指标,而这部分一向是相对复杂的,需要资深的工程师才能保证质量。

10. 软件对硬件设计高度依赖,不能保持相对稳定,可维护性和可重用性差

11. 软件不能离开硬件单独测试和验证,往往需要和硬件验证同步进行,造成进度前松后紧,错误定位范围扩大。

针对这些问题,有几方面的解决思路:

1. 用软件实现硬件功能。选用更强大的处理器,用软件来实现部分硬件功能,不仅可以降低对硬件的依赖,在响应变化,避免对特定型号和厂商的依赖方面都很有好处。这在一些行业里已经成为了趋势。

2. 将对硬件的依赖独立成硬件抽象层,尽可能使软件的其他部分硬件无关,并可以脱离硬件运行。一方面将硬件变更甚至换件的风险控制在有限的范围内,另一方面提高软件部分的可测试性。

2. 稳定性要求高

大部分嵌入式软件都对程序的长期稳定运行有较高的要求。比如手机经常几个月开机,通讯设备则要求24*7正常运行,即使是通讯上的测试设备也要求至少正常运行8小时。为了稳定性的目标,有一些比较常用的设计手段:

1. 将不同的任务分布在独立的进程中。良好的模块化设计是关键

2. Watch Dog, Heart beat,重新启动失效的进程。

3. 完善而统一的日志系统以快速定位问题。嵌入式设备一般缺乏有力的调试器,日志系统尤其重要。

4. 将错误孤立在最小的范围内,避免错误的扩散和连锁反应。核心代码要经过充分的验证,对非核心代码,可以在监控或者沙盒中运行,避免其破坏整个系统。

举例,Symbian上的GPRS访问受不同硬件和操作系统版本影响,功能不是非常稳定。其中有一个版本上当关闭GPRS连接时一定会崩溃,而且属于known issue。将GPRS连接,HTTP协议处理,文件下载等操作独立到一个进程中,虽然每次操作完毕该进程都会崩溃,对用户却没有影响。

5. 有条件的话可以对程序进行备份


(二)设计目标

架构设计要做到以下几个点:

1、层次分明,结构清晰

2、尽可能的方便后续的功能扩展和移植。

3、实现最大限度的代码复用,也就是避免重复造轮子。

4、尽可能的达到高内聚低耦合。 

 

(三)设计思路

用分层的思想对整个架构进行规划,一般采用3到5层,层数太多会导致无用代码增多,函数调用太深,效率下降,层次太少则增加代码耦合度。我们分层的目的是使得某一层的改动最多只对上一层造成影响,而不会影响上上层。上层也无需关心下层的实现,只需要调用下层提供的API。

常用的分层:

硬件驱动层-->硬件适配层-->功能模块层-->业务逻辑层-->应用层

对应的软件架构图:

为了实现上述设计目标,有几点要求:

1.层与层之间最好不要跨层调用。

跨层调用就违背了分层设计的思想,这样做的坏处我举个例子:功能模块层跨过硬件适配层直接调用硬件驱动层的接口,某一天换了个不同厂家的芯片,驱动进行了重写,导致功能模块层直接调用的地方也要做相应的修改。

2.模块与模块尽可能各自独立,无依赖关系。

这里有一个特例,就是模块可以调用通用模块提供的api,除此之外尽量不要调用其他模块的接口。什么是通用模块,比如日志模块,其他模块可能也要打印日志,还有一些封装过后的基础机制,比如共享内存,套接字,内存分配等。

3.每一层都提供统一的接口供上层调用,模块的内外接口分明。

最常用的做法是将对外接口定义在单独的.c文件和.h头文件中,文件可以命名为模块名_API.c,模块名_API.h,接口可以命名成模块名_函数功能_API,内部接口和内部变量全部定义成staic,实现对外不可见。

这样子查看头文件就可以一目了然的知道该模块对外提供了哪些接口,在阅读代码时通过接口名字也快速可以该接口知道来自哪一个模块,提高代码可读性。

我们来对每一层做个说明:

硬件驱动层

硬件驱动层包含板载硬件资源正常运行所需的所有驱动程序并提供API给上层调用。

硬件适配层

这一层是我自己额外提出的,也可以理解为硬件驱动层的一部分。本来的话功能模块层直接调用硬件驱动层就可以了,硬件适配层的出现是为了应对多平台的情况,对多个平台提供的驱动接口进行再次封装,保持统一的对外接口。比如,每个平台的芯片操作io口的函数并不相同,硬件适配层可以将io读写抽象成:

Gpio_read(int group,int num,int *vaule)

Gpio_write(int group,int num,int value)

group表示GPIO组

num表示组内序号

Value是读到的或者是要写入的值。

这样不管底层如何改动,硬件适配层对上的接口都不会改动。

功能模块层

实现具体的功能模块,通过调用硬件适配层API实现相应功能,同时提供可调用的API给应用层。

建议在完成每个功能模块后,都输出相应的测试用例。单元测试是软件测试的最基本单位,是由开发人员执行以保证其所开发代码正确的过程。开发人员应该提交经过测试的代码。未经单元测试的代码在进入软件后,不仅发现问题后很难定位,而且通过系统测试是很难做到对代码分支的完全覆盖的。

业务逻辑层

这一层有时候可能和功能模块层合并在一起,并不是必须的。

应用层

将各个功能模块进行整合调用,完成整个产品的功能。

在目录结构上我习惯这样来组织整个工程

|--Module

|--模块1

|--模块2

|--object生成的临时文件,比如.o,.d文件

|--src .c,.cpp文件

|--inc头文件

|--lib生成的.so或者.a库

|--unit_test测试用例


(四)借助多进程解耦

对于带操作系统的程序而言,还有一个方法可以实现解耦,那便是采用多进程的方式。一些独立的功能可以考虑拆分成独立的进程,拆分的依据就要按实际情况来了。多进程的方式除了可以实现程序解耦,还有利于项目的并行开发,分配任务和后续维护也可以按进程来划分。

 

讲到这里,我们再扩展的说下多进程的好处,下面是某位前辈的语录,已经找不到出处了,按他自己的介绍,从事嵌入式软件开发已经有8、9年了。


1 模块的解耦:很多开发人员维护开发的多线程模型项目应该都多少会存在下面的问题:跨模块间的直接调用,如果不相信,好,你的项目一定是分模块的吧,现在随机的删掉一个模块,build下看能build通过吗(只需要build不需要运行),我相信大部分情况下一定会遇到某个函数调用,某个全局变量找不到的情况,这种情况说明你的模块间存在强耦合了。

 

由于多线程天然的优势,地址空间的相互可见,导致直接调用十分容易,很多经验尚浅的工程师,很容易就写出直接调用的简单粗暴的接口,如果遇到个static接口的函数,图方便也会把static去掉,直接拿过来用了。这样整个工程随着功能不断的添加,模块间的交叉越来越多,耦合越高。其实我自己偷懒的时候也这样做过。

而我之所以推崇多进程的原因就是,多进程能从物理上隔绝了这种“方便”的通讯方式,导致在想实现一个模块交互时,会多思考下这个交互是必要的吗,如果是必要的,则会进一步思考接口定义是否简单明了(因为进程间的通讯相对会麻烦些,开发人员会本着能减少交互,明确接口的想法去仔细考虑接口,协议的定义,否则折腾的是自己了),这如同人生,如果一直顺风顺水,人们可能不会想太多,思考太多,而如果道路上有些坎坷,则会有另一种感悟吧。

所以我的想法是多进程的模型会逼迫你去更多的思考想程序的设计,物理上减少模块的耦合。

抽象通用组件,分离通用功能和业务逻辑功能:当把一个多线程模型修改为多进程模型的过程中,经常会发现有些接口代码重复的出现在多个进程模块中,因为之前接口函数是在一个进程空间,大家都可以直接调用的,比如接口A被模块a,b调用,模块a,b分离为两个独立的进程后,接口A需要在a,b中分别实现了,无需解释,重复代码这个在软件工程中是大忌,必须消除。做法也很简单,将这些被多个模块调用的接口分离处理做成通用模块,供其他模块调用,当你完成这部分工作后,你发现了什么,是不是剥离的接口,可以作为整个项目的通用组件存在了。

方便定位问题:多线程模型中当又一个线程异常退出,会导致整个进程退出,当然通过一些crash信息,可以定位是哪个线程死掉。但如果这些线程模块是由多个小组、人员维护,当整个进程崩溃掉后,如何判断由哪个小组解决,会是一个大的问题。而且有时还会出现的现象是挂在一个线程,但其实是另外一个线程模块引起的(耦合的祸端),遇到这种情况,难免出现小组间的扯皮,推诿。(自信自私的工程师都认为我的代码没有问题)。

而如果采用多进程的模型,好吧,你的服务进程挂了,你自己找原因吧,没什么可争辩的了。

3 方便性能测试:多线程种单个线程的资源占用不是很好查看(至少有些嵌入式系统没有完善的命令),当整个进程资源消耗很高时,如何判断定位时哪个模块线程的问题,同前边问题一样难以抉择。而如果是多进程的模型,谁的进程占了好多资源,谁就去查下吧,其实这个还是个颗粒度的问题。同样的系统,划分成多个进程,复杂度一定比只有一个进程的复杂度低的多,复杂度降低,也就更容易定位查找各种问题。

 

每一句讲的都很有道理,如果你不能理解,只能说明你的水平还不够。所谓的年少不知曲中意,再听已是曲中人。

 

各位看官,喜欢的话点个在看吧。

 

 




推荐阅读
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • mysql-cluster集群sql节点高可用keepalived的故障处理过程
    本文描述了mysql-cluster集群sql节点高可用keepalived的故障处理过程,包括故障发生时间、故障描述、故障分析等内容。根据keepalived的日志分析,发现bogus VRRP packet received on eth0 !!!等错误信息,进而导致vip地址失效,使得mysql-cluster的api无法访问。针对这个问题,本文提供了相应的解决方案。 ... [详细]
  • mac php错误日志配置方法及错误级别修改
    本文介绍了在mac环境下配置php错误日志的方法,包括修改php.ini文件和httpd.conf文件的操作步骤。同时还介绍了如何修改错误级别,以及相应的错误级别参考链接。 ... [详细]
  • Linux下安装免费杀毒软件ClamAV及使用方法
    本文介绍了在Linux系统下安装免费杀毒软件ClamAV的方法,并提供了使用该软件更新病毒库和进行病毒扫描的指令参数。同时还提供了官方安装文档和下载地址。 ... [详细]
  • C#多线程解决界面卡死问题的完美解决方案
    当界面需要在程序运行中不断更新数据时,使用多线程可以解决界面卡死的问题。一个主线程创建界面,使用一个子线程执行程序并更新主界面,可以避免卡死现象。本文分享了一个例子,供大家参考。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
author-avatar
蓬从蓉Tahirah
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有