kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

编译qemu-kvm和安装qemu-kvm

http://smilejay.com/2012/06/qemu-kvm_compilation_installation/

3.4 编译和安装qemu-kvm

除了在内核空间的KVM模块之外,在用户空间需要QEMU[注6]来模拟所需要CPU和设备模型以及用于启动客户机进程,这样才有了一个完整的KVM运行环境。而qemu-kvm是为了针对KVM专门做了修改和优化的QEMU分支[注7],在本书写作的2012年,qemu-kvm分支里面的小部分特性还没有完全合并进入到qemu的主干代码之中,所以本书中采用qemu-kvm来讲解。

在编译和安装了KVM并且启动到编译的内核之后,下面来看一下qemu-kvm的编译和安装。

3.4.1 下载qemu-kvm源代码

目前的QEMU项目针对KVM的代码分支qemu-kvm也是由Redhat公司的Gleb Natapov和Marcelo Tosatti作维护者(Maintainer),代码也是托管在kernel.org上。

qemu-kvm开发代码仓库的网页连接为:http://git.kernel.org/?p=virt/kvm/qemu-kvm.git

其中,可以看到有如下3个URL连接可供下载开发中的最新qemu-kvm的代码仓库。 git://git.kernel.org/pub/scm/virt/kvm/qemu-kvm.git
http://git.kernel.org/pub/scm/virt/kvm/qemu-kvm.git
https://git.kernel.org/pub/scm/virt/kvm/qemu-kvm.git

可以根据自己实际需要选择3个中任一个用git clone命令下载即可,它们是完全一样的。

另外,可以到sourceforge.net的如下链接中根据需要下载qemu-kvm各个发布版本的代码压缩包(作者建议使用最新的正式发布版本,因为它的功能更多,同时也比较稳定)。

http://sourceforge.net/projects/kvm/files/qemu-kvm/

在本节讲解编译时,以下载开发中的最新的qemu-kvm.git为例,获取其代码仓库过程如下:

1
2
3
4
5
6
7
8
9
10
[root@jay-linux kvm_demo]# git clone\ git://git.kernel.org/pub/scm/virt/kvm/qemu-kvm.git qemu-kvm.git
Initialized empty Git repository in /root/kvm_demo/qemu-kvm.git/.git/
remote: Counting objects: 145222, done.
remote: Compressing objects: 100% (35825/35825), done.
remote: Total 145222 (delta 114656), reused 137663 (delta 107444)
Receiving objects: 100% (145222/145222), 40.83 MiB | 10.33 MiB/s, done.
Resolving deltas: 100% (114656/114656), done.
[root@jay-linux kvm_demo]# cd qemu-kvm.git
[root@jay-linux kvm.git]# pwd
/root/kvm_demo/qemu-kvm.git
3.4.2 配置和编译qemu-kvm

qemu-kvm的配置并不复杂,通常情况下,可以直接运行代码仓库中configure文件进行配置即可。当然,如果对其配置并不熟悉,可以运行“./configure –help”命令查看配置的一些选项及其帮助信息。

显示配置的帮助手册信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@jay-linux qemu-kvm.git]# ./configure --help
Usage: configure [options]
Options: [defaults in brackets after descriptions]
 
Standard options:
--help                   print this message
--prefix=PREFIX          install in PREFIX [/usr/local]
--interp-prefix=PREFIX   where to find shared libraries, etc.
use %M for cpu name [/usr/gnemul/qemu-%M]
--target-list=LIST       set target list (default: build everything)
Available targets: i386-softmmu x86_64-softmmu
<!- 此处省略百余行帮助信息的输出 ->
--disable-guest-agent    disable building of the QEMU Guest Agent
--enable-guest-agent     enable building of the QEMU Guest Agent
--with-coroutine=BACKEND coroutine backend. Supported options:
gthread, ucontext, sigaltstack, windows

NOTE: The object files are built at the place where configure is launched

执行configure文件进行配置的过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@jay-linux qemu-kvm.git]# ./configure
Install prefix    /usr/local
BIOS directory    /usr/local/share/qemu
binary directory  /usr/local/bin
library directory /usr/local/lib
include directory /usr/local/include
config directory  /usr/local/etc
Manual directory  /usr/local/share/man
ELF interp prefix /usr/gnemul/qemu-%M
Source path       /root/kvm_demo/qemu-kvm.git
C compiler        gcc
Host C compiler   gcc
<!– 此处省略数十行 –>
VNC support       yes     #通常需要通过VNC连接到客户机中
<!– 此处省略十余行 –>
KVM support       yes     #这是对KVM的支持
TCG interpreter   no
KVM device assig. yes    #这是对KVM中VT-d功能的支持
<!– 此处省略十余行 –>
OpenGL support    yes
libiscsi support  no
build guest agent yes
coroutine backend ucontext

需要注意的是,上面命令行输出的KVM相关的选项需要是配置为yes,另外,一般VNC的支持也是配置为yes的(因为通常需要用VNC连接到客户机中)。

【2013.05.13 updated】 在configure时,可能遇到“glib-2.12 required to compile QEMU”的错误,是需要安装glib2和glib2-dev软件包,在RHEL上的安装命令为“yum install glib2 glib2-devel”,在Ubuntu上安装的过程为“apt-get install libglib2.0 libglib2.0-dev”。

经过配置之后,进行编译就很简单了,直接执行make即可进行编译,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@jay-linux qemu-kvm.git]# make -j 20
GEN   config-host.h
GEN   trace.h
GEN   qemu-options.def
GEN   qmp-commands.h
GEN   qapi-types.h
GEN   qapi-visit.h
GEN   tests/test-qapi-types.h
GEN   tests/test-qapi-visit.h
GEN   tests/test-qmp-commands.h
CC    libcacard/cac.o
CC    libcacard/event.o
<!– 此处省略数百行的编译时输出信息 –>
CC    x86_64-softmmu/target-i386/cpu.o
CC    x86_64-softmmu/target-i386/machine.o
CC    x86_64-softmmu/target-i386/arch_memory_mapping.o
CC    x86_64-softmmu/target-i386/arch_dump.o
CC    x86_64-softmmu/target-i386/kvm.o
CC    x86_64-softmmu/target-i386/hyperv.o
LINK  x86_64-softmmu/qemu-system-x86_64

可以看到,最后有编译生成qemu-system-x86_64文件,它就是我们常用的qemu-kvm的命令行工具(在多数Linux发行版中自带的qemu-kvm软件包的命令行是qemu-kvm,只是名字不同而已)。

3.4.2 安装qemu-kvm

编译完成之后,运行“make install”命令即可安装qemu-kvm,其过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@jay-linux qemu-kvm.git]# make install | tee make-install.log
install -d -m 0755 “/usr/local/share/qemu”
install -d -m 0755 “/usr/local/etc/qemu”
install -c -m 0644 /root/kvm_demo/qemu-kvm.git/sysconfigs/target/target-x86_64.conf “/usr/local/etc/qemu”
install -c -m 0644 /root/kvm_demo/qemu-kvm.git/sysconfigs/target/cpus-x86_64.conf “/usr/local/share/qemu”
install -d -m 0755 “/usr/local/bin”
install -c -m 0755  vscclient qemu-ga qemu-nbd qemu-img qemu-io  “/usr/local/bin”
install -d -m 0755 “/usr/local/libexec”
<!– 此处省略数行的安装时输出信息 –>
make[1]: Entering directory `/root/kvm_demo/qemu-kvm.git/x86_64-softmmu’
install -m 755 qemu-system-x86_64 “/usr/local/bin”
strip “/usr/local/bin/qemu-system-x86_64″
make[1]: Leaving directory `/root/kvm_demo/qemu-kvm.git/x86_64-softmmu’

qemu-kvm的安装过程的主要任务有这几个:创建qemu的一些目录,拷贝一些配置文件到相应的目录下,拷贝一些firmware文件(如:sgabios.bin, kvmvapic.bin)到目录下以便qemu-kvm的命令行启动时可以找到对应的固件提供给客户机使用,拷贝keymaps到相应的目录下以便在客户机中支持各种所需键盘类型,拷贝qemu-system-x86_64、qemu-img等可执行程序到对应的目录下。下面的一些命令行检查了qemu-kvm被安装了之后的系统状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@jay-linux qemu-kvm.git]# which qemu-system-x86_64
/usr/local/bin/qemu-system-x86_64
[root@jay-linux qemu-kvm.git]# which qemu-img
/usr/local/bin/qemu-img
[root@jay-linux qemu-kvm.git]# ls /usr/local/share/qemu/
bamboo.dtb        mpc8544ds.dtb     petalogix-ml605.dtb       pxe-pcnet.rom    slof.bin            vgabios-vmware.bin
bios.bin          multiboot.bin     petalogix-s3adsp1800.dtb  pxe-rtl8139.rom  spapr-rtas.bin
cpus-x86_64.conf  openbios-ppc      ppc_rom.bin               pxe-virtio.rom   vgabios.bin
keymaps           openbios-sparc32  pxe-e1000.rom             qemu-icon.bmp    vgabios-cirrus.bin
kvmvapic.bin      openbios-sparc64  pxe-eepro100.rom          s390-zipl.rom    vgabios-qxl.bin
linuxboot.bin     palcode-clipper   pxe-ne2k_pci.rom          sgabios.bin      vgabios-stdvga.bin
[root@jay-linux qemu-kvm.git]# ls /usr/local/share/qemu/keymaps/
ar    common  de     en-gb  es  fi  fr     fr-ca  hr  is  ja  lv  modifiers  nl-be  pl  pt-br  sl  th
bepo  da      de-ch  en-us  et  fo  fr-be  fr-ch  hu  it  lt  mk  nl         no     pt  ru     sv  tr

由于qemu-kvm是用户空间的程序,安装之后不用重启系统,直接用qemu-system-x86_64、qemu-img这样的命令行工具即可使用qemu-kvm了。

KVM源代码分析4:内存虚拟化

http://www.oenhan.com/kvm-src-4-mem

在虚拟机的创建与运行中pc_init_pci负责在qemu中初始化虚拟机,内存初始化也是在这里完成的,还是一步步从qemu说起,在vl.c的main函数中有ram_size参数,由qemu入参标识QEMU_OPTION_m设定,顾名思义就是虚拟机内存的大小,通过machine->init一步步传递给pc_init1函数。在这里分出了above_4g_mem_size和below_4g_mem_size,即高低端内存(也不一定是32bit机器..),然后开始初始化内存,即pc_memory_init,内存通过memory_region_init_ram下面的qemu_ram_alloc分配,使用qemu_ram_alloc_from_ptr。

插播qemu对内存条的模拟管理,是通过RAMBlock和ram_list管理的,RAMBlock就是每次申请的内存池,ram_list则是RAMBlock的链表,他们结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct RAMBlock {
	//对应宿主的内存地址
	uint8_t *host;
	//block在ramlist中的偏移
	ram_addr_t offset;
	//block长度
	ram_addr_t length;
	uint32_t flags;
	//block名字
	char idstr[256];
	QLIST_ENTRY(RAMBlock) next;
#if defined(__linux__) && !defined(TARGET_S390X)
	int fd;
#endif
} RAMBlock;

typedef struct RAMList {
	//看代码理解就是list的head,但是不知道为啥叫dirty...
	uint8_t *phys_dirty;
	QLIST_HEAD(ram, RAMBlock) blocks;
} RAMList;

下面再回到qemu_ram_alloc_from_ptr函数,使用find_ram_offset赋值给new block的offset,find_ram_offset具体工作模型已经在KVM源代码分析2:虚拟机的创建与运行中提到了,不赘述。然后是一串判断,在kvm_enabled的情况下使用new_block->host = kvm_vmalloc(size),最终内存是qemu_vmalloc分配的,使用qemu_memalign干活。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void *qemu_memalign(size_t alignment, size_t size)
{
	void *ptr;
	//使用posix进行内存针对页大小对齐
#if defined(_POSIX_C_SOURCE) && !defined(__sun__)
	int ret;
	ret = posix_memalign(&ptr, alignment, size);
	if (ret != 0) {
		fprintf(stderr, "Failed to allocate %zu B: %s\n",
				size, strerror(ret));
		abort();
	}
#elif defined(CONFIG_BSD)
	ptr = qemu_oom_check(valloc(size));
#else
	//所谓检查oom就是看memalign对应malloc申请内存是否成功
	ptr = qemu_oom_check(memalign(alignment, size));
#endif
	trace_qemu_memalign(alignment, size, ptr);
	return ptr;
}

以上qemu_vmalloc进行内存申请就结束了。在qemu_ram_alloc_from_ptr函数末尾则是将block添加到链表,realloc整个ramlist,用memset初始化整个ramblock,madvise对内存使用限定。 然后一层层的退回到pc_memory_init函数。

此时pc.ram已经分配完成,ram_addr已经拿到了分配的内存地址,MemoryRegion ram初始化完成。下面则是对已有的ram进行分段,即ram-below-4g和ram-above-4g,也就是高端内存和低端内存。用memory_region_init_alias初始化子MemoryRegion,然后将memory_region_add_subregion添加关联起来,memory_region_add_subregion具体细节“KVM源码分析2”中已经说了,参考对照着看吧,中间很多映射代码过程也只是qemu遗留的软件实现,没看到具体存在的意义,直接看到kvm_set_user_memory_region函数,内核真正需要kvm_vm_ioctl传递过去的参数是什么, struct kvm_userspace_memory_region mem而已,也就是

1
2
3
4
5
6
7
struct kvm_userspace_memory_region {
	__u32 slot;
	__u32 flags;
	__u64 guest_phys_addr;
	__u64 memory_size;    /* bytes */
	__u64 userspace_addr; /* start of the userspace allocated memory */
};

kvm_vm_ioctl进入到内核是在KVM_SET_USER_MEMORY_REGION参数中,即执行kvm_vm_ioctl_set_memory_region,然后一直向下,到__kvm_set_memory_region函数,check_memory_region_flags检查mem->flags是否合法,而当前flag也就使用了两位,KVM_MEM_LOG_DIRTY_PAGES和KVM_MEM_READONLY,从qemu传递过来只能是KVM_MEM_LOG_DIRTY_PAGES,下面是对mem中各参数的合规检查,(mem->memory_size & (PAGE_SIZE – 1))要求以页为单位,(mem->guest_phys_addr & (PAGE_SIZE – 1))要求guest_phys_addr页对齐,而((mem->userspace_addr & (PAGE_SIZE – 1)) || !access_ok(VERIFY_WRITE,(void __user *)(unsigned long)mem->userspace_addr,mem->memory_size))则保证host的线性地址页对齐而且该地址域有写权限。

id_to_memslot则是根据qemu的内存槽号得到kvm结构下的内存槽号,转换关系来自id_to_index数组,那映射关系怎么来的,映射关系是一一对应的,在kvm_create_vm虚拟机创建过程中,kvm_init_memslots_id初始化对应关系,即slots->id_to_index[i] = slots->memslots[i].id = i,当前映射是没有意义的,估计是为了后续扩展而存在的。

扩充了new的kvm_memory_slot,下面直接在代码中注释更方便:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//映射内存有大小,不是删除内存条
if (npages) {
	//内存槽号没有虚拟内存条,意味内存新创建
	if (!old.npages)
		change = KVM_MR_CREATE;
	else { /* Modify an existing slot. */
		//修改已存在的内存修改标志或者平移映射地址
		//下面是不能处理的状态(内存条大小不能变,物理地址不能变,不能修改只读)
		if ((mem->userspace_addr != old.userspace_addr) ||
			(npages != old.npages) ||
			((new.flags ^ old.flags) & KVM_MEM_READONLY))
			goto out;
		//guest地址不同,内存条平移
		if (base_gfn != old.base_gfn)
			change = KVM_MR_MOVE;
		else if (new.flags != old.flags)
			//修改属性
			change = KVM_MR_FLAGS_ONLY;
		else { /* Nothing to change. */
			r = 0;
			goto out;
		}
	}
} else if (old.npages) {
	//申请插入的内存为0,而内存槽上有内存,意味删除
	change = KVM_MR_DELETE;
} else /* Modify a non-existent slot: disallowed. */
	goto out;

另外看kvm_mr_change就知道memslot的变动值了:

1
2
3
4
5
6
enum kvm_mr_change {
	KVM_MR_CREATE,
	KVM_MR_DELETE,
	KVM_MR_MOVE,
	KVM_MR_FLAGS_ONLY,
};

在往下是一段检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if ((change == KVM_MR_CREATE) || (change == KVM_MR_MOVE)) {
	/* Check for overlaps */
	r = -EEXIST;
	kvm_for_each_memslot(slot, kvm->memslots) {
		if ((slot->id >= KVM_USER_MEM_SLOTS) ||
			//下面排除掉准备操作的内存条,在KVM_MR_MOVE中是有交集的
			(slot->id == mem->slot))
			continue;
		//下面就是当前已有的slot与new在guest线性区间上有交集
		if (!((base_gfn + npages <= slot->base_gfn) ||
			  (base_gfn >= slot->base_gfn + slot->npages)))
			goto out;
			//out错误码就是EEXIST
	}
}

如果是新插入内存条,代码则走入kvm_arch_create_memslot函数,里面主要是一个循环,KVM_NR_PAGE_SIZES是分页的级数,此处是3,第一次循环,lpages = gfn_to_index(slot->base_gfn + npages – 1,slot->base_gfn, level) + 1,lpages就是一级页表所需要的page数,大致是npages>>09,然后为slot->arch.rmap[i]申请了内存空间,此处可以猜想,rmap就是一级页表了,继续看,lpages约为npages>>19,此处又多为lpage_info申请了同等空间,然后对lpage_info初始化赋值,现在看不到lpage_info的具体作用,看到后再补上。整体上看kvm_arch_create_memslot做了一个3级的软件页表。

如果有脏页,并且脏页位图为空,则分配脏页位图, kvm_create_dirty_bitmap实际就是”页数/8″.

1
2
3
4
if ((new.flags & KVM_MEM_LOG_DIRTY_PAGES) && !new.dirty_bitmap) {
	if (kvm_create_dirty_bitmap(&new) < 0)
		goto out_free;
}

当内存条的改变是KVM_MR_DELETE或者KVM_MR_MOVE,先申请一个slots,把kvm->memslots暂存到这里,首先通过id_to_memslot获取准备插入的内存条对应到kvm的插槽是slot,无论删除还是移动,将其先标记为KVM_MEMSLOT_INVALID,然后是install_new_memslots,其实就是更新了一下slots->generation的值,