高性能网络I/O框架-netmap源码分析(4)
作者:gfree.wind@gmail.com
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
微博:weibo.com/glinuxer
QQ技术群:4367710
前面的文章学习了netmap对驱动的修改,以及netmap的初始化和加载。接下来就要从netmap的使用,自上而下的学习分析一下netmap的代码了。
netmap的应用示例
netmap的网站上给出了一个简单的例子——说简单,其实也涵盖了netmap的框架的调用。
struct netmap_if *nifp; struct nmreq req; int i, len; char *buf; 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); for (;;) { struct pollfd x[1]; struct netmap_ring *ring = NETMAP_RX_RING(nifp, 0); x[0].fd = fd; x[0].events = POLLIN; poll(x, 1, 1000); 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); } }
咱们还是一路走来,走到哪看到哪。
open操作
这个其实跟netmap没有多大关系。记得前文中的netmap注册了一个misc设备netmap_cdevsw吗?
static struct file_operations netmap_fops = { .mmap = linux_netmap_mmap, LIN_IOCTL_NAME = linux_netmap_ioctl, .poll = linux_netmap_poll, .release = netmap_release, }; static struct miscdevice netmap_cdevsw = { /* same name as FreeBSD */ MISC_DYNAMIC_MINOR, "netmap", &netmap_fops, };
netmapcdevsw为对应的设备结构体定义,netmapfops为对应的操作函数。这里面没有自定义的open函数,那么应该就使用linux内核默认的open——这个是我的推测,暂时不去查看linux代码了。
NIOCREG ioctl操作
ioctl就是内核的一个垃圾桶啊,什么都往里装,什么都能做。
netmap的ioctl
long linux_netmap_ioctl(struct file *file, u_int cmd, u_long data /* arg */) { int ret; struct nmreq nmr; bzero(&nmr, sizeof(nmr)); /* 从上面的例子和这里可以看出,struct nmreq就是netmap内核与用户空间的消息结构体。 两者的互动就靠它了。 */ if (data && copy_from_user(&nmr, (void *)data, sizeof(nmr) ) != 0) return -EFAULT; ret = netmap_ioctl(NULL, cmd, (caddr_t)&nmr, 0, (void *)file); if (data && copy_to_user((void*)data, &nmr, sizeof(nmr) ) != 0) return -EFAULT; return -ret; }
进入netmap_ioctl,真正的netmap的ioctl处理函数
static int netmap_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct netmap_priv_d *priv = NULL; struct ifnet *ifp; struct nmreq *nmr = (struct nmreq *) data; struct netmap_adapter *na; int error; u_int i, lim; struct netmap_if *nifp; /* 为了去除warning警告——没用的参数。 void应用的一个小技巧 */ (void)dev; /* UNUSED */ (void)fflag; /* UNUSED */ /* Linux下这两个红都是空的 */ CURVNET_SET(TD_TO_VNET(td)); /* devfs_get_cdevpriv在linux下是一个宏定义。 得到struct file->private_data; 当private_data不为NULL时,返回0;为null时,返回ENOENT。 所以对于linux,后面的条件判断永远为假 */ error = devfs_get_cdevpriv((void **)&priv); if (error != ENOENT && error != 0) { CURVNET_RESTORE(); return (error); } error = 0; /* Could be ENOENT */ /* 又可见到高手代码健壮性的体现。 对于运行在kernel中的代码,一定要稳定!强制保证nmr->nr_name字符串长度的合法性 */ nmr->nr_name[sizeof(nmr->nr_name) - 1] = '\0'; /* truncate name */ 。。。。。。 。。。。。。
为了流程的清楚,对于netmap_ioctl的分析就到这里。依然按照之前的使用的流程走。
写到这里我发现netmap网站给的实例应该是老古董了。按照netmap当前的代码,上面的例子根本无法使用。不过木已成舟,大家凑合意会理解这个例子吧,还好流程没有太大的变化。
既然示例代码不可信了,那么就按照ioctl支持的命令顺序,来分析netmap吧。
NIOCGINFO
用于返回netmap的基本信息
case NIOCGINFO: /* return capabilities etc */ /* memsize is always valid */ /* 如果是我写,我可能先去做后面的版本检查 netmap这样选择,应该是因为这些信息与版本无关。 */ nmr->nr_memsize = nm_mem->nm_totalsize; nmr->nr_offset = 0; nmr->nr_rx_rings = nmr->nr_tx_rings = 0; nmr->nr_rx_slots = nmr->nr_tx_slots = 0; if (nmr->nr_version != NETMAP_API) { D("API mismatch got %d have %d", nmr->nr_version, NETMAP_API); nmr->nr_version = NETMAP_API; error = EINVAL; break; } if (nmr->nr_name[0] == '\0') /* just get memory info */ break; /* Linux下调用dev_get_by_name通过网卡名得到网卡struct net_device。 并且通过NETMAP_CAPABLE来检查netmap是否attach了这个net_device——忘记NETMAP_CAPABLE和attach的同学请自行查看前面几篇文章。 */ error = get_ifp(nmr->nr_name, &ifp); /* get a refcount */ if (error) break; /* 得到attach到网卡结构的netmap结构体 */ na = NA(ifp); /* retrieve netmap_adapter */ /* 得到ring的个数,以及每个ring有多少slot */ nmr->nr_rx_rings = na->num_rx_rings; nmr->nr_tx_rings = na->num_tx_rings; nmr->nr_rx_slots = na->num_rx_desc; nmr->nr_tx_slots = na->num_tx_desc; nm_if_rele(ifp); /* return the refcount */ break;
NIOCREGIF
将特定的网卡设置为netmap模式
case NIOCREGIF: if (nmr->nr_version != NETMAP_API) { nmr->nr_version = NETMAP_API; error = EINVAL; break; } if (priv != NULL) { /* thread already registered */ /* 重新设置对哪个ring感兴趣,这个函数,留到后面说 */ error = netmap_set_ringid(priv, nmr->nr_ringid); break; } /* 下面几行拿到netmap_device结构的代码,和NIOCGINFO case没什么区别 */ /* find the interface and a reference */ error = get_ifp(nmr->nr_name, &ifp); /* keep reference */ if (error) break; na = NA(ifp); /* retrieve netmap adapter */ /* * Allocate the private per-thread structure. * XXX perhaps we can use a blocking malloc ? */ priv = malloc(sizeof(struct netmap_priv_d), M_DEVBUF, M_NOWAIT | M_ZERO); if (priv == NULL) { error = ENOMEM; nm_if_rele(ifp); /* return the refcount */ break; } /* 这里循环等待net_device可用 */ for (i = 10; i > 0; i--) { na->nm_lock(ifp, NETMAP_REG_LOCK, 0); if (!NETMAP_DELETING(na)) break; na->nm_lock(ifp, NETMAP_REG_UNLOCK, 0); tsleep(na, 0, "NIOCREGIF", hz/10); } if (i == 0) { D("too many NIOCREGIF attempts, give up"); error = EINVAL; free(priv, M_DEVBUF); nm_if_rele(ifp); /* return the refcount */ break; } /* 保存设备net_device指针*/ priv->np_ifp = ifp; /* store the reference */ /* 设置感兴趣的ring,即准备哪些ring来与用户态交互 */ error = netmap_set_ringid(priv, nmr->nr_ringid); if (error) goto error; /* 每一个netmap的描述符,对应每一个网卡,都有一个struct netmap_if, 即priv->np_nifp. */ priv->np_nifp = nifp = netmap_if_new(nmr->nr_name, na); if (nifp == NULL) { /* allocation failed */ error = ENOMEM; } else if (ifp->if_capenable & IFCAP_NETMAP) { /* was already set */ /* 网卡对应的netmap_device的扩展已经设置过了 */ } else { /* Otherwise set the card in netmap mode * and make it use the shared buffers. */ /* 这时,这块网卡真正要进入netmap模式,开始初始化一些成员变量 */ for (i = 0 ; i num_tx_rings + 1; i++) mtx_init(&na->tx_rings[i].q_lock, "nm_txq_lock", MTX_NETWORK_LOCK, MTX_DEF); for (i = 0 ; i num_rx_rings + 1; i++) { mtx_init(&na->rx_rings[i].q_lock, "nm_rxq_lock", MTX_NETWORK_LOCK, MTX_DEF); } /* 设置网卡为netmap mode为打开模式 对于e1000驱动来说,nm_register即e1000_netmap_reg */ error = na->nm_register(ifp, 1); /* mode on */ if (error) netmap_dtor_locked(priv); } if (error) { /* reg. failed, release priv and ref */ error: na->nm_lock(ifp, NETMAP_REG_UNLOCK, 0); nm_if_rele(ifp); /* return the refcount */ bzero(priv, sizeof(*priv)); free(priv, M_DEVBUF); break; } na->nm_lock(ifp, NETMAP_REG_UNLOCK, 0); /* Linux平台,将priv保存到file->private_data*/ error = devfs_set_cdevpriv(priv, netmap_dtor); if (error != 0) { /* could not assign the private storage for the * thread, call the destructor explicitly. */ netmap_dtor(priv); break; } /* return the offset of the netmap_if object */ nmr->nr_rx_rings = na->num_rx_rings; nmr->nr_tx_rings = na->num_tx_rings; nmr->nr_rx_slots = na->num_rx_desc; nmr->nr_tx_slots = na->num_tx_desc; nmr->nr_memsize = nm_mem->nm_totalsize; /* 得到nifp在内存池中的偏移。 因为netmap的基础就是利用内核与用户空间的内存共享。但是众所周知,内核和用户空间的地址范围是不用的。 这样同样的物理内存,在内核态和用户态地址肯定不同。所以必须利用偏移来对应相同的内存。 */ nmr->nr_offset = netmap_if_offset(nifp); break;
这个NIOCREGIF太长了,其它的case留到下一篇吧
http://info.iet.unipi.it/~luigi/netmap/
http://www.manualpages.de/FreeBSD/FreeBSD-8.3-RELEASE/man4/netmap.4.html