高性能网络I/O框架-netmap源码分析(3)
作者:gfree.wind@gmail.com
原文地址:<http://blog.chinaunix.net/uid-23629988-id-3614187.html>
前面学习了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 < NM_BRIDGES; i++) mtx_init(&nm_bridges[i].bdg_lock, "bdg lock", "bdg_lock", MTX_DEF); } #endif return (error); }
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<<17) #define LINE_ROUND 64 /* 这个检查应该是netmap不允许申请过于大的结构的内存池 */ if (objsize >= 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 < p->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 << (i & 31) ); if (i % clustentries == 0 && p->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 < lim; i++, clust += objsize) { /* 1. bitmap是32位,所以i >> 5; 2. 为什么(i&31),也是这个原因;—— 这就是代码的健壮性。 */ p->bitmap[ (i>>5) ] |= ( 1 << (i & 31) ); p->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的整体性和高层面的文章,不拘泥于代码细节。这就需要先把细节看懂才行。
(未完待续。。。)