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

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

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

前面学习了netmap对e1000网卡驱动的修改,今天开始进入netmap的核心代码。一切从init开始。。。

netmap_init

Linux环境下,netmap使用动态模块加载,由linuxnetmapinit调用netmap_init。

static int
netmap_init(void)
{
    int error;

    /* 
    申请netmap的各个内存池,包括netmap_if,netmap_ring,netmap_buf以及内存池的管理结构
    */
    error = netmap_memory_init();
    if (error != 0) {
        printf("netmap: unable to initialize the memory allocator.\n");
        return (error);
    }
    printf("netmap: loaded module with %d Mbytes\n",
        (int)(nm_mem->nm_totalsize >> 20));

    /* 
    在Linux上,调用的实际上是misc_register。make_dev为一共宏定义。
    创建一个名为netmap的misc设备,作为userspace和kernel的接口
    */
    netmap_dev = make_dev(&netmap_cdevsw, 0, UID_ROOT, GID_WHEEL, 0660,
                  "netmap");

#ifdef NM_BRIDGE
    {
        int i;
        for (i = 0; i 

netmapmemoryinit

netmap目前有两套内存分配管理代码,一个是netmapmem1.c,另一个是netmapmem2.c。默认使用的是后者。

static int
netmap_memory_init(void)
{
    struct netmap_obj_pool *p;

    /* 先申请netmap内存管理结构 */
    nm_mem = malloc(sizeof(struct netmap_mem_d), M_NETMAP,
                  M_WAITOK | M_ZERO);
    if (nm_mem == NULL)
        goto clean;

    /* netmap_if的内存池 */
    p = netmap_new_obj_allocator("netmap_if",
        NETMAP_IF_MAX_NUM, NETMAP_IF_MAX_SIZE);
    if (p == NULL)
        goto clean;
    nm_mem->nm_if_pool = p;

    /* netmap_ring的内存池 */
    p = netmap_new_obj_allocator("netmap_ring",
        NETMAP_RING_MAX_NUM, NETMAP_RING_MAX_SIZE);
    if (p == NULL)
        goto clean;
    nm_mem->nm_ring_pool = p;

    /* netmap_buf的内存池 */
    p = netmap_new_obj_allocator("netmap_buf",
        NETMAP_BUF_MAX_NUM, NETMAP_BUF_SIZE);
    if (p == NULL)
        goto clean;

    /* 对于netmap_buf,为了以后的使用方便,将其中的一些信息保存到其它明确的全局变量中 */
    netmap_total_buffers = p->objtotal;
    netmap_buffer_lut = p->lut;
    nm_mem->nm_buf_pool = p;
    netmap_buffer_base = p->lut[0].vaddr;


    mtx_init(&nm_mem->nm_mtx, "netmap memory allocator lock", NULL,
         MTX_DEF);

    nm_mem->nm_totalsize =
        nm_mem->nm_if_pool->_memtotal +
        nm_mem->nm_ring_pool->_memtotal +
        nm_mem->nm_buf_pool->_memtotal;

    D("Have %d KB for interfaces, %d KB for rings and %d MB for buffers",
        nm_mem->nm_if_pool->_memtotal >> 10,
        nm_mem->nm_ring_pool->_memtotal >> 10,
        nm_mem->nm_buf_pool->_memtotal >> 20);
    return 0;

clean:
    if (nm_mem) {
        netmap_destroy_obj_allocator(nm_mem->nm_ring_pool);
        netmap_destroy_obj_allocator(nm_mem->nm_if_pool);
        free(nm_mem, M_NETMAP);
    }
    return ENOMEM;
} 

netmapnewobj_allocator

进入内存池的申请函数——这是netmap中比较长的函数了。

static struct netmap_obj_pool *
netmap_new_obj_allocator(const char *name, u_int objtotal, u_int objsize)
{
    struct netmap_obj_pool *p;
    int i, n;
    u_int clustsize;    /* the cluster size, multiple of page size */
    u_int clustentries; /* how many objects per entry */

#define MAX_CLUSTSIZE   (1= MAX_CLUSTSIZE) {
        /* we could do it but there is no point */
        D("unsupported allocation for %d bytes", objsize);
        return NULL;
    }

    /* 
    让obj的size取整到64字节。为啥呢? 
    因为CPU的cache line大小一般是64字节。所以object的size如果和cache line对齐,可以获得更好的性能。
    关于cache line对性能的影响,可以看一下我以前写得一篇博文《多核编程:选择合适的结构体大小,提高多核并发性能》
    */
    /* make sure objsize is a multiple of LINE_ROUND */
    i = (objsize & (LINE_ROUND - 1));
    if (i) {
        D("XXX aligning object by %d bytes", LINE_ROUND - i);
        objsize += LINE_ROUND - i;
    }
    /*
     * Compute number of objects using a brute-force approach:
     * given a max cluster size,
     * we try to fill it with objects keeping track of the
     * wasted space to the next page boundary.
     */
    /*
    这里有一个概念:cluster。
    暂时没有找到相关的文档介绍这里的cluster的概念。
    这里,我只能凭借下面的代码来说一下我的理解:
    cluster是一组内存池分配对象object的集合。为什么要有这么一个集合呢?
    众所周知,Linux的内存管理是基于页的。而object的大小或小于一个页,或大于一个页。如果基于object本身进行内存分配,会造成内存的浪费。
    所以这里引入了cluster的概念,它占用一个或多个连续页。这些页的内存大小或为object大小的整数倍,或者是浪费空间最小。
    下面的方法是一个比较激进的计算cluster的方法,它尽可能的追求上面的目标直到cluster的占用的大小超出设定的最大值——MAX_CLUSTSIZE。
    */
    for (clustentries = 0, i = 1;; i++) {
        u_int delta, used = i * objsize;
        /* 不能一味的增长cluster,最大占用空间为MAX_CLUSTSIZE */
        if (used > MAX_CLUSTSIZE)
            break;
        /* 最后页面占用的空间 */
        delta = used % PAGE_SIZE;
        if (delta == 0) { // exact solution
            clustentries = i;
            break;
        }
        /* 这次利用页面空间的效率比上次的高,所以更新当前的clustentries,即cluster的个数*/
        if (delta > ( (clustentries*objsize) % PAGE_SIZE) )
            clustentries = i;
    }
    // D("XXX --- ouch, delta %d (bad for buffers)", delta);
    /* compute clustsize and round to the next page */
    /* 得到cluster的大小,并将其与PAGE SIZE对齐 */
    clustsize = clustentries * objsize;
    i =  (clustsize & (PAGE_SIZE - 1));
    if (i)
        clustsize += PAGE_SIZE - i;
    D("objsize %d clustsize %d objects %d",
        objsize, clustsize, clustentries);

    /* 申请内存池管理结构的内存 */
    p = malloc(sizeof(struct netmap_obj_pool), M_NETMAP,
        M_WAITOK | M_ZERO);
    if (p == NULL) {
        D("Unable to create '%s' allocator", name);
        return NULL;
    }
    /*
     * Allocate and initialize the lookup table.
     *
     * The number of clusters is n = ceil(objtotal/clustentries)
     * objtotal' = n * clustentries
     */
    /* 初始化内存池管理结构 */
    strncpy(p->name, name, sizeof(p->name));
    p->clustentries = clustentries;
    p->_clustsize = clustsize;
    /* 根据要设定的内存池object的数量,来调整cluster的个数 */
    n = (objtotal + clustentries - 1) / clustentries;
    p->_numclusters = n;
    /* 这是真正的内存池中的object的数量,通常是比传入的参数objtotal要多 */
    p->objtotal = n * clustentries;
    /* 为什么0和1是reserved,暂时不明。搁置争议,留给后面解决吧。:) */
    p->objfree = p->objtotal - 2; /* obj 0 and 1 are reserved */
    p->_objsize = objsize;
    p->_memtotal = p->_numclusters * p->_clustsize;

    /* 物理地址与虚拟地址对应的查询表 */
    p->lut = malloc(sizeof(struct lut_entry) * p->objtotal,
        M_NETMAP, M_WAITOK | M_ZERO);
    if (p->lut == NULL) {
        D("Unable to create lookup table for '%s' allocator", name);
        goto clean;
    }

    /* Allocate the bitmap */
    /* 申请内存池位图,用于表示那个object被分配了 */
    n = (p->objtotal + 31) / 32;
    p->bitmap = malloc(sizeof(uint32_t) * n, M_NETMAP, M_WAITOK | M_ZERO);
    if (p->bitmap == NULL) {
        D("Unable to create bitmap (%d entries) for allocator '%s'", n,
            name);
        goto clean;
    }
    /*
     * Allocate clusters, init pointers and bitmap
     */
    for (i = 0; i objtotal;) {
        int lim = i + clustentries;
        char *clust;

        clust = contigmalloc(clustsize, M_NETMAP, M_WAITOK | M_ZERO,
            0, -1UL, PAGE_SIZE, 0);
        if (clust == NULL) {
            /*
             * If we get here, there is a severe memory shortage,
             * so halve the allocated memory to reclaim some.
             */
            D("Unable to create cluster at %d for '%s' allocator",
                i, name);
            lim = i / 2;
            for (; i >= lim; i--) {
                p->bitmap[ (i>>5) ] &=  ~( 1 lut[i].vaddr)
                    contigfree(p->lut[i].vaddr,
                        p->_clustsize, M_NETMAP);
            }
            p->objtotal = i;
            p->objfree = p->objtotal - 2;
            p->_numclusters = i / clustentries;
            p->_memtotal = p->_numclusters * p->_clustsize;
            break;
        }
        /* 初始化位图即虚拟地址和物理地址插叙表 */
        for (; i > 5;
            2. 为什么(i&31),也是这个原因;—— 这就是代码的健壮性。
            */
            p->bitmap[ (i>>5) ] |=  ( 1 lut[i].vaddr = clust;
            p->lut[i].paddr = vtophys(clust);
        }
    }

    /* 与前面一样,保留第0位和第1位。再次搁置争议。。。 */
    p->bitmap[0] = ~3; /* objs 0 and 1 is always busy */
    D("Pre-allocated %d clusters (%d/%dKB) for '%s'",
        p->_numclusters, p->_clustsize >> 10,
        p->_memtotal >> 10, name);

    return p;

clean:
    netmap_destroy_obj_allocator(p);
    return NULL;
} 

netmapnewobj_allocator的分析结束。关于netmap的内存管理,依然按照事件的主线分析,而不是集中将一部分搞定。

以后带全部看完netmap的代码后,会写一些netmap的整体性和高层面的文章,不拘泥于代码细节。这就需要先把细节看懂才行。

(未完待续。。。)

上一篇:前端优化系列 - 基于UC内核的极致Web体验


下一篇:无影云电脑启动大规模商用 已服务众多行业客户