ns3 Scale Up Ethernet(sue)仿真代码分析

ns3 仿真器数据包收发流程

 博客NS3 WiFi环境中Packet从Socket到NetDevice的调用过程分析ns3使用udp协议的数据包收发过程。

数据包发送过程

text 复制代码
   Socket::Send
          |
UdpSocketImpl::Send
          |
UdpSocketImpl::DoSend
          |
UdpSocketImpl::DoSendTo
          |
UdpL4Protocol::Send
          |
Ipv4L3Protocol::Send
          |
Ipv4L3Protocol::SendRealOut
          |
Ipv4Interface::Send
          |
TrafficControlLayer::Send
          |
NetDevice->Send

数据包接收过程

text 复制代码
PointToPointNetDevice::m_rxCallback
          |
Node::NonPromiscReceiveFromDevice
          |
Node::ReceiveFromDevice
          |
TrafficControlLayer::Receive 
          |
Ipv4L3Protocol::Receive
          |
Ipv4L3Protocol::LocalDeliver
          |
UdpL4Protocol::Receive
          |
Ipv4EndPoint::ForwardUp
          |
UdpSocketImpl::ForwardUp
          |
Socket::NotifyDataRecv

Node向device注册数据包接收回调,device->SetReceiveCallback。

复制代码
uint32_t
Node::AddDevice(Ptr<NetDevice> device)
{
    NS_LOG_FUNCTION(this << device);
    uint32_t index = m_devices.size();
    m_devices.push_back(device);
    device->SetNode(this);
    device->SetIfIndex(index);
    device->SetReceiveCallback(MakeCallback(&Node::NonPromiscReceiveFromDevice, this));
    Simulator::ScheduleWithContext(GetId(), Seconds(0), &NetDevice::Initialize, device);
    NotifyDeviceAdded(device);
    return index;
}

SUE-Sim

 Scale-Up Ethernet(SUE) 提供了一个基于以太网、面向 XPU 规模化扩展网络的低时延、高带宽互连框架。SUE 的目标是提供用于在 XPUs 之间传输内存事务的传输层和以太网数据链路层。SUE-Sim是在在ns3实现的协议仿真模块。

架构

Traffic Generator

Continuously generates transactions according to configured data rates

Load Balancer

Balances traffic across SUE instances

Credit-Based Flow Control Module (CBFC)

Maintains credit usage for each VC to control flow

SUE Instance Packer

Opportunistically packs transactions up to preconfigured size limits

Schedules destination queues to send transactions

Link-Level Retransmission Module (LLR)

Retransmits corrupted packets between peer devices

Switch

Implements a basic Layer 2 switch, supporting MAC address table lookup and frame forwarding

Credit-Based Flow Control

信用初始化与配置

 每个 PointToPointSueNetDevice 在初始化时通过 InitializeCbfc() 创建 CbfcManager 实例,并设置:

  • m_initialCredits:每个 VC 的初始信用数量(或共享池的总量)。
  • m_numVcs:虚拟通道数量。
  • m_linkCreditMode:SHARED(所有 VC 共享一个信用池)或 EXCLUSIVE(每个 VC 独立配额)。
  • m_bytesPerCredit:用于动态计算包大小对应信用数的粒度(默认 256 字节/信用)。
    对于交换机设备,还会为每个内部对端(其他端口)分配信用,计算方式基于 SwitchCredits 和端口组竞争数。

数据包入队(EnqueueToVcQueue)

 数据包(非控制包)通过 Send() → EnqueueToVcQueue() 进入对应的 VC 队列。

 此时不检查信用,只检查队列容量(m_queueManager 的字节数限制)。

 若队列未满,则入队并触发调度 TryTransmit()。

PointToPointSueNetDevice::EnqueueToVcQueue

发送调度(TryTransmit)

PointToPointSueNetDevice::TryTransmit

 调度器轮询各 VC 队列(带权轮询)。

对每个 VC,先检查 CBFC 是否启用:

  • 若启用,则调用 m_cbfcManager->HasEnoughCredits(remoteMac, vc, packetSize)。

    • 该函数内部调用 CalculateCreditsForPacket(packetSize),根据包大小计算所需信用数:
      credits = ceil(packetSize / m_bytesPerCredit),并取 max(credits, m_baseCredit)(m_baseCredit 通常为 1)。
    • 然后比较当前可用信用数是否 ≥ 所需信用数。
  • 若信用充足,则:

    • 从 VC 队列中取出数据包(DequeueFromVcQueue)。
    • 调用 m_cbfcManager->ConsumeDynamicCredits(remoteMac, vc, packetSize) 消耗相应信用。该函数调用 DecrementTxCredits(mac, vcId, creditsNeeded) 将信用计数减少。
    • 调用 TransmitStart(packet) 实际发送。
  • 若信用不足,则跳过该 VC,继续轮询其他 VC,该包留在队列中等待后续信用到达。

实际发送(TransmitStart)

PointToPointSueNetDevice::TransmitStart

将包交给底层通道(m_channel->TransmitStart),并调度 TransmitComplete 事件。

接收端处理(信用返还)

对端设备收到数据包后,在 ReceiveCommon 中:

  1. 如果启用了 CBFC,会调用 m_cbfcManager->HandleCreditReturn,累计待返还信用(m_rxCreditsToReturnMap)。
  2. 之后调用 m_cbfcManager->CreditReturn,当累计信用达到 m_creditBatchSize 时,构造 CBFC 更新包并发送回发送端。

发送端接收信用更新(ReceiveCommon 处理 CBFC 包)

当发送端收到 CBFC 更新包(PROT_CBFC_UPDATE)时:

  • 解析出 vcId 和 credits(增量值)。
  • 调用 m_cbfcManager->AddTxCredits(sourceMac, vcId, credits),将信用加回 m_txCreditsMap。

若是 CBFC 同步包(PROT_CBFC_SYNC),则直接覆盖当前信用值(用于修复漂移)。

核心数据流总结

阶段 函数/操作 信用变化
发送前检查 HasEnoughCredits 只读,不改变
发送时消耗 ConsumeDynamicCredits → DecrementTxCredits
接收端处理 HandleCreditReturn + CreditReturn 生成待发送的信用增量
发送端接收更新 AddTxCredits 信用增加(增量)
信用同步(可选) SetTxCredits 覆盖为指定值

Link‑Level Retransmission(LLR)的处理过程

 LLR 是一种逐跳的链路层可靠传输机制,主要用于在有损链路(例如丢包场景)下恢复丢失的数据包和控制包,避免因链路错误导致上层协议超时。该实现包含两个管理器:LlrNodeManager(用于终端节点 NIC)和 LlrSwitchPortManager(用于交换机端口),两者逻辑高度相似,后者额外支持多对端 MAC 地址。

LLR 核心数据结构

每个 VC(Virtual Channel)独立维护一套 LLR 状态,包括:

发送序列号 m_sendSeqvc:下一个待分配序列号。

接收期望序列号 m_waitSeqvc(或 per‑MAC 映射):下一个期望收到的序列号。

发送基序号 m_sendBaseSeqvc:已经确认的最后一个序列号 +1,窗口的起始。

发送列表 m_sendListvc:map 存储已发送但未确认的数据包副本(key 为序列号)。

重传相关:m_llrResendingvc 标志是否正在重传,m_llrResendseqvc 下一个要重传的序列号,m_llrResendEndSeqvc 重传范围的终点。

定时器 m_resendPktvc:每个 VC 的超时重传定时器。

窗口大小 m_llrWindowSize:限制在途未确认包数量。

发送端流程

LlrSendPacket

  1. 分配序列号:获取 m_sendSeqvc 作为当前包序列号,然后递增。
  2. 更新 SueTag:
  • 若包已有 SueTag(可能是内部转发),则更新其序列号和链路类型(linkType 表示跳数:NIC→Switch 为 0,Switch 内部为 1,Switch→NIC 为 2)。
  • 若无,则创建新 Tag。
  1. 存储副本:将当前包的副本存入 m_sendListvcsequenceNumber,用于潜在重传。
  2. 若列表为空,则更新 m_sendBaseSeqvc = sequenceNumber。
  3. 启动超时定时器:若该 VC 尚未有定时器,则调度 Resend 事件,超时时间为 m_llrTimeout。

接收端流程

LlrReceivePacket

接收端收到数据包后,提取其序列号 seq_rev 和 VC ID,比较与期望序列号 m_waitSeqvc 的关系:

  • 有序包(seq_rev == expectedSeq):
    • 更新 m_waitSeqvc++。
    • 发送 ACK(累积确认,确认该序列号)。
    • 返回 true,表示允许上层继续处理该包(如转发或递交协议栈)。
  • 乱序包(seq_rev > expectedSeq):
    • 检测到丢包(至少期望的包未到达)。
    • 若针对该 expectedSeq 尚未发送过 NACK(通过 m_lastNackSeqvc 防重复),则发送 NACK,指定丢失的序列号 expectedSeq。
    • 返回 false,表示该包不能继续上送(等待重传)。
  • 重复包(seq_rev < expectedSeq):
    • 说明已收到过该包,直接发送 ACK(帮助发送端前进窗口)并返回 false(丢弃)

ACK 处理

ProcessLlrAck

当收到 ACK 控制包(协议 ACK_REV)时:

  1. 从包中提取 ACK 所确认的序列号 seq 和源 MAC 地址(交换机管理器需区分对端)。
  2. 检查 seq 是否小于当前 m_sendBaseSeqvc(旧 ACK),若是则忽略。
  3. 在发送列表 m_sendListvc 中查找 seq。若找不到,可能为重复或乱序 ACK,忽略。
  4. 累积确认:将 m_sendListvc 中从开头到 seq(含)的所有条目删除,更新 m_sendBaseSeqvc = seq + 1。
  5. 若当前处于重传状态(m_llrResendingvc)且 m_llrResendseqvc < m_sendBaseSeqvc,则将重传起始指针前移(加速恢复)。
  6. 取消现有超时定时器,若发送列表仍非空,则重新调度超时定时器(等待剩余未确认包)。

NACK 处理

ProcessLlrNack

收到 NACK 控制包(协议 NACK_REV)时:

  1. 提取 NACK 指向的丢失序列号 seq。
  2. 若当前已处于重传状态且 seq == m_sendBaseSeqvc(重复 NACK),忽略。
  3. 检查 seq 是否在发送列表范围内:
  • 若 seq < m_sendBaseSeqvc,为旧 NACK,忽略。
  • 若 seq 不在列表中,可能为重复或乱序 NACK,忽略。
  • 否则,删除发送列表中 seq 之前的所有条目(表示这些已确认),将 m_sendBaseSeqvc 更新为 seq。
  1. 设置重传范围:
  • 若该 VC 已经发送过至少一个包(m_hasSentvc),则重传终点 m_llrResendEndSeqvc 为 m_lastSentSeqvc(最近一次发送的序列号),否则为 seq。
  • 设置 m_llrResendingvc = true,m_llrResendseqvc = seq。
  1. 取消现有定时器,立即调用 m_tryTransmit() 触发重传,并重新调度超时定时器。

超时重传

Resend

超时定时器触发时(表明长时间未收到任何 ACK/NACK 推进窗口):

  1. 若发送列表为空,直接返回。
  2. 将重传起点设为发送列表中的最小序列号(即 m_sendList.begin()->first)。
  3. 重传终点设为 m_lastSentSeqvc(如果已发送过)或该最小序列号。
  4. 设置 m_llrResendingvc = true,调用 m_tryTransmit() 触发重传出队,并重新调度下一次超时定时器。

重传出队

LlrResendPacket

该方法由上层调度器(TryTransmit)调用,用于获取下一个需要重传的数据包:

  1. 检查 m_llrResendingvc 和发送列表非空。
  2. 若 m_llrResendseqvc 超过重传终点 m_llrResendEndSeqvc,则停止重传状态。
  3. 在发送列表中查找 m_llrResendseqvc,若不存在则停止重传。
  4. 复制该包,确保其 SueTag 中的序列号和链路类型正确(通过 UpdateSequenceAndLinkTypeInPacket)。
  5. 递增 m_llrResendseqvc,返回包副本供发送。

发送窗口控制

IsWithinSendWindow

在调度新数据包(非重传)时,调用该方法检查是否允许发送新包。窗口上限为 m_sendBaseSeqvc + m_llrWindowSize。

若当前序列号超出窗口,则不允许发送,从而限制在途包数量,避免接收端缓冲区溢出。

Reference

broadcom scale-up-ethernet-framework

Scale Up Ethernet 协议 中文翻译

Ultra Ethernet's Design Principles and Architectural Innovations

UALink vs. UALoE/SUE vs. RoCE:AI Scale-Up互连技术性能评估

Credit-based Flow Control 的前世今生