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


    from:http://blog.chinaunix.net/uid-23629988-id-3594118.html

    博主这篇文章写的很好 感觉很有借签意义 值得阅读

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

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

    前几天听一个朋友提到这个netmap,看了它的介绍和设计,确实是个好东西。其设计思想与业界不谋而合——因为为了提高性能,几个性能瓶颈放在那里,解决方法自然也是类似的。

    由于保密性,我是不可能拿公司的设计来和大家讨论和分享的。而netmap的出现,提供了这么一个好机会。它既实现了一个高性能的网络I/O框架,代码量又不算大,非常适合学习和研究。

    netmap简单介绍

    首先要感谢netmap的作者,创造出了netmap并无私的分享了他的设计和代码。netmap的文档写得很不错,这里我简单说明一下为什么netmap可以达到高性能。
    1. 利用mmap,将网卡驱动的ring内存空间映射到用户空间。这样用户态可以直接访问到原始的数据包,避免了内核和用户态的两次拷贝;——前两天我还想写这么一个东西呢。
    2. 利用预先分配的固定大小的buff来保存数据包。这样减少了内核原有的动态分配;——对于网络设备来说,固定大小的内存池比buddy要有效的多。之前我跟Bean_lee也提过此事呵。
    3. 批量处理数据包。这样就减少了系统调用;

    更具体的内容,大家直接去netmap的官方网站上看吧,写得很详细。虽然英文,大家还是耐着性子好好看看,收获良多。

    netmap的源码分析

    从上面netmap的简单介绍中可以看到,netmap不可避免的要修改网卡驱动。不过这个修改量很小。

    驱动的修改

    下面我以e1000.c为例来分析。由于netmap最早是在FreeBSD上实现的,为了在linux达到最小的修改,使用了大量的宏,这给代码的阅读带来了一些困难。

    e1000_probe的修改

    俺不是写驱动的。。。e1000_probe里面很多代码看不明白,但是不影响我们对netmap的分析。通过netmap的patch,知道是在e1000完成一系列硬件初始化以后,并注册成功,这时调用e1000_netmap_attach

    @@ -1175,6 +1183,10 @@ static int __devinit e1000_probe(struct
    if (err)
        goto err_register;
    
    +#ifdef DEV_NETMAP
    +   e1000_netmap_attach(adapter);
    +#endif /* DEV_NETMAP */
    +
    
    /* print bus type/speed/width info */
    e_info(probe, "(PCI%s:%dMHz:%d-bit) %pM
    ",
        ((hw->bus_type == e1000_bus_type_pcix) ? "-X" : ""), 

    下面是e1000_netmap_attach的代码

    static void
    e1000_netmap_attach(struct SOFTC_T *adapter)
    {
        struct netmap_adapter na;
        bzero(&na, sizeof(na));
    
        na.ifp = adapter->netdev;
        na.separate_locks = 0;
        na.num_tx_desc = adapter->tx_ring[0].count;
        na.num_rx_desc = adapter->rx_ring[0].count;
        na.nm_register = e1000_netmap_reg;
        na.nm_txsync = e1000_netmap_txsync;
        na.nm_rxsync = e1000_netmap_rxsync;
        netmap_attach(&na, 1);
    } 

    SOFTC_T是一个宏定义,对于e1000,实际上是e1000_adapter,即e1000网卡驱动对应的private data。 下面是struct netmap_adapter的定义

    /*
    * This struct extends the 'struct adapter' (or
    * equivalent) device descriptor. It contains all fields needed to
    * support netmap operation.
    */
    struct netmap_adapter {
        /*
         * On linux we do not have a good way to tell if an interface
         * is netmap-capable. So we use the following trick:
         * NA(ifp) points here, and the first entry (which hopefully
         * always exists and is at least 32 bits) contains a magic
         * value which we can use to detect that the interface is good.
         */
        uint32_t magic;
        uint32_t na_flags;  /* future place for IFCAP_NETMAP */
        int refcount; /* number of user-space descriptors using this
                 interface, which is equal to the number of
                 struct netmap_if objs in the mapped region. */
        /*
         * The selwakeup in the interrupt thread can use per-ring
         * and/or global wait queues. We track how many clients
         * of each type we have so we can optimize the drivers,
         * and especially avoid huge contention on the locks.
         */
        int na_single;  /* threads attached to a single hw queue */
        int na_multi;   /* threads attached to multiple hw queues */
    
        int separate_locks; /* set if the interface suports different
                       locks for rx, tx and core. */
    
        u_int num_rx_rings; /* number of adapter receive rings */
        u_int num_tx_rings; /* number of adapter transmit rings */
    
        u_int num_tx_desc; /* number of descriptor in each queue */
        u_int num_rx_desc;
    
    
        /* tx_rings and rx_rings are private but allocated
         * as a contiguous chunk of memory. Each array has
         * N+1 entries, for the adapter queues and for the host queue.
         */
        struct netmap_kring *tx_rings; /* array of TX rings. */
        struct netmap_kring *rx_rings; /* array of RX rings. */
    
        NM_SELINFO_T tx_si, rx_si;  /* global wait queues */
    
        /* copy of if_qflush and if_transmit pointers, to intercept
         * packets from the network stack when netmap is active.
         */
        int     (*if_transmit)(struct ifnet *, struct mbuf *);
    
        /* references to the ifnet and device routines, used by
         * the generic netmap functions.
         */
        struct ifnet *ifp; /* adapter is ifp->if_softc */
    
        NM_LOCK_T core_lock;    /* used if no device lock available */
    
        int (*nm_register)(struct ifnet *, int onoff);
        void (*nm_lock)(struct ifnet *, int what, u_int ringid);
        int (*nm_txsync)(struct ifnet *, u_int ring, int lock);
        int (*nm_rxsync)(struct ifnet *, u_int ring, int lock);
    
        int bdg_port;
    #ifdef linux
        struct net_device_ops nm_ndo;
        int if_refcount;    // XXX additions for bridge
    #endif /* linux */
    }; 

    从struct netmap_adapter可以看出,netmap的注释是相当详细。所以后面,我不再列出netmap的结构体定义,大家可以自己查看,免得满篇全是代码。————这样的注释,有几个公司能够做到?

    e1000_netmap_attach完成简单的初始化工作以后,调用netmap_attach执行真正的attach工作。前者是完成与具体驱动相关的attach工作或者说是准备工作,而后者则是真正的attach。

    int
    netmap_attach(struct netmap_adapter *na, int num_queues)
    {
        int n, size;
        void *buf;
        /* 这里ifnet又是一个宏,linux下ifnet实际上是net_device */
        struct ifnet *ifp = na->ifp;
    
        if (ifp == NULL) {
            D("ifp not set, giving up");
            return EINVAL;
        }
        /* clear other fields ? */
        na->refcount = 0;
        /* 初始化接收和发送ring */
        if (na->num_tx_rings == 0)
            na->num_tx_rings = num_queues;
        na->num_rx_rings = num_queues;
        /* on each direction we have N+1 resources
         * 0..n-1   are the hardware rings
         * n        is the ring attached to the stack.
         */
        /* 
        这么详细的注释。。。还用得着我说吗?
        0到n-1的ring是用于转发的ring,而n是本机协议栈的队列
        n+1为哨兵位置
        */
        n = na->num_rx_rings + na->num_tx_rings + 2;
        /* netmap_adapter与其ring统一申请内存 */
        size = sizeof(*na) + n * sizeof(struct netmap_kring);
    
        /* 
        这里的malloc,实际上为kmalloc。  
        这里还有一个小trick。M_DEVBUF,M_NOWAIT和M_ZERO都是FreeBSD的定义。那么在linux下怎么使用呢? 
        我开始以为其被定义为linux对应的flag,如GFP_ATOMIC和__GFP_ZERO,于是grep了M_NOWAIT,也没有找到任何的宏定义。
        正在奇怪的时候,想到一种情况。让我们看看malloc的宏定义
    
    
        /* use volatile to fix a probable compiler error on 2.6.25 */
        #define malloc(_size, type, flags)                      
                ({ volatile int _v = _size; kmalloc(_v, GFP_ATOMIC | __GFP_ZERO); })
        这里type和flags完全没有任何引用的地方。所以在linux下,上面的M_DEVBUG实际上直接被忽略掉了。
        */
        buf = malloc(size, M_DEVBUF, M_NOWAIT | M_ZERO);
        if (buf) {
            /* Linux下重用了struct net_device->ax25_ptr,用其保存buf的地址 */
            WNA(ifp) = buf;
            /* 初始化tx_rings和rx_rings,tx_rings和rx_rings之间用了一个额外的ring分隔,目前不知道这个ring是哨兵呢,还是本主机的ring */
            na->tx_rings = (void *)((char *)buf + sizeof(*na));
            na->rx_rings = na->tx_rings + na->num_tx_rings + 1;
            /* 复制netmap_device并设置对应的标志位,用于表示其为netmap_device*/
            bcopy(na, buf, sizeof(*na));
            NETMAP_SET_CAPABLE(ifp);
    
            na = buf;
            /* Core lock initialized here.  Others are initialized after
             * netmap_if_new.
             */
            mtx_init(&na->core_lock, "netmap core lock", MTX_NETWORK_LOCK,
                MTX_DEF);
            if (na->nm_lock == NULL) {
                ND("using default locks for %s", ifp->if_xname);
                na->nm_lock = netmap_lock_wrapper;
            }
        }
        /* 这几行Linux才用的上的代码,是为linux网卡的驱动框架准备的。未来有用处 */
    #ifdef linux
        if (ifp->netdev_ops) {
            D("netdev_ops %p", ifp->netdev_ops);
            /* prepare a clone of the netdev ops */
            na->nm_ndo = *ifp->netdev_ops;
        }
        na->nm_ndo.ndo_start_xmit = linux_netmap_start;
    #endif
        D("%s for %s", buf ? "ok" : "failed", ifp->if_xname);
    
        return (buf ? 0 : ENOMEM);
    } 

    完成了netmap_attach,e1000的probe函数e1000_probe即执行完毕。

    hadoop大数据相关
  • 相关阅读:
    关于这个 blog
    P6499 [COCI2016-2017#2] Burza 题解
    CF1172F Nauuo and Bug 题解
    CF1479D Odd Mineral Resource 题解
    CF1442E Black, White and Grey Tree 题解
    CF1442D Sum 题解
    CF1025D Recovering BST 题解
    CF1056E Check Transcription 题解
    CF1025F Disjoint Triangles 题解
    红包算法的PHP实现
  • 原文地址:https://www.cnblogs.com/zhangzl/p/4402732.html
Copyright © 2020-2023  润新知