You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux内核网络中Ring Buffer的位置及实现相关疑问

NIC接收Ring Buffer核心问题解析

存储位置

NIC的接收Ring Buffer(描述符环+关联的数据包缓冲区)必须位于内核态可DMA访问的物理连续内存中(若系统启用IOMMU,也可以是经地址映射的非连续内存)。原因是NIC通过DMA直接写入数据,无法访问用户态内存,且需要物理地址完成硬件寻址。

所属结构体与结构形态

Ring Buffer没有统一的内核标准结构体,不同NIC驱动会根据硬件规格自定义,但核心结构大同小异:

  • 描述符环结构体:管理整个Ring的元数据,包括描述符数组的虚拟/物理地址、Ring长度、读写索引(标记NIC已写入的位置和驱动已处理的位置),以及每个描述符对应的sk_buff(内核网络缓冲区)指针数组。
  • 描述符结构体:单个描述符记录数据包的物理地址、长度、状态位(如是否已被NIC写入)等信息,作为NIC与驱动的交互载体。

以Linux下Intel e1000网卡驱动为例:

描述符环结构体

struct e1000_rx_ring {
    void *desc;                  // 描述符环的内核虚拟地址
    dma_addr_t dma;              // 描述符环的物理地址(供NIC DMA访问)
    unsigned int count;          // 描述符总数
    unsigned int next_to_use;    // 驱动下一个要填充的描述符索引
    unsigned int next_to_clean;  // 驱动下一个要处理的已完成描述符索引
    struct sk_buff **skb;        // 每个描述符对应的skb缓冲区指针数组
};

单个接收描述符结构体

struct e1000_rx_desc {
    __le64 buffer_addr;  // 数据包缓冲区的物理地址(NIC写入目标)
    __le16 length;       // 接收到的数据包长度
    __le16 csum;         // 校验和信息
    __le8 status;        // 描述符状态(比如是否已完成接收)
    __le8 errors;        // 接收错误信息
    __le16 special;      // 硬件特定字段
};

驱动中是否需要手动分配

是的,Ring Buffer的分配和初始化是网卡驱动初始化流程的核心步骤之一,需驱动手动完成,大致流程:

  1. 调用内核DMA分配接口(如dma_alloc_coherent)分配描述符环的物理连续内存。
  2. 为每个描述符分配对应的sk_buff数据包缓冲区,通过dma_map_single将缓冲区映射为NIC可访问的物理地址并写入描述符。
  3. 将描述符环的物理地址写入NIC硬件寄存器,让NIC定位Ring位置并开始写入数据。

源码示例(e1000驱动简化版)

以下是e1000驱动中初始化单个Rx Ring的简化代码,展示核心逻辑:

static int e1000_init_rx_ring(struct e1000_adapter *adapter, struct e1000_rx_ring *rx_ring, int count)
{
    int i;
    const size_t desc_size = count * sizeof(struct e1000_rx_desc);
    const unsigned int buf_size = E1000_RX_BUFFER_SIZE;

    // 分配DMA可访问的描述符环内存
    rx_ring->desc = dma_alloc_coherent(&adapter->pdev->dev, desc_size, &rx_ring->dma, GFP_KERNEL);
    if (!rx_ring->desc)
        return -ENOMEM;

    // 初始化Ring元数据
    rx_ring->count = count;
    rx_ring->next_to_use = 0;
    rx_ring->next_to_clean = 0;

    // 分配skb指针数组,关联描述符与内核缓冲区
    rx_ring->skb = kcalloc(count, sizeof(struct sk_buff *), GFP_KERNEL);
    if (!rx_ring->skb) {
        dma_free_coherent(&adapter->pdev->dev, desc_size, rx_ring->desc, rx_ring->dma);
        return -ENOMEM;
    }

    // 逐个初始化描述符,绑定skb缓冲区
    for (i = 0; i < count; i++) {
        struct e1000_rx_desc *desc = &((struct e1000_rx_desc *)rx_ring->desc)[i];
        struct sk_buff *skb = netdev_alloc_skb(adapter->netdev, buf_size);
        
        if (!skb) {
            e1000_free_rx_ring(adapter, rx_ring);
            return -ENOMEM;
        }

        // 将skb缓冲区映射为NIC可访问的物理地址
        dma_addr_t skb_dma = dma_map_single(&adapter->pdev->dev, skb->data, buf_size, DMA_FROM_DEVICE);
        desc->buffer_addr = cpu_to_le64(skb_dma);
        desc->status = 0;
        rx_ring->skb[i] = skb;
    }

    // 将描述符环物理地址写入NIC寄存器,启动接收
    writel((u32)rx_ring->dma, adapter->hw_addr + E1000_RDBAL);
    writel((u32)(rx_ring->dma >> 32), adapter->hw_addr + E1000_RDBAH);
    writel(count - 1, adapter->hw_addr + E1000_RDLEN);

    return 0;
}

多Ring Buffer的处理逻辑

支持RSS(接收端缩放)的NIC会提供多个Rx Ring,每个Ring绑定到一个CPU队列,实现数据包的多核分发。驱动会创建多个独立的Ring结构体实例,分别完成分配、初始化和硬件注册流程,每个Ring的管理逻辑与单个Ring一致。

内容的提问来源于stack exchange,提问作者ZephyrRainn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 01:44:54