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

高性能网络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

上一篇:第三方Mac应用软件安全问题在哪?


下一篇:《软件工程方法与实践》—— 1.4 软件工程的基本原理与基本原则