嵌入式架构师成长之路--架构设计

本文探讨了嵌入式环境下软件设计的关键特点,如资源受限、实时性、可靠性等,并强调分层设计(包括硬件驱动层、硬件适配层、功能模块层等)以及多进程解耦的重要性,以提高代码的可维护性和性能。设计目标强调层次分明、模块独立和代码复用,通过实例解析了各层次的功能和接口设计原则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

嵌入式软件设计具有以下几个特点:

  1. 资源受限:嵌入式系统通常具有有限的资源,包括处理器性能、内存容量、存储空间等。因此,在设计嵌入式软件时需要充分考虑资源的限制,并进行有效的资源管理。
  2. 实时性要求:很多嵌入式系统需要满足实时性要求,即能够在特定的时间范围内响应和处理输入。实时性要求需要在软件设计中考虑任务的优先级、调度算法、中断处理等,以确保系统能够及时响应外部事件。
  3. 可靠性要求:嵌入式系统通常用于一些关键应用领域,例如医疗设备、航空航天等,因此对可靠性的要求较高。在设计嵌入式软件时,需要考虑如何防止系统崩溃、如何处理错误和异常情况,以提高系统的可靠性。
  4. 硬件相关:嵌入式软件需要与硬件进行交互,包括读取传感器数据、控制执行器、操作外设等。因此,在设计嵌入式软件时需要考虑如何与硬件进行有效的接口设计和通信。
  5. 低功耗设计:嵌入式系统通常需要在有限的电源供应下运行,因此需要进行低功耗设计。这包括优化算法、降低处理器频率、合理管理设备的休眠和唤醒等方面。
  6. 可移植性:嵌入式软件可能需要在不同的硬件平台上运行,因此需要具备一定的可移植性。可移植性设计可以使软件更易于在不同的硬件平台上移植和重用。
  7. 安全性要求:随着物联网的发展,嵌入式系统面临着越来越多的安全威胁。因此,在设计嵌入式软件时需要考虑安全性要求,包括数据加密、身份验证、安全通信等方面。

总之,嵌入式软件设计具有资源受限、实时性要求、可靠性要求、硬件接口、低功耗设计、可移植性和安全性要求等特点。设计人员需要综合考虑这些特点,以满足嵌入式系统的需求并提高系统的性能和可靠性。

2 设计目标

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

1、层次分明,结构清晰

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

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

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

3 设计思路

3.1 分层设计

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

常用的分层:

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

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

1.最好不要跨层调用。

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

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

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

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

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

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

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

硬件驱动层

硬件驱动层包含板载硬件资源正常运行所需的所有驱动程序并提供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给应用层。

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

业务逻辑层

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

应用层

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

3.2 多进程解耦

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

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

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

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

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

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

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值