我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。
老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师:
屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节能减排。
无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事.而不是让内心的烦躁、焦虑、毁掉你本就不多的热情和定力。
时间不知不觉中,快要来到夏初。一年又过去了一大半,成年人的时间是真的不经过。
本文主要分享电子电气架构 —SOMEIP/SD初入门。
1、 前言
为什么需要someip
简单介绍
巨大例子
2、环境构建 - linux 端
cmake
boost 库
vsomeip
路由配置
3、 协议介绍
some/ip 所属层级
什么是someip
Event
Method
Fields
someip 服务内容以及通信方式 举例
Method
Event
field
4、SOMEIP
报文格式
序列化
传输协议绑定 (RPC)
5、 SOMEIP-SD
报文格式
SD 状态机
6、 实际报文解读
一、为什么需要someip
why 1
传统的 signal base 比如CAN 通讯,以及不足以应付复杂的数据以及控制
高性能,高算力的只能ECU 进入汽车
以太网组成的新的网络架构
复杂的服务接口,方法,事件,通过以太网传输
why 2
需要添加新功能到整车
集成新的功能最小的改变已有的功能
让车更高度的连接数字世界
someip在autosar中的位置
someip 只是一个中间件吗?
合适方便将来的网络架构
可描述,可规范
可测
可以传输autosar 定义的pdu
有五个ECU 分别是
图片
head
vehicle
front camera
radar 1
radar 2
后面我们尽量根据这张图,来做一下实例 (maybe not 哈哈)
二、 协议介绍
首先我们看一下someip在autosar中的位置
COM协议栈的最上层,也就是说和 DCM, COM 等等属于一个层级关系。
从上下游关系属于PDUR之上如下图
对比一下传统的互联网 7层,5层架构来说,属于最上层的应用层。
有了这个最初始的认识,我们就可以从 someip 应该经过什么,不应该经过什么的角度去思考,去继续往下看。
“SOME/IO”: “Scalable service-Oriented MiddlewarE ove IP”
SOMEIP 就是在传输层之上的通讯协议。
someip 是一种汽车嵌入式的通讯协议,主要用于控制报文通讯,支持下列功能。
远程服务调用
服务发布/订阅
序列化
服务发现
UDP 报文分段
someip 通过网络提供面向服务的通信
SOME/IP 传输的主体是 Client Server, 传输的内容是 service (服务).
Event
Method
可供客户端发布RPC, RPC 运行在服务器上。Method 可以是一个被调用的 方法,程序,函数,子程序等。
method 有两种方式。
Request/Response(RR)和Fire&Forget(FF),Request/Response即请求/响应,是最常见的,
Client(客户端)发送请求,
Server(服务器)回复响应,属于同步调用,对应的报文类型为0x00 REQUEST(报文类型在上篇文章中有介绍)和0x80 RESPONSE。
Fire&Forget,可以直译成点火即忘,触发了但不在乎结果,Client发送请求,但不需要服务器的响应,属于异步调用,对应的报文类型为0x01 REQUEST_NO_RETURN
RPC 是什么
RPC(Remote Procedure Call)远程过程调用协议。一个通俗的描述是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样。
Fields
表示状态信息,包含有效值。下面三种元素至少存在一种
notifier
发生改变时从服务器向客户端发送数据。这一条 notifier 发送event message 也属于事件通知类服务
getter
可以被客户端调用来获取服务器上的值,即:允许对字段进行 读 访问请求/响应调用
setter
可以被客户端调用来改边服务器上的值,即:允许对字段进行 写 访问请求/响应调用
Field,可以用来表示具体的“属性”,比如状态、模式或者数值等。Field是一个合集,里面包含了Getter、Setter和Notifier三种方式,Client可以用Getter去主动获取,可以用Setter去设置,也支持当满足一定触发条件时,Server主动发出通知,即Notifer。定义Filed时,可以选择只包含Getter、Setter还是Notifer,或者包含其中两个以及全部。
someip 服务内容以及通信方式 举例
Method
request/response
图片
client 端发送一个RPC 请求, 请求的method 是 乘法, 并且给了输入的参数是2 和 6, 这时候 server 收到 就会回复 计算结果 和 方法本身。流程如下
fire Forget
图片
client 端发送一个RPC 请求,请求的方法是关闭空调,但是这里不要求回复,所以server端有没有关闭空调,其实是不知道的。或者是设计要求也无所谓知道不知道。流程如下
图片
2.3.2 Event
Event 即可以理解为有情况,通知我一下。
图片
提莫队长正在待命,对的,有情况,我会通知你的。所以首先要订阅一下event本身。这里就有event group的概念。
Event Group 是在 someip-SD 协议里面的内容,Event 是在Someip 协议里面的内容,这个我们需要知道。
在autosar 中,SD 是介于socket 和 BswM 之间的内容。
关系如下
回到例子本身
订阅一个事件组,有人跑路了给我说。
上面就监控了,发现张三跑路了,就通知一下。刘三跑路了,也通知一下。流程如下
field
这里面有订阅主动通知机制,
也有主动去设置机制,
还有主动获取某个数值的机制。流程如下
分别为setter, getter, notifier
SOMEIP
报文格式
someip 的报文格式 摘自autosar 官方文档
一般来说我们会用E2E 来对someip的数据进行保护。那么 带有E2E 的报文格式呢?
在应用 E2E 通信保护的情况下,E2E Header 将置于返回代码之后,具体取决于为 E2E Header 选择的偏移值。默认的偏移值是 64 位,它将 E2E Header 恰好放在返回代码和有效载荷之间。
有了整体对报文格式的认识,我们来进一步对每一个字段进行认识。
Message Id
前面我们说到,SOME/IP 传输的主体是 Client Server, 传输的内容是 service (服务)。
Message ID 是一个32位的标识符,用于识别 RPC 调用程序的method 或者 识别 Event.
所以传输过程中,是必须有这个service ID 存在的。
Message ID Header Field 结构应分为 16 Bit Service ID Header Field(用于区分多达 216 种服务)和 16 bit Method ID Header Field,用于区分多达 216
通常的做法是,建议将 Method ID 的空间划分为方法(Methods)和事件/通知(Events/Notifications)。方法的范围为 0x0000-0x7FFF(Method-ID 的第一位为 0),事件/通知的范围为 0x8000-0x8FFF(Method-ID 的第一位为 1)如下图
Eventgroup 是服务内部字段的事件和通知事件的逻辑分组,以便于订阅。Eventgroup 至少应包含一个 event。
Event 以及 Field 通知(notifier) 至少要映射到一个EventGroup 中。
Length
Length Field 包含以字节为单位的长度,从请求 ID/Client ID 开始,直到 SOME/IP 消息结束。
Request ID
从之前的例子,Client 端请求一个乘法的method. 但是实际情况下, Client 可能会请求很多次 乘法。
那么这个请求就需要被区别开,当然response , 也需要被client 识别到,这次response 是属于哪一次 request.
这里面的Request ID 就可以解决这个问题。其格式如下。请求 ID 应由 客户端 ID 和会话 ID 构成。
请求 ID 允许 Server 和 Client 区分同一 Method、Getter 或 Setter 的多个并行使用。
对于请求-响应 ,请求 ID 应是唯一的,以区分同一 Method 的多次调用
在生成响应信息时,Provider 应将请求 ID 从请求复制到响应信息。
在响应到达或预计不再到达(超时)之前,请求 ID 不得重复使用。
这意味着 ECU 的实现者可以根据自己的实现需要定义 Client-ID ,而 Provider 不需要知道这些布局或定义,因为他只需在响应中复制完整的 Request-ID 即可。
客户端 ID 也应通过可配置的前缀或固定值(例如 客户端 ID 的最重要字节是诊断地址或为特定应用配置的 客户端 ID/SW-C)支持在整个车辆中的唯一性。
要不然画一张图解释一下吧。
正常请求(信息类型 0x00)如无错误发生,应由响应(信息类型 0x80 ) 回 复 。如 果 出 现 错 误 , 则 应 发 送 错 误 信 息 ( 信 息 类 型 0x81 ) 。
发送没有响应信息的请求(信息类型 0x01)
对于通过通知更新值,存在一个回调接口(消息类型 0x02)
消息类型(=0x20)的第 3 个最高位称为 TP-Flag,应设为 1,表 示 当 前 SOME/IP 消 息 是 一 个 段 。报 文 类 型 的 其 他 位 按 节 说 明 设 置.
信息类型请求 (0x00) 的段具有信息类型 (0x20),信息类型响应 (0x80) 的段具有信息类型 (0xa0),以此类推。
SOME/IP 的大型 UDP 消息
仔细掰扯一下这个内容。
UDP 与 SOME/IP 的绑定只能传输直接适合 IP 数据包的 SOME/IP 信息。如果较大的SOME/IP 信息需要通过 UDP 传输(例如 32 KB),则应使用 SOME/IP 传输协议(SOME/IP-TP) 。SOME/IP 报文太大,无法直接用 UDP 绑定传输,应称为 " original " SOME/IP 报文。在 SOME/IP-TP 报文中传输的原始 " pieces " 报文有效载荷应称为 " segments "。
只有当需要传输的数据块非常大(> 1400 Bytes),并且在出现错误时没有硬延迟需求时,才使用 TCP 。
这里面的会话ID ,SOME/IP 使用 SOME/IP-TP 的消息应激活会话处理(会话 ID 对于原始消息必须是唯一的)
所有 SOME/IP-TP 段都应携带原始信息的会话 ID;因此,它们都具有相同的 Session-ID。
这样在接收端,能把这所有一样的ID 的内容,组在一起。
那么接收方肯定需要知道现在的数据是不是TP 的数据。那么
SOME/IP-TP 段应将 TP-Flag 的信息类型设为 1。
SOME/IP-TP 网段应在 SOME/IP Header 之后(即在 SOME/IP 有效载荷之前)有一个 TP Header,其结构如下(位数从高到低)
这意味着,对于传输段的 SOME/IP-TP 报文,SOME/IP 长度包括 SOME/IP Header 的 8 个字节、TP Header 的 4 个字节以及段本身。
最大分段长度 { 基于 UDP 的 SOME/IP 信息的有效载荷限制为1400 字节。因此,正确对齐的段的最大长度为 1392 字节。
下面根据autosar 给的例子来解释一下。
如何传输 SOME/IP 有效载荷为 5880 字节的原始报文。原始 SOME/IP 报文的 Length Field 设置为 8 + 5880 字节。
一个最长的"Payload" 为1400 byte. 这里面我们想要发 5880个byte. 这个5880 只是实际应用层使用数据。所以发送的实际数据是1400 - 下图红色框框的长度
所以通过计算我们需要拆成五个包。分别如下。
这???offset value 为何如此奇怪,因为:
请注意,在 Field 中提供的偏移值是以 16 字节为单位的,也就是说:87 的偏移值对应1392 字节的有效载荷。
也就是说 (87 + 174 + 261 + 348)*16 + 324 = 5880 这样就清晰了很多。
所以说前面的四个包应该是如下图的格式
最后一个包的格式应该是下图
-> 使用SOME/IP-TP的SOME/IP消息应激活Session ID处理;
-> 原始信息必须具有唯一的Session ID;
-> 所有SOME/IP-TP分段应携带原始消息的Session ID,因此,它们都具有相同的Session ID;
-> SOME/IP-TP分段应将Message类型的TP标志设置为1;
-> 发送时应对More Segment Flag = 1的信息进行等长分段(为1392byte,除最后一片),且按顺序/升序发送,不可以重复发送分片报文;
-> 接收时根据SOME/IP-TP包中的Message ID、Protocol Version、Interface Version、Message Type等标志进行数据重组;
-> 可接收来自不同客户端(IP、port、ID)相同Message ID重新组合多条消息(良好的缓冲机制);
-> 当接收数据大于设置重组缓冲区时,剩余的数据将会被舍弃;
-> 当新分片数据来临,旧分片的重组任务未结束,则应抛弃旧分片开始新的分片任务;
-> 当检测到分片数据丢失后,应取消该片所属的重组任务;
-> 当检测到分片数据重复后,应可以将重复分片覆盖,使重组可以正常进行;
-> 接收完每个分片之后都应该返回Return Code,但只用最后一个分片的Return Code;
-> 数据重组后应该进行完整性校验,正确重组的数据才能传递给应用程序;
-> 数据重组后,应将分段标志设置为0;
-> 接收数据时,应该可以升序或者降序的对分片数据进行重组;
-> 接收数据时,应一个缓冲区对应一个原始数据的分片的重组,避免相同事件(IP、port、Message ID、TP);
return code
返回代码用于提示请求是否已成功处理。为简化 Header 布局,每条报文都传送 Field 返回代码
Payload
SOME/IP 有效载荷 Field 的大小取决于所使用的传输协议。使用 UDP 时,SOME/IP 有效载荷应介于 0 和 1400 字节之间。需要限制 1400 字节,以便将来对协议栈进行修改(如更改为 IPv6 或增加安全手段)。由于 TCP 支持对有效载荷进行分段,因此可以自动支持更大的有效载荷
所有 SOME/IP Header 字段都应按网络字节序(big endian)编码。
“大端”和“小端”表示多字节值的哪一端存储在该值的起始地址处;小端存储在起始地址处,即是小端字节序;大端存储在起始地址处,即是大端字节序。
大端存储模式:数据的低位保存在内存中的高地址中,数据的高位保存在内存中的低地址中
小端存储模式:数据的低位保存在内存中的低地址中,数据的高位保存在内存中的高地址中
序列化
将对象存储到介质(如文件、内存缓冲区)中或是以二进制的方式通过网络传输;之后可通过反序列化从这些连续的字节数据重新构建一个与原始对象状态相同的对象。
数据结构的序列化
序列化以接口规范定义的参数列表为基础。接口规范定义了所有数据结构在 PDU 中的确切位置,并必须考虑内存对齐问题。
对齐是通过在数据后插入填充元素来对齐数据的开头,以确保对齐后的数据从特定的内存地址开始。
如果可变大小数据不是序列化数据流中的最后一个元素,则应通过在可变大小数据后插入填充元素来实现数据对齐。
下面有两个填充实例。
注意:固定长度数据元素后面不得有填充,以确保以下数据的对齐。
如果固定长度数据元素后面的数据需要填充,则必须在数据类型定义中明确考虑。
长度可变的数据元素后面的数据的对齐方式应为 8、16、32、64、128 或 256位。不过在autosar 的mcu cp 里面一般没有可变长度。
为实现前向和后向兼容性,可在结构体的成员和方法的参数前增加Data-ID
基本数据类型
方法参数的序列化
为了增强向前和向后的兼容性,可以在结构成员或 Method 参数前面添加一个额外的数据 ID。这样,接收方就可以跳过未知的成员/参数,即数据 ID 未知。在序列化字节流中传输数据 ID 时,可在任意位置添加新成员/参数。
也就是说在传输过程中,以后人加了新的数据,这个接收端没有见过,不重要,可以跳过去。做到向前兼容。
数据 ID 在结构体的直接成员或 Method 的参数中应是唯一的。
注意:数据 ID 在不同的结构体或方法中不必是唯一的。
除数据 ID 外,Wire 类型还编码以下成员的数据类型。数据 ID 和Wire 类型在所谓的标记中编码。
标签应包括
传输协议绑定 (RPC)
为了传输 SOME/IP 信息,可以使用不同的传输协议。SOME/IP 目前支持 UDP 和 TCP。
一个 Service-Instance 可以使用以下设置来进行所有方法、事件和通知的通信
最多一个TCP 连接
图片
最多一个UDP 单播连接
图片
最多一个UDP 组播连接
图片
解释一下单播与组播
单播是主机间一对一的通讯模式,网络中的设备根据网络报文中包含的目的地址选择传输路径,将单播报文传送到指定的目的地,只对接收到的数据进行转发,不会进行复制。它能够针对每台主机及时的响应,现在的网页浏览全部都是采用单播模式。
组播是主机间一对多的通讯模式, 组播是一种允许一个或多个组播源发送同一报文到多个接收者的技术。组播源将一份报文发送到特定的组播地址,组播地址不同于单播地址,它并不属于特定某个主机,而是属于一组主机。一个组播地址表示一个群组,需要接收组播报文的接收者都加入这个群组。
一份数据报文如图所示,通过单播传输需要使用一个单播地址作为目的地址。数据源向每个接收者发送一份独立的报文。如果网络中存在N个接收者,则数据源需要发送N份报文;通过组播传输时使用一个组播地址作为目的地址,数据源向组播组发送且仅发送一份报文。如果网络中存在N个接收者,数据源也仅需要发送一份数据报文。