项目的软件总结

本来想写一些架构性的东西,但考虑来考虑去,并不适合发表在CSDN上,因为和公司的业务高度相关。具体的小技巧又和硬件平台高度相关,写起来,共鸣的少。考虑来考虑去,把一些项目中共性的东西抽取出来总结在这里。希望对大家有帮助。也希望高手不吝赐教,指点迷津。

总结一下实践中我们嵌入式项目中的移植性如何权衡如何保证。

移植性实际上会影响软件的整体架构,很难权衡在整体架构中的多少。移植性有至少有两个层面,a.硬件更换,CPU或者外部的器件更换(硬件方面);b.硬件不更换,操作系统更换或者中间件更换(软件方面)。c.工具链方面。 如果没有硬件上的更换,实际上是不用考虑a方面的问题。一般不考虑a的都是一些小型的项目。但就这类项目而言,外部器件的更换也是有可能的。所以,完全不考虑是不现实的。对于b,是属于软件层面的移植性,这类一般都是大型项目所需要考虑的问题。小型项目其实有时候也是要考虑的。比如说,公司投入一个新项目,总想这这个新项目的代码能被下一个项目所使用(部分),通过不断的完善,形成一个代码库。这样的话,这个库虽然可能是个小型项目,但是硬件的更换和软件的更换都是必须考虑的。所以这种小项目设计起来,一点不比大项目轻松。

硬件的更换有两种,1是cpu更换,这会影响到内存、指令集和中断。外设的更换,会影响到软件的控制方式,也就是设备的抽象方式。如果最初抽象的不合理,对后期的移植将会带来很大的困难。在实际的项目中,可以参考OS的设计,将C语言的代码最大话,而汇编指令最小化。如果一些要求争分夺秒的驱动或设备也需要用汇编写,有时候需要。可以反复的测试,找出关键的函数,只是将关键部分汇编化,没必要全部驱动都是用汇编语言写。

中断,中断实际上可以看作一个中断控制器的驱动,抽象成无差别的外设设备。只是和CPU高度相关而已,这样就和外部设备统一在一起。

内存的移植,内存的移植多半和工具链相关,比如说内存的映射,ARM官方的软件和链接脚本和gcc的链接脚本就不一样。IAR EWARM的链接脚本都不一样。所以,这个移植和工具链、加载方法、CPU等都有关系,暂时还没什么好办法去统一。只能是一个项目一个内存映射。不过,好消息是,一般来说都不会太复杂,链接脚本修改起来很简单。同一工具链下都还比较简单。不过,对于一个新CPU内存的理解依然是个比较大的挑战。

外设的驱动更换,只能是通过比较好的软件架构来抗衡。除此之外别无他法。我曾经做过一个40W行标准C/c++的项目,带着5~6个人Coding,我天真的以为团队可以想一个新的轻量级的结构,应对嵌入式对性能的最大渴求。但实际上,1年过去了,2年过去了。再回头看看,实际上这种想法有点不切实际。硬件的种类繁多,控制方式也千差万别。所以,对于一个小型的团队,或者对于一个个人,创造一个全新的架构是不现实的。 最好的办法莫过于借鉴一些成熟稳定的软件的架构。但这些架构隐匿于复杂庞大的软件代码中,没有熟悉的人很难剥离出来。对于一般的简单的前后台系统,参考一些设计精良的RTOS的驱动结构是最好的办法。一般来说,真正的rtos代码都少,很容易剥离其驱动结构,甚至是照搬。这样做得最大好处是,RTOS下全部既有的驱动全部可以复用。并且有很高的代码质量。也不用担心兼容问题,有点历史的rtos都发展了很多年,如rtems就有十几年了,受过很多大型项目的考验,所以,其代码驱动架构已经是稳定的。

一旦外设代码,甚至CPU代码都驱动化,必然带来个问题,那就是业务层代码也必然模块化了,业务层的代码如何保证其移植性。前后台系统和带有操作系统的处理完全不一样,前后台系统缺少操作系统的支撑,没有进程和线程的通讯支持,可能使用共享变量的办法。这使得各个模块之间的耦合度变高。变得难以移植,随着前后台系统的复杂化,耦合度越来越高,调试和移植都变成一个复杂而又漫长的过程。对于这种,可以借鉴一些RTOS的思路,将一些变量使用inline函数封装起来,变成诸如信号量或者消息队列等等的形式,不用每次开中断关中断那么复杂,只有中断与前台之间共享时,前台才需要关中断。简单的抽象一般就可以使用了。复杂点的,也有。如果真的想保持高度的可移植性,将之间的通讯想成一个硬件外设(诸如双口RAM或文件等),针对这个外设写一个驱动。 那么,自然就解决了模块间的耦合问题。就算移植到操作系统上,将这个底层驱动一改,也不必担心与操作系统的兼容性问题。

对于操作系统上的代码的可移植性问题,更复杂一些。有了操作系统的支撑,面对各种各样的功能和接口,如何选择。如rtems的posix、itron和一些原生接口等,选择这些是非常困难的。一旦考虑需要将应用程序对操作系统或中间件可移植,那么也意味着,越通用越好。如rtems下的posix相比itron和原生接口就通用多了。几乎是个像样的操作系统都支持。但是有时候并不是这么简单,多利用操作系统的自身特性可以提高软件的稳定性,封装的层数越多,调试起来越困难。所以,实际中可以直接采用posix接口直接撰写。或者采用另外一个思路,自己仿照posix撰写一套接口,简单的调用系统接口就行了。

使用操作系统也意味着,产品的一致性和延续性得到了更多的保证,应多采用操作系统保证的东西。如读写文件自己开一个缓冲区增加访问呢的速度和效率。我并不这么认为。自己开缓冲区,多个系统读写时一定要使用信号量同步,很糟糕的事情。因为这个编程并不是一个简单的事情。我更倾向于,选择合适的文件系统,扩大文件系统自己的读写缓冲区,增加读写的效率,其效率让操作系统保证。模块间的通讯也有很多办法,但最糟糕的办法莫过于自己在操作系统下写一个消息队列去通讯。很少有这样的必要,更倾向于使用操作系统提供的消息队列,如果操作系统的消息队列不能满足要求,也是尽量的使用操作系统的消息队列+共享内存的办法。我曾经做了一个项目,需要大概256个消息队列通讯,每个消息队列中都有大量的数据流转。并且每个数据包的大小并不一样,对操作系统来说这也是小菜一碟,但有一个问题,不得不考虑:太多的内存申请释放,内存碎片又得不到有效的回收,会影响整个系统的长时间稳定运行。所以,我采用了消息队列+共享内存的方式。消息队列只是每个数据的标准信息头,是固定长度的数据。大量的变长数据都放在共享内存中,这样,不会产生大量的碎片也不会因为自己撰写同步机制造成难以调试的错误。操作系统的消息队列和共享内存是比较好移植的(尤其是共享内存)。

工具链的移植性实际上是看自己对标准c/c++的熟悉程度和工具链的熟悉程度。尽量采用标准c/c++的代码去撰写程序,而不要使用工具链的扩展特性,尽管有时候这样方便。比如说尽量使用标准的编译指令#pragma pack(push, 1) 和#pragma pack(pop)来对齐,少用各个工具自己的编译指令。又如,IAR下的 __little_endian、__irq、__arm等关键字。嵌入式里这种东西可多了,要多多阅读工具链的文档,找出差别。

一些小经验,总结总结。欢迎朋友们拍砖灌水。



你可能感兴趣的:(汇编,中间件,脚本,嵌入式,工具,通讯)