高性能网络I/O框架-netmap源码分析(2)


作者:gfree.wind@gmail.com
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
微博:weibo.com/glinuxer
QQ技术群:4367710

前面e1000_probe的分析,按照Linux驱动框架,接下来就该e1000_open。netmap并没有对e1000_open进行任何修改,而是改动了e1000_configure,其会被e1000_open及e1000_up调用。

e1000_configure的修改

按照惯例,还是先看diff文件

@@ -393,6 +397,10 @@ static void e1000_configure(struct e1000
    e1000_configure_tx(adapter);
    e1000_setup_rctl(adapter);
    e1000_configure_rx(adapter);
+#ifdef DEV_NETMAP
+   if (e1000_netmap_init_buffers(adapter))
+       return;
+#endif /* DEV_NETMAP */
    /* call E1000_DESC_UNUSED which always leaves
    * at least 1 descriptor unused to make sure
    * next_to_use != next_to_clean */ 

从diff文件可以看出,netmap替代了原有的e1000申请ring buffer的代码。如果e1000_netmap_init_buffers成功返回,e1000_configure就直接退出了。

接下来进入e1000_netmap_init_buffers:

/*
* Make the tx and rx rings point to the netmap buffers.
*/
static int e1000_netmap_init_buffers(struct SOFTC_T *adapter)
{
    struct e1000_hw *hw = &adapter->hw;
    struct ifnet *ifp = adapter->netdev;
    struct netmap_adapter* na = NA(ifp);
    struct netmap_slot* slot;
    struct e1000_tx_ring* txr = &adapter->tx_ring[0];
    unsigned int i, r, si;
    uint64_t paddr;

    /* 
    还记得前面的netmap_attach吗?
    所谓的attach,即申请了netmap_adapter,并将net_device->ax25_ptr保存了指针,并设置了NETMAP_SET_CAPABLE。
    因此这里做一个sanity check,以免影响正常的网卡驱动
    */
    if (!na || !(na->ifp->if_capenable & IFCAP_NETMAP))
        return 0;
    /* e1000_no_rx_alloc如其名,为一个不该调用的函数,只输出一行错误日志 */
    adapter->alloc_rx_buf = e1000_no_rx_alloc;
    for (r = 0; r num_rx_rings; r++) {
        struct e1000_rx_ring *rxr;
        /* 初始化对应的netmap对应的ring */
        slot = netmap_reset(na, NR_RX, r, 0);
        if (!slot) {
            D("strange, null netmap ring %d", r);
            return 0;
        }
        /* 得到e1000对应的ring */
        rxr = &adapter->rx_ring[r];

        for (i = 0; i count; i++) {
            // XXX the skb check and cleanup can go away
            struct e1000_buffer *bi = &rxr->buffer_info[i];
            /* 将当前的buff索引转换为netmap的buff索引 */
            si = netmap_idx_n2k(&na->rx_rings[r], i);
            /* 获得netmap的buff的物理地址 */
            PNMB(slot + si, &paddr);
            if (bi->skb)
                D("rx buf %d was set", i);
            bi->skb = NULL;
            // netmap_load_map(...)
            /* 现在网卡的这个buffer已经指向了netmap申请的buff地址了 */
            E1000_RX_DESC(*rxr, i)->buffer_addr = htole64(paddr);
        }

        rxr->next_to_use = 0;

        /* 
        下面这几行代码没看明白怎么回事。
        有明白的同学指点一下,多谢。
        */
        /* preserve buffers already made available to clients */
        i = rxr->count - 1 - na->rx_rings[0].nr_hwavail;
        if (i count;
        D("i now is %d", i);
        wmb(); /* Force memory writes to complete */
        writel(i, hw->hw_addr + rxr->rdt);
    }

    /* 
    初始化发送ring,与接收类似.
    区别在于没有考虑发送多队列。难道是因为e1000只可能是接收多队列,发送只可能是一个队列?
    这个问题不影响后面的代码阅读。咱们可以暂时将其假设为e1000只有一个发送队列
    */
    /* now initialize the tx ring(s) */
    slot = netmap_reset(na, NR_TX, 0, 0);
    for (i = 0; i num_tx_desc; i++) {
        si = netmap_idx_n2k(&na->tx_rings[0], i);
        PNMB(slot + si, &paddr);
        // netmap_load_map(...)
        E1000_TX_DESC(*txr, i)->buffer_addr = htole64(paddr);
    }
    return 1;
} 

e1000cleanrx_irq的修改

@@ -3952,6 +3973,11 @@ static bool e1000_clean_rx_irq(struct e1
    bool cleaned = false;
    unsigned int total_rx_bytes=0, total_rx_packets=0;

+#ifdef DEV_NETMAP
+   ND("calling netmap_rx_irq");
+   if (netmap_rx_irq(netdev, 0, work_done))
+       return 1; /* seems to be ignored */
+#endif /* DEV_NETMAP */
    i = rx_ring->next_to_clean;
    rx_desc = E1000_RX_DESC(*rx_ring, i);
    buffer_info = &rx_ring->buffer_info[i]; 

进入netmap_rx_irq, int netmaprxirq(struct ifnet *ifp, int q, int *workdone) { struct netmapadapter *na; struct netmap_kring *r; NMSELINFOT *main_wq;

 if (!(ifp->if_capenable & IFCAP_NETMAP))
        return 0;

    na = NA(ifp);

    /* 
    尽管函数名为rx,但实际上这个函数服务于rx和tx两种情况,用work_done做区分。
    */
    if (work_done) { /* RX path */
        r = na->rx_rings + q;
        r->nr_kflags |= NKR_PENDINTR;
        main_wq = (na->num_rx_rings > 1) ? &na->rx_si : NULL;
    } else { /* tx path */
        r = na->tx_rings + q;
        main_wq = (na->num_tx_rings > 1) ? &na->tx_si : NULL;
        work_done = &q; /* dummy */
    }


    /* 
    na->separate_locks只在ixgbe和bridge中会被设置为1。
    根据下面的代码,这个separate_locks表示多队列时,是每个队列使用一个锁。——这样可以提高性能
    其余的代码基本相同。都是唤醒等待数据的进程。
     */
    if (na->separate_locks) {
        mtx_lock(&r->q_lock);
        selwakeuppri(&r->si, PI_NET);
        mtx_unlock(&r->q_lock);
        if (main_wq) {
            mtx_lock(&na->core_lock);
            selwakeuppri(main_wq, PI_NET);
            mtx_unlock(&na->core_lock);
        }
    } else {
        mtx_lock(&na->core_lock);
        selwakeuppri(&r->si, PI_NET);
        if (main_wq)
            selwakeuppri(main_wq, PI_NET);
        mtx_unlock(&na->core_lock);
    }
    *work_done = 1; /* do not fire napi again */
    return 1;
} 


发送部分的修改与接收类似,就不重复了。
今天的学习,到此为止 (未完待续。。。)


上一篇:ZT自老罗的博客 Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析


下一篇:终端神器 iterm