这两个 blockdev 声明有什么区别?

这两个 blockdev 声明有什么区别?

在 Debian bullseye 中,我通过命令行使用 QEMU / KVM 启动虚拟机(即,没有virsh或其他帮助器 / 包装器)。其中一个虚拟机从声明如下的块设备启动:

-blockdev driver=file,node-name=q1,filename=/dev/loop0 \

就在今天,我无意中注意到 QEMU 在启动该 VM 时发出以下警告:

Opening a block device as a file using the 'file' driver is deprecated

一些研究表明这个警告是已知的,并且有解决方案,例如@Stephen Kitt's接受在这里回答其中提出以下blockdev声明:

-blockdev node-name=q1,driver=raw,file.driver=host_device,file.filename=/dev/loop0 \

这个解决方案无疑是有效的,但我找不到任何有关file.driver=host_device.因此,我测试了一些其他选项,并得出了以下似乎也有效的解决方案:

-blockdev driver=host_device,node-name=q1,filename=/dev/loop0 \

有人可以简单解释一下这两个声明之间的区别吗?值得注意的是,其中一个在延迟或吞吐量方面是否会优于另一个?

作为一个额外的问题,有人知道文档在哪里host_device吗?在上面链接的其他问题/答案中,有一个链接提交可能实现了该驱动程序。但是,我也找不到该链接背后的任何文档。

答案1

根据QEMU 变更日志,在 QEMU 版本 3.0 中,访问主机块设备时弃用了驱动file程序并引入了驱动程序。host_device

您提到的 Stephen Kitt 答案的 PDF 演示文稿(参见第 20...24 页)表示定义 QEMU 存储的最详尽方法意味着首先使用 a 设置数据位置(filehost_devicenode_name=,然后使用--blockdev带有 a 的另一个选项file=<previously_defined_node_name>在第一层之上添加另一层指定数据格式(例如rawqcow2例如)。这为用户提供了极好的控制,但对于大多数基本情况来说就太过分了。

在 QEMU 源代码存储库中,文件qemu-options.hx似乎-blockdev对我迄今为止见过的选项有最好的描述。

有一段话或许可以为我们解开这个file.<something>=<something>谜团提供一些启示:

期望引用另一个节点(例如file)的选项可以通过两种方式给出。您可以指定已存在节点的节点名称 ( file=node-name),也可以定义内联新节点,在点 ( file.filename=path,file.aio=native) 后添加引用节点的选项。

因此,file.<something>=语法本质上是指定另一个-blockdev声明的简写方式,同时也避免了node_name“中间节点”名称使命名空间变得混乱。

因此,斯蒂芬·基特的blockdev声明是:

-blockdev node-name=q1,driver=raw,file.driver=host_device,file.filename=/dev/loop0

似乎相当于扩展形式:

-blockdev node_name=<hidden_node>,driver=host_device,filename=/dev/loop0 \
-blockdev node_name=q1,driver=raw,file=<hidden_node>

这和你的声明之间的区别

-blockdev driver=host_device,node-name=q1,filename=/dev/loop0 

这是一个比较棘手的问题,需要深入研究 QEMU 源代码才能弄清楚。

QEMU的-blockdev驱动程序filehost_device并且host_cdrom针对不同的主机架构有多个版本。对于 Linux,适用的位于block/file-posix.c。搜索字符串的实例BlockDriver bdrv_,您将找到每一个。 (您还会发现,host_cdrom出于某种原因,FreeBSD有一个完全独立的定义。)

每个BlockDriver似乎都是由(大部分)函数指针的结构定义的。这些指针可能指向特定于驱动程序的函数,或者引用与另一个驱动程序共享的公共实现。

驱动file程序定义如下:

BlockDriver bdrv_file = {
    .format_name = "file",
    .protocol_name = "file",
    .instance_size = sizeof(BDRVRawState),
    .bdrv_needs_filename = true,
    .bdrv_probe = NULL, /* no probe for protocols */
    .bdrv_parse_filename = raw_parse_filename,
    .bdrv_file_open = raw_open,
    .bdrv_reopen_prepare = raw_reopen_prepare,
    .bdrv_reopen_commit = raw_reopen_commit,
    .bdrv_reopen_abort = raw_reopen_abort,
    .bdrv_close = raw_close,
    .bdrv_co_create = raw_co_create,
    .bdrv_co_create_opts = raw_co_create_opts,
    .bdrv_has_zero_init = bdrv_has_zero_init_1,
    .bdrv_co_block_status = raw_co_block_status,
    .bdrv_co_invalidate_cache = raw_co_invalidate_cache,
    .bdrv_co_pwrite_zeroes = raw_co_pwrite_zeroes,
    .bdrv_co_delete_file = raw_co_delete_file,

    .bdrv_co_preadv         = raw_co_preadv,
    .bdrv_co_pwritev        = raw_co_pwritev,
    .bdrv_co_flush_to_disk  = raw_co_flush_to_disk,
    .bdrv_co_pdiscard       = raw_co_pdiscard,
    .bdrv_co_copy_range_from = raw_co_copy_range_from,
    .bdrv_co_copy_range_to  = raw_co_copy_range_to,
    .bdrv_refresh_limits = raw_refresh_limits,
    .bdrv_attach_aio_context = raw_aio_attach_aio_context,

    .bdrv_co_truncate                   = raw_co_truncate,
    .bdrv_co_getlength                  = raw_co_getlength,
    .bdrv_co_get_info                   = raw_co_get_info,
    .bdrv_get_specific_info             = raw_get_specific_info,
    .bdrv_co_get_allocated_file_size    = raw_co_get_allocated_file_size,
    .bdrv_get_specific_stats = raw_get_specific_stats,
    .bdrv_check_perm = raw_check_perm,
    .bdrv_set_perm   = raw_set_perm,
    .bdrv_abort_perm_update = raw_abort_perm_update,
    .create_opts = &raw_create_opts,
    .mutable_opts = mutable_opts,
};

所以它本质上是驱动程序的克隆raw,几乎所有功能都引用它。

定义host_device稍微复杂一点:

static BlockDriver bdrv_host_device = {
    .format_name        = "host_device",
    .protocol_name        = "host_device",
    .instance_size      = sizeof(BDRVRawState),
    .bdrv_needs_filename = true,
    .bdrv_probe_device  = hdev_probe_device,
    .bdrv_parse_filename = hdev_parse_filename,
    .bdrv_file_open     = hdev_open,
    .bdrv_close         = raw_close,
    .bdrv_reopen_prepare = raw_reopen_prepare,
    .bdrv_reopen_commit  = raw_reopen_commit,
    .bdrv_reopen_abort   = raw_reopen_abort,
    .bdrv_co_create_opts = bdrv_co_create_opts_simple,
    .create_opts         = &bdrv_create_opts_simple,
    .mutable_opts        = mutable_opts,
    .bdrv_co_invalidate_cache = raw_co_invalidate_cache,
    .bdrv_co_pwrite_zeroes = hdev_co_pwrite_zeroes,

    .bdrv_co_preadv         = raw_co_preadv,
    .bdrv_co_pwritev        = raw_co_pwritev,
    .bdrv_co_flush_to_disk  = raw_co_flush_to_disk,
    .bdrv_co_pdiscard       = hdev_co_pdiscard,
    .bdrv_co_copy_range_from = raw_co_copy_range_from,
    .bdrv_co_copy_range_to  = raw_co_copy_range_to,
    .bdrv_refresh_limits = raw_refresh_limits,
    .bdrv_attach_aio_context = raw_aio_attach_aio_context,

    .bdrv_co_truncate                   = raw_co_truncate,
    .bdrv_co_getlength                  = raw_co_getlength,
    .bdrv_co_get_info                   = raw_co_get_info,
    .bdrv_get_specific_info             = raw_get_specific_info,
    .bdrv_co_get_allocated_file_size    = raw_co_get_allocated_file_size,
    .bdrv_get_specific_stats = hdev_get_specific_stats,
    .bdrv_check_perm = raw_check_perm,
    .bdrv_set_perm   = raw_set_perm,
    .bdrv_abort_perm_update = raw_abort_perm_update,
    .bdrv_probe_blocksizes = hdev_probe_blocksizes,
    .bdrv_probe_geometry = hdev_probe_geometry,

    /* generic scsi device */
#ifdef __linux__
    .bdrv_co_ioctl          = hdev_co_ioctl,
#endif

    /* zoned device */
#if defined(CONFIG_BLKZONED)
    /* zone management operations */
    .bdrv_co_zone_report = raw_co_zone_report,
    .bdrv_co_zone_mgmt = raw_co_zone_mgmt,
    .bdrv_co_zone_append = raw_co_zone_append,
#endif
};

它还引用raw驱动程序的大部分功能,但有自己的专用功能,例如:

  • 探测设备
  • 解析文件名(大概是为了检查有效的主机设备)
  • 打开设备(如果您深入挖掘,您会发现以 MacOS 上的用户身份访问块设备需要相当多的额外代码,因此这很可能是特定于操作系统的事情所需要的)
  • 快速写入大块零
  • 丢弃数据
  • 检查和设置设备权限
  • 获取设备统计信息
  • 检测设备的块大小和驱动器几何形状
  • 传递通用 SCSI 命令

其中一些host_device特定函数只是执行额外的错误检查,然后调用raw驱动程序的等效函数。但它可能是最后一个提供了host_device驱动程序的大部分价值:它允许虚拟机显式地查看主机设备的实际块大小和几何结构(如果适用)。通过通用 SCSI 命令,VM 可以被授予访问磁带库机器人、大容量磁带驱动器和 DVD 刻录机等内容的权限......以及许多其他内容。

您的较短声明有效,因为在大多数情况下host_device声明本质上都会回落到驱动程序的功能。raw

请注意,上述内容仅适用于 POSIX 风格的操作系统:如果您在 Windows 上运行 QEMU,设备访问可能必须与常规文件访问完全不同,并且您的较短声明可能根本不起作用。file和之间的区别可能host_device主要是为了将 QEMU 移植到其他系统架构,其中设备访问与常规文件有很大不同。

你担心性能,但我希望性能基本相同,因为这三个驱动程序最终都调用完全相同的代码来实现核心功能bdrv_co_preadvbdrv_co_pwritev函数。

当将驱动程序彼此分层时,可以非常简单地检测给定驱动程序是否使用与将在其之上分层的驱动程序完全相同的功能,并优化消除重复。我的第一个猜测是,这样的优化实际上是非常必要的,以避免各种愚蠢的行为。因此,如果配置最终执行完全相同的操作,我希望它执行相同的操作,无论它是如何声明的。

相关内容