第2章:诊断协议栈架构:OSI七层模型与UDS映射、诊断应用层、诊断会话层、诊断传输层
各位工程师朋友,大家好。今天我们聊一个很基础但特别容易搞混的话题——诊断协议栈的架构。
我记得刚入行那会儿,看UDS协议栈的文档,满眼都是OSI七层模型、应用层、会话层、传输层……说实话,当时我脑子里就一个想法:这玩意儿到底怎么分层?每层管什么?跟我写代码有什么关系?
后来踩了不少坑,才慢慢理清楚。今天我就把这些经验掰开了讲给你听。
2.1 OSI七层模型与UDS的映射关系
先说说OSI七层模型。这个模型是国际标准化组织定义的网络通信框架,从下到上分别是:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
但在汽车诊断领域,我们不会把七层全用上。实际上,UDS协议栈只用了其中几层。我个人习惯把它简化成四层来看:
| OSI层 | UDS协议栈对应 | 典型协议/实现 |
|---|---|---|
| 应用层(第7层) | 诊断应用层 | UDS服务(如0x22读数据、0x2E写数据) |
| 表示层(第6层) | (通常合并到应用层) | 数据编码格式 |
| 会话层(第5层) | 诊断会话层 | 会话状态管理(默认、扩展、编程) |
| 传输层(第4层) | 诊断传输层 | ISO 15765-2(CAN上的多帧传输) |
| 网络层(第3层) | (通常由CAN控制器处理) | CAN ID过滤、路由 |
| 数据链路层(第2层) | CAN数据链路层 | CAN帧格式、错误检测 |
| 物理层(第1层) | CAN物理层 | CAN总线电平、收发器 |
你想想看,为什么UDS不把七层全用上?说白了,汽车诊断通信的场景相对固定,不需要像互联网那样复杂的协议栈。我们更关心的是:怎么把诊断请求发出去,怎么把响应收回来,中间别丢包、别超时。
核心要点:UDS协议栈真正需要开发者关注的,是应用层、会话层和传输层这三层。物理层和数据链路层通常由CAN硬件和驱动搞定。
2.2 诊断应用层——你写的代码都在这里
诊断应用层,说白了就是UDS服务本身。你平时写的那些处理0x10、0x22、0x2E的代码,全在这一层。
这一层负责什么呢?
- 服务解析:收到一个诊断请求,先看SID(服务标识符)是什么,然后分发给对应的处理函数。
- 参数校验:检查请求中的子功能、数据长度、格式是否合法。
- 执行操作:比如读DID、写DID、执行例程、上传下载数据。
- 响应组装:把执行结果打包成响应报文,包括肯定响应和否定响应。
我在项目中遇到过一个问题:某ECU在收到0x22读数据请求时,总是返回否定响应0x7F。查了半天,发现是应用层代码里把DID的地址范围写错了。嗯,这种低级错误其实挺常见的,尤其是当DID数量超过100个的时候。
我的建议:在应用层实现时,一定要把DID的合法性检查放在最前面。先判断DID是否支持,再判断当前会话是否允许访问。顺序搞反了,可能会漏掉一些安全漏洞。
2.3 诊断会话层——状态机是核心
诊断会话层,很多人容易忽略它。但说实话,这一层要是没写好,整个诊断系统都会出问题。
会话层管理的是ECU的诊断会话状态。UDS定义了三种标准会话:
- 默认会话(Default Session):上电后的初始状态,只允许执行一些基础服务,比如读VIN码、读故障码。
- 扩展会话(Extended Session):允许执行更多服务,比如写数据、执行例程。通常需要先通过0x10服务切换过来。
- 编程会话(Programming Session):用于刷写固件,权限最高。一般需要安全访问认证后才能进入。
会话层的工作就是维护这个状态机。每次收到诊断请求,先检查当前会话是否允许执行这个服务。如果不允许,直接返回0x7F(服务不支持或会话不允许)。
我曾经踩过一个坑:某项目在编程会话下执行了写DID操作,结果把校准数据写坏了。后来分析发现,是会话层没有限制写DID只能在扩展会话下执行。从那以后,我要求团队在会话层做严格的权限矩阵检查。
注意:会话层还有一个重要功能——会话超时管理。如果ECU在某个非默认会话下长时间没有收到诊断请求,应该自动切回默认会话。这个超时时间通常是5秒,但OEM可以自定义。我建议设成3~10秒之间,太短了容易误切,太长了不安全。
2.4 诊断传输层——数据怎么安全到达
诊断传输层,在CAN总线上对应的是ISO 15765-2协议。这一层解决的核心问题是:当诊断数据超过单帧CAN报文容量(8字节)时,怎么拆包、怎么重组。
传输层的主要工作包括:
- 单帧/多帧判断:如果诊断数据长度 ≤ 7字节(CAN标准帧),直接用单帧发送。如果超过7字节,就需要拆成多帧。
- 流控制管理:发送方发完第一帧后,接收方会回复流控制帧,告诉发送方「你可以继续发了」或者「等一下,我还没准备好」。
- 超时重传:如果发送方没收到流控制帧,或者接收方没收到后续帧,都需要超时重传机制。
- 顺序重组:接收方把收到的多帧数据按顺序拼起来,还原成完整的诊断请求或响应。
我记得有一次调试,发现ECU总是收不到完整的诊断请求。用CAN分析仪抓包一看,原来是发送方发的连续帧太快了,接收方的缓冲区溢出了。嗯,这就是典型的流控制没做好。
避坑指南:我曾经在传输层实现中忘记处理「连续帧之间的间隔时间」。结果某些CAN卡发送速度太快,ECU根本来不及处理。后来我加了一个最小帧间隔检查,问题就解决了。这个间隔一般建议设成0.5ms以上。
2.5 三层之间的协作关系
这三层不是各自为政的,它们之间有明确的调用关系。我画个简单的流程给你看:
诊断请求到达(CAN报文)
↓
传输层:接收CAN帧,判断单帧/多帧
↓ (重组完整报文)
会话层:检查当前会话状态,判断服务是否允许
↓ (通过检查)
应用层:解析SID,执行具体服务逻辑
↓ (生成响应)
会话层:检查响应是否允许在当前会话下发送
↓ (通过检查)
传输层:拆包(如果需要),发送CAN帧
↓
诊断响应发出
你想想看,如果会话层和应用层的顺序搞反了,会怎么样?比如先执行了服务,再检查会话权限。那万一服务执行过程中修改了关键数据,然后会话层说「不行,这个会话不允许」,这时候数据已经改了,回不去了。
所以,我个人习惯的检查顺序是:传输层完整性检查 → 会话层权限检查 → 应用层服务执行。这个顺序不能乱。
2.6 实际项目中的分层建议
最后,我结合项目经验,给你几个分层实现的建议:
- 代码分层要清晰:传输层、会话层、应用层的代码要放在不同的模块里,不要混在一起。我见过有人把会话状态检查写在应用层的处理函数里,结果后面要改会话权限,得翻遍所有服务代码。
- 接口要标准化:每一层对外暴露的接口要固定。比如传输层提供SendDiagnosticMessage和ReceiveDiagnosticMessage两个接口,会话层提供CheckSessionAccess接口,应用层提供ProcessService接口。
- 日志要分层打:调试的时候,每一层都要打日志。传输层打CAN帧的原始数据,会话层打会话状态切换,应用层打服务执行结果。这样出问题的时候,一眼就能看出是哪一层的问题。
- 单元测试要覆盖:每一层都要单独做单元测试。传输层测试多帧拆包重组,会话层测试状态机切换,应用层测试每个服务的正反逻辑。
一个小技巧:在开发初期,可以先实现一个「透传模式」——传输层收到什么,直接丢给应用层,应用层原样返回。这样能快速验证传输层和CAN通信是否正常。等传输层稳定了,再逐步加入会话层和应用层的逻辑。
好了,关于诊断协议栈的架构,今天就聊到这里。下一章我们会深入讲解诊断应用层的具体服务实现,包括0x10、0x22、0x2E这些常用服务的细节。到时候我会结合代码示例,带你一步步实现。
记住一句话:协议栈分层不是给你添麻烦的,是帮你把复杂问题拆成小块,每个小块都容易搞定。你只要把每一层做好,整个系统就不会差到哪里去。