在传统 CAN 通信中,一帧报文最大也就几十个字节,开发人员很少会认真思考:

这几十个字节到底被 memcpy 了几次?

但进入以太网时代之后,情况完全不同了。

SOME/IP 服务的数据可能是:

  • 几百字节
  • 几KB
  • 十几KB
  • 甚至几十KB

如果这些数据在 AUTOSAR 通信栈中被重复复制:

APP Buffer
    ↓ Copy
SomeIpXf Buffer
    ↓ Copy
SomeIpTp Buffer
    ↓ Copy
PduR Buffer
    ↓ Copy
SoAd Buffer
    ↓ Copy
TcpIp Buffer
    ↓ Copy
Eth Buffer

那么一次发送可能就产生大量 CPU memcpy 和 RAM 占用。

于是很多工程中都会出现几个非常实际的问题:

SOME/IP TP 发送到底经过哪些模块?

SomeIpXf 会不会额外复制一次数据?

PduR 会不会保存完整 Payload?

SoAd 有没有自己的发送 Buffer?

TcpIp 和 EthIf 谁负责真正的数据 Buffer?

TC397 最终是怎么把数据交给 Ethernet DMA 的?

这篇文章就以 Vector MICROSAR CP + Infineon TC397 + SOME/IP TP 为背景,从真实的数据流角度,把整个发送链路拆开。

需要说明的是:不同Autosar软件包、不同 TCP/UDP 配置以及具体 Eth 驱动实现,实际 Copy 次数会存在差异。本文重点分析 AUTOSAR 架构下最典型的数据流和 Buffer 生命周期。


一、先别急着看模块,先看数据到底怎么走

如果工程中配置了 SomeIpXf,一个典型的发送路径如下:

Application
    │
    ▼
RTE
    │
    ▼
SomeIpXf
    │
    ▼
SomeIpTp
    │
    ▼
PduR
    │
    ▼
SoAd
    │
    ▼
TcpIp
    │
    ▼
EthIf
    │
    ▼
Eth Driver
    │
    ▼
TC397 Ethernet DMA
    │
    ▼
Ethernet MAC

如果没有配置 SomeIpXf

看起来只是少了一个模块。

但实际上:

SomeIpXf 是否存在,很可能决定整个发送链路中是否多了一份完整 Payload Buffer。

而真正决定性能的,并不是模块数量,而是:

谁拥有Payload?

谁申请Buffer?

谁执行Copy?

什么时候释放Buffer?


二、SomeIpXf到底干了什么?

很多开发人员第一次看到 SomeIpXf 时,会认为它只是:

给 SOME/IP 数据做一个格式转换。

实际上它的作用远不只是简单转换。

假设应用层有一个结构体:

typedef struct
{
    uint32 Speed;
    uint16 RPM;
    uint8 Status;
} VehicleData;

应用层内存中可能是:

Application RAM

+----------------------+
| VehicleData          |
|                      |
| Speed                |
| RPM                  |
| Status               |
+----------------------+

但 SOME/IP 网络上传输的并不是 MCU 内存中的结构体。

因为还涉及:

  • 字节序
  • 数据类型
  • Array
  • String
  • Struct
  • Optional Data
  • Dynamic Length
  • 序列化格式

所以必须经过:

Application Data
        │
        │ Serialize
        ▼
SOME/IP Payload

最终可能变成:

00 00 00 64

00 00 13 88

01

...

这意味着:

SomeIpXf 很可能成为第一份完整 Payload Buffer。


三、有SomeIpXf时,第一处大数据搬运出现了

整个过程可以理解成:

┌─────────────────────────┐
│ Application Buffer      │
│                         │
│ struct / array / signal │
└────────────┬────────────┘
             │
             │ Serialize
             ▼
┌─────────────────────────┐
│ SomeIpXf Buffer         │
│                         │
│ Serialized Payload      │
└─────────────────────────┘

这里发生的不是简单的指针传递。

因为:

Application Struct

和:

Network Payload

通常并不是同一种数据布局。

因此 SomeIpXf 的主要成本包括:

CPU:
Serialize
Endian Conversion
Memory Copy

RAM:
Serialized Payload Buffer

如果你的 SOME/IP Payload 是:

8 KB

并且每 10ms 发送一次:

8KB × 100 = 800KB/s

这还只是一次序列化产生的数据搬运。

如果后面还有两三次 Copy,CPU 和 Memory Bandwidth 就开始明显增加。


四、SomeIpTp会不会再开一个完整Buffer?

这是一个很容易误解的地方。

实际上,对于 SOME/IP TP 来说,SomeIpTp 的核心职责是:

对大 Payload 进行 TP 分段发送。

例如:

10KB Payload

需要发送成:

Segment 1

Segment 2

Segment 3

...

因此 SomeIpTp 更需要维护的是:

Current Offset

Remaining Length

Segment Number

Transmission State

例如:

┌─────────────────────────┐
│ SomeIpTp Tx Context     │
├─────────────────────────┤
│ TotalLength             │
│ CurrentOffset           │
│ RemainingLength         │
│ SegmentNumber           │
│ TxState                 │
└─────────────────────────┘

它不一定需要:

10KB SomeIpTp Payload Buffer

更理想的方式是:

SomeIpXf Buffer
       │
       │ Offset + Length
       ▼
SomeIpTp

SomeIpTp 只记录:

现在发送到哪里

还剩多少

下一段从哪里开始

而真正的数据仍然在:

SomeIpXf Buffer


五、TP发送的关键:不是Push,而是Pull

理解 SOME/IP TP 数据流最关键的一点就是:

下层通常不是等上层把完整数据推下来,而是在需要发送数据时,主动向上层取数据。

可以理解为:

TcpIp:我需要1000 Byte

        │
        ▼

SoAd:我去拿

        │
        ▼

PduR:我继续转

        │
        ▼

SomeIpTp:Offset是多少?

        │
        ▼

SomeIpXf Buffer

最终形成:

Lower Layer
      │
      │ Need Data
      ▼
SoAd_CopyTxData()
      │
      ▼
PduR_CopyTxData()
      │
      ▼
SomeIpTp_CopyTxData()
      │
      ▼
Payload Source

因此在一个优化较好的 TP 链路中:

SomeIpTp

PduR

SoAd

可能主要承担:

状态管理

Offset管理

PduId转换

API转发

CopyTxData转发

而不是每个模块都保存一份完整 Payload。


六、PduR真的会开Buffer吗?

答案是:

不一定。

PduR 的主要职责是:

Routing

而不是:

Payload Storage

例如:

SomeIpTp
    │
    ▼
PduR
    │
    ▼
SoAd

很多情况下只是:

PduId转换

↓

调用对应模块接口

↓

转发CopyTxData请求

因此:

PduR ≠ 一定存在完整Payload Buffer

但是如果涉及:

  • TP Gateway
  • Queue
  • FIFO
  • 多路由
  • Buffer管理

那么 PduR 也可能配置自己的 Buffer。

所以分析一个真实工程时,不能简单认为:

每经过一个模块 = Copy一次

这是错误的。

真正应该看的是:

这个模块是否拥有Payload Buffer?


七、SoAd:看起来只是Socket适配层,但它其实很关键

SomeIpTp 再往下到SoAd:

实际上从 TP 发送角度,它还是一个非常重要的:

Transmission State Manager

典型过程更可能是:

SoAd_TpTransmit()

↓

保存Transmission Request

↓

SoAd MainFunction

↓

下层开始请求数据

↓

SoAd_CopyTxData()

SoAd 可能维护:

┌─────────────────────────┐
│ SoAd Tx Context         │
├─────────────────────────┤
│ SocketId                │
│ TxPduId                 │
│ TotalLength             │
│ CurrentOffset           │
│ Transmission State      │
└─────────────────────────┘

所以通常:

SoAd 更可能保存发送上下文,而不是完整 SOME/IP Payload。

当然,如果配置了某些 Queue 或 Buffering 机制,也可能产生额外 Buffer。


八、真正的分水岭:UDP还是TCP

这里是整个 Buffer 分析中最重要的部分。

因为:

SomeIpTp

下面可能走:

UDP

也可能走:

TCP

两条路径的数据生命周期完全不同。


九、UDP:可能是一条非常高效的链路

典型路径:

SomeIpTp
    │
    ▼
PduR
    │
    ▼
SoAd
    │
    ▼
TcpIp_UdpTransmit()
    │
    ▼
EthIf_ProvideTxBuffer()
    │
    ▼
Eth Tx Buffer
    │
    ▼
EthIf_Transmit()
    │
    ▼
DMA

其中最关键的是:

EthIf_ProvideTxBuffer()

这个接口的意义非常重要。

它意味着:

下层可以直接提供一个用于发送的 Ethernet Buffer。

例如:

Eth Tx Buffer

地址被返回:

BufPtr

然后上层开始填数据:

SomeIpTp
    │
    │ CopyTxData
    ▼
Eth Tx Buffer

最后:

EthIf_Transmit()

触发发送。

因此,一个理想的 UDP TP 路径可能是:

SomeIpXf Buffer
       │
       │ CopyTxData
       ▼
TC397 Eth Tx Buffer
       │
       │ DMA Read
       ▼
Ethernet MAC

中间:

PduR

SoAd

甚至不需要完整 Payload Copy。


十、没有SomeIpXf时,链路可能进一步减少一次Copy

如果没有 SomeIpXf,可以理解为:

应用层已经准备好了可以作为 SOME/IP Payload 使用的数据。

于是:

Application Buffer
        │
        ▼
SomeIpTp
        │
        ▼
PduR
        │
        ▼
SoAd
        │
        ▼
TcpIp
        │
        ▼
Eth Tx Buffer

最理想的数据搬运:

Application Buffer
        │
        │ Copy
        ▼
Ethernet Tx Buffer
        │
        │ DMA
        ▼
MAC

即:

APP → Eth Tx Buffer → MAC

其中:

APP → Eth Buffer

可能是 CPU Copy。

而:

Eth Buffer → MAC

通常是 DMA 完成。


十一、有SomeIpXf和没有SomeIpXf的最大区别

我们把两种架构直接放在一起。

方案一:有SomeIpXf

Application Data
       │
       │ ① Serialize / Copy
       ▼
SomeIpXf Buffer
       │
       │ ② CopyTxData
       ▼
Eth Tx Buffer
       │
       │ DMA
       ▼
Ethernet MAC

方案二:没有SomeIpXf

Application Buffer
       │
       │ ① CopyTxData
       ▼
Eth Tx Buffer
       │
       │ DMA
       ▼
Ethernet MAC

理论上:

CPU Copy减少一次

因此:

SomeIpXf 的主要代价并不是模块调用本身,而是序列化和 Payload Buffer。

尤其是:

大Payload

高频发送

多Service

多实例

同时存在时,这个影响会更加明显。


十二、TCP为什么会多出一层Buffer?

如果 SOME/IP TP 走 TCP:

SomeIpTp
    │
    ▼
SoAd
    │
    ▼
TcpIp TCP
    │
    ▼
EthIf

和 UDP 最大的不同是:

TCP需要维护发送状态,并且数据不能发送后立即丢弃。

因为:

发送
  ↓
等待ACK
  ↓
收到ACK
  ↓
释放Buffer

因此 TcpIp TCP 通常需要维护自己的发送数据。

典型逻辑:

Payload
    │
    ▼
TcpIp Tx Buffer
    │
    ▼
TCP Segment Queue
    │
    ▼
Eth Tx Buffer
    │
    ▼
DMA

所以 TCP 场景下:

Application
    ↓
SomeIpXf Buffer
    ↓
TcpIp Tx Buffer
    ↓
Eth Tx Buffer

如果 Payload 很大、发送频率很高,TCP 的 RAM 占用通常更值得关注。


十三、TC397最底层到底有什么?

最终,无论上面经过多少模块,数据都会进入:

Ethernet Tx Buffer

TC397 Ethernet MAC 的发送通常依赖 DMA。

概念上:

Tx Descriptor Ring


┌────────────────────┐
│ Tx Descriptor 0    │
│                    │
│ Buffer Address ────┼──────► Tx Buffer 0
└────────────────────┘


┌────────────────────┐
│ Tx Descriptor 1    │
│                    │
│ Buffer Address ────┼──────► Tx Buffer 1
└────────────────────┘

内存可能是:

RAM

TxBuffer0

TxBuffer1

TxBuffer2

TxBuffer3

...

发送时:

CPU
 │
 │ 设置Descriptor
 ▼
Tx Descriptor
 │
 │ Buffer Address
 ▼
Ethernet DMA
 │
 ▼
Ethernet MAC
 │
 ▼
PHY

因此最终:

真正的 Payload 硬件发送资源,一定会落在 Tx Buffer 和 DMA Descriptor 上。

注意:

DMA Descriptor

主要保存的是:

Buffer Address

Length

Control

Status

它本身不是完整 Payload Buffer。


十四、到底哪些模块可能开Buffer?

直接给一个工程分析表。

模块可能存在的数据是否可能保存完整Payload
Application原始业务数据
RTESender/Receiver Data看配置而定
SomeIpXfSerialized Payload通常会
SomeIpTpTP Context/Segment状态通常不一定
PduRRouting/Queue/Gateway Buffer看配置
SoAdSocket/TP Tx Context通常不是完整Payload
TcpIp UDP临时发送数据/头部依实现
TcpIp TCPTCP发送队列通常会
EthIfTx Buffer管理可能
Eth DriverDMA Tx Buffer一定存在发送资源
DMA DescriptorDescriptor Ring不保存完整Payload

所以:

AUTOSAR 模块数量 ≠ 数据 Copy 次数。

真正需要分析的是:

谁申请Buffer?

谁拥有Buffer?

谁调用memcpy?

谁只是传递指针?

谁只是转发CopyTxData?


十五、结尾

理解 AUTOSAR 以太网通信性能,不能只看:

模块调用链

而应该换成另一个视角:

数据到底在哪里?

每经过一个模块,都应该问四个问题:

① 这个模块有没有自己的Payload Buffer?

② 数据是Copy,还是传递指针?

③ Buffer什么时候释放?

④ 下层是Push数据,还是Pull数据?

并不一定意味着每经过一层就复制一次完整 Payload。

所以最终可以总结成一句话:

AUTOSAR以太网发送链路真正消耗CPU的,不是模块多,而是Payload到底被复制了多少次;真正消耗RAM的,不是调用链长,而是谁长期持有了完整Payload Buffer。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐