根据上面所说的, 模拟磁盘的初始化分为前端(磁盘设备)和后端(镜像文件)的初始化, 在后端的初始化中, 入口函数是 drive_init(), 到最后的 qemu_open() 终止. 上图 中红色得模块就是 Cache 初始化最重要的两个地方.
简单的分析一下流程.
int bdrv_parse_cache_flags(const char *mode, int *flags)
{
*flags &= ~BDRV_O_CACHE_MASK;
/*
* - BDRV_O_NOCACHE: host end 绕过 cache
* - BDRV_O_CACHE_WB: guest 的磁盘设备启用 writeback cache
* - BDRV_O_NO_FLUSH: 在 host end 永远不要把 cache 里的数据同步到文件里
* 这几个宏的具体应用后面分析到数据读写的时候会进行分析
*/
if (!strcmp(mode, "off") || !strcmp(mode, "none")) {
/* 由上, 这个组合表示的是 host end 不用 Cache, 数据直接在用户空间(QEMU)
* 和真实设备之间通过 DMA 直接传输, 但是同时, 告诉 guest 模拟的磁盘设备
* 是有 cache 的, guest 能发起 flush 的操作(fsync/fdatasync) */
*flags |= BDRV_O_NOCACHE | BDRV_O_CACHE_WB;
} else if (!strcmp(mode, "directsync")) {
/* 很好理解, 完全没有 cache, host end 和 guest end 都没有 cache, guest
* 不会发起 flush 的操作 */
*flags |= BDRV_O_NOCACHE;
} else if (!strcmp(mode, "writeback")) {
/* 和上面相反, host side 和 guest side 都有 cache, 性能最好, 但是如果
* host 掉电, 会导致数据的损失 */
*flags |= BDRV_O_CACHE_WB;
} else if (!strcmp(mode, "unsafe")) {
/* 见文可知意, 最不安全的模式, guest side 有cache, 但是 host side 不理睬
* guest 发起的 flush 操作, 完全忽略, 这种情况性能最高, snapshot 默认使用
* 的就是这种模式 */
*flags |= BDRV_O_CACHE_WB;
*flags |= BDRV_O_NO_FLUSH;
} else if (!strcmp(mode, "writethrough")) {
/* host end 有 cache, guest 没有 cache, 其实通过后面的代码分析可以知道,
* 这种模式其实是 writeback + flush 的组合, 也就是每次写操作同时触发
* 一个 host flush 的操作, 会带来一定的性能损失, 尤其是非 raw(e.g. qcow2)
* 的网络存储(e.g. ceph), 但是很遗憾, 这是 QEMU 默认的 Cache 模式 */
/* this is the default */
} else {
return -1;
}
return 0;
}
DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
{
/* code snippet */
value = qemu_opt_get(all_opts, "cache");
if (value) {
int flags = 0;
/* 解析命令行 -drive 的 cache= 选项 */
if (bdrv_parse_cache_flags(value, &flags) != 0) {
error_report("invalid cache option");
return NULL;
}
/* Specific options take precedence */
if (!qemu_opt_get(all_opts, "cache.writeback")) {
qemu_opt_set_bool(all_opts, "cache.writeback",
!!(flags & BDRV_O_CACHE_WB));
}
if (!qemu_opt_get(all_opts, "cache.direct")) {
qemu_opt_set_bool(all_opts, "cache.direct",
!!(flags & BDRV_O_NOCACHE));
}
if (!qemu_opt_get(all_opts, "cache.no-flush")) {
qemu_opt_set_bool(all_opts, "cache.no-flush",
!!(flags & BDRV_O_NO_FLUSH));
}
qemu_opt_unset(all_opts, "cache");
}
return blockdev_init(all_opts, block_default_type);
} |
int bdrv_parse_cache_flags(const char *mode, int *flags)
{
*flags &= ~BDRV_O_CACHE_MASK;
/*
* - BDRV_O_NOCACHE: host end 绕过 cache
* - BDRV_O_CACHE_WB: guest 的磁盘设备启用 writeback cache
* - BDRV_O_NO_FLUSH: 在 host end 永远不要把 cache 里的数据同步到文件里
* 这几个宏的具体应用后面分析到数据读写的时候会进行分析
*/
if (!strcmp(mode, "off") || !strcmp(mode, "none")) {
/* 由上, 这个组合表示的是 host end 不用 Cache, 数据直接在用户空间(QEMU)
* 和真实设备之间通过 DMA 直接传输, 但是同时, 告诉 guest 模拟的磁盘设备
* 是有 cache 的, guest 能发起 flush 的操作(fsync/fdatasync) */
*flags |= BDRV_O_NOCACHE | BDRV_O_CACHE_WB;
} else if (!strcmp(mode, "directsync")) {
/* 很好理解, 完全没有 cache, host end 和 guest end 都没有 cache, guest
* 不会发起 flush 的操作 */
*flags |= BDRV_O_NOCACHE;
} else if (!strcmp(mode, "writeback")) {
/* 和上面相反, host side 和 guest side 都有 cache, 性能最好, 但是如果
* host 掉电, 会导致数据的损失 */
*flags |= BDRV_O_CACHE_WB;
} else if (!strcmp(mode, "unsafe")) {
/* 见文可知意, 最不安全的模式, guest side 有cache, 但是 host side 不理睬
* guest 发起的 flush 操作, 完全忽略, 这种情况性能最高, snapshot 默认使用
* 的就是这种模式 */
*flags |= BDRV_O_CACHE_WB;
*flags |= BDRV_O_NO_FLUSH;
} else if (!strcmp(mode, "writethrough")) {
/* host end 有 cache, guest 没有 cache, 其实通过后面的代码分析可以知道,
* 这种模式其实是 writeback + flush 的组合, 也就是每次写操作同时触发
* 一个 host flush 的操作, 会带来一定的性能损失, 尤其是非 raw(e.g. qcow2)
* 的网络存储(e.g. ceph), 但是很遗憾, 这是 QEMU 默认的 Cache 模式 */
/* this is the default */
} else {
return -1;
}
return 0;
}
DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
{
/* code snippet */
value = qemu_opt_get(all_opts, "cache");
if (value) {
int flags = 0;
/* 解析命令行 -drive 的 cache= 选项 */
if (bdrv_parse_cache_flags(value, &flags) != 0) {
error_report("invalid cache option");
return NULL;
}
/* Specific options take precedence */
if (!qemu_opt_get(all_opts, "cache.writeback")) {
qemu_opt_set_bool(all_opts, "cache.writeback",
!!(flags & BDRV_O_CACHE_WB));
}
if (!qemu_opt_get(all_opts, "cache.direct")) {
qemu_opt_set_bool(all_opts, "cache.direct",
!!(flags & BDRV_O_NOCACHE));
}
if (!qemu_opt_get(all_opts, "cache.no-flush")) {
qemu_opt_set_bool(all_opts, "cache.no-flush",
!!(flags & BDRV_O_NO_FLUSH));
}
qemu_opt_unset(all_opts, "cache");
}
return blockdev_init(all_opts, block_default_type);
}
接下来的流程稍微复杂一点:
blockdev_init
-> bdrv_open
-> bdrv_file_open
-> raw_open
-> raw_open_common
-> raw_parse_flags
-> qemu_open
首先是从 blockdev_init 到 raw_open 的流程, 简单分析如下:
static DriveInfo *blockdev_init(QemuOpts *all_opts,
BlockInterfaceType block_default_type)
{
DriveInfo *dinfo;
/* code snippet 解析和配置各种各样的参数, e.g. 磁盘格式, 启动顺序等等,
* 最后填充到 dinfo 对象中 */
snapshot = qemu_opt_get_bool(opts, "snapshot", 0);
file = qemu_opt_get(opts, "file");
if (qemu_opt_get_bool(opts, "cache.writeback", true)) {
bdrv_flags |= BDRV_O_CACHE_WB;
}
if (qemu_opt_get_bool(opts, "cache.direct", false)) {
bdrv_flags |= BDRV_O_NOCACHE;
}
if (qemu_opt_get_bool(opts, "cache.no-flush", false)) {
bdrv_flags |= BDRV_O_NO_FLUSH;
}
if (snapshot) {
/* 前面讲过, snapshot 打开磁盘时候, 使用 unsafe 的 cache 模式 */
bdrv_flags &= ~BDRV_O_CACHE_MASK;
bdrv_flags |= (BDRV_O_SNAPSHOT|BDRV_O_CACHE_WB|BDRV_O_NO_FLUSH);
}
bdrv_flags |= ro ? 0 : BDRV_O_RDWR;
/* 使用 bdrv_open 打开文件 */
ret = bdrv_open(dinfo->bdrv, file, bs_opts, bdrv_flags, drv, &error);
/* 返回配置好的 DriveInfo *dinfo, 这个对象在初始化模拟磁盘设备
* 的时候被传入, 写入该磁盘设备的 PCI config space */
return dinfo;
}
int bdrv_open(BlockDriverState *bs, const char *filename, QDict *options,
int flags, BlockDriver *drv, Error **errp)
{
BlockDriverState *file = NULL;
const char *drvname;
/* 打开文件, QEMU 支持的镜像格式都有一个后端的 format, 比如 raw 和
* qcow2 这个 format 就是 file, 其他的还有 sheepdog, glusterfs 的
* gluster 等. 所以这里其实是打开一个本地文件 */
ret = bdrv_file_open(&file, filename, file_options,
bdrv_open_flags(bs, flags | BDRV_O_UNMAP), &local_err);
/* 其实 bdrv_file_open() 就会调用 bdrv_open_common 函数, 只不过那个时候调用
* bdrv_open_common() 用的是 file 这个 BlockDriver, 现在使用的是磁盘文件
* format 的 BlockDriver(qcow2, raw 等), 所以这里的函数用来初始化特定格式的
* 磁盘文件, 如 qcow2_open 等 */
ret = bdrv_open_common(bs, file, options, flags, drv, &local_err);
}
int bdrv_file_open(BlockDriverState **pbs, const char *filename,
QDict *options, int flags, Error **errp)
{
BlockDriverState *bs;
/* 找到相应的格式的 BlockDriver, 由于这里是 file 的 open, 因为 file
* 还没有被打开, 所以这里第二个指针传递的是空, 注意 drv 这个参数, 表示
* file 的 BlockDriver */
ret = bdrv_open_common(bs, NULL, options, flags, drv, &local_err);
return ret;
}
static int bdrv_open_common(BlockDriverState *bs, BlockDriverState *file,
QDict *options, int flags, BlockDriver *drv, Error **errp)
{
bs->open_flags = flags;
open_flags = bdrv_open_flags(bs, flags);
/* 注意这里, flags 保存着之前 bdrv_parse_cache_flags 获取到的 flags,
* 如果用户没有指定 none, writeback, 或者是 unsafe, 那么 guest 看到
* 的这个磁盘设备是没有 cache 的, 后面我会以 hdparm 这个工具来验证,
* 同时, 这个变量(bs->enable_write_cache)控制着 QEMU 怎么模拟 cache
* 行为, 后面写文件的时候会分析到 */
bs->enable_write_cache = !!(flags & BDRV_O_CACHE_WB);
/* 打开文件, 因为从上面的流程可以看到, 这里作为 file 打开, 而不是
* 作为 image format(e.g. qcow2) 打开, 所以这里调用的是 bdrv_file_open()
* 方法, 也就是 raw-posix.c 中 format_name="file" 的 BlockDriver 里面的
* 的 raw_open */
if (drv->bdrv_file_open) {
assert(file == NULL);
assert(drv->bdrv_parse_filename || filename != NULL);
ret = drv->bdrv_file_open(bs, options, open_flags, &local_err);
} else {
if (file == NULL) {
error_setg(errp, "Can't use '%s' as a block driver for the "
"protocol level", drv->format_name);
ret = -EINVAL;
goto free_and_fail;
}
bs->file = file;
ret = drv->bdrv_open(bs, options, open_flags, &local_err);
}
return ret;
} |
static DriveInfo *blockdev_init(QemuOpts *all_opts,
BlockInterfaceType block_default_type)
{
DriveInfo *dinfo;
/* code snippet 解析和配置各种各样的参数, e.g. 磁盘格式, 启动顺序等等,
* 最后填充到 dinfo 对象中 */
snapshot = qemu_opt_get_bool(opts, "snapshot", 0);
file = qemu_opt_get(opts, "file");
if (qemu_opt_get_bool(opts, "cache.writeback", true)) {
bdrv_flags |= BDRV_O_CACHE_WB;
}
if (qemu_opt_get_bool(opts, "cache.direct", false)) {
bdrv_flags |= BDRV_O_NOCACHE;
}
if (qemu_opt_get_bool(opts, "cache.no-flush", false)) {
bdrv_flags |= BDRV_O_NO_FLUSH;
}
if (snapshot) {
/* 前面讲过, snapshot 打开磁盘时候, 使用 unsafe 的 cache 模式 */
bdrv_flags &= ~BDRV_O_CACHE_MASK;
bdrv_flags |= (BDRV_O_SNAPSHOT|BDRV_O_CACHE_WB|BDRV_O_NO_FLUSH);
}
bdrv_flags |= ro ? 0 : BDRV_O_RDWR;
/* 使用 bdrv_open 打开文件 */
ret = bdrv_open(dinfo->bdrv, file, bs_opts, bdrv_flags, drv, &error);
/* 返回配置好的 DriveInfo *dinfo, 这个对象在初始化模拟磁盘设备
* 的时候被传入, 写入该磁盘设备的 PCI config space */
return dinfo;
}
int bdrv_open(BlockDriverState *bs, const char *filename, QDict *options,
int flags, BlockDriver *drv, Error **errp)
{
BlockDriverState *file = NULL;
const char *drvname;
/* 打开文件, QEMU 支持的镜像格式都有一个后端的 format, 比如 raw 和
* qcow2 这个 format 就是 file, 其他的还有 sheepdog, glusterfs 的
* gluster 等. 所以这里其实是打开一个本地文件 */
ret = bdrv_file_open(&file, filename, file_options,
bdrv_open_flags(bs, flags | BDRV_O_UNMAP), &local_err);
/* 其实 bdrv_file_open() 就会调用 bdrv_open_common 函数, 只不过那个时候调用
* bdrv_open_common() 用的是 file 这个 BlockDriver, 现在使用的是磁盘文件
* format 的 BlockDriver(qcow2, raw 等), 所以这里的函数用来初始化特定格式的
* 磁盘文件, 如 qcow2_open 等 */
ret = bdrv_open_common(bs, file, options, flags, drv, &local_err);
}
int bdrv_file_open(BlockDriverState **pbs, const char *filename,
QDict *options, int flags, Error **errp)
{
BlockDriverState *bs;
/* 找到相应的格式的 BlockDriver, 由于这里是 file 的 open, 因为 file
* 还没有被打开, 所以这里第二个指针传递的是空, 注意 drv 这个参数, 表示
* file 的 BlockDriver */
ret = bdrv_open_common(bs, NULL, options, flags, drv, &local_err);
return ret;
}
static int bdrv_open_common(BlockDriverState *bs, BlockDriverState *file,
QDict *options, int flags, BlockDriver *drv, Error **errp)
{
bs->open_flags = flags;
open_flags = bdrv_open_flags(bs, flags);
/* 注意这里, flags 保存着之前 bdrv_parse_cache_flags 获取到的 flags,
* 如果用户没有指定 none, writeback, 或者是 unsafe, 那么 guest 看到
* 的这个磁盘设备是没有 cache 的, 后面我会以 hdparm 这个工具来验证,
* 同时, 这个变量(bs->enable_write_cache)控制着 QEMU 怎么模拟 cache
* 行为, 后面写文件的时候会分析到 */
bs->enable_write_cache = !!(flags & BDRV_O_CACHE_WB);
/* 打开文件, 因为从上面的流程可以看到, 这里作为 file 打开, 而不是
* 作为 image format(e.g. qcow2) 打开, 所以这里调用的是 bdrv_file_open()
* 方法, 也就是 raw-posix.c 中 format_name="file" 的 BlockDriver 里面的
* 的 raw_open */
if (drv->bdrv_file_open) {
assert(file == NULL);
assert(drv->bdrv_parse_filename || filename != NULL);
ret = drv->bdrv_file_open(bs, options, open_flags, &local_err);
} else {
if (file == NULL) {
error_setg(errp, "Can't use '%s' as a block driver for the "
"protocol level", drv->format_name);
ret = -EINVAL;
goto free_and_fail;
}
bs->file = file;
ret = drv->bdrv_open(bs, options, open_flags, &local_err);
}
return ret;
}
最后剩下核心的 raw_open 函数, 这个函数和 host side 的 Cache 相关, 通过上面 的分析, 已经比较简单了.
/* 这个函数其实是 raw_open_common */
static int raw_open(BlockDriverState *bs, QDict *options, int flags,
Error **errp)
{
BDRVRawState *s = bs->opaque;
s->type = FTYPE_FILE;
return raw_open_common(bs, options, flags, 0);
}
static int raw_open_common(BlockDriverState *bs, QDict *options,
int bdrv_flags, int open_flags)
{
s->open_flags = open_flags;
/* 解析 open 的参数, 把 QEMU 的 BDRV_O_* 映射成 open 的 O_*, 下面详细分析 */
raw_parse_flags(bdrv_flags, &s->open_flags);
s->fd = -1;
/* 用上面解析到得参数打开文件 */
fd = qemu_open(filename, s->open_flags, 0644);
s->fd = fd;
}
static void raw_parse_flags(int bdrv_flags, int *open_flags)
{
assert(open_flags != NULL);
/* 首先清空其他标志 */
*open_flags |= O_BINARY;
*open_flags &= ~O_ACCMODE;
/* 设置读写权限位 */
if (bdrv_flags & BDRV_O_RDWR) {
*open_flags |= O_RDWR;
} else {
*open_flags |= O_RDONLY;
}
/* 如果设置了 cache=none, 那么直接用 O_DIRECT 打开文件, 这个标志保证数据
* 的传输将不会通过内核空间, 而是使用 DMA 直接在用户空间到存储设备之间
* 传送, 不保证数据是否同步. 这就是 cache=none 的由来 */
if ((bdrv_flags & BDRV_O_NOCACHE)) {
*open_flags |= O_DIRECT;
}
} |
/* 这个函数其实是 raw_open_common */
static int raw_open(BlockDriverState *bs, QDict *options, int flags,
Error **errp)
{
BDRVRawState *s = bs->opaque;
s->type = FTYPE_FILE;
return raw_open_common(bs, options, flags, 0);
}
static int raw_open_common(BlockDriverState *bs, QDict *options,
int bdrv_flags, int open_flags)
{
s->open_flags = open_flags;
/* 解析 open 的参数, 把 QEMU 的 BDRV_O_* 映射成 open 的 O_*, 下面详细分析 */
raw_parse_flags(bdrv_flags, &s->open_flags);
s->fd = -1;
/* 用上面解析到得参数打开文件 */
fd = qemu_open(filename, s->open_flags, 0644);
s->fd = fd;
}
static void raw_parse_flags(int bdrv_flags, int *open_flags)
{
assert(open_flags != NULL);
/* 首先清空其他标志 */
*open_flags |= O_BINARY;
*open_flags &= ~O_ACCMODE;
/* 设置读写权限位 */
if (bdrv_flags & BDRV_O_RDWR) {
*open_flags |= O_RDWR;
} else {
*open_flags |= O_RDONLY;
}
/* 如果设置了 cache=none, 那么直接用 O_DIRECT 打开文件, 这个标志保证数据
* 的传输将不会通过内核空间, 而是使用 DMA 直接在用户空间到存储设备之间
* 传送, 不保证数据是否同步. 这就是 cache=none 的由来 */
if ((bdrv_flags & BDRV_O_NOCACHE)) {
*open_flags |= O_DIRECT;
}
}