【转】KVM I/O虚拟化分析

2023-11-11

最近在看多队列的东西,看到下面两篇文章,记录下。以后自己也深入写个这方向的知识。

 

0:背景
         当今的I/O虚拟化主要有几种模式:

         1):通过设备的模拟

         设备的模拟主要分为两种,一种是直接在VMM中完成模拟(如xen、vmware),一种是在另一个应用程序中完成模拟(如kvm/qemu)

图一 基于VMM模拟的I/O虚拟化

                                                               

图二 基于用户空间模拟的I/O虚拟化

         2):I/O设备直通

         针对某些不好模拟的I/O设备(如显卡、串口等),也可以使用直通技术直接将设备透传给虚拟机使用(Intel的VT-D、AMD的IOMMU),设备直通由于host保存了设备的一些信息,因此对迁移的支持相对不够。

                              

图三 设备直通技术

         3):SR-IOV

         通过 SR-IOV,一个 PCIe 设备不仅可以导出多个PCI 物理功能,还可以导出共享该 I/O 设备上的资源的一组虚拟功能,在这个模型中,不需要任何透传,因为虚拟化在终端设备上发生,从而允许管理程序简单地将虚拟功能映射到 VM 上以实现本机设备性能和隔离安全。

                                                                              

图四 SR-IOV模型

1:virtio
         在完全虚拟化场景下,Guest每次I/O操作时,都会被kvm捕获,kvm再通知qemu完成I/O的软件模拟,模拟完成后再回到kvm并将I/0的结果返回给Guest,整体流程较长,性能差。

         因此现在kvm的磁盘I/O基本采用半虚拟化virtio方案。半虚拟化下,通过Guest和Qemu完成前后端配合,前后端之间通过1个或多个vqueue环形队列完成信息传递。后端直接与host宿主机的设备交互,不需要qemu进行软件模拟,并且可以将多次的I/O操作合并打包,减少Guest、Host之间切换次数,对I/O操作性能有较大提升。

                                      

图五 virtio架构图

 

         1):在GuestOS实现了前端驱动

                   virtio_blk  -----  drivers/block/virtio_blk.c

                   virtio_net  -----  ./drivers/net/virtio_net.c

                   virtio_ballon----- ./drivers/virtio/virtio_balloon.c

                   virtio_scsi  ----  ./drivers/scsi/virtio_scsi.c

                   virtio_console--- ./drivers/char/virtio_console.c

         2):在qemu侧实现了后端驱动

                   ./hw/block/dataplane/virtio-blk.c

                   ./hw/virtio/virtio-balloon.c

                   ./hw/net/virtio-net.c

                   ./hw/char/virtio-console.c

                   ./hw/scsi/virtio-scsi.c

2:磁盘I/O虚拟化
         kvm可以在host创建存储池(默认路径/etc/libvirt/storage),这样一些本地目录或远端(scsi、san、nfs等)分配过来的存储目录就可以通过存储池设置给虚拟机使用。这些存储池里的卷被映射成虚拟机里的一个个虚拟磁盘(磁盘类型可以在xml里指定,比如把host本地的一个卷(该卷可以是任一类型的存储介质)映射成虚拟机里的virtio设备(vda、vdb。。。)、scsi设备(sda、sdb。。。)等),虚拟机在对这些磁盘做I/O操作的时候,可以使用完全虚拟化技术让虚拟机不感知其跑在Guest环境下,也可以使用半虚拟化技术提升I/O性能。

         1:virtio_blk

         virtio_blk通过在GuestOS增加相应的ko模块作为前端驱动,与Qemu后端之间通过virtqueue方式通讯,可以减少数据的拷贝,并且virtqueue可以将多次的I/O操作合并打包并做统一提交,这样可以减少vm陷出的次数,提升了整体的I/O性能。

         2:读磁盘I/O过程分析

                                                 

图六 virtio_blk处理流程

         1):GuestOS调用write系统调用进行写操作,以ext4文件系统为例,最终会调用到ext4_io_submit完成I/O操作的提交

         2):根据submit里的I/0请求,执行request操作

    do {
        struct request_queue *q = bdev_get_queue(bio->bi_bdev);
 
        if (likely(blk_queue_enter(q, false) == 0)) {
            struct bio_list lower, same;
 
            /* Create a fresh bio_list for all subordinate requests */
            bio_list_on_stack[1] = bio_list_on_stack[0];
            bio_list_init(&bio_list_on_stack[0]);
            ret = q->make_request_fn(q, bio);
 
            blk_queue_exit(q);
 
            /* sort new bios into those for a lower level
             * and those for the same level
             */
            bio_list_init(&lower);
            bio_list_init(&same);
            while ((bio = bio_list_pop(&bio_list_on_stack[0])) != NULL)
                if (q == bdev_get_queue(bio->bi_bdev))
                    bio_list_add(&same, bio);
                else
                    bio_list_add(&lower, bio);
            /* now assemble so we handle the lowest level first */
            bio_list_merge(&bio_list_on_stack[0], &lower);
            bio_list_merge(&bio_list_on_stack[0], &same);
            bio_list_merge(&bio_list_on_stack[0], &bio_list_on_stack[1]);
        } else {
            bio_io_error(bio);
        }
        bio = bio_list_pop(&bio_list_on_stack[0]);
    } while (bio);
         3):这里的make_request_fn会在virtbrk_probe的时候设置为blk_queue_make_request,

         在blk_queue_make_request里将每个I/0操作都封装成一个request请求

         4):进一步调用到blk_mq_run_hw_queue,完成请求的分发   

         void blk_mq_run_hw_queue(struct blk_mq_hw_ctx *hctx, bool async)
         {
             __blk_mq_delay_run_hw_queue(hctx, async, 0);
         }
         5):blk_mq_dispatch_rq_list从virtqueue里获取可用的buffer,并将请求的I/0数据填充进去                

    do {
        struct blk_mq_queue_data bd;
 
        rq = list_first_entry(list, struct request, queuelist);
        if (!blk_mq_get_driver_tag(rq, &hctx, false)) {
            if (!queued && reorder_tags_to_front(list))
                continue;
 
            /*
             * The initial allocation attempt failed, so we need to
             * rerun the hardware queue when a tag is freed.
             */
            if (!blk_mq_dispatch_wait_add(hctx))
                break;
 
            /*
             * It's possible that a tag was freed in the window
             * between the allocation failure and adding the
             * hardware queue to the wait queue.
             */
            if (!blk_mq_get_driver_tag(rq, &hctx, false))
                break;
        }
 
        list_del_init(&rq->queuelist);
 
        bd.rq = rq;
 
        /*
         * Flag last if we have no more requests, or if we have more
         * but can't assign a driver tag to it.
         */
        if (list_empty(list))
            bd.last = true;
        else {
            struct request *nxt;
 
            nxt = list_first_entry(list, struct request, queuelist);
            bd.last = !blk_mq_get_driver_tag(nxt, NULL, false);
        }
 
        ret = q->mq_ops->queue_rq(hctx, &bd);
        switch (ret) {
        case BLK_MQ_RQ_QUEUE_OK:
            queued++;
            break;
        case BLK_MQ_RQ_QUEUE_BUSY:
            blk_mq_put_driver_tag_hctx(hctx, rq);
            list_add(&rq->queuelist, list);
            __blk_mq_requeue_request(rq);
            break;
        default:
            pr_err("blk-mq: bad return on queue: %d\n", ret);
        case BLK_MQ_RQ_QUEUE_ERROR:
            errors++;
            blk_mq_end_request(rq, -EIO);
            break;
        }
 
        if (ret == BLK_MQ_RQ_QUEUE_BUSY)
            break;
    } while (!list_empty(list));                                 
         6):将I/0操作填入vbuffer之后,调用virtqueue_notify,在notify函数里会执行一个iowrite操作(iowrite16(vq->index,(void __iomem *)vq->priv);),该操作会导致vm陷出到host。这里的vq->priv会在setup_vq里设置为VIRTIO_PCI_QUEUE_NOTIFY的偏移地址(vq->priv =(void __force *)vp_dev->ioaddr + VIRTIO_PCI_QUEUE_NOTIFY),后续kvm检测到I/0操作,将控制权交给qemu的时候,qemu根据这个偏移找到I/0虚拟化后端处理入口(virtio_queue_notify)。

        bool virtqueue_notify(struct virtqueue *_vq)
        {
            struct vring_virtqueue *vq = to_vvq(_vq);
 
            if (unlikely(vq->broken))
               return false;
 
            /* Prod other side to tell it about changes. */
            if (!vq->notify(_vq)) {
               vq->broken = true;
               return false;
            }
            return true;
        }
         7):kvm捕获到I/O访问异常后,根据异常向量表,找到对应的处理函数handle_io;

static int (*const kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = {
    [EXIT_REASON_EXCEPTION_NMI]           = handle_exception,
    [EXIT_REASON_EXTERNAL_INTERRUPT]      = handle_external_interrupt,
    [EXIT_REASON_TRIPLE_FAULT]            = handle_triple_fault,
    [EXIT_REASON_NMI_WINDOW]          = handle_nmi_window,
    [EXIT_REASON_IO_INSTRUCTION]          = handle_io,
    [EXIT_REASON_CR_ACCESS]               = handle_cr,
    [EXIT_REASON_DR_ACCESS]               = handle_dr,
    [EXIT_REASON_CPUID]                   = handle_cpuid,
    [EXIT_REASON_MSR_READ]                = handle_rdmsr,
    [EXIT_REASON_MSR_WRITE]               = handle_wrmsr,
    [EXIT_REASON_PENDING_INTERRUPT]       = handle_interrupt_window,
    [EXIT_REASON_HLT]                     = handle_halt,
    [EXIT_REASON_INVD]              = handle_invd,
    [EXIT_REASON_INVLPG]              = handle_invlpg,
    [EXIT_REASON_RDPMC]                   = handle_rdpmc,
    [EXIT_REASON_VMCALL]                  = handle_vmcall,
    [EXIT_REASON_VMCLEAR]                  = handle_vmclear,
    [EXIT_REASON_VMLAUNCH]                = handle_vmlaunch,
    [EXIT_REASON_VMPTRLD]                 = handle_vmptrld,
    [EXIT_REASON_VMPTRST]                 = handle_vmptrst,
    [EXIT_REASON_VMREAD]                  = handle_vmread,
    [EXIT_REASON_VMRESUME]                = handle_vmresume,
    [EXIT_REASON_VMWRITE]                 = handle_vmwrite,
    [EXIT_REASON_VMOFF]                   = handle_vmoff,
    [EXIT_REASON_VMON]                    = handle_vmon,
    [EXIT_REASON_TPR_BELOW_THRESHOLD]     = handle_tpr_below_threshold,
    [EXIT_REASON_APIC_ACCESS]             = handle_apic_access,
    [EXIT_REASON_APIC_WRITE]              = handle_apic_write,
    [EXIT_REASON_EOI_INDUCED]             = handle_apic_eoi_induced,
    [EXIT_REASON_WBINVD]                  = handle_wbinvd,
    [EXIT_REASON_XSETBV]                  = handle_xsetbv,
    [EXIT_REASON_TASK_SWITCH]             = handle_task_switch,
    [EXIT_REASON_MCE_DURING_VMENTRY]      = handle_machine_check,
    [EXIT_REASON_EPT_VIOLATION]          = handle_ept_violation,
    [EXIT_REASON_EPT_MISCONFIG]           = handle_ept_misconfig,
    [EXIT_REASON_PAUSE_INSTRUCTION]       = handle_pause,
    [EXIT_REASON_MWAIT_INSTRUCTION]          = handle_mwait,
    [EXIT_REASON_MONITOR_TRAP_FLAG]       = handle_monitor_trap,
    [EXIT_REASON_MONITOR_INSTRUCTION]     = handle_monitor,
    [EXIT_REASON_INVEPT]                  = handle_invept,
    [EXIT_REASON_INVVPID]                 = handle_invvpid,
    [EXIT_REASON_XSAVES]                  = handle_xsaves,
    [EXIT_REASON_XRSTORS]                 = handle_xrstors,
    [EXIT_REASON_PML_FULL]              = handle_pml_full,
    [EXIT_REASON_PREEMPTION_TIMER]          = handle_preemption_timer,
};
         在handle_io里最终会调用到emulator_pio_in_out,并将退出原因置为KVM_EXIT_IO(PIO的英文拼写是“Programming Input/Output Model”,PIO模式是一种通过CPU执行I/O端口指令来进行数据的读写的数据交换模式。)在kernel_pio里会判断当前如果有vhost_net,则会唤醒vhost内核线程,完成后端处理,如果没有,则回到qemu继续后端处理。

static int emulator_pio_in_out(struct kvm_vcpu *vcpu, int size,
                   unsigned short port, void *val,
                   unsigned int count, bool in)
{
    vcpu->arch.pio.port = port;
    vcpu->arch.pio.in = in;
    vcpu->arch.pio.count  = count;
    vcpu->arch.pio.size = size;
 
    if (!kernel_pio(vcpu, vcpu->arch.pio_data)) {
        vcpu->arch.pio.count = 0;
        return 1;
    }
 
    vcpu->run->exit_reason = KVM_EXIT_IO;
    vcpu->run->io.direction = in ? KVM_EXIT_IO_IN : KVM_EXIT_IO_OUT;
    vcpu->run->io.size = size;
    vcpu->run->io.data_offset = KVM_PIO_PAGE_OFFSET * PAGE_SIZE;
    vcpu->run->io.count = count;
    vcpu->run->io.port = port;
 
    return 0;
}


         8):vcpu退出大循环后,回到qemu用户态

        case KVM_EXIT_IO:             DPRINTF("handle_io\n");             /* Called outside BQL */             kvm_handle_io(run->io.port, attrs,                           (uint8_t *)run + run->io.data_offset,                           run->io.direction,                           run->io.size,                           run->io.count);             ret = 0;             break;

         在qemu退出vcpu_run后,检测退出原因为KVM_EXIT_IO,调用kvm_handle_io,最终找到后端处理入口virtio_queue_notify

         9):virtio_queue_notify通知virtio后端有数据需要处理,virtio后端通过virtqueue_pop(在virtio_blk_get_request里调用)将virtqueue里设置的buffer信息提取出来,并通过virtio_blk_handle_request将请求合并,在virtio_blk_submit_multireq里统一提交处理。

bool virtio_blk_handle_vq(VirtIOBlock *s, VirtQueue *vq)
{
    VirtIOBlockReq *req;
    MultiReqBuffer mrb = {};
    bool progress = false;
 
    aio_context_acquire(blk_get_aio_context(s->blk));
    blk_io_plug(s->blk);
 
    do {
        virtio_queue_set_notification(vq, 0);
 
        while ((req = virtio_blk_get_request(s, vq))) {
            progress = true;
            if (virtio_blk_handle_request(req, &mrb)) {
                virtqueue_detach_element(req->vq, &req->elem, 0);
                virtio_blk_free_request(req);
                break;
            }
        }
 
        virtio_queue_set_notification(vq, 1);
    } while (!virtio_queue_empty(vq));
 
    if (mrb.num_reqs) {
        virtio_blk_submit_multireq(s->blk, &mrb);
    }
 
    blk_io_unplug(s->blk);
    aio_context_release(blk_get_aio_context(s->blk));
    return progress;
}
        10):qemu完成处理后,重新回到KVM_RUN的vcpu循环里,并通过kvm将vcpu拉回到Guest里继续执行。

         2:磁盘I/O虚拟化下的几种cache策略

图七 虚拟化磁盘I/O路径

       如上图为虚拟化下的磁盘I/O路径,通过图可以看出,Guest执行一个I/O操作时,需要先经过GuestOS层的vfs、pagecache、I/O schedule等过程写到虚拟磁盘上,这里的虚拟磁盘相对于host上的一个文件,因此host上也需要经过host层面的vfs、pagecache、I/Oschedule等过程才能将最终的数据写到物理磁盘上。

         在host上,有几种cache策略可选择,当前qemu/kvm默认为writethough

         writethough:直接将数据写到物理磁盘上,这种方式比较安全,但是必须直接操作物理磁盘,保证I/O操作完成后才能返回,性能较差。

         writeback:I/O数据写到pagecache层面就可以返回。这种方式性能较好,但是安全性不行,如果数据存在pagecahe还没来的及刷新到物理磁盘上时机器断电,则数据会丢失。

         none:I/O数据写到buffer cache返回。性能好于writethough,安全性好于writeback。

 

         intel实现了一种writeback+passing through的方案,兼顾了性能与安全 ----- 后续研究下

         3:virtio_blk性能加速方案(Red hat实现,当前还未合入社区)

                   1):Bio-based virtio-blk

                   2):vhost-blk

 

图八 vhost-blk架构

         如上图为vhost-blk架构,vhost-blk与virtio-blk的区别在于后端的实现放在host的一个模块里(vhost-blk.ko)。当加载vhost-blk.ko的时候,会在创建一个内核线程vhost-pid,该线程正常情况处于睡眠状态。当Guest读写 I/O时,同样会陷出到host,在host(kvm)里模拟PIO的时候会唤醒vhost线程,然后在vhost线程里完成I/O读写,读写完成的结果通过中断注入的方式通知给Guest。

         这种方案的优势是直接在host完成了后端模拟,而不需要回到Qemu用户态,减少了内核态、用户态切换次数。

3:网络I/O虚拟化
1:虚拟网卡的生成

2:virtio-net

         与virtio-blk类似,只不过这里实现了两个virtqueue(一个收、一个发),而virtio-blk只有一个virtqueue队列。

图九 virtio_net整体架构图

3:vhost-net

         如下图为vhost_net整体框图,可以看出vhost_net不需要qemu作为后端,而是在内核新增了一个vhost_net模块。vhost_net内核模块与Guest共享一个virtqueue,这样就可以避免了处理消息包时从host切换到qemu用户态。

图十 vhost-net整体架构图

         1):插入vhost_net.ko的时候会生成/dev/vhost-net设备文件

         2):qemu启动的时候,获取到/dev/vhost-net描述符,并通过VHOST_SET_OWNERioctl接口通知host创建一个vhost-pid的内核线程,其中pid为当前qemu线程的pid。   

long vhost_dev_set_owner(struct vhost_dev *dev)
{
    struct task_struct *worker;
    int err;
 
    /* Is there an owner already? */
    if (vhost_dev_has_owner(dev)) {
        err = -EBUSY;
        goto err_mm;
    }
 
    /* No owner, become one */
    dev->mm = get_task_mm(current);
    worker = kthread_create(vhost_worker, dev, "vhost-%d", current->pid);
    if (IS_ERR(worker)) {
        err = PTR_ERR(worker);
        goto err_worker;
    }
 
    dev->worker = worker;
    wake_up_process(worker);    /* avoid contributing to loadavg */
 
    err = vhost_attach_cgroups(dev);
    if (err)
        goto err_cgroup;
 
    err = vhost_dev_alloc_iovecs(dev);
    if (err)
        goto err_cgroup;
 
    return 0;
err_cgroup:
    kthread_stop(worker);
    dev->worker = NULL;
err_worker:
    if (dev->mm)
        mmput(dev->mm);
    dev->mm = NULL;
err_mm:
    return err;
}
         3):GuestOS将待发送的数据包通过try_fill_recv函数添加到virtqueue的buffer里,然后通过virtqueue_kick陷出到host,在host里将vhost线程唤醒

static bool try_fill_recv(struct virtnet_info *vi, struct receive_queue *rq,
              gfp_t gfp)
{
    int err;
    bool oom;
 
    gfp |= __GFP_COLD;
    do {
        if (vi->mergeable_rx_bufs)
            err = add_recvbuf_mergeable(vi, rq, gfp);
        else if (vi->big_packets)
            err = add_recvbuf_big(vi, rq, gfp);
        else
            err = add_recvbuf_small(vi, rq, gfp);
 
        oom = err == -ENOMEM;
        if (err)
            break;
    } while (rq->vq->num_free);
    virtqueue_kick(rq->vq);
    return !oom;
}
         4):从vhost_work处理函数可以看出,当该线程被唤醒时会调用work->fn完成数据包的收发处理,处理完之后又将自己睡眠,接下来分析下fn是在哪里设置的?

static int vhost_worker(void *data)
{
    struct vhost_dev *dev = data;
    struct vhost_work *work, *work_next;
    struct llist_node *node;
    mm_segment_t oldfs = get_fs();
 
    set_fs(USER_DS);
    use_mm(dev->mm);
 
    for (;;) {
        /* mb paired w/ kthread_stop */
        set_current_state(TASK_INTERRUPTIBLE);
 
        if (kthread_should_stop()) {
            __set_current_state(TASK_RUNNING);
            break;
        }
 
        node = llist_del_all(&dev->work_list);
        if (!node)
            schedule();
 
        node = llist_reverse_order(node);
        /* make sure flag is seen after deletion */
        smp_wmb();
        llist_for_each_entry_safe(work, work_next, node, node) {
            clear_bit(VHOST_WORK_QUEUED, &work->flags);
            __set_current_state(TASK_RUNNING);
            work->fn(work);
            if (need_resched())
                schedule();
        }
    }
    unuse_mm(dev->mm);
    set_fs(oldfs);
    return 0;
}
         当open/dev/vhost-net设备的时候,会执行vhost-net-open函数,该函数首先为vqueue的收发队列分别设置一个handle_kick函数;

static int vhost_net_open(struct inode *inode, struct file *f)
{
    struct vhost_net *n;
    struct vhost_dev *dev;
    struct vhost_virtqueue **vqs;
    int i;
 
    n = kvmalloc(sizeof *n, GFP_KERNEL | __GFP_REPEAT);
    if (!n)
        return -ENOMEM;
    vqs = kmalloc(VHOST_NET_VQ_MAX * sizeof(*vqs), GFP_KERNEL);
    if (!vqs) {
        kvfree(n);
        return -ENOMEM;
    }
 
    dev = &n->dev;
    vqs[VHOST_NET_VQ_TX] = &n->vqs[VHOST_NET_VQ_TX].vq;
    vqs[VHOST_NET_VQ_RX] = &n->vqs[VHOST_NET_VQ_RX].vq;
    n->vqs[VHOST_NET_VQ_TX].vq.handle_kick = handle_tx_kick;
    n->vqs[VHOST_NET_VQ_RX].vq.handle_kick = handle_rx_kick;
    for (i = 0; i < VHOST_NET_VQ_MAX; i++) {
        n->vqs[i].ubufs = NULL;
        n->vqs[i].ubuf_info = NULL;
        n->vqs[i].upend_idx = 0;
        n->vqs[i].done_idx = 0;
        n->vqs[i].vhost_hlen = 0;
        n->vqs[i].sock_hlen = 0;
    }
    vhost_dev_init(dev, vqs, VHOST_NET_VQ_MAX);
 
    vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, POLLOUT, dev);
    vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, POLLIN, dev);
 
    f->private_data = n;
 
    return 0;
         然后在执行vhost设备的初始化vhost_dev_init,在该函数里最终调用vhost_work_init将work->fn设置为刚才设置的handle_kick函数。这样,当vhost线程被唤醒的时候就会执行handle_kick函数(handle_tx_kick、handle_rx_kick)。 handle_tx_kick里将消息包从vringbuffer里取出来,调用send_msg发送出去。

void vhost_work_init(struct vhost_work *work, vhost_work_fn_t fn)
{
    clear_bit(VHOST_WORK_QUEUED, &work->flags);
    work->fn = fn;
    init_waitqueue_head(&work->done);
}
         5):当tap设备收到消息包时,进入tun_net_xmit,在该函数里将vhost线程唤醒,唤醒后执行handle_rx_kick,handle_rx_kick里将数据包填充到vringbuffer里,然后通过中断注入的方式(vp_interrrupt)通知Guest获取消息包。

static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
    struct tun_struct *tun = netdev_priv(dev);
    int txq = skb->queue_mapping;
    struct tun_file *tfile;
    u32 numqueues = 0;
 
    rcu_read_lock();
    tfile = rcu_dereference(tun->tfiles[txq]);
    numqueues = ACCESS_ONCE(tun->numqueues);
 
    /* Drop packet if interface is not attached */
    if (txq >= numqueues)
        goto drop;
 
#ifdef CONFIG_RPS
    if (numqueues == 1 && static_key_false(&rps_needed)) {
        /* Select queue was not called for the skbuff, so we extract the
         * RPS hash and save it into the flow_table here.
         */
        __u32 rxhash;
 
        rxhash = skb_get_hash(skb);
        if (rxhash) {
            struct tun_flow_entry *e;
            e = tun_flow_find(&tun->flows[tun_hashfn(rxhash)],
                    rxhash);
            if (e)
                tun_flow_save_rps_rxhash(e, rxhash);
        }
    }
#endif
 
    tun_debug(KERN_INFO, tun, "tun_net_xmit %d\n", skb->len);
 
    BUG_ON(!tfile);
 
    /* Drop if the filter does not like it.
     * This is a noop if the filter is disabled.
     * Filter can be enabled only for the TAP devices. */
    if (!check_filter(&tun->txflt, skb))
        goto drop;
 
    if (tfile->socket.sk->sk_filter &&
        sk_filter(tfile->socket.sk, skb))
        goto drop;
 
    if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))
        goto drop;
 
    skb_tx_timestamp(skb);
 
    /* Orphan the skb - required as we might hang on to it
     * for indefinite time.
     */
    skb_orphan(skb);
 
    nf_reset(skb);
 
    if (skb_array_produce(&tfile->tx_array, skb))
        goto drop;
 
    /* Notify and wake up reader process */
    if (tfile->flags & TUN_FASYNC)
        kill_fasync(&tfile->fasync, SIGIO, POLL_IN);
    tfile->socket.sk->sk_data_ready(tfile->socket.sk);
 
    rcu_read_unlock();
    return NETDEV_TX_OK;
 
drop:
    this_cpu_inc(tun->pcpu_stats->tx_dropped);
    skb_tx_error(skb);
    kfree_skb(skb);
    rcu_read_unlock();
    return NET_XMIT_DROP;
}
4:vhsot_user

         上面提到的vhost_net可以直接在内核态完成数据包的收发,相比virtio可以减少内核态、用户态的切换次数及系统调用次数(Qemu发送数据到tap的时候需要通过系统调用的方式)。但是由于vhost_net还是使用了内核的网络协议栈进行处理,因此跟当前的一些用户态协议栈(如DPDK)比,性能也还是有一定的差距。因此,后面又发展了一种vhost_user,其实就是把vhost_net内核态部分的处理挪到用户态,然后配合DPDK、ovs等技术直接在用户态操作物理网卡,完成收发包处理。
--------------------- 
作者:zgy666 
来源:CSDN 
原文:https://blog.csdn.net/zgy666/article/details/78469142 
版权声明:本文为博主原创文章,转载请附上博文链接!

https://www.cnblogs.com/scottieyuyang/p/6053376.html

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【转】KVM I/O虚拟化分析 的相关文章

  • qemu-linux-user ELF vs XCOFF 1

    最后更新2021 12 14 注 以下及以后本系列都是个人对相关技术在此时此刻的理解和研究 会根据学习深入 不断修正 但过去历史文章不见得会被 及时 修改订正 见谅 当然欢迎读者批评指正 本人虚心接受 但什么时候把文章修改正确 不好说 qe
  • QEMU-从buildroot里面编译kernel(7)

    上面是我的微信和QQ群 欢迎新朋友的加入 下载交叉编译工具 https snapshots linaro org gnu toolchain 选一个最新的 选择压缩包 解压 sudo apt get install g sudo mv gc
  • QEMU-在内核中增加驱动(6)

    上面是我的微信和QQ群 欢迎新朋友的加入 进入linux源码目录 增加驱动 hello c include
  • Real-time Linux

    所谓实时操作系统 Real time Opearting System 是指当外接世界或数据产生时 能够接受并以足够快的速度予以处理 其处理的结果又能在规定的时间之内来控制生产过程或对处理系统做出快速响应 调度一切可利用的资源完成实时任务
  • 为什么macos(x86)可以运行docker arm容器arm64v8/alpine?

    我碰巧发现我的macos x86 可以为arm镜像arm64v8 alpine运行docker容器 但有以下警告 docker run it arm64v8 alpine uname a WARNING The requested imag
  • 如何在QEMU源代码中添加新设备?

    模拟 添加新设备的逐步方法是什么qemu使用 QOM 方法 DeviceState BusState 和其他属性可能会发生哪些变化以及在哪里发生变化 edu树内教育 PCI 设备 https github com qemu qemu blo
  • Rustc/LLVM 为 aarch64 生成错误代码,opt-level=0

    我有两个文件被组装 编译 链接到简约内核中 start s set CPACR EL1 FPEN 0b11 lt lt 20 set BOOT STACK SIZE 8 1024 global boot stack global start
  • 在linux下构建edk2

    我开始用 edk2 编写一个小而简单的应用程序 因此 要编写一个简单的 edk2 UEFI 应用程序 我是这样开始的 git克隆https github com tianocore edk2 git edksetup sh BaseTool
  • 如何用GDB调试交叉编译的QEMU程序?

    我在使用 GDB 调试 QEMU 中运行的简单程序时遇到问题 GDB似乎无法找到我在程序中的位置 因为它总是显示 作为我当前的位置 并且它永远不会达到我设置的任何断点 在一个终端中 我运行 QEMU cat add c int main i
  • 升级到 macOS Catalina 后 Qemu 没有响应

    将我的 Mac 升级到 Catalina 后 我无法再使用 qemu 每当我尝试启动它时 它就会挂起 当 qemu 窗口处于活动状态时 菜单栏也不会响应 跑步qemu system i386 help不过确实有效 Qemu版本 4 1 0
  • 程序集 32 位打印显示在 qemu 上运行的代码,无法在真实硬件上运行

    我已经用 x86 汇编语言编写了一小段在裸硬件上运行的代码 此时 它已启用受保护的 32 位模式 然而 我遇到了与屏幕打印有关的问题 我读到 要在不中断的情况下执行此操作 可以将字符加载到特殊的内存区域 即 RAM 地址 0xb8000 知
  • 运行Qemu后只是黑屏

    我刚刚安装了 QEMU 并编译了支持 ARM 的 Linux 内核 但是当我运行下面的命令时 qemu system arm M versatilepb m 128M kernel home arit QEMU linux 3 8 4 ar
  • 为 Android 模拟器创建虚拟硬件、内核、qemu 以生成 OpenGL 图形

    我是 Android 新手 希望使用模拟器 我想做的是创建自己的虚拟硬件 可以收集 OpenGL 命令并生成 OpenGL 图形 有人告诉我 为了做到这一点 我需要编写一个 Linux 内核驱动程序来实现与硬件的通信 此外 我需要编写一个
  • 如何在 Qemu 下启动 FreeBSD 镜像

    我有一个 FreeBSD 映像 其中包含 boot loader 和 boot kernel 等 它在 EC2 实例下启动正常 但我想使用 Qemu 启动它 我尝试过各种方法 但都没有效果 见下文 qemu system x86 64 ke
  • 如何用汇编语言打印字符串

    我正在尝试使用 NASM 在 Q Emulator 中打印字符串 我的代码如下 mov bx HELLO mov ah 0x0e int 0x10 HELLO db Hello 0 jmp times 510 db 0 dw 0xaa55
  • 在 mac M1 芯片上构建 docker amd64 镜像

    我正在尝试以两种方式在我的 mac M1 arm64 上构建 dockerfile 下面是 dockerfile 的重要部分 FROM erlang 24 elixir expects utf8 ENV ELIXIR VERSION v1
  • 如何使用 QEMU 的简单跟踪后端?

    这是后续this https stackoverflow com questions 37522552 qemu simple backend tracing dosent print anything comment65639854 37
  • qemu:未捕获的目标信号 11(分段错误)- 尝试返回结构时核心已转储

    我刚刚注意到我无法让函数返回结构 我在启用了线程的 ARM32 debian docker 映像上运行它 这是给我运行时错误的函数 struct CEC call des CEC call char buffy char request b
  • 读取时不返回写入 I2C 的缓冲区

    我试图在写入后从 I2C 总线上的内存位置读取一个值 当我在终端中运行它时 我得到奇怪的输出 这是我的程序 include
  • Yocto“无法运行 qemu:无法初始化 SDL(x11 不 > 可用)”

    所以我在本地构建服务器上安装了 Yocto 因为谁希望大规模构建占用他们的工作区 amirite 主机和服务器是Arch Linux 4 19 44 1 lts 无论如何 我只是从找到的快速构建页面运行示例here https www yo

随机推荐

  • 用python来爬取某鱼的商品信息(1/2)

    目录 前言 第一大难题 找到网站入口 曲线救国 模拟搜索 第二大难题 登录 提一嘴 登录cookie获取 第一种 第二种 第四大难题 无法使用导出的cookie 原因 解决办法 最后 出现小问题 总结 下一篇博客 大部分代码实现 前言 本章
  • 王爽著的《汇编语言》第3版笔记

    王爽著的 汇编语言 第3版 于2013年出版 虽然是2013年出版的 但书中部分内容感觉已过时 1 基于intel 8086 CPU介绍 intel 8086是英特尔公司上个世纪生产的芯片 是16位的 早已停产 2 现在PC机上的intel
  • Harbor-registry 使用 NFS 做后端存储实现高可用

    目录 需求分析 方案实施 安装 NFS 配置 harbor registry 配置后端存储为 NFS 配置 harbor registry 副本数为 2 配置 K8S Registry 配置 k8s registry 配置文件 配置 reg
  • 华为OD机试真题 Java 实现【打印文件】【2023Q1 100分】

    一 题目描述 有 5 台打印机打印文件 每台打印机有自己的待打印队列 因为打印的文件内容有轻重缓急之分 所以队列中的文件有1 10不同的优先级 其中数字越大优先级越高 打印机会从自己的待打印队列中选择优先级最高的文件来打印 如果存在两个优先
  • 具有柔性结构的孤岛直流微电网的分级控制(Malab代码实现)

    欢迎来到本博客 目前更新 电力系统相关知识 期刊论文 算法 机器学习和人工智能学习 支持 如果觉得博主的文章还不错或者您用得到的话 可以关注一下博主 如果三连收藏支持就更好啦 这就是给予我最大的支持 本文目录如下 目录 1 概述 2 数学模
  • 设计模式:Service Mesh

    原文链接 http philcalcado com 2017 08 03 pattern service mesh html 自从数十年前 分布式系统的概念诞生以来 工程师们越来越明白 利用分布式系统可以完成许多意想不到的功能 但是 分布式
  • 自动生成存储过程

    感觉非常有用 就记录下来 希望看到的你有帮助 函数 清理默认值 create FUNCTION dbo fnCleanDefaultValue sDefaultValue varchar 4000 RETURNS varchar 4000
  • 在云原生环境中构建可扩展的大数据平台:方法和策略

    文章目录 1 选择适当的云提供商 2 采用容器化和微服务架构 3 分层架构设计 4 弹性计算资源 5 使用分布式计算框架 6 数据分区和分片 7 使用列式存储 8 缓存和数据预取 9 监控和优化 10 数据压缩和压缩 11 考虑数据分片和复
  • mysql到doris踩坑记录(如果有问题希望大家帮忙指出问题)

    1安装mysql 该步骤晚上很多 不做记录 2安装docker 同上 3安装并部署doris 下载镜像步骤省略 sudo docker run p 9030 9030 p 8030 8030 p 8040 8040 itd starrock
  • 设计模式——解释器模式

    解释器模式 解释器模式是什么 解释器模式解决什么问题 解释器模式实现 解释器模式是什么 给定一个语言 定义它的文法的一种表示 并定义一个解释器 这个解释器使用该表示来解释语言中的句子 解释器模式解决什么问题 如匹配字符串的正则表达式 解释器
  • STM32基于WiFi和蓝牙的内外网通信

    目录 通信模块选择 WiFi模块 蓝牙模块 基本框架 1 内网通信 近距离通信 2 外网通信 远程通信 3 关于WiFi配网以及云平台验证问题 4 关于蓝牙名称问题 模块连接图示 重要驱动开发 STM32的ID获取 蓝牙修改名称 WIFI配
  • Springboot租房管理平台 计算机毕设源码54739

    摘 要 2l世纪 随看全球经济的逢勃发展 众多经济字豕纷纷提出了新的管理理念 信息管理 强调了用信息支持决策 随着社会的发展 人们又提出了一个新的名词 管理信息系统 管理信息系统在强调信息的现代社会中变的越来越普及 它是一个利用计算机软硬件
  • oracle数据库学习之第三篇(权限及一些常用命令的演示)

    接上篇我们创建了新的数据库 test 由于中间出了很多的问题 所以这次的演示均在原来的数据库orcl上进行演示 同时也将我oracle数据库的版本切换到了11g 连接好数据库后 select name from v database v d
  • 数据分析毕业设计 金融数据分析与可视化系统 - python

    文章目录 0 前言 1 金融风控 一 题目理解 1 1 题目概况 1 2数据概况 1 3预测指标 三 查看数据 四 分类指标计算示例 4 1混淆矩阵 4 2准确度 4 3precision 精确度 recall 召回率 f1 score 4
  • 堆排序的C语言实现

    include
  • 机器学习-----聚类kmeans肘部图、轮廓图的绘制、以及聚类和聚类中心散点图的绘制

    1 kmeans肘部图和轮廓图 import pandas as pd import numpy as np import matplotlib pyplot as plt from sklearn cluster import KMean
  • Ubuntu持久化开放某端口

    1 查看Ubuntu防火墙状态 sudo ufw status 可以看到状态是未开启 2 打开443端口 sudo ufw allow 443 3 开启Ubuntu防火墙 sudo ufw enable 4 查看Ubuntu防火墙状态以及所
  • vue类与样式的绑定&&列表渲染

    目录 1 类与样式的绑定 1 1绑定 HTML class 1 2绑定数组 1 3绑定内联样式 绑定数组 2 列表渲染 2 1v for 2 2v for 与对象 2 3在 v for 里使用范围值 1 类与样式的绑定 1 1绑定 HTML
  • hashcat跑包小秘诀

    跑包工具千万种 小编就介绍一款最强的跑包工具hashcat 其实 抓包这些过程都比较简单 跑包是无线密码破解整个过程的重中之重 核心思路就是穷举密码 暴力破解 这个环节很耗时 小编尝试过许多工具均不理想 速度太慢 完全不能用于实际操作 当然
  • 【转】KVM I/O虚拟化分析

    最近在看多队列的东西 看到下面两篇文章 记录下 以后自己也深入写个这方向的知识 0 背景 当今的I O虚拟化主要有几种模式 1 通过设备的模拟 设备的模拟主要分为两种 一种是直接在VMM中完成模拟 如xen vmware 一种是在另一个应用