Linux虚拟子系统(笔记)

重新回顾了一下qemu-kvm中,如何在客户机上虚拟化CPU以及内存的过程,感觉每看一遍都有一些新的收获,发现在内核中一切的数据结构,函数调用关系都是有章可循,简洁美妙,所以在这里把自己的一些收获整理一下:

1. 对于各种Module的模拟(反射思想:配置中的string类型,如何生成对应的 Class类MachineClass,并且根据这个类来生成对应的实例MachineState)

alt

CPU虚拟化

2. CPU虚拟化的调用链很长,但是要清楚以下几个点:

  • vCPU的调度其实就相当于是物理机上对于一个线程的调度,并且伴随着VM Entry和VM Exit的状态变化,来改变寄存器的状态(与进程的调度类似)
  • 不同体系结构的CPU会有着类似的继承关系,以x86为例,继承关系为:TYPE_DEVICE->TYPE_CPU->TYPE_X86_CPU alt

alt

内存虚拟化

alt

存储虚拟化(上)

  • virtio的基本原理: alt

  • virtio的架构 alt

  • qemu中VirtIODevice,VirtQueue,vring的对应关系 alt

  • 存储虚拟化的大致流程 alt

  • qcow2文件格式

    uint32_t magic;
    uint32_t version;
    uint64_t backing_file_offset;
    uint32_t backing_file_size;
    uint32_t cluster_bits;
    uint64_t size; /* in bytes */
    uint32_t crypt_method;
    uint32_t l1_size; /* XXX: save number of clusters instead ? */
    uint64_t l1_table_offset;
    uint64_t refcount_table_offset;
    uint32_t refcount_table_clusters;
    uint32_t nb_snapshots;
    uint64_t snapshots_offset;

    /* The following fields are only valid for version >= 3 */
    uint64_t incompatible_features;
    uint64_t compatible_features;
    uint64_t autoclear_features;

    uint32_t refcount_order;
    uint32_t header_length;
} QEMU_PACKED QCowHeader;

存储虚拟化(下)

存储虚拟化场景下,虚拟机内写入的完整过程:

  1. 在虚拟机里面,应用层调用 write 系统调用写入文件。
  2. write 系统调用进入虚拟机里面的内核,经过 VFS,通用块设备层,I/O 调度层,到达块设备驱动。
  3. 虚拟机里面的块设备驱动是 virtio_blk,它和通用的块设备驱动一样,有一个 request queue,另外有一个函数 make_request_fn 会被设置为 blk_mq_make_request,这个函数用于将请求放入队列。
  4. 虚拟机里面的块设备驱动是 virtio_blk 会注册一个中断处理函数 vp_interrupt。当 qemu 写入完成之后,它会通知虚拟机里面的块设备驱动。
  5. blk_mq_make_request 最终调用 virtqueue_add,将请求添加到传输队列 virtqueue 中,然后调用 virtqueue_notify 通知 qemu。
  6. 在 qemu 中,本来虚拟机正处于 KVM_RUN 的状态,也即处于客户机状态。
  7. qemu 收到通知后,通过 VM exit 指令退出客户机状态,进入宿主机状态,根据退出原因,得知有 I/O 需要处理。
  8. qemu 调用 virtio_blk_handle_output,最终调用 virtio_blk_handle_vq。
  9. virtio_blk_handle_vq 里面有一个循环,在循环中,virtio_blk_get_request 函数从传输队列中拿出请求,然后调用 virtio_blk_handle_request 处理请求。
  10. virtio_blk_handle_request 会调用 blk_aio_pwritev,通过 BlockBackend 驱动写入 qcow2 文件。
  11. 写入完毕之后,virtio_blk_req_complete 会调用 virtio_notify 通知虚拟机里面的驱动。数据写入完成,刚才注册的中断处理函数 vp_interrupt 会收到这个通知。 alt

附:中间 virtio 队列的管理 alt alt

网络虚拟化

  1. 在虚拟机里面的用户态,应用程序通过 write 系统调用写入 socket。
  2. 写入的内容经过 VFS 层,内核协议栈,到达虚拟机里面的内核的网络设备驱动,也即 virtio_net。
  3. virtio_net 网络设备有一个操作结构 struct net_device_ops,里面定义了发送一个网络包调用的函数为 start_xmit。
  4. 在 virtio_net 的前端驱动和 qemu 中的后端驱动之间,有两个队列 virtqueue,一个用于发送,一个用于接收。
  5. 然后,我们需要在 start_xmit 中调用 virtqueue_add,将网络包放入发送队列,然后调用 virtqueue_notify 通知 qemu。
  6. qemu 本来处于 KVM_RUN 的状态,收到通知后,通过 VM exit 指令退出客户机模式,进入宿主机模式。发送网络包的时候,virtio_net_handle_tx_bh 函数会被调用。
  7. 接下来是一个 for 循环,我们需要在循环中调用 virtqueue_pop,从传输队列中获取要发送的数据,然后调用 qemu_sendv_packet_async 进行发送。
  8. qemu 会调用 writev 向字符设备文件写入,进入宿主机的内核。
  9. 在宿主机内核中字符设备文件的 file_operations 里面的 write_iter 会被调用,也即会调用 tun_chr_write_iter。
  10. 在 tun_chr_write_iter 函数中,tun_get_user 将要发送的网络包从 qemu 拷贝到宿主机内核里面来,然后调用 netif_rx_ni 开始调用宿主机内核协议栈进行处理。
  11. 宿主机内核协议栈处理完毕之后,会发送给 tap 虚拟网卡,完成从虚拟机里面到宿主机的整个发送过程。 alt

参考-刘超《趣谈Linux操作系统》

全部评论

相关推荐

1 6 评论
分享
牛客网
牛客企业服务