virtio的理解和guest中的virtio-blk分析

    xiaoxiao2021-03-27  45

    virtio 有分为guest 中的前端程序和qemu中的后端程序 virtio中的五种前端程序为 virtio-blk:/drivers/block/virtio-blk.c virtio-net:/drivers/net/virtio-net.c virtio-pci:/drivers/virtio/virtio-pci.c virtio-ballon:/drivers/virtio/virtio-ballon.c virtio-console:/drivers/virtio/virtio-console.c 这五种往下调用/drivers/virtio/virtio.c -> /drivers/virtio/virtio_ring.c 总结一下virtio的flow:guest->qemu->host kernel ->hw 下来我们先看看virtio-blk.c static int __init init(void) {     int error;     virtblk_wq = alloc_workqueue("virtio-blk", 0, 0);     if (!virtblk_wq)         return -ENOMEM;     major = register_blkdev(0, "virtblk");     if (major < 0) {         error = major;         goto out_destroy_workqueue;     }     error = register_virtio_driver(&virtio_blk);     if (error)         goto out_unregister_blkdev;     return 0; out_unregister_blkdev:     unregister_blkdev(major, "virtblk"); out_destroy_workqueue:     destroy_workqueue(virtblk_wq);     return error; } 在其入口函数中,申请了一个static struct workqueue_struct *virtblk_wq;,新建了一个virtblk的block设备,并调用register_virtio_driver 注册static struct virtio_driver virtio_blk int register_virtio_driver(struct virtio_driver *driver) {     /* Catch this early. */     BUG_ON(driver->feature_table_size && !driver->feature_table);     driver->driver.bus = &virtio_bus;     return driver_register(&driver->driver); } 可见所有的virtio_driver 有自己的总线virtio_bus 当通过qemu启动guest的时候如果指定-device virtio-blk-device。例如: qemu-system-aarch64 -machine virt -cpu host -kernel Image -drive if=none,file=ubuntu.img,id=fs -device virtio-blk-device,drive=fs -append "console=ttyAMA0 root=/dev/vda1" -nographic -D -d -enable-kvm 就会调用virtio_blk的virtblk_probe函数 在virtblk_probe 函数中,分配一个    struct virtio_blk *vblk;这个代表一个virtio的blk设备     vdev->priv = vblk = kmalloc(sizeof(*vblk), GFP_KERNEL); 并保存到virtio_device的priv变量中。 通过    err = init_vq(vblk);申请一个    struct virtqueue **vqs; static int init_vq(struct virtio_blk *vblk) {     int err;     int i;     vq_callback_t **callbacks;     const char **names;     struct virtqueue **vqs;     unsigned short num_vqs;     struct virtio_device *vdev = vblk->vdev;     err = virtio_cread_feature(vdev, VIRTIO_BLK_F_MQ,                    struct virtio_blk_config, num_queues,                    &num_vqs);     if (err)         num_vqs = 1;     vblk->vqs = kmalloc_array(num_vqs, sizeof(*vblk->vqs), GFP_KERNEL);     if (!vblk->vqs)         return -ENOMEM; //通过kmalloc_array 申请数组     names = kmalloc_array(num_vqs, sizeof(*names), GFP_KERNEL);     callbacks = kmalloc_array(num_vqs, sizeof(*callbacks), GFP_KERNEL);     vqs = kmalloc_array(num_vqs, sizeof(*vqs), GFP_KERNEL);     if (!names || !callbacks || !vqs) {         err = -ENOMEM;         goto out;     } //给callback 和name 这两个数组赋值     for (i = 0; i < num_vqs; i++) {         callbacks[i] = virtblk_done;         snprintf(vblk->vqs[i].name, VQ_NAME_LEN, "req.%d", i);         names[i] = vblk->vqs[i].name;     } //看是否可以找到virtqueues,并将前面赋值的callback和names 写道configuration中     /* Discover virtqueues and write information to configuration.  */     err = vdev->config->find_vqs(vdev, num_vqs, vqs, callbacks, names);     if (err)         goto out; //将    struct virtqueue **vqs; 保存到vblk->vqs[i].vq 中     for (i = 0; i < num_vqs; i++) {         spin_lock_init(&vblk->vqs[i].lock);         vblk->vqs[i].vq = vqs[i];     }     vblk->num_vqs = num_vqs; out:     kfree(vqs);     kfree(callbacks);     kfree(names);     if (err)         kfree(vblk->vqs);     return err; } 回到virtblk_probe 中通过 vblk->disk = alloc_disk(1 << PART_BITS); 申请一个gendisk,这里代表的是virtio blk的物理硬盘 分配blk 用的queue q = vblk->disk->queue = blk_mq_init_queue(&vblk->tag_set); 其中对request的操作函数是通过     memset(&vblk->tag_set, 0, sizeof(vblk->tag_set));     vblk->tag_set.ops = &virtio_mq_ops; 来设定的。 static struct blk_mq_ops virtio_mq_ops = {     .queue_rq    = virtio_queue_rq,     .complete    = virtblk_request_done,     .init_request    = virtblk_init_request, }; 因此virtio blk的queue_rq 函数是virtio_queue_rq static int virtio_queue_rq(struct blk_mq_hw_ctx *hctx,                const struct blk_mq_queue_data *bd) {     struct virtio_blk *vblk = hctx->queue->queuedata;     struct request *req = bd->rq;     struct virtblk_req *vbr = blk_mq_rq_to_pdu(req);     unsigned long flags;     unsigned int num;     int qid = hctx->queue_num;     int err;     bool notify = false;     BUG_ON(req->nr_phys_segments + 2 > vblk->sg_elems);     vbr->req = req;     blk_mq_start_request(req); //将vbr添加到sg中     num = blk_rq_map_sg(hctx->queue, vbr->req, vbr->sg);     if (num) {         if (rq_data_dir(vbr->req) == WRITE)             vbr->out_hdr.type |= cpu_to_virtio32(vblk->vdev, VIRTIO_BLK_T_OUT);         else             vbr->out_hdr.type |= cpu_to_virtio32(vblk->vdev, VIRTIO_BLK_T_IN);     }     spin_lock_irqsave(&vblk->vqs[qid].lock, flags);     err = __virtblk_add_req(vblk->vqs[qid].vq, vbr, vbr->sg, num);     if (err) {         virtqueue_kick(vblk->vqs[qid].vq);         blk_mq_stop_hw_queue(hctx);         spin_unlock_irqrestore(&vblk->vqs[qid].lock, flags);         /* Out of mem doesn't actually happen, since we fall back          * to direct descriptors */         if (err == -ENOMEM || err == -ENOSPC)             return BLK_MQ_RQ_QUEUE_BUSY;         return BLK_MQ_RQ_QUEUE_ERROR;     }     if (bd->last && virtqueue_kick_prepare(vblk->vqs[qid].vq))         notify = true;     spin_unlock_irqrestore(&vblk->vqs[qid].lock, flags);     if (notify)         virtqueue_notify(vblk->vqs[qid].vq);     return BLK_MQ_RQ_QUEUE_OK; } 最终调用virtqueue_kick 来通知qemu(及所谓的后端),当qemu处理完后又调用virtio_mq_ops的virtblk_request_done函数。这样就完成了guest->qemu->host kernel ->hw 一个循环. 给磁盘设置名字virtblk_name_format("vd", index, vblk->disk->disk_name, DISK_NAME_LEN); 从这里看到由于是virto设备,这里是以vd开头的. 给disk的其他成员变量赋值     vblk->disk->major = major;     vblk->disk->first_minor = index_to_minor(index);     vblk->disk->private_data = vblk;     vblk->disk->fops = &virtblk_fops;     vblk->disk->flags |= GENHD_FL_EXT_DEVT;     vblk->index = index;

    而在register_virtio_driver->device_register->device_add时会调用    kobject_uevent(&dev->kobj, KOBJ_ADD); 通知user space,新添加了一个device。注意这里的aciton是KOBJ_ADD

    最后调用    device_add_disk(&vdev->dev, vblk->disk); 添加磁盘.
    转载请注明原文地址: https://ju.6miu.com/read-664477.html

    最新回复(0)