分类: LINUX
高性能网络I/O框架-netmap源码分析(6)
作者:gfree.wind@gmail.com
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
微博:weibo.com/glinuxer
QQ技术群:4367710
因为最近比较忙,很久没有更新博客了,但是netmap源码分析还没有写完,今天继续分析。这里就显示出了写博客的好处。如果不是写博客,可能自己私下看代码,看了一半,就半途而废了。而写了博客,每次打开自己的主页,看到还未完成的系列,就会坚持把这件事情做完。
唠了一点闲话,也是鼓励大家多写文章,给ChinaUnix贡献一些文字吧。
上次已经把netmap_ioctl分析完了,根据netmap的示例,下面该分析netmap的mmap的实现了。
定位netmap的mmap
前文提到过netmap会创建一个设备
static struct miscdevice netmap_cdevsw = { /* same name as FreeBSD */ MISC_DYNAMIC_MINOR, "netmap", &netmap_fops, };
netmap_fops定义了netmap设备支持的操作
static struct file_operations netmap_fops = { .mmap = linux_netmap_mmap, LIN_IOCTL_NAME = linux_netmap_ioctl, .poll = linux_netmap_poll, .release = netmap_release, };
OK,现在我们找到了mmap的入口,linuxnetmapmmap。
linux_netmap_mmap分析
现在直接进入linux_netmap_mmap的代码
static int linux_netmap_mmap(struct file *f, struct vm_area_struct *vma) { int lut_skip, i, j; int user_skip = 0; struct lut_entry *l_entry; const struct netmap_obj_pool *p[] = { nm_mem->nm_if_pool, nm_mem->nm_ring_pool, nm_mem->nm_buf_pool }; /* * vma->vm_start: start of mapping user address space * vma->vm_end: end of the mapping user address space */ /* 这里又是一个编程技巧,使用(void)f既不会产生任何真正的代码,又可以消除变量f没有使用的warning。 为什么f不使用,还会出现在参数列表中呢?没办法啊,只是Linux框架决定的。linux_netmap_mmap只是一个注册回调,自然要遵从linux的框架了。 */ (void)f; /* UNUSED */ // XXX security checks for (i = 0; i < 3; i++) { /* loop through obj_pools */ /* * In each pool memory is allocated in clusters * of size _clustsize , each containing clustentries * entries. For each object k we already store the * vtophys malling in lut[k] so we use that, scanning * the lut[] array in steps of clustentries, * and we map each cluster (not individual pages, * it would be overkill). */ /* 上面的注释说的很明白。 每个pool里的object都是由_clustsize组成的,每一个都包含clustertries个基础内存块。 一个pool公有_numclusters个基础内存块。 所以,在进行内存映射的时候,user_skip表示已经映射的内存大小,vma->start+user_skip也就是当前未映射内存的起始地址,lut_skip表示当前待映射的物理内存池的块索引 */ for (lut_skip = 0, j = 0; j < p[i]->_numclusters; j++) { l_entry = &p[i]->lut[lut_skip]; if (remap_pfn_range(vma, vma->vm_start + user_skip, l_entry->paddr >> PAGE_SHIFT, p[i]->_clustsize, vma->vm_page_prot)) return -EAGAIN; // XXX check return value lut_skip += p[i]->clustentries; user_skip += p[i]->_clustsize; } } /* 循环执行完毕后,netmap在内核中的3个对象池已经完全映射到用户空间 真正执行映射的函数是remap_pfn_range,这是内核函数,用于将内核空间映射到用户空间 这个函数超出了本文的主题范围了,我们只需要知道它是做什么的就行了。 */ return 0; }
用户态得到对应网卡的netmap结构
在将netmap内核态的内存映射到用户空间以后,netmap的示例通过offset来得到对应网卡的netmap结构。
fd = open("/dev/netmap", 0); strcpy(req.nr_name, "ix0"); // register the interface ioctl(fd, NIOCREG, &req); // offset of the structure mem = mmap(NULL, req.nr_memsize, PROT_READ|PROT_WRITE, 0, fd, 0); nifp = NETMAP_IF(mem, req.nr_offset);
在此例中,使用ioctl,得到req.nroffset是ix0网卡的netmap结构的偏移——准确的说是netmap管理网卡结构内存池的偏移。mmap后,mem是netmap内存的映射,而网卡结构内存是内存中的第一项,那么mem同样可以视为netmap管理网卡结构的内存池的起始地址。因此,利用前面的req.nroffset,就得到了ix0的netmap结构,即struct netmap_if。
走读netmap的示例中工作代码
按照netmap示例,马上就要进入netmap真正工作的代码了。
for (;;) { struct pollfd x[1]; /* 根据netmap的代码,NETMAP_RXRING的定义如下 #define NETMAP_RXRING(nifp, index) \ ((struct netmap_ring *)((char *)(nifp) + \ (nifp)->ring_ofs[index + (nifp)->ni_tx_rings + 1] ) ) 得到该网卡的接收ring buffer。 吐个槽,为什么英文接收Receive要缩写为RX呢。。。我在别的地方也见过。 */ struct netmap_ring *ring = NETMAP_RX_RING(nifp, 0); x[0].fd = fd; x[0].events = POLLIN; /* 超时1秒等接收事件发生 */ poll(x, 1, 1000); /* 收到ring->avail个包 */ for ( ; ring->avail > 0 ; ring->avail--) { /* 得到当前包索引 */ i = ring->cur; /* 得到对应的数据包 */ buf = NETMAP_BUF(ring, i); /* 用户态处理该数据包 */ use_data(buf, ring->slot[i].len); /* 移到下一个待处理数据包 */ ring->cur = NETMAP_NEXT(ring, i); } }
(netmap源码分析,未完待续。。。)