netmap源码分析(三)内核态收包过程

netmap 收发报文是采用 poll,主要函数是netmap_poll,我们这里只分析收包过程,发包过程类似。

一、netmap_kring

先来看一个重要的结构netmap_kring

struct netmap_kring {
    struct netmap_ring  *ring;

    uint32_t    nr_hwcur;
    uint32_t    nr_hwtail;

    // 对应 netmap_ring 的 head, cur 和 tail
    uint32_t    rhead;
    uint32_t    rcur;
    uint32_t    rtail;

    uint32_t    nkr_num_slots;  // 槽位个数
    int32_t     nkr_hwofs;      // 网卡和netmap槽位相差的偏移

    NM_SELINFO_T    si;     /* 等待队列 */
    NM_LOCK_T   q_lock;     /* 队列的锁 */
    NM_ATOMIC_T nr_busy;    /* 防止并发访问 */

    struct netmap_adapter *na;

    // 相关的回调函数
    int (*nm_sync)(struct netmap_kring *kring, int flags);
    int (*nm_notify)(struct netmap_kring *kring, int flags);
};

二、Ring 内部布局

TX/RX 内部布局图

            RxRING                         TxRING

      +-----------------+            +-----------------+
      |                 |            |                 |
      |XXX free slot XXX|            |XXX free slot XXX|
      +-----------------+            +-----------------+
head->| owned by user   |<-hwcur     | not sent to nic |<-hwcur
      |                 |            | yet             |
      +-----------------+            |                 |
 cur->| available to    |            |                 |
      | user, not read  |            +-----------------+
      | yet             |       cur->| (being          |
      |                 |            |  prepared)      |
      |                 |            |                 |
      +-----------------+            +     ------      +
tail->|                 |<-hwtail    |                 |<-hwlease
      | (being          | ...        |                 | ...
      |  prepared)      | ...        |                 | ...
      +-----------------+ ...        |                 | ...
      |                 |<-hwlease   +-----------------+
      |                 |      tail->|                 |<-hwtail
      |                 |            |                 |
      |                 |            |                 |
      |                 |            |                 |
      +-----------------+            +-----------------+

三、netmap_poll 代码分析

int netmap_poll(struct netmap_priv_d *priv, int events, NM_SELRECORD_T *sr)
{
    struct netmap_adapter *na;
    struct netmap_kring *kring;
    struct netmap_ring *ring;
#define want_rx want[NR_RX]
    struct mbq q;
    enum txrx t;

    mbq_init(&q);
    mb();

    if (want_rx) {
        want_rx = 0;
        t = NR_RX;

        // 遍历所有收包队列,一旦有队列是空的,那就可以收包了
        for (i = priv->np_qfirst[t]; i < priv->np_qlast[t]; i++) {
            kring = &NMR(na, t)[i]; /* 取出一个收包队列 */

            // 如果队列的 cur 和 tail 指针在同一位置,说明这个队列还没有数据,want_rx 置1,准备收包
            if (kring->ring->cur == kring->ring->tail || kring->rhead != kring->ring->head) {
                want_rx = 1;
            }
        }
        if (!want_rx)  // 队列中还有数据
            revents |= events & (POLLIN | POLLRDNORM);
    }

    if (want_rx) {
do_retry_rx:
        for (i = priv->np_qfirst[NR_RX]; i < priv->np_qlast[NR_RX]; i++) {
            int found = 0;

            kring = &na->rx_rings[i];
            ring = kring->ring;

            if (unlikely(nm_kr_tryget(kring, 1, &revents)))  // 看一下这个队列是否可以操作
                continue;

            kring->nr_kflags &= ~NR_FORWARD;

            // nm_sync 指向 na->nm_rxsync,对于 ixgbe 是 ixgbe_netmap_rxsync 函数
            if (kring->nm_sync(kring, 0))
                revents |= POLLERR;
            else
                nm_sync_finalize(kring);
            send_down |= (kring->nr_kflags & NR_FORWARD);

            // rcur 和 rtail 如果不在同一位置说明队列中有数据
            found = kring->rcur != kring->rtail;
            nm_kr_put(kring);
            if (found) {
                revents |= want_rx;
                retry_rx = 0;  // 如果队列中有数据,则置标志位为 0
                kring->nm_notify(kring, 0);  // 还记得前一篇文章中提到的这个函数么,用于唤醒等待队列
            }
        }

        if (retry_rx && sr) {
            // nm_os_selrecord 调用的是 poll_wait() 函数
            nm_os_selrecord(sr, check_all_rx ?
                &na->si[NR_RX] : &na->rx_rings[priv->np_qfirst[NR_RX]].si);
        }
        if (send_down > 0 || retry_rx) {
            retry_rx = 0;
            if (send_down)
                goto flush_tx;
            else
                goto do_retry_rx;  // 如果 retry_rx 标志位非 0,说明接收队列都还满着,继续遍历队列
        }
    }
}

四、ixgbe_netmap_rxsync 分析

我们来看看收包的函数 ixgbe_netmap_rxsync

static int ixgbe_netmap_rxsync(struct netmap_kring *kring, int flags)
{
    struct netmap_adapter *na = kring->na;
    struct ifnet *ifp = na->ifp;
    struct netmap_ring *ring = kring->ring;
    u_int ring_nr = kring->ring_id;
    u_int nm_i;   // netmap队列中槽位的索引
    u_int nic_i;  // 网卡队列中槽位的索引
    u_int n;
    u_int const lim = kring->nkr_num_slots - 1;
    u_int const head = kring->rhead;  // 队列的有效槽位指针

    struct SOFTC_T *adapter = netdev_priv(ifp);  // 网卡适配器
    struct ixgbe_ring *rxr = NM_IXGBE_RX_RING(adapter, ring_nr);  // 网卡接收队列
    rmb();  // 内存屏障

    if (netmap_no_pendintr || force_update) {
        uint16_t slot_flags = kring->nkr_slot_flags;

        nic_i = rxr->next_to_clean;

        // 通过网卡队列槽位索引计算netmap队列槽位索引
        nm_i = netmap_idx_n2k(kring, nic_i);

        for (n = 0; ; n++) {
            union ixgbe_adv_rx_desc *curr = NM_IXGBE_RX_DESC(rxr, nic_i); // ixgbe 接收队列信息描述结构
            uint32_t staterr = le32toh(curr->wb.upper.status_error);

            if ((staterr & IXGBE_RXD_STAT_DD) == 0)  // 查看 ixgbe 接收队列是否准备好
                break;
            ring->slot[nm_i].len = le16toh(curr->wb.upper.length);
            ring->slot[nm_i].flags = (!(staterr & IXGBE_RXD_STAT_EOP) ? NS_MOREFRAG |
                                        slot_flags:slot_flags);
            nm_i = nm_next(nm_i, lim);    // 指向下一个netmap槽位索引
            nic_i = nm_next(nic_i, lim);  // 指向下一个网卡槽位索引
        }
        if (n) {  // 更新指针位置
            rxr->next_to_clean = nic_i;
            kring->nr_hwtail = nm_i;
        }
    }

    nm_i = kring->nr_hwcur;
    if (nm_i != head) {  // 如果槽位索引不相同
        nic_i = netmap_idx_k2n(kring, nm_i);
        for (n = 0; nm_i != head; n++) {
            struct netmap_slot *slot = &ring->slot[nm_i];
            uint64_t paddr;
            void *addr = PNMB(na, slot, &paddr);  // 计算netmap队列槽位的地址

            union ixgbe_adv_rx_desc *curr = NM_IXGBE_RX_DESC(rxr, nic_i);
            curr->read.pkt_addr = htole64(paddr);  // 网卡报文地址与netmap槽位地址关联
            nm_i = nm_next(nm_i, lim);
            nic_i = nm_next(nic_i, lim);
        }
        kring->nr_hwcur = head;
        rxr->next_to_use = nic_i;
        wmb();

        // 保证最后一个槽位为空的,内核态使用
        nic_i = nm_prev(nic_i, lim);
        IXGBE_WRITE_REG(&adapter->hw, IXGBE_RDT(rxr->reg_idx), nic_i);
    }

    return 0;
}

现在队列里面已经有数据了,那么我们来看看 netmap 是如何映射到用户态的吧

五、linux_netmap_mmap

static int linux_netmap_mmap(struct file *f, struct vm_area_struct *vma)
{
    unsigned long off;
    u_int memsize, memflags;
    struct netmap_priv_d *priv = f->private_data;
    struct netmap_adapter *na = priv->np_na;

    mb();

    // na->nm_mem 指向全局的 nm_mem
    error = netmap_mem_get_info(na->nm_mem, &memsize, &memflags, NULL);
    if (error)
        return -error;

    // 中间部分我去掉了一些关于有效性的判断

    off = vma->vm_pgoff << PAGE_SHIFT;
    if (off + (vma->vm_end - vma->vm_start) > memsize)
        return -EINVAL;
    if (memflags & NETMAP_MEM_IO) {
        vm_ooffset_t pa;

        pa = netmap_mem_ofstophys(na->nm_mem, 0);
        if (pa == 0)
            return -EINVAL;

        // remap_pfn_range 函数的作用就是映射内核态内存到用户态
        return remap_pfn_range(vma, vma->vm_start,
                pa >> PAGE_SHIFT,  // vm_start 为映射用户态空间起始位置
                vma->vm_end - vma->vm_start,  // vm_end 是结束位置
                vma->vm_page_prot);
    }
    return 0;
}

全局的内存分配器nm_mem

struct netmap_mem_d nm_mem = {
    .pools = {
        [NETMAP_IF_POOL] = {  // netmap_if
            .name   = "netmap_if",
            .objminsize = sizeof(struct netmap_if),
            .objmaxsize = 4096,
            .nummin     = 10,
            .nummax     = 10000,
        },
        [NETMAP_RING_POOL] = {  // netmap_ring
            .name   = "netmap_ring",
            .objminsize = sizeof(struct netmap_ring),
            .objmaxsize = 32*PAGE_SIZE,
            .nummin     = 2,
            .nummax     = 1024,
        },
        [NETMAP_BUF_POOL] = {  // netmap_buf
            .name   = "netmap_buf",
            .objminsize = 64,
            .objmaxsize = 65536,
            .nummin     = 4,
            .nummax     = 1000000,
        },
    },

    .nm_id = 1,
    .nm_grp = -1,

    .prev = &nm_mem,
    .next = &nm_mem,

    .ops = &netmap_mem_global_ops
};

用户态如何得到这些东东呢?

六、用户态映射过程

简单来看是这样的

fd = open("/dev/netmap", 0);
strcpy(req.nr_name, "ethX");
ioctl(fd, NIOCREGIF, &req);
mem = mmap(NULL, req.nr_memsize, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);
nifp = NETMAP_IF(mem, req.nr_offset);  // 用户态通过 offset 得到 struct netmap_if
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
netmap是一个基于零拷贝思想的高速网络I/O架构,它通过在网卡运行在netmap模式下与主机协议栈断开连接,并创建一个netmap环来实现高效的数据包处理。\[2\]netmap的架构包括了网卡环、netmap环和用于与主机协议栈交互的环。网卡环是网卡直接将数据包存入的缓存,而netmap环是应用程序可以通过调用netmap API访问的缓存。这些缓存位于共享空间,应用程序可以直接访问数据包内容,实现了网络数据包的零拷贝。\[2\] netmap的数据结构包括了netmap_if、nmreq、netmap_ring等。netmap_if是一个结构体,用于表示一个netmap接口,其中包含了与接口相关的信息。nmreq是一个结构体,用于向内核注册一个netmap接口。netmap_ring是一个结构体,用于表示一个netmap环,其中包含了环的相关信息,如可用的数据包数量、当前处理的数据包索引等。\[3\] 在使用netmap时,可以通过打开字符设备"/dev/netmap"来获取一个文件描述符,然后使用ioctl函数来注册网卡。接下来,可以使用mmap函数将共享内存映射到用户空间,从而可以访问netmap环中的数据包内容。最后,可以使用poll函数来等待数据包的到达,并通过遍历netmap环中的数据包来处理数据。\[3\] 需要注意的是,上述提供的代码示例是一个官方的例子,可能已经过时,不能直接使用。但是它可以大致说明netmap的使用过程。\[3\] #### 引用[.reference_title] - *1* *3* [netmap 介绍](https://blog.csdn.net/fengfengdiandia/article/details/52869290)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Netmap分析(一)](https://blog.csdn.net/superbfly/article/details/51224920)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值