netmap源码分析(五)ioctl 注册过程

上篇文章中,提到用户态 netmap 的使用过程,其中有一个 nm_open 函数。

nm_open 封装好了一些内部操作,包括注册部分ioctl(d->fd, NIOCREGIF, &d->req),这次我们来分析一下netmap_ioctl函数。

这个函数比较长,这里只分析注册部分:

netmap_ioctl()
  |-- netmap_do_regif()
        |-- netmap_update_config()
              |-- nm_config()

netmap_ioctl

int netmap_ioctl(struct netmap_priv_d *priv, u_long cmd, caddr_t data, struct thread *td)
{
    struct nmreq *nmr = (struct nmreq *) data;
    struct netmap_adapter *na = NULL;
    struct ifnet *ifp = NULL;
    int error = 0;
    struct netmap_if *nifp;
    enum txrx t;

    if (cmd == NIOCGINFO || cmd == NIOCREGIF) {
        nmr->nr_name[sizeof(nmr->nr_name) - 1] = '\0';  // nr_name 就是 net interface 名 
        if (nmr->nr_version != NETMAP_API) {
            nmr->nr_version = NETMAP_API;
        }

        // 判断版本是否有效,当前 netmap API 版本是 11
        if (nmr->nr_version < NETMAP_MIN_API ||
            nmr->nr_version > NETMAP_MAX_API) {
            return EINVAL;
        }
    }

    switch (cmd) {
    case NIOCREGIF:
        i = nmr->nr_cmd; // nr_cmd 为 0

        NMG_LOCK();  // 防止并发注册
        do {
            u_int memflags;
            struct ifnet *ifp;

            if (priv->np_nifp != NULL) {  // 如果已经注册过了,跳出 switch
                error = EBUSY;
                break;
            }

            error = netmap_get_na(nmr, &na, &ifp, 1);  // 获取 na 和 interface
            if (error)
                break;
            if (NETMAP_OWNED_BY_KERN(na)) {  // 查看设备是否是 Busy 状态 
                netmap_unget_na(na, ifp);
                error = EBUSY;
                break;
            }

            // 判断是否可以处理 virtio-net
            if (na->virt_hdr_len && !(nmr->nr_flags & NR_ACCEPT_VNET_HDR)) {
                netmap_unget_na(na, ifp);
                error = EIO;
                break;
            }

            error = netmap_do_regif(priv, na, nmr->nr_ringid, nmr->nr_flags);
            if (error) {  // 如果注册失败
                netmap_unget_na(na, ifp);
                break;
            }
            nifp = priv->np_nifp;
            priv->np_td = td;

            // 队列数和槽位数赋值
            nmr->nr_rx_rings = na->num_rx_rings;
            nmr->nr_tx_rings = na->num_tx_rings;
            nmr->nr_rx_slots = na->num_rx_desc;
            nmr->nr_tx_slots = na->num_tx_desc;
            error = netmap_mem_get_info(na->nm_mem, &nmr->nr_memsize, &memflags,
                &nmr->nr_arg2);

            // 遍历所有等待队列 wait_queue
            for_rx_tx(t) {
                priv->np_si[t] = nm_si_user(priv, t) ?
                    &na->si[t] : &NMR(na, t)[priv->np_qfirst[t]].si;
            }
            nmr->nr_offset = netmap_mem_if_offset(na->nm_mem, nifp);  // 计算 nifp 的偏移
            priv->np_ifp = ifp;
        } while (0);
        NMG_UNLOCK();
        break;
    }

    return error;
}

netmap_do_regif —— 真 · 注册部分

int netmap_do_regif(struct netmap_priv_d *priv, struct netmap_adapter *na, 
    uint16_t ringid, uint32_t flags)
{
    struct netmap_if *nifp = NULL;
    int error;

    netmap_update_config(na);  // 更新配置,详细见下文
    priv->np_na = na;
    error = netmap_set_ringid(priv, ringid, flags);  // 设置 ring ID 
    error = netmap_mem_finalize(na->nm_mem, na);  // 更新一些配置
    error = netmap_krings_get(priv);  // 确保 netmap 有内核态队列
    nifp = netmap_mem_if_new(na);  // 创建一个新的 nifp
    na->active_fds++;  // 增加这个 interface 的引用计数
    mb();
    priv->np_nifp = nifp;

    return 0;
}

netmap_update_config

从硬件设备中获取相关配置信息并更新到 netmap 中

int netmap_update_config(struct netmap_adapter *na)
{
    u_int txr, txd, rxr, rxd;
    txr = txd = rxr = rxd = 0;

    // nm_config 指向 netmap_linux_config
    if (na->nm_config == NULL || na->nm_config(na, &txr, &txd, &rxr, &rxd)) {
        txr = na->num_tx_rings;
        txd = na->num_tx_desc;
        rxr = na->num_rx_rings;
        rxd = na->num_rx_desc;
    }

    if (na->active_fds == 0) {  // 更新收发队列和槽位的个数
        na->num_tx_rings = txr;
        na->num_tx_desc = txd;
        na->num_rx_rings = rxr;
        na->num_rx_desc = rxd;
        return 0;
    }
    return 1; 
}

netmap_linux_config

int netmap_linux_config(struct netmap_adapter *na,
        u_int *txr, u_int *txd, u_int *rxr, u_int *rxd)
{
    struct ifnet *ifp = na->ifp;
    int error = 0;

    rtnl_lock();

    if (ifp == NULL) {
        error = ENXIO;
        goto out;
    }
    error = nm_os_generic_find_num_desc(ifp, txd, rxd);  // 获取硬件网卡的槽位个数
    if (error)
        goto out;
    nm_os_generic_find_num_queues(ifp, txr, rxr);  // 获取硬件网卡的TX/RX个数

out:
    rtnl_unlock();
    return error;
}

nm_os_generic_find_num_desc

获取槽位

int nm_os_generic_find_num_desc(struct ifnet *ifp, unsigned int *tx, unsigned int *rx)
{
    int error = EOPNOTSUPP;
#ifdef NETMAP_LINUX_HAVE_GET_RINGPARAM
    struct ethtool_ringparam rp;

    // 通过 ethtool_ops 获取槽位
    if (ifp->ethtool_ops && ifp->ethtool_ops->get_ringparam) {
        ifp->ethtool_ops->get_ringparam(ifp, &rp);
        *tx = rp.tx_pending ? rp.tx_pending : rp.tx_max_pending;
        *rx = rp.rx_pending ? rp.rx_pending : rp.rx_max_pending;
    }
    return error;
}

注意:下面这个 nm_os_generic_find_num_queues( ) 函数需要稍加注意一下

nm_os_generic_find_num_queues

我当时使用 netmap 环境:

  • OS:RHEL6.5 x86_64
  • Kernel:Linux 2.6.32-431

这里有一个问题,linux 2.6.32 的 ethtool_ops 中并没有get_channels,这就导致 RX 被设置成了 1,那么会有什么问题呢?

比如我用 Intel 82599EB 10-Gigabit 测试,网卡是 8 TX 和 8 RX,不过 netmap 测试时,收包相当低,经过排查发现只使用了 1 个 RX。

更改方法见我之前的文章 使用 netmap 提升 ixgbe 性能(续),算是临时方案吧。

获取队列

void nm_os_generic_find_num_queues(struct ifnet *ifp, u_int *txq, u_int *rxq)
{
    struct ethtool_channels ch;
    memset(&ch, 0, sizeof(ch));

    // 也是通过 ethtool_ops 获取队列信息
    if (ifp->ethtool_ops && ifp->ethtool_ops->get_channels) {
        ifp->ethtool_ops->get_channels(ifp, &ch);
        *txq = ch.tx_count ? ch.tx_count : ch.combined_count;
        *rxq = ch.rx_count ? ch.rx_count : ch.combined_count;
    } else {
        *rxq = 1;
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值