vsomeip porting 札记

6 篇文章 27 订阅

​今天整理和分享一下把vsomeip分别porting到Android和QNX的过程,以及一些通过porting对vsomeip作的探索和基于vsomeip怎么实现架构的思考。

当需要移植一个开源库到某个平台时,我们首先会做的一件事情是什么?当然是熟练打开搜索网页,输入关键字“xxx”(库的名称)+“xxx”(目标平台的名称),按下回车,一顿操作猛如虎,总能搞出个七七八八。我一般也是这么干的,但最近发现吧,这个方法真是越来越不好使了,虽然我的问题并不罕见,事实上我经常搜到跟我有一样问题的评论,但我想找的答案确实越来越难找了。比如我想要的是不仅仅把vsomeip移植到Android上,更想实现把vsomeip的C++接口封装成Java接口。我找了很久也没找到一个能直接用的示例。幸运的是,还是找到了两个porting成功的HelloWorld,把它俩跑通了,至少可以解决把vsomeip和CommonAPI-SomeIP移植到Android上的问题。

但这两个HelloWorld,其实没有对vsomeip作任何JNI接口封装,所以完全没办法给我什么参考。什么是我想做的?我的实现目标是封装一个Jar包,提供vsomeip的Java接口,即调用这个Jar包的API,可以实现类似快速上手 vsomeip中服务端和客户端之间的SOME/IP通信。把Github快翻了个底朝天,也没找到一个vsomeip的JNI封装例子,只好硬着头皮自己做了。

实现从C++接口到Java接口,需要用到NDK相关技术。其实,这是我第一次做NDK开发。在我过去的认知中,其实不太喜欢JNI接口的长相,意想不到的是,如今较新版本的Android Studio,对C++和CMake的支持可以说是非常不错了,NDK开发的体验并不差,连JNI接口的显示也优化了。整个JNI->Jar->Demo的开发过程,竟然比想象中顺利很多。需要学习的一块新知识是关于C++和Java之间相互调来调去时如何做数据转换,C++回调Java时如何找类找方法,说白了就是如何调用JNI提供的一套API,而这些问题,基本上有一些普遍适用的解决方法,通过搜索关键字都能找到答案。除了这块,C++还是C++,Java还是Java,该怎么开发还是怎么开发,并没有什么特别。

开发Jar包的过程中,要对vsomeip的接口能力进行挖掘,以便能够开放出vsomeip完整的功能。于是,我研究了vsomeip的源码结构和其中application模块的接口。下图是vsomeip源码的整体框架,可以看到,application模块是vsomeip的核心所在,由它来调度vsomeip的各个模块,如configuration(配置)、endpoints(端点,即网络连接)、routing(消息路由)、message(消息封装和解析)。事实也确实如此,回顾我们在写代码时第一步要做的,就是先拿到一个application的智能指针,接下来几乎所有的操作不都是用它去调接口来实现的嘛。其实在当前版本(3.1.20,2020.12Release)的vsomeip里,除了图中这些模块,还有e2e_protection、security等新模块,文档没有体现,开源项目多少会有点这样的问题,文档可能滞后于代码。

vsomeip overview,摘自源码Document目录EA文档

application模块的类图和源码已经不怎么一致了,对照application.hpp,我画了类图,便于对比,官方文档的类图我也摘了出来,见下图。如果不是画了类图,我也不会了解到实际源码和文档竟有这么大的差异,难怪vsomeip_v3和vsomeip要用不同的命名空间区分,头文件和动态库的名字也是区分开的。其实做兼容,不是全揉到一起才是好的设计,有时作为使用者是有必要知道自己正在使用的是哪个版本的接口吧,所谓对上层透明,与底层解耦,并不一定就显得多么高级。只要你的应用场景和底层通信本身就有着千丝万缕的联系,那从某种程度来说,绝对的解耦是不现实的。

vsomeip_v3::application 类图 (自制)

 

vsomeip::application 类图,摘自官方EA文档

一边画类图,一边看源码里关于每个接口的注释说明,我会想,为什么vsomeip_v3要加这几个接口,要改那几个接口,要设计得和原来不一样,是原来的接口有什么缺欠吗?这些改动和新增,意义是什么?我的Jar包怎么设计?如果我的Jar包还是封装了原来那几个主要的接口,是不是也就没有发挥出vsomeip_v3的优势?那么,所谓的优势又是什么?是能解决什么更复杂的应用场景?还是性能方面表现得更好?还是说新增了什么可配置的安全策略?而这些优势,是不是我的上层应用们正迫切需要的呢?对我的架构设计来说,是多此一举,还是锦上添花?

写这篇文章的时候,还没有想好这些问题的答案,这关乎到我的Jar包最终会做成什么样子。第一版提交,过于粗糙,几乎谈不上什么设计,只是实现了基础需求,做了一个DemoApp验证porting的结果。那么接下来,我希望可以设计和实现一些更高阶的需求,比如接口具有通用性、可扩展性、同步调用机制、异步回调机制、消息队列、异常捕获等。我决定把这个Jar包的实现,作为一个开源项目放在Github上进行长期的开发和迭代,目标是实现一套基于vsomeip通用的Java接口封装。为什么想做这个?

根据GENIVI官网的说法,应该不会再开发Java语言的vsomeip了,那就意味着Android中的应用也好,服务也罢,是无法直接调用vsomeip接口的。目前,多数车载娱乐系统采用的操作系统基本上是Android。如果用SOME/IP作为SOA通信协议。想象一下好了,如果对Android的某个服务或应用来说,要接入SOME/IP,看着就不太好弄的样子,总要先了解SOME/IP协议和机制吧,然后还要研究vsomeip接口怎么用吧,最后再C++->Java跨语言开发一波,可想而知,让每个服务都这么走一遍的阻力会有多大,在设计上也是不合理的,每个服务应该专注于实现自身的业务逻辑,而不是要花很大的力气才能适配到某个平台。这时我们还会想到另一个办法,只做一个中间转发服务不就行了吗,让它去和外部ECU以SOME/IP通信交互,别的服务和它之间只要做进程间的通信就行了,至于具体什么IPC方式,Android里的选择那可太多了。

乍看,一点没毛病,与其把所有服务都卷进来,不如就让一个服务来承受这一切,万一搞砸了,那也就一个服务的工作量白费,怎么看都是划算的。所以现实中,我们其实经常看到这样的解决方案。但是,这样做带来最主要的问题是什么?这个中转服务,几乎不可能在最初就把所有服务的所有接口都实现好,第一数量极其庞大,第二当下没有需求驱动,第三容易导致负荷过大等问题,性能堪忧。这意味着,它能提供的服务能力和接口必然是有限的。如果在以前,需求变更不频繁,确实没什么问题,但是时代不同了,唯一不变的是变化,一旦出现了新需求,现有接口无法满足,新增接口就是家常便饭,同时它的上下游也都要参与对接和联调,整个过程所需要的各种耗费是不容忽视的。然而,所谓的“新接口”可能是某个服务原本早就具备的能力,只是它没有开放出来,缺的是让每个服务能够把自身有价值的能力和接口很好地开放出来的平台架构。这是理解为什么要服务化的关键。

我想从这个Jar包开始,首先实现Android域中SOME/IP底层通信的基础设施,进而探索如何从技术层面去解决SOA服务化的难点。只有服务化做好了,SOA才可能发挥其优势,不然就算每个ECU都通上了SOME/IP,也只是换个协议的事罢了。如果你了解有类似的开源项目,或者技术上的想法和建议,都欢迎在后台留言给我~

如果说porting到Android的过程是意料之外的顺利,那么porting到QNX的过程就是意料之中的艰辛。众所周知,QNX是商业的操作系统,网上资料非常有限,很少有完美支持QNX的开源库,porting任何一个稍许复杂点儿的开源库都不会特别顺利,有时不但需要修改编译脚本,甚至要修改源码才可以,比如vsomeip就是如此。在vsomeip的issue里搜到这样一个问题:

得益于此,我找到了vsomeip2在QNX上成功porting的项目链接。感谢这位外国大牛的开源,把porting的步骤写得这么详细,我很快把vsomeip-2.5.2和vsomeip-2.6.1都编译出来了。但毕竟2.6.1感觉还是稍旧了些。于是,我想试一试交叉编译最新的3.1.20,我觉得这事儿一定不简单,不然怎么到现在那个问题底下还没点解答。我的思路是这样的,既然vsomeip2能编成功,那么先研究一下vsomeip2都改了哪些地方,是怎么改的。用了“对比大法”,发现vsomeip2改了的地方,vsomeip3基本上也要改。endpoints是修改的重点模块,主要做法是通过预编译宏,剔除netlink和credentials相关的调用,因为QNX不支持像Linux一样透出netlink接口,并且setsockopt不支持SO_PASSCRED这个选项。vsomeip3代码明显比vsomeip2多,要改的地方也多一些。改的时候要有耐心,注意别把不用改的地方也一起改了。endpoints模块编过了以后,有几个模块不用改也可以直接编过,但到了routing模块又开始作妖,分析原因在于uid_t和gid_t在QNX中的类型定义和Linux不同,导致各种右值引用、类型不匹配等错误,这是新的security模块用到的,所以vsomeip2不会有此问题,最后我是通过在primitive_type.hpp中将其定义为uint32_t解决了问题。再后面的编译也开始顺畅了起来。

看我说得还挺轻巧,其实花了一整个周末的时间。反复捣鼓,报错,查问题,改完了试。编译成功了以后,又做了两次复盘。第一次复盘时,发现改了不用改的地方,第二次力求只改必须改的。上面总结的几个主要修改和问题原因,都是在第二次复盘时得出的,前面看问题还来不及,哪有心情去归纳。所以,学会复盘是非常重要的。其实还有很多可以去完善的地方,比如boost版本较老,直接用了大牛porting成功的1.55,为了控制变量嘛,如果两个版本都升级了,出了问题两个库都要查,现在vsomeip编过了,可以再升级boost到较新版本;boost只编了静态库,这样编出来的vsomeip动态库会比较大,竟达到了55M,可以改成动态库试试;图省事,直接用了大牛的patch和.cmake,有时间还是要自己写一下。我也fork了vsomeip,再把改好的放到了Github上,后面还可以持续改进,希望以后能作为那个issue的一个解答。

之前一直想找个时间好好研究一下vsomeip的源码,回头也写个“深度剖析”系列什么的,像“libevent源码深度剖析”一样。后来觉得吧,vsomeip和libevent还是不太一样的,它主要是基于boost实现的,应用的成分更多,深度剖析原理的价值可能没有很高。以前觉得porting不就是解决一些编译错误的过程,其实不一定的,对于vsomeip这种需要改到源码的porting,实际上,顺便也熟悉了一遍源码,有的模块甚至扫了不止一遍。有一个小点吧,诸如CMake的语法,Shell的语法,项目的构建等这些知识,通过看书学习,我反正觉得挺枯燥的,基本记不住,更谈不上实际应用。但在cmake报错时,就不得不去研究别人是怎么写的,有一定代码量的开源库的编译脚本,一般都会写得非常有条理,各种条件的判断和依赖等表达,都可以学到。模仿,对我这种庸人来说,真是性价比最高的学习方法了。

之所以没有把一步步都怎么做的,写得非常详细,主要在当时,一门心思只想着怎么才能编译成功,刷刷的报错根本没心情去想给写公众号留点素材啥的,然后还是那句话,绝知此事要躬行,自己做一遍和看别人做一遍,天差地别。所谓“札记”,分享思路为主哈,水平有限,如有错误,烦请指教。

提到的几个开源项目,参考的和我写的,回复“porting”,有本篇全部的链接。不是什么牛逼的代码,仅作为分享和交流,极其简陋,改善空间相当大,各路大牛大神们有什么绝妙代码想提PR的热烈欢迎,时刻准备着接受diss。

我们下篇再见~

 

  • 0
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值