目标
- 内存迁移在有内存压力的情况下,如果脏页产生速率很快,会有迁移无法完成的情况。即使内存规格很小的虚机(假设是1GB),如果脏页产生速率很大,也可能无法迁移完成。
- qemu为了让内存迁移能够完成开发了很多特性,比如post-copy、auto-converge、compression、bandwidth tuning、multifd,还有最暴力最有效的暂停虚机。
- 以上特性都能够在某种场景下让迁移更容易完成,但我们需要一种工具,用来判断以上特性对内存迁移的影响,从而决定在哪种场景下使用哪种特性,这就是内存迁移压测工具需要解决的问题。简单说,压测工具主要用来评估在脏页速率大的情况下,哪种迁移配置更能有效完成迁移。
使用方法
编译
- 为了使虚机在内核启动后,就运行定制得内存加压程序,套件使用了seabios+kernel+initramfs的方式启动系统。其中seabios使用qemu组件附带的seabios,kernel可以是发行版本的kernel,即/boot/vmlinuz-xxx文件。也可以是自己编译内核得到的arch/x86/boot/bzImage。
- 对于initramfs我们需要定制,不能用内核源码目录下的usr/gen_initramfs.sh脚本生成,qemu使用Makefile编译系统时,定义了initrd-stress.img目标用于编译定制的加压工具,qemu使用meson编译系统时,定义了custom_target initrd-stress.img用于制作加压工具。由于加压工具需要连接基本的c库和线程库,在内核启动根分区还未挂载阶段,这些动态库没有加载,因此加压工具只能使用静态编译,防止在运行时依赖动态c库出错。
- initrd-stress.img的编译制作命令如下:
yum install -y glib2-static.x86_64 glibc-static.x86_64 /* 安装glib的静态库和libc静态库*/
cd {meson_builddir} && meson compile initrd-stress.img /* 静态编译加压工具 */
- initrd-stress.img实际上是一个定制的initramfs文件,qemu通过tests/migration/meson.build指定其编译和制作方式,内容如下:
/* executable函数实现具体编译 */
stress = executable(
'stress',
files('stress.c'),
dependencies: [glib],
link_args: ['-static'],
build_by_default: false,
)
/* 自定义的meson编译目标
* 调用外部脚本initrd-stress.sh
* 制作initramfs镜像 */
custom_target(
'initrd-stress.img',
output: 'initrd-stress.img',
input: stress,
command: [find_program('initrd-stress.sh'), '@OUTPUT@', '@INPUT@']
)
/* 删除meson compile生成的文件 */
meson compile --clean
/* 编译initrd-stress.img镜像并输出详细信息 */
meson compile initrd-stress.img -v
/* 执行顶层的编译命令,该命令在meson setup时生成,存放在compile_commands.json文件中 */
[1/3] cc -Itests/migration/stress.p -Itests/migration -I../tests/migration -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include -fdiagnostics-color=auto -pipe -Wall -Winvalid-pch -Werror -std=gnu99 -g -m64 -mcx16 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -Wstrict-prototypes -Wredundant-decls -Wundef -Wwrite-strings -Wmissing-prototypes -fno-strict-aliasing -fno-common -fwrapv -Wold-style-declaration -Wold-style-definition -Wtype-limits -Wformat-security -Wformat-y2k -Winit-self -Wignored-qualifiers -Wempty-body -Wnested-externs -Wendif-labels -Wexpansion-to-defined -Wno-missing-include-dirs -Wno-shift-negative-value -Wno-psabi -fstack-protector-strong -isystem /home/qemu_ML/linux-headers -isystem linux-headers -iquote /home/qemu_ML/tcg/i386 -iquote . -iquote /home/qemu_ML -iquote /home/qemu_ML/accel/tcg -iquote /home/qemu_ML/include -iquote /home/qemu_ML/disas/libvixl -pthread -fPIE -MD -MQ tests/migration/stress.p/stress.c.o -MF tests/migration/stress.p/stress.c.o.d -o tests/migration/stress.p/stress.c.o -c ../tests/migration/stress.c
/* 静态编译生成压力测试工具stress */
[2/3] cc -o tests/migration/stress tests/migration/stress.p/stress.c.o -Wl,--as-needed -Wl,--no-undefined -pie -Wl,--warn-common -Wl,-z,relro -Wl,-z,now -m64 -fstack-protector-strong -static -pthread -Wl,--start-group -lgthread-2.0 -lglib-2.0 -Wl,--end-group
/* 将stress工具制作成initramfs镜像 */
[3/3] /home/qemu_ML/tests/migration/initrd-stress.sh tests/migration/initrd-stress.img tests/migration/stress
- 看一下initramfs镜像的制作脚本initrd-stress.sh:
#!/bin/sh
#解析要生成的initramfs镜像名字: initrd-stress.img
INITRD="$1"
#解析要放在initramfs镜像中的文件: stress
STRESS="$2"
#在/tmp下创建临时目录/tmp/initrd-stress.DnHQ3n用于制作镜像
INITRD_DIR=$(mktemp -d -p '' "initrd-stress.XXXXXX")
#在程序退出后删除目录
trap 'rm -rf $INITRD_DIR' EXIT
#将stress拷贝到/tmp/initrd-stress.DnHQ3n/init
cp "$STRESS" "$INITRD_DIR/init"
#进入/tmp/initrd-stress.DnHQ3n目录将init程序制作成cpio格式并通过gzip工具压缩,输出到initrd-stress.img
(cd "$INITRD_DIR" && (find | cpio --quiet -o -H newc | gzip -9)) > "$INITRD"
mkdir initrd-stress
cd initrd-stress
gzip -cd /path/to/initrd-stress.img | cpio -imd --quiet
运行
- 测试套件是一个名为guestperf.py的python脚本,位于qemu源码目录的tests/migration/下,通过-h命令可以查看其使用方法:
python3.6 guestperf.py -h
python3.6 guestperf.py --cpus 4 --mem 8 --post-copy --post-copy-iters 3 --bandwidth 125 \
--dst-host localhost --transport unix --kernel /home/secure/Hyman/migrate/vmlinuz-3.10.0-1127.el7.x86_64 \
--binary /home/secure/code/kvm-dirty-ring-qemu/build/qemu-system-x86_64 \
--initrd /home/secure/Hyman/migrate/initrd-stress.img --output postcopy.json --debug
展示
- 测试一旦成功完成,会产生文件postcopy.json文件,该文件需要通过python的plotly工具解析并图形化展示,如下:
/* 安装图形化工具库 */
pip3.6 install --user plotly
/* 解析postcopy.json文件并生成postcopy.html文件 */
python3.6 guestperf-plot.py --split-guest-cpu --qemu-cpu --vcpu-cpu --migration-iters --output postcopy.html postcopy.json
/* 通过网页展示html中图形的内容,需要plotly.min.js在html文件的相同目录,拷贝 */
cp /usr/local/lib/python3.6/site-packages/plotly/package_data/plotly.min.js /var/www/html/
cp postcopy.html /var/www/html/
- 网页效果如下:
原理
虚机侧
initrd
- 在介绍initramfs之前,首先介绍它的前身
initrd
。initrd(initial RAM disk)
是一个文件系统,它在内核启动阶段被挂载到根目录。initrd被设计出来的主要目的是让系统的启动分成两个阶段,在第一阶段中kernel镜像和initrd镜像被加载,这个阶段基本的硬件驱动被加载;第二阶段从initrd被加载开始,这之后会查找指定的块设备,挂载根目录并加载所有驱动。initrd作为一个系统启动过程中的临时文件系统,负责从第一阶段跳转到第二阶段。在普通场景下,initrd镜像被挂载成根文件系统后,会执行/sbin/init可执行程序,该程序负责加载磁盘上的根文件系统,进而加载所有的内核驱动模块。
- initrd是ram disk机制,它的原理是使用额外系统内存创建一个假的块设备,然后将这个块设备作为文件系统的后端,因此说initrd是基于内存的块设备ram disk。initrd在出现后流行了一段时间,但目前已经被initramfs所替代,现在处于半废弃状态,原因有以下几点:
- ram disk挂载的文件系统大小固定,因为其后端内存大小固定。
- ram disk浪费了内存带宽,它的很大一部分工作是将内存拷贝到后端的块设备,而这个块设备本身就是内存模拟的,所以这个拷贝实际上是内存到内存的拷贝。
- ram disk除了内存带宽的浪费,还会污染cpu的cache,降低cpu缓存命中率,增加不必要的开销。
- ram disk的块设备在使用时需要文件系统驱动(比如ext2)将其格式化,然后再使用文件系统驱动解释其数据。
ramfs
ramfs(ram filesystem)
是一个基于内存的文件系统,相比ram disk,ramfs的实现原理大有不同,它利用linux的磁盘缓存机制,将缓存在内存中的page cache
导出为一个动态大小可变的文件系统。
- 正常情况下,所有从磁盘读取的文件会被缓存起来,存在于内存中(称为
page cache
),此时占用page cache的内存被标记为clean
状态,当系统内存不足时可以被回收,当cpu再次访问文件时,首先从page cache中读取,如果命中后有写入操作,直接写入page cache,此时page cache被标记为dirty
状态,文件系统负责将page cache的修改同步到磁盘上,page cache再次被标记为clean
状态。对于处于dirty状态的page cache,linux的内存管理单元不会回收该内存,它认为该内存需要写入后端磁盘上,否则就会有数据的丢失。
- ramfs利用以上机制,将linux的磁盘缓存导出成一个文件系统,提供对应的文件系统接口给用户。当用户通过ramfs读写文件时,ramfs对应地分配内存用于缓存page cache数据,但ramfs后端没有磁盘,因此被用户写入数据的page cache将永远不会被标记为
clean
状态,linux的内存管理单元永远不会回收这块内存。ramfs直接利用的磁盘缓存导出成文件系统,占用很少的内存,因此ramfs不支持卸载,因为就算卸载也节省不了多少空间,通常做法是将根文件系统直接挂在到根目录,覆盖挂载点。在linux-2.6及之后的版本,内核在启动阶段会默认将ramfs挂载到根目录,也即所谓的rootfs,并且rootfs不能够卸载,就像init进程不能被杀掉一样。
- ramfs相比于ram disk有以下几个优点:
- ramfs没有后端块设备的模拟,因此不需要文件系统驱动来格式和读取数据,因此initramfs文件在制作时不需要使用文件系统格式化工具(比如mke2fs工具),直接将cpio格式的文件通过gzip工具压缩就可以制作initramfs文件。
- ramfs利用linux已存在的磁盘缓存机制,其实现相对简单,而且缓存机制是linux必须的,占用的内存带宽少。
initramfs
initramfs(initial ramfs)
并非一个新的文件系统,它只是一个linux系统启动阶段的文件,之所以名称中有ramfs,可能是因为linux启动阶段会默认挂载ramfs,initramfs文件中的内容会被提取到ramfs的挂载点。initramfs与initrd的作用是类似的,都是负责挂载磁盘上的根文件系统并加载内核驱动,但initramfs的结构非常简单,它是通过gzip工具压缩的cpio格式的文件,当内核挂载ramfs后,会将initramfs中包含的init
程序提取到根目录,然后执行,init进程的通常工作是查找并挂载根设备,如果内核没有找到init程序,就回退到initrd的处理方式,直接查找并挂载根分区到根目录,然后执行根目录中下的/sbin/init
程序。
- initramfs相比initrd有几点不同:
- initrd的内容是一些单独的文件,制作时可以使用linux工具独立制作,而initramfs通常伴随linux内核一起编译,内核源码的usr目录专门负责编译initramfs文件。
- initrd是一个通过gzip压缩的文件系统镜像,内核需要文件系统的驱动(比如ext2)才能访问其数据。而initramfs是仅仅是一个通过gzip压缩的cpio文件,内核访问其数据只需要解压gzip文件并使用cpio的文件提取代码就可以了,相对简单。
- initrd被加载时,运行的是
/initrd
,运行完之后控制权会交还内核,initramfs被加载时,运行的是/init
,它会永远运行,因此不会将控制权交还内核。
- 在我们的需求中,虚拟机没有磁盘,只有kernel+initramfs,init进程不负责挂载根分区,而是作为内存压力测试工具执行内存加压。注意,通常情况下,initramfs的内容都是在内核编译的时候一并生成的,内核会保证initramfs中的二进制程序正常运行。但我们的内存压力测试工具是自己制作的,运行时可能需要动态链接一些基础c库,比如线程接口需要链接pthread库,但在kernel启动阶段并且还未挂载磁盘根分区,所有二进制程序需要的基础库都没有加载,因此不能用动态链接的方式。我们的init程序必须静态编译,将所有依赖的基础库都编译到init二进制程序中。否则,内核在执行init程序时会因为没有相关的依赖库而报错。
主机侧
- 由于没有libvirt工具的管理,因此要迁移的虚机只能通过qemu命令行直接启动。这里我们已unix socket迁移为例,说明迁移的qemu命令行配置。
- 对于源端虚机,我们需要配置串口查看其是否正常启动,也需要配置mointor服务,向其发送迁移配置以及迁移命令,类似如下命令行:
{qemu-kvm} -display none -vga none \
-name guest=mig_src,debug-threads=on \
-monitor stdio \ /* 将monitor服务的接口重定向到标准输入输出 */
-accel kvm -cpu host \
-kernel /path/to/vmlinuz-3.10.0-1127.el7.x86_64 \ /* 指定虚机的内核镜像 */
-initrd /path/to/initrd-stress.img \ /* 指定虚机的initramfs镜像 */
/* 配置虚机内核的终端,将其设置为ttyS0,输出到串口 */
-append "noapic edd=off printk.time=1 noreplace-smp cgroup_disable=memory pci=noearly console=ttyS0 debug ramsize=4" \
-chardev file,id=charserial0,path=/var/log/mig_src_console.log \ /* 将后端串口配置为文件,以此获取到虚机内部的信息 */
-serial chardev:charserial0 \
-m 4608 -smp 2 -device sga
- 对于目的端,我们也需要配置串口查看其迁移后是否运行正常,源端和目的端的硬件配置一模一样,除此之外,还需要指定从哪里读取虚机内存用作虚机启动的内存,类似命令如下:
{qemu-kvm} -display none -vga none \
-name guest=mig_dst,debug-threads=on \
-monitor stdio \
-accel kvm -cpu host \
-kernel /path/to/vmlinuz-3.10.0-1127.el7.x86_64 \
-initrd /path/to/initrd-stress.img \
-append "noapic edd=off printk.time=1 noreplace-smp cgroup_disable=memory pci=noearly console=ttyS0 debug ramsize=4" \
-chardev file,id=charserial0,path=/var/log/mig_dst_console.log \
-serial chardev:charserial0 \
-m 4608 -smp 2 -device sga \
-incoming unix:/var/tmp/qemu-migrate-incoming.migrate /* 配置qemu启动时从哪里读取内存 */
- 通过以上方式配置好虚机后,源端monitor服务设置迁移参数,发起迁移,然后不断查询迁移状态,搜集迁移信息,如下:
(qemu) migrate_set_capability multifd on /* 设置迁移特性:打开multifd */
(qemu) migrate_set_parameter multifd-channels 4
(qemu) migrate -d unix:/var/tmp/qemu-migrate-incoming.migrate /* 开始后台迁移虚机 */
(qemu) info migrate /* 迁移过程种不断查询虚机状态 */
globals:
store-global-state: on
only-migratable: off
send-configuration: on
send-section-footer: on
decompress-error-check: on
clear-bitmap-shift: 18
Migration status: active
total time: 18664 ms
expected downtime: 300 ms
setup: 3 ms
transferred ram: 2451487 kbytes
throughput: 1073.94 mbps
remaining ram: 2021944 kbytes
total ram: 4719368 kbytes
duplicate: 62817 pages
skipped: 0 pages
normal: 611539 pages
normal bytes: 2446156 kbytes
dirty sync count: 1
page size: 4 kbytes
multifd bytes: 0 kbytes
pages-per-second: 32710
- 除了此种方式,目的端虚机也可以通过defer参数启动:
-incoming defer
- 在启动目的端qemu进程后,通过monitor命令设置内存读取的uri,如下:
(qemu) info status
VM status: paused (inmigrate)
(qemu) migrate_incoming unix:/var/tmp/qemu-migrate-incoming.migrate
(qemu) info migrate
globals:
store-global-state: on
only-migratable: off
send-configuration: on
send-section-footer: on
decompress-error-check: on
clear-bitmap-shift: 18
socket address: [
unix:/var/tmp/qemu-migrate-incoming.migrate
]
(qemu) migrate_set_capability multifd on /* 设置迁移特性:打开multifd */
(qemu) migrate_set_parameter multifd-channels 4
dst: 监听本机9000端口
(qemu) migrate_incoming tcp:{dst_ip}:9000
src: 往目的端9000端口迁移
(qemu) migrate -d tcp:{dst_ip}:9000
- 注意:以上方式并非压测工具的迁移实现,而是我们的交互式方式的实现,压测工具实现类似,不同的地方是它启动虚机时配置monitor服务为socket,压测工具通过向socket服务发送qmp命令实现迁移的配置和命令发起。
qtest压测工具
- qemu的内存迁移压测工具是一个针对内存性能的专用套件,对于不是特别关注qemu迁移性能的人,只想保证迁移的基本特性(比如auto-converge,multifd)好用,这种测试在qtest中保证。因此qtest中也有针对迁移压力模拟的设计。在
tests/qtest/migration-test.c
中,有关于auto-converge
的测试例test_migrate_auto_converge
,我们分析一下这个测试例怎么模拟迁移压力的。
test_migrate_auto_converge
/* 使用unix方式迁移 */
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
/* 启动源端qemu进程 */
test_migrate_start(&from, &to, uri, args);
/* 设置迁移参数 */
migrate_set_capability(from, "auto-converge", true);
migrate_set_parameter_int(from, "cpu-throttle-initial", init_pct);
migrate_set_parameter_int(from, "cpu-throttle-increment", inc_pct);
migrate_set_parameter_int(from, "max-cpu-throttle", max_pct);
/*
* Set the initial parameters so that the migration could not converge
* without throttling.
*/
migrate_set_parameter_int(from, "downtime-limit", 1);
- 测试auto-converge时将
downtime-limit
设置成了1,注释中写道只有使用auto-converge才可能让迁移成功,因此源端的qemu进程肯定是引导程序有内存读写,否则不可能有压力。我们分析一下源端qemu的启动过程:
test_migrate_start
/* 设置引导程序路径 */
bootpath = g_strdup_printf("%s/bootsect", tmpfs);
if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
/* the assembled x86 boot sector should be exactly one sector large */
/* 将x86_bootsect中的内容拷贝给引导程序 */
init_bootfile(bootpath, x86_bootsect, sizeof(x86_bootsect));
memory_size = "150M";
/* 指定从bootpath引导虚机 */
arch_source = g_strdup_printf("-drive file=%s,format=raw", bootpath);
- bootpath相当于磁盘镜像,其内容来自于
x86_bootsect
:
/* This file is automatically generated from the assembly file in
* tests/migration/i386. Edit that file and then run "make all"
* inside tests/migration to update, and then remember to send both
* the header and the assembler differences in your patch submission.
*/
unsigned char x86_bootsect[] = {
0xfa, 0x0f, 0x01, 0x16, 0x74, 0x7c, 0x66, 0xb8, 0x01, 0x00, 0x00, 0x00,
0x0f, 0x22, 0xc0, 0x66, 0xea, 0x20, 0x7c, 0x00, 0x00, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x92, 0x0c, 0x02,
0xe6, 0x92, 0xb8, 0x10, 0x00, 0x00, 0x00, 0x8e, 0xd8, 0x66, 0xb8, 0x41,
0x00, 0x66, 0xba, 0xf8, 0x03, 0xee, 0xb3, 0x00, 0xb8, 0x00, 0x00, 0x10,
0x00, 0xfe, 0x00, 0x05, 0x00, 0x10, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x40,
0x06, 0x7c, 0xf2, 0xfe, 0xc3, 0x75, 0xe9, 0x66, 0xb8, 0x42, 0x00, 0x66,
0xba, 0xf8, 0x03, 0xee, 0xeb, 0xde, 0x66, 0x90, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00,
0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00, 0x27, 0x00, 0x5c, 0x7c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa
};
- 从注释看这个内容是根据汇编程序
tests/migration/i386/a-b-bootblock.S
编译成二进制后,通过读取其内容得到的:
# x86 bootblock used in migration test
# repeatedly increments the first byte of each page in a 100MB
# range.
# Outputs an initial 'A' on serial followed by repeated 'B's
#
# Copyright (c) 2016 Red Hat, Inc. and/or its affiliates
# This work is licensed under the terms of the GNU GPL, version 2 or later.
# See the COPYING file in the top-level directory.
#
# Author: [email protected]
.code16
.org 0x7c00
.file "fill.s"
.text
.globl start
.type start, @function
start: # at 0x7c00 ?
cli
lgdt gdtdesc
mov $1,%eax
mov %eax,%cr0 # Protected mode enable
data32 ljmp $8,$0x7c20
......
- 该汇编程序首先向串口打印一个字符
A
,然后一直重复打印字符B
。我们验证一下:
cd tests/migration/i386
make x86.bootsect /* 制作二进制固件 */
cat > fast_qemu.sh << EOF /* 编写qemu命令行启动脚本,指定固件作为第一个启动盘 */
#!/bin/bash
/usr/bin/qemu-system-x86_64 \
-display none -vga none \
-name guest=mig_src,debug-threads=on \
-monitor stdio \
-accel kvm,dirty-ring-size=4096 -cpu host \
-drive file=/home/ml_code/my_qemu/tests/migration/i386/x86.bootsect,format=raw \
-chardev file,id=charserial0,path=/var/log/mig_src_console.log \
-serial chardev:charserial0 \
-qmp unix:/tmp/qmp-sock,server,nowait \
-D /var/log/mig_src.log \
--trace events=/home/work/fast_qemu/events \
-m 4096 -smp 1 -device sga
EOF
chmod +x fast_qemu.sh
./fast_qemu.sh /* 启动虚机 */
tailf /var/log/mig_src_console.log /* 观察串口输出*/
- 串口输出如下: