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

简介: 高性能网络I/O框架-netmap源码分析(1) 作者:gfree.wind@gmail.com 博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net 微博:weibo.com/glinuxer QQ技术群:4367710 前几天听一个朋友提到这个netmap,看了它的介绍和设计,确实是个好东西。

高性能网络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\n",
    ((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即执行完毕。

相关文章
|
2月前
|
Java
网络 I/O:单 Selector 多线程(单线程模型)
网络 I/O:单 Selector 多线程(单线程模型)
|
2月前
|
Java
如何理解网络阻塞 I/O:BIO
如何理解网络阻塞 I/O:BIO
|
2月前
|
存储 消息中间件 监控
一文搞懂常见的网络I/O模型
一文搞懂常见的网络I/O模型
39 0
|
3月前
|
缓存 负载均衡 应用服务中间件
高性能网络编程技术 Nginx 的概念与实践
Nginx 是一款高性能、轻量级的Web服务器和反向代理服务器,它在网络编程技术领域中被广泛应用。本文将详细介绍Nginx的概念和实践,包括其核心原理、功能特点、优势和应用场景等方面。同时,还将深入探讨如何使用Nginx进行高性能网络编程,结合实际案例进行分析。
|
4月前
|
缓存 网络协议 Unix
Linux(UNIX)五种网络I/O模型与IO多路复用
Linux(UNIX)五种网络I/O模型与IO多路复用
108 0
|
4月前
|
云安全 安全 大数据
华为增强版专线卫士高性能真安全,守护甘肃兰投集团网络安全,保障专线体验品质
甘肃移动政企团队协同华为乾坤云安全团队进行现网出入流量检查和分析,通过华为乾坤云平台直观的攻击源信息显示和高级情报分析,发现兰投集团OA办公系统存在wpsAssistServlet任意文件上传漏洞,由于兰投致远OA某些接口能被未授权访问,且安全防护存在不足,攻击者可以通过构造恶意请求,可在未授权的情况下上传恶意脚本文件,导致服务器或其他核心信息资产存在安全隐患。
|
1月前
|
NoSQL Java Linux
【Linux IO多路复用 】 Linux 网络编程 认知负荷与Epoll:高性能I-O多路复用的实现与优化
【Linux IO多路复用 】 Linux 网络编程 认知负荷与Epoll:高性能I-O多路复用的实现与优化
61 0
|
2月前
|
弹性计算 安全 数据中心
阿里云香港服务器提供高性能、安全、CN2高速网络、BGP多线精品
阿里云香港服务器提供高性能、安全、CN2高速网络、BGP多线精品