xen网络后端驱动分析(发送篇)

这篇来研究netback的报文发送,后端设备创建过程中会调用netif_map,映射tx, rx的IO ring,同时绑定event channel中断。当netfront要发送skb时,会触发irq让后端发送,对应的ISR为netif_be_int

irqreturn_t netif_be_int(int irq, void *dev_id)
{
    struct xen_netif *netif = dev_id;

    add_to_net_schedule_list_tail(netif);
    maybe_schedule_tx_action();

直接触发net_tx_action软中断发送

    if (netif_schedulable(netif) && !netbk_queue_full(netif))
        netif_wake_queue(netif->dev);

netif_wake_queue最终取出qdisc上queue的skb,触发net_tx_action软中断发送

    return IRQ_HANDLED;
}


netback报文发送会用到如下数据结构

static struct page **mmap_pages

mmap_pages是后端用来让前端映射的页数组,前端把需要发送的sg类型skb对应的page通过grant table映射到后端mmap_pages指向的页(如果映射失败,那么拷贝skb内容到page中)。对于这些page而言,page->mapping - 1被用来存放page在mmap_pages中的index,e.g.

static inline void netif_set_page_index(struct page *pg, unsigned int index)
{
    *(unsigned long *)&pg->mapping = index + 1;
}

static inline int netif_page_index(struct page *pg)
{
    unsigned long idx = (unsigned long)pg->mapping - 1;

    if (!PageForeign(pg))
        return -1;

    if ((idx >= MAX_PENDING_REQS) || (mmap_pages[idx] != pg))
        return -1;

    return idx;
}

netback_init的初始化例程会初始化好mmap_pages数组,

    mmap_pages = alloc_empty_pages_and_pagevec(MAX_PENDING_REQS);

    for (i = 0; i < MAX_PENDING_REQS; i++) {
        page = mmap_pages[i];
        SetPageForeign(page, netif_page_release);
        netif_set_page_index(page, i);
        INIT_LIST_HEAD(&pending_inuse[i].list);
    }

SetPageForeign把mmap_pages中的page设置成为foreign,这是xen支持的一种特殊的page,大致是说page的owener是其他的domain。同时page的析构函数为netif_page_release,函数通过page查找到mmap_pages的index,然后调用了netif_idx_release

static void netif_idx_release(u16 pending_idx)
{
    static DEFINE_SPINLOCK(_lock);
    unsigned long flags;

    spin_lock_irqsave(&_lock, flags);
    dealloc_ring[pending_index(dealloc_prod)] = pending_idx;
    /* Sync with net_tx_action_dealloc: insert idx /then/ incr producer. */
    smp_wmb();
    dealloc_prod++;
    spin_unlock_irqrestore(&_lock, flags);

    tasklet_schedule(&net_tx_tasklet);
}

netif_idx_release把mmap_pages中第pending_idx个page挂到dealloc_prod下面,触发net_tx_tasklet软中断。在软中断处理例程net_tx_action中会调用net_tx_action_dealloc处理从dealloc_cons到dealloc_prod之间的需要释放的page


static u16 dealloc_ring[MAX_PENDING_REQS];
static pending_ring_idx_t dealloc_prod, dealloc_cons;

dealloc_ring是一个线性数组用来当做ring使用,dealloc_prod, dealloc_cons分别是单向增长的生产者和消费者


/* Doubly-linked list of in-use pending entries. */
static struct netbk_tx_pending_inuse pending_inuse[MAX_PENDING_REQS];
static LIST_HEAD(pending_inuse_head);

struct netbk_tx_pending_inuse {
    struct list_head list;
    unsigned long alloc_time;
};

pending_inuse实际是一个list_head的数组,其中某些list_head组成了一个double-linked list,头部是pending_inuse_head。这个list_head代表了目前正在等待被发送的request


static struct pending_tx_info {
    struct xen_netif_tx_request req;
    struct xen_netif *netif;
} pending_tx_info[MAX_PENDING_REQS];
static u16 pending_ring[MAX_PENDING_REQS];

static pending_ring_idx_t pending_prod, pending_cons;

pending_prod, pending_cons是pending_ring数组的生产者和消费者,pending_ring数组存放的是pending_tx_info的index值,每一个pending_tx_info结构体都包含了代表后端设备的xen_netif结构体指针和代表发送请求的xen_netif_tx_request指针


inline static void net_tx_action_dealloc(void)
{
    struct netbk_tx_pending_inuse *inuse, *n;
    struct gnttab_unmap_grant_ref *gop;
    u16 pending_idx;
    pending_ring_idx_t dc, dp;
    struct xen_netif *netif;
    int ret;
    LIST_HEAD(list);

    dc = dealloc_cons;
    gop = tx_unmap_ops;

    /*
     * Free up any grants we have finished using
     */

    do {
        dp = dealloc_prod;

        /* Ensure we see all indices enqueued by netif_idx_release(). */
        smp_rmb();

        while (dc != dp) {
            unsigned long pfn;

            pending_idx = dealloc_ring[pending_index(dc++)];
            list_move_tail(&pending_inuse[pending_idx].list, &list);

            pfn = idx_to_pfn(pending_idx);
            /* Already unmapped? */
            if (!phys_to_machine_mapping_valid(pfn))
                continue;

            gnttab_set_unmap_op(gop, idx_to_kaddr(pending_idx),
                        GNTMAP_host_map,
                        grant_tx_handle[pending_idx]);
            gop++;
        }

这段代码遍历从dealloc_cons到dealloc_prod的dealloc_ring数组项,dealloc_ring中保存了对应的mmap_pages的index. 在pending_inuse数组中找到对应的netbk_tx_pending_inuse项,把list_head从原来的pending_inuse_head的list_head中拿下来,加到新的list中

同时,对每一个pending_idx,设置gnttab_unmap_grant_ref结构体,用来删除一个GR,其中GR的handle为grant_tx_handle[pending_idx],GR的host地址为idx_to_kaddr(pending_idx)

所有生成的gnttab_unmap_grant_ref数组被存到tx_unmap_ops数组中

        if (netbk_copy_skb_mode != NETBK_DELAYED_COPY_SKB ||
            list_empty(&pending_inuse_head))
            break;

        /* Copy any entries that have been pending for too long. */
        list_for_each_entry_safe(inuse, n, &pending_inuse_head, list) {
            if (time_after(inuse->alloc_time + HZ / 2, jiffies))
                break;

            pending_idx = inuse - pending_inuse;

            pending_tx_info[pending_idx].netif->nr_copied_skbs++;

            switch (copy_pending_req(pending_idx)) {
            case 0:
                list_move_tail(&inuse->list, &list);
                continue;
            case -EBUSY:
                list_del_init(&inuse->list);
                continue;
            case -ENOENT:
                continue;
            }
            break;
        }

对于在pending_inuse_head的list中停留时间过长的请求,会调用copy_pending_req把mmap_pages中对应的页替换为一个新的页,该过程通过调用gnttab_copy_grant_page完成。

gnttab_copy_grant_page会通过alloc_page申请一个新页,把老页的内容memcpy给新页,之后通过GNTTAB_unmap_and_replace的操作,把原来映射到老页的GR,映射到新页上


    } while (dp != dealloc_prod);

    dealloc_cons = dc;

    ret = HYPERVISOR_grant_table_op(
        GNTTABOP_unmap_grant_ref, tx_unmap_ops, gop - tx_unmap_ops);
    BUG_ON(ret);

通过GNTTAB_unmap_grant_ref操作,释放GR,下面就可以告诉前端,发送已经完成,这些pages可以用来做下一次发送了



    list_for_each_entry_safe(inuse, n, &list, list) {
        pending_idx = inuse - pending_inuse;

        netif = pending_tx_info[pending_idx].netif;

        make_tx_response(netif, &pending_tx_info[pending_idx].req,
                 NETIF_RSP_OKAY);

        /* Ready for next use. */
        gnttab_reset_grant_page(mmap_pages[pending_idx]);
        pending_ring[pending_index(pending_prod++)] = pending_idx;
        netif_put(netif);
        list_del_init(&inuse->list);
    }

对于pending_inuse的page,调用make_tx_response生成xen_netif_tx_response并通知前端发送成功,返回给前端NETIF_RSP_OKAY。同时调用gnttab_reset_grant_page重置mmap_pages中对应的page. 这些空闲的page又可以拿来给下一次请求使用

}

这里我们基本可以猜出几个MAX_PENDING_REQS大小的数组的作用了:

1. mmap_pages是一个struct page* 数组,用来存放用来给GR映射的页指针,与之对应的是grant_tx_handle数组,存放GR对应的grant_handle_t

2. 当前端发送请求来到后端之后,会被映射到mmap_pages中空闲的页中(我们知道一个xen_netif_tx_request最多只对应一个page,请看我对netfront代码的分析),此时通过pending_ring来获取空闲的mmap_pages。pending_ring的pending_prod指向空闲页在mmap_pages中的位置。

3. pending_prod指向的pending_ring_idx_t同时也指向了pending_tx_info数组中请求内容的位置。这个请求的内容xen_netif_tx_request,和对应的netback设备xen_netif都存在这个pending_tx_info结构体中

4. pending_cons用来处理pending_prod指向的前端发送请求,所有正在被后端发送的请求都会被放入struct netbk_tx_pending_inuse pending_inuse数组,这里struct netbk_tx_pending_inuse实际是一个list_head,pending_inuse_head是这个list_head的头部,list成员按照alloc time从早到晚依次加入到尾部

5. 当请求被成功发送后,dealloc_prod会相应增加。dealloc_ring数组用来存放需要被释放页在mmap_pages中的位置。一旦某个page被释放,其pending_inuse_idx_t对应的pending_inuse数组成员会被从pending_inuse_head链表中移除掉

6. 总结:pending_ring, dealloc_ring构成了一个IO环,可以把pending_prod, pending_cons看作request prod, request cons, 而把dealloc_prod, dealloc_cons看作是rsp_prod, rsp_cons

pending_cons一直到pending_prod的slots表示pending_ring中还有多少空闲的slots可以用来发送,MAX_PENDING_REQS - pending_prod + pending_cons表示已经被待发送请求占用的slots个数,也就是nr_pending_reqs返回的值


static void make_tx_response(struct xen_netif *netif,
                 struct xen_netif_tx_request *txp,
                 s8       st)
{
    RING_IDX i = netif->tx.rsp_prod_pvt;
    struct xen_netif_tx_response *resp;
    int notify;

    resp = RING_GET_RESPONSE(&netif->tx, i);
    resp->id     = txp->id;
    resp->status = st;

    if (txp->flags & NETTXF_extra_info)
        RING_GET_RESPONSE(&netif->tx, ++i)->status = NETIF_RSP_NULL;

    netif->tx.rsp_prod_pvt = ++i;
    RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(&netif->tx, notify);

    /*
     * netfront_smartpoll_active indicates whether netfront timer
     * is active.
     */
    if ((netif->smart_poll == 1)) {
        if (!(netif->rx.sring->private.netif.smartpoll_active)) {
            notify_remote_via_irq(netif->irq);
            netif->rx.sring->private.netif.smartpoll_active = 1;
        }
    } else if (notify)
        notify_remote_via_irq(netif->irq);
}

make_tx_response比较简单,就是生成xen_netif_tx_response后把响应返回给前端。注意对于NETTXF_extra_info类型的请求,响应是NETIF_RSP_NULL


static unsigned net_tx_build_mops(void)
{
    struct gnttab_map_grant_ref *mop;
    struct sk_buff *skb;
    int ret;

    mop = tx_map_ops;

tx_map_ops是一个gnttab_map_grant_ref的数组,而tx_unmap_ops则是一个gnttab_unmap_grant_ref的数组


    while (((nr_pending_reqs() + MAX_SKB_FRAGS) < MAX_PENDING_REQS) &&
        !list_empty(&net_schedule_list)) {

如果此时pending_ring的可用slots可以容纳最大的一个请求,同时net_schedule_list里有活动的xen_netif,那么循环处理发送请求

        struct xen_netif *netif;
        struct xen_netif_tx_request txreq;
        struct xen_netif_tx_request txfrags[MAX_SKB_FRAGS];
        struct xen_netif_extra_info extras[XEN_NETIF_EXTRA_TYPE_MAX - 1];
        u16 pending_idx;
        RING_IDX idx;
        int work_to_do;
        unsigned int data_len;

        /* Get a netif from the list with work to do. */
        netif = list_first_entry(&net_schedule_list, struct xen_netif, list);
        netif_get(netif);
        remove_from_net_schedule_list(netif);

        RING_FINAL_CHECK_FOR_REQUESTS(&netif->tx, work_to_do);
        if (!work_to_do) {
            netif_put(netif);
            continue;
        }

        idx = netif->tx.req_cons;
        rmb(); /* Ensure that we see the request before we copy it. */
        memcpy(&txreq, RING_GET_REQUEST(&netif->tx, idx), sizeof(txreq));
        /* Credit-based scheduling. */
        if (txreq.size > atomic64_read(&netif->remaining_credit) &&
            tx_credit_exceeded(netif, txreq.size)) {
            netif_put(netif);
            continue;
        }

        atomic64_sub(txreq.size,&netif->remaining_credit);

        work_to_do--;
        netif->tx.req_cons = ++idx;

如果netif有请求要处理,同时QoS也允许发送,那么把请求可能的第一个frag拷贝到txreq,同时netif->tx.req_cons自增1

        memset(extras, 0, sizeof(extras));
        if (txreq.flags & NETTXF_extra_info) {
            work_to_do = netbk_get_extras(netif, extras,
                              work_to_do);
            idx = netif->tx.req_cons;
            if (unlikely(work_to_do < 0)) {
                netbk_tx_err(netif, &txreq, idx);
                continue;
            }
        }

如果请求是一个gso请求(说明有多个sg分片),那么调用netbk_get_extras,计算前端还有多少个类型为XEN_NETIF_EXTRA_FLAG_MORE的xen_netif_tx_request请求(一般来说也就一个而已,所以这段代码基本无用)

        ret = netbk_count_requests(netif, &txreq, txfrags, work_to_do);
        if (unlikely(ret < 0)) {
            netbk_tx_err(netif, &txreq, idx - ret);
            continue;
        }
        idx += ret;

调用netbk_count_requests,把所有NETTXF_more_data的分片连同最后一个分片全部拷贝到txfrags数组中。所以对于gso请求,所有的xen_netif_tx_request分片都被存到了txreq, txfrags数组中

        if (unlikely(txreq.size < ETH_HLEN)) {
            DPRINTK("Bad packet size: %d\n", txreq.size);
            netbk_tx_err(netif, &txreq, idx);
            continue;
        }

        /* No crossing a page as the payload mustn't fragment. */
        if (unlikely((txreq.offset + txreq.size) > PAGE_SIZE)) {
            DPRINTK("txreq.offset: %x, size: %u, end: %lu\n",
                txreq.offset, txreq.size,
                (txreq.offset &~PAGE_MASK) + txreq.size);
            netbk_tx_err(netif, &txreq, idx);
            continue;
        }

txreq不能跨page, txreq长度也不能少于一个二层包头,不然都是非法请求

        pending_idx = pending_ring[pending_index(pending_cons)];

        data_len = (txreq.size > PKT_PROT_LEN &&
                ret < MAX_SKB_FRAGS) ?
            PKT_PROT_LEN : txreq.size;

        skb = alloc_skb(data_len + NET_SKB_PAD + NET_IP_ALIGN,
                GFP_ATOMIC | __GFP_NOWARN);
        if (unlikely(skb == NULL)) {
            DPRINTK("Can't allocate a skb in start_xmit.\n");
            netbk_tx_err(netif, &txreq, idx);
            break;
        }

        /* Packets passed to netif_rx() must have some headroom. */
        skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);

        if (extras[XEN_NETIF_EXTRA_TYPE_GSO - 1].type) {
            struct xen_netif_extra_info *gso;
            gso = &extras[XEN_NETIF_EXTRA_TYPE_GSO - 1];

            if (netbk_set_skb_gso(skb, gso)) {
                kfree_skb(skb);
                netbk_tx_err(netif, &txreq, idx);
                continue;
            }
        }

分配sk_buff,设置skb的gso配置

        gnttab_set_map_op(mop, idx_to_kaddr(pending_idx),
                  GNTMAP_host_map | GNTMAP_readonly,
                  txreq.gref, netif->domid);
        mop++;

设置gnttab_map_grant_ref的GR

        memcpy(&pending_tx_info[pending_idx].req,
               &txreq, sizeof(txreq));
        pending_tx_info[pending_idx].netif = netif;


        *((u16 *)skb->data) = pending_idx;
/* 这里很tricky,skb->data的前2个字节用来存储第一个page的pending_idx */

        __skb_put(skb, data_len);

/* data_len最多为72个字节 */

        skb_shinfo(skb)->nr_frags = ret;
        if (data_len < txreq.size) {
            skb_shinfo(skb)->nr_frags++;
            skb_shinfo(skb)->frags[0].page =
                (void *)(unsigned long)pending_idx;
        } else {
            /* Discriminate from any valid pending_idx value. */
            skb_shinfo(skb)->frags[0].page = (void *)~0UL;
        }


        __skb_queue_tail(&tx_queue, skb);

设置skb,并把skb追加到tx_queue发送队列。注意由于第一个txreq对应的skb线性区域最多只能有72个字节,那么溢出的部分有可能会被设置到frag第一个分片里。

        pending_cons++;

        mop = netbk_get_requests(netif, skb, txfrags, mop);

        netif->tx.req_cons = idx;
        netif_schedule_work(netif);

        if ((mop - tx_map_ops) >= ARRAY_SIZE(tx_map_ops))
            break;
    }
设置完txreq的mop之后,自增pending_cons,然后调用netbk_get_requests设置txfrags数组的mop。netbk_get_requests会依次设置好gnttab_mop_grant_ref,把xen_netif_tx_request拷贝到pending_tx_info数组对应的slot。注意这里skb_shared_info里的frags[i].page不是struct page*指针了,而是这个page在mmap_pages中的idx位置。

    return mop - tx_map_ops;
}


static void net_tx_submit(void)
{
    struct gnttab_map_grant_ref *mop;
    struct sk_buff *skb;

    mop = tx_map_ops;
    while ((skb = __skb_dequeue(&tx_queue)) != NULL) {
        struct xen_netif_tx_request *txp;
        struct xen_netif *netif;
        u16 pending_idx;
        unsigned data_len;

        pending_idx = *((u16 *)skb->data);
        netif       = pending_tx_info[pending_idx].netif;
        txp         = &pending_tx_info[pending_idx].req;

skb->data开头的两个字节用来暂存线性区域page的index,后续就没用了,这个值由net_tx_build_mops函数传入

这里通过pending_idx得到struct xen_netif, struct xen_netif_tx_request


        /* Check the remap error code. */
        if (unlikely(netbk_tx_check_mop(skb, &mop))) {
            DPRINTK("netback grant failed.\n");
            skb_shinfo(skb)->nr_frags = 0;
            kfree_skb(skb);
            continue;
        }

        data_len = skb->len;
        memcpy(skb->data,
               (void *)(idx_to_kaddr(pending_idx)|txp->offset),
               data_len);
        if (data_len < txp->size) {
            /* Append the packet payload as a fragment. */
            txp->offset += data_len;
            txp->size -= data_len;
        } else {
            /* Schedule a response immediately. */
            netif_idx_release(pending_idx);
        }
把pending_idx的页的内容拷贝到skb->data开头的空间中,长度为data_len = skb->len

如果data_len < txp->size,此时data_len为72字节,txp->size大于72字节的部分由第一个frag指向。这种情况下,pending_idx所指向的页还不能释放

如果data_len >= txp->size,此时pending_idx所指向的页的内容已经完全拷贝到了skb线性区域了,此时可以调用netif_idx_release把第pending_idx个页加到dealloc_ring的dealloc_prod中

        /*
         * Old frontends do not assert data_validated but we
         * can infer it from csum_blank so test both flags.
         */
        if (txp->flags & (NETTXF_data_validated|NETTXF_csum_blank))
            skb->ip_summed = CHECKSUM_PARTIAL;
        else
            skb->ip_summed = CHECKSUM_NONE;

        netbk_fill_frags(skb);

前面已经设置好了skb->nr_frags,和每个skb_frag_t的page所在的pending_idx,netbk_fill_frags遍历skb->nr_frags,把pending_idx指向的页加到pending_inuse_head链表中,在基于xen_netif_tx_request,设置好每个skb_frag_t的page,offset, size

        /*
         * If the initial fragment was < PKT_PROT_LEN then
         * pull through some bytes from the other fragments to
         * increase the linear region to PKT_PROT_LEN bytes.
         */
        if (skb_headlen(skb) < PKT_PROT_LEN && skb_is_nonlinear(skb)) {
            int target = min_t(int, skb->len, PKT_PROT_LEN);
            __pskb_pull_tail(skb, target - skb_headlen(skb));
        }
保证skb线性区域的大小为PKT_PROT_LEN

        skb->dev      = netif->dev;
        skb->protocol = eth_type_trans(skb, skb->dev);

        netif->stats.rx_bytes += skb->len;
        netif->stats.rx_packets++;

        if (skb->ip_summed == CHECKSUM_PARTIAL) {
            if (skb_checksum_setup(skb)) {
                DPRINTK("Can't setup checksum in net_tx_action\n");
                kfree_skb(skb);
                continue;
            }
        }

如果需要网卡进行校验和计算,那么调用skb_checksum_setup设置skb->csum_start, skb->csum_offset。这里没有对icmp设置任何校验和项,是个潜在的问题


        if (unlikely(netbk_copy_skb_mode == NETBK_ALWAYS_COPY_SKB) &&
            unlikely(skb_linearize(skb))) {
            DPRINTK("Can't linearize skb in net_tx_action.\n");
            kfree_skb(skb);
            continue;
        }


        netif_rx(skb);
        netif->dev->last_rx = jiffies;
    }

    if (netbk_copy_skb_mode == NETBK_DELAYED_COPY_SKB &&
        !list_empty(&pending_inuse_head)) {
        struct netbk_tx_pending_inuse *oldest;

        oldest = list_entry(pending_inuse_head.next,
                    struct netbk_tx_pending_inuse, list);
        mod_timer(&netbk_tx_pending_timer, oldest->alloc_time + HZ);
    }

对还在pending_inuse_head的list中尚未处理完的请求,设置timer定时器,继续调用软中断来处理
}


不知道到这里为止,你们是否觉得事情已经做完了?是不是还缺点什么?bingo,当netif_rx处理完发送请求之后,那些mmap_pages里用过的页如何释放呢?dealloc_prod总得有人增加吧,答案就在这里SetPageForeign

    for (i = 0; i < MAX_PENDING_REQS; i++) {
        page = mmap_pages[i];
        SetPageForeign(page, netif_page_release);
        netif_set_page_index(page, i);
        INIT_LIST_HEAD(&pending_inuse[i].list);
    }

我们看到netback_init中,对mmap_pages的所有页都调用了SetPageForeign. foreign page是xen引入的一种页类型。netif_page_release是页的析构函数,就是说每次想要释放这个页,其实并没有释放而是调用了netif_page_release

static void netif_page_release(struct page *page, unsigned int order)
{
    int idx = netif_page_index(page);
    BUG_ON(order);
    BUG_ON(idx < 0);
    netif_idx_release(idx);
}

idx是该page在mmap_pages中的索引,netif_idx_release才是真正把这个page的pending_idx加到了dealloc_ring中,同时增加dealloc_prod的家伙

static void netif_idx_release(u16 pending_idx)
{
    static DEFINE_SPINLOCK(_lock);
    unsigned long flags;

    spin_lock_irqsave(&_lock, flags);
    dealloc_ring[pending_index(dealloc_prod)] = pending_idx;
    /* Sync with net_tx_action_dealloc: insert idx /then/ incr producer. */
    smp_wmb();
    dealloc_prod++;
    spin_unlock_irqrestore(&_lock, flags);

    tasklet_schedule(&net_tx_tasklet);
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值