Qemu学习-体系结构&参数选项&定时器

Qemu的体系架构

概述

QEMU是一个主机上的VMM(virtual machine monitor),通过动态二进制转换来模拟CPU,并提供一系列的硬件模型。
KVM(Kernel-Based Virtual Machine)是基于内核的虚拟机,实现对CPU和内存的虚拟化。KVM需要处理器硬件本身支持虚拟化扩展,如intel VTAMD AMD-V技术。同时它是Linux内核的一个可加载模块,KVMLinux 2.6.20以后已被作为内核组件。
从存在形式来看,它包括两个内核模块:kvm.ko用于实现核心虚拟化功能和kvm_intel.ko(或 kvm_amd.ko)处理器强相关的模块。 本质上,KVM是管理虚拟硬件设备的驱动,该驱动使用字符设备/dev/kvm(由KVM本身创建)作为管理接口,主要负责vCPU的创建,虚拟内存的分配,vCPU寄存器的读写以及vCPU的运行。

有了KVM以后,guest os的CPU指令不用再经过QEMU来转译便可直接运行,大大提高了运行速度。但KVMkvm.ko本身只提供了CPU和内存的虚拟化,所以它必须结合QEMU才能构成一个完整的虚拟化技术。

QEMU-KVMKVM运行在内核空间,QEMU运行在用户空间,实际模拟创建、管理各种虚拟硬件,QEMUKVM整合了进来,通过ioctl 调用/dev/kvm ,从而将CPU指令的部分交给内核模块来做,KVM实现了CPU和内存的虚拟化,但KVM不能虚拟其他硬件设备,因此QEMU还有模拟IO设备(磁盘,网卡,显卡等)的作用,KVM加上QEMU后就是完整意义上的服务器虚拟化。 由于QEMU纯模拟IO设备的效率不高,一般采用半虚拟化的VIRTIO来虚拟IO设备。

kvm加速的伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
kvmfd = open("/dev/kvm")
ioctl(kvmfd,KVM_CREATE_VM,0)
ioctl(kvmfd,KVM_CREATE_VCPU,0)
for (;;) {
ioctl(KVM_RUN)
switch (exit_reason) {
case KVM_EXIT_IO:
case KVM_EXIT_MMIO:
case KVM_EXIT_IRQ_WINDOW_OPEN:
case KVM_EXIT_SHUTDOWN:
...
}
}

为了使用KVM执行虚拟机代码,QEMU进程打开/dev/kvm并发出ioctl(KVM_RUN)KVM内核模块使用现代IntelAMD CPU上的硬件虚拟化扩展来直接执行虚拟机代码。 当guest虚拟机访问硬件设备寄存器,或是暂停虚拟机CPU或是执行其他特殊操作时,KVM将退出并将控制权转给QEMU。 此时,QEMU可以模拟操作的预期输出,或者只是客户CPU在暂停的情况下等待下一个客户机中断。

具体分工为:KVM负责对CPU和内存模拟,QEMU负责对IO设备模拟并对各种虚拟设备的创建和调度进行管理。

代码结构

QEMU是一个模拟器,它能够动态模拟特定架构的CPU指令,如X86PPCARM等等。QEMU模拟的架构叫目标架构,运行QEMU的系统架构叫主机架构,QEMU中有一个模块叫做微型代码生成器(TCG),它用来将目标代码翻译成主机代码。

image-20200811115719848

我们也可以将运行在虚拟CPU上的代码叫做客户机代码,QEMU的主要功能就是不断提取客户机代码并且转化成主机指定架构的代码。整个翻译任务分为两个部分:第一个部分是将做目标代码(TB)转化成TCG中间代码,然后再将TCG中间代码转化成主机代码

核心代码位置

主要比较重要的c文件有:/vl.c/cpus.c/exec-all.c/exec.c/cpu-exec.c

QEMUmain函数定义在/vl.c中,它也是执行的起点,这个函数的功能主要是建立一个虚拟的硬件环境,它通过参数的解析,将初始化内存,需要的模拟的设备初始化,CPU参数,初始化KVM等等。接着程序就跳转到其他的执行分支文件如:/cpus.c/exec-all.c/exec.c/cpu-exec.c

所有的硬件设备都在/hw/目录下面,所有的设备都有独自的文件,包括总线,串口,网卡,鼠标等等。它们通过设备模块串在一起,在vl.c中的machine _init中初始化。

现在QEMU模拟的CPU架构有:AlphaARMCrisi386M68KPPCSparcMipsMicroBlazeS390XSH4等。

QEMU中使用./configure可以配置运行的架构,这个脚本会自动读取本机真实机器的CPU架构,并且编译的时候就编译对应架构的代码。对于不同的QEMU做的事情都不同,所以不同架构下的代码在不同的目录下面。/target-arch/目录就对应了相应架构的代码,如/target-i386/就对应了x86系列的代码部分。虽然不同架构做法不同,但是都是为了实现将对应客户机CPU架构的TBs转化成TCG的中间代码,这个就是TCG的前半部分。

使用TCG代码生成宿主机的代码位于/tcg/里,在这个目录里面也对应了不同的架构,分别在不同的子目录里面,如i386就在/tcg/i386中,整个生成宿主机代码的过程也可以当做TCG的后半部分。

1
2
3
4
5
/vl.c:                      //最主要的模拟循环,虚拟机机器环境初始化,和CPU的执行。
/target-arch/translate.c //将客户机代码转化成不同架构的TCG操作码。
/tcg/tcg.c //主要的TCG代码。
/tcg/arch/tcg-target.c //将TCG代码转化生成宿主机代码
/cpu-exec.c //其中的cpu-exec()函数主要寻找下一个TB(翻译代码块),如果没找到就请求得到下一个TB,并且操作生成的代码块。

TCG

TCG说白了就是一个解释器,或者说是编译器,将客服机的代码翻译为TCG中间代码(前端),再将TCG中间代码翻译为宿主机代码(后端):

image-20200811122300653

客户机代码部分:

image-20200811122312739

TCG中间代码部分:

image-20200811122324627

宿主机代码部分:

image-20200811122334537

当在我们需要的时候TCG才会动态的转变代码,这个想法的目的是用更多的时间去执行我们生成的代码。当新的代码从TB中生成以后, 将会被保存到一个code cache中,因为很多相同的TB会被反复的进行操作,所以这样类似于内存的cache,能够提高使用效率,而code cache的刷新使用LRU算法。

image-20200811122246132

Chaining of TBs

Returning from the code cache to the static code (QEMU code) and jumping back into the code cache is generally slow. To solve this QEMU chains every TB to the next TB. So after the execution of one TB the execution directly jumps to the next TB without returning to the static code. The chaining of block happens when the Tb returns to the static code. Thus when TB1 returns (as there was no chaining) to static code the next TB, TB2, is found, generated and executed. When TB2 returns it is immediately chained to TB1. This makes sure that next time when TB1 is executed TB2 follows it without returning to the static code. Figure 7.8 (a-c) in the following page illustrates chaining of TBs.

code cache返回到static code(Qemu的代码)中和从static code代码进入code cache中是很费时间的,为了解决这个问题,QemuTB连接成了一条chain,所以执行完一块TB之后会直接跳到下一个TB继续执行而不会返回到static code中。

image-20200811123158082

具体代码分析,这里代码全部选自Qemu Detailed Study Chapter-7,版本应该比较久远了,我和现在的代码对比了下,有部分差别但是核心应该是没变的:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
main_loop(...){/vl.c}:
//函数main_loop初始化qemu_main_loop_start(),然后进入无限循环cpu_exec_all(),这个是QEMU的一个主要循环,在里面会不断的判断一些条件,如虚拟机的关机断电之类,在下文的Main loop部分详解

qemu_main_loop_start(...){/cpus.c}:
//函数设置系统变量qemu_system_ready = 1并且重启所有的线程并且等待一个条件变量.

cpu_exec_all(...){/cpus.c}:
//它是cpu循环,QEMU能够启动256个cpu核,但是这些核将会分时运行,然后执行qemu_cpu_exec().

struct CPUState{/target-xyz/cpu.h}:
//它是CPU状态结构体,关于cpu的各种状态,不同架构下面还有不同。

cpu_exec(...){/cpu-exec.c}:
//这个函数是主要的执行循环,这里第一次翻译之前说道德TB,TB被初始化为(TranslationBlock *tb),然后不停的执行异常处理。其中嵌套了两个无限循环find tb_find_fast()和tcg_qemu_tb_exec().

cantb_find_fast() //为客户机初始化查询下一个TB,并且生成主机代码。

tcg_qemu_tb_exec() //执行生成的主机代码

struct TranslationBlock {/exec-all.h}:
//结构体TranslationBlock包含下面的成员:PC, CS_BASE, Flags(表明TB), tc_ptr(指向这个TB翻译代码的指针), tb_next_offset[2], tb_jmp_offset[2](接下去的Tb), *jmp_next[2], *jmp_first (之前的TB).

tb_find_fast(...){/cpu-exec.c}:
//函数通过调用获得程序指针计数器,然后传到一个哈希函数从tb_jmp_cache[](一个哈希表)得到TB的所以,所以使用tb_jmp_cache可以找到下一个TB。如果没有找到下一个TB,则使用tb_find_slow。

tb_find_slow(...){/cpu-exec.c}:
//这个是在快速查找失败以后试图去访问物理内存,寻找TB。

tb_gen_code(...){/exec.c}:
//开始分配一个新的TB,TB的PC是刚刚从CPUstate里面通过using get_page_addr_code()找到的

phys_pc = get_page_addr_code(env, pc);

tb = tb_alloc(pc);
//ph当调用cpu_gen_code()以后,接着会调用tb_link_page(),它将增加一个新的TB,并且指向它的物理页表。

cpu_gen_code(...){translate-all.c}:
//函数初始化真正的代码生成,在这个函数里面有下面的函数调用:

gen_intermediate_code(){/target-arch/translate.c}->gen_intermediate_code_internal(){/target-arch/translate.c }->disas_insn(){/target-arch/translate.c}

disas_insn(){/target-arch/translate.c}
//函数disas_insn()真正的实现将客户机代码翻译成TCG代码,它通过一长串的switch case,将不同的指令做不同的翻译,最后调用tcg_gen_code。

tcg_gen_code(...){/tcg/tcg.c}:
//这个函数将TCG的代码转化成主机代码,这个就不细细说明了,和前面类似。

#define tcg_qemu_tb_exec(...){/tcg/tcg.g}:
//通过上面的步骤,当TB生成以后就通过这个函数进行执行.

next_tb = tcg_qemu_tb_exec(tc_ptr) :

extern uint8_t code_gen_prologue[];

#define tcg_qemu_tb_exec(tb_ptr) ((long REGPARM(*)(void *)) code_gen_prologue)(tb_ptr)

功能

从一个软件的功能的角度来看,qemu主要分为两大部分:

  • 硬件设备模拟(CPU、内存、外设等)

  • 对虚拟机的运维和管理,包括monitorQMP(QEMU monitor protocol),主要是接收管理命令并处理,包括查询状态、动态添加删除设备、虚拟机迁移等

qemu的源码都是为了这两个功能而服务的。

选项子系统

QEMU的设备模拟是从QEMU的选项解析开始的,QEMU的选项定义了QEMU要模拟的虚拟机的形态,比如虚拟机支持的CPU类型、有多少内存、虚拟机上有哪些外设等。QEMU支持的选项多达上百个,因此采用硬编码的方法来处理并不合适,需要把他们有效的组织和管理起来。
选项解析的第一步是查询从命令行中传入的选项是否是QEMU支持的选项,这需要从QEMU支持的所有的选项集合中去匹配该选项,并给出该选项的id或索引,以在switch语句中去定位选项的处理流程。在QEMU中所有的选项保存在QEMUOption qemu_options[]数组中。保存每个具体选项的结构体为QEMUOption

1
2
3
4
5
6
typedef struct QEMUOption {
const char *name;
int flags;
int index;
uint32_t arch_mask;
} QEMUOption;

识别了选项之后,后面就是要解析选项了。QEMU是大型软件,它运行过程中的很多行为都是跟选项相关的,因此很多选项并不是解析的时候马上都会用到,所以需要保存起来,在需要用到的时候再来查询该选项。QEMU的选项有非常多,其中很多选项都有很多子选项,因此有很多选项是相关的,作用于同一类设备或同一个子系统的。QEMU在保存解析出的选项的时候是分类来保存的。QEMU与解析后的选项保存相关的数据结构有4个不同的结构体:

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
29
30
31
32
33
struct QemuOptsList {
const char *name;
const char *implied_opt_name;
bool merge_lists; /* Merge multiple uses of option into a single list? */
QTAILQ_HEAD(, QemuOpts) head;
QemuOptDesc desc[];
};
struct QemuOpts {
char *id;
QemuOptsList *list;
Location loc;
QTAILQ_HEAD(QemuOptHead, QemuOpt) head;
QTAILQ_ENTRY(QemuOpts) next;
};
struct QemuOpt {
char *name;
char *str;

const QemuOptDesc *desc;
union {
bool boolean;
uint64_t uint;
} value;

QemuOpts *opts;
QTAILQ_ENTRY(QemuOpt) next;
};
typedef struct QemuOptDesc {
const char *name;
enum QemuOptType type;
const char *help;
const char *def_value_str;
} QemuOptDesc

QemuOptsList结构体用来保存某一类的选项,QEMU把选项分成了很多类,比如-drive选项,很多存储相关的选项及子选项保存在drive大选项中;再比如-device选项,有很多不同种类的子选项,它也是一类选项。QEMU中维护了一个QemuOptsList的数组,该数组中的每个成员都代表了解析出的一类选项,即QEMU是按照QemuOptsList来分类的。并不是所有的选项都按分类的方式保存在QemuOptsList中了,有些简单的只有一个选择的选项,就没必要分类的保存在数组中了,直接保存在一个变量中就行了,比如-pidfile选项,指定存储qemu进程pid号的文件,直接把文件名参数保存在pid_file变量中就行了。

QemuOptsList中保存的大选项中有很多子选项,这些子选项保存在QemuOpts结构体中,一个QemuOpts保存了一个大选项相关的所有子选项,每个子选项都是一个QemuOpt结构,因此QemuOpts里面实际保存是QemuOpt结构的链表。
QemuOpt结构保存的是一个个具体的子选项,以key、value对的方式保存,key是子选项的名称,value是命令行参数指定的子选项的值。

QemuOptsList结构体的定义可以看出QemuOptsList中保存的也是QemuOpts结构体的链表,就是说一个大选项可能有多个QemuOpts,但是QemuOpts已经保存了所有的子选项了,为什么会有多个QemuOpts结构体?这是因为有些QEMU选项在命令行中不会使用一次,有可能一个选项使用多次,即一个大选项可能有多个同时存在的实例。比如-device选项是用来创建虚拟机里面的外设的,你可以用来创建一个字符设备,也可以用来创建一个网络设备等,所以qemu命令行可以可能同时使用多次-device选项以创建多个不同的设备,每个设备都指定有各自的子选项,多个QemuOpts是用来保存同一类选项的不同实例的。

QemuOptDesc保存的是选项的帮助信息或描述信息。

qemu_opts_parse_noisily()函数解析某一个大选项,他先创建一个QemuOpts,然后从参数中中解析出每个子选项key、value对,并为每个key、value对创建QemuOptqemu_opt_set()/opt_set()函数用于在QemuOpts中新添加一个QemuOpt结构的key、value对。
QEMU的选项解析会调用解析函数解析每个命令行参数,并把解析出的选项保存在QemuOptsList数组中,一些不需要保存在数组中的选项保存在相关的变量中。

QOM

详见QEMU学习--QOM篇章。

Main loop(主事件循环)

QEMU中的主事件循环是为QEMU在设备模拟过程中的各个任务遵循的执行模型或者执行流。在一般持续的C程序中(比如单片机的程序),在进行一定的初始化后都会进入一个while循环,while循环不断的检测执行条件,当条件满足时就执行循环体里面的任务。QEMU的主事件循环就是这个while主循环,与while主循环的区别是,while主循环是一个非常原始和粗放的方式,而QEMU的主事件循环要先进的多。

QEMU的主循环不能采用原始的方式,必须经过精巧的涉及。这是因为QEMU对性能的要求很高且QEMU的主循环中要处理非常多的事件,执行非常多的任务,为了协调这些任务的执行,必须采用非常精巧的方式来执行主事件循环。QEMU的主事件循环是在glib库提供的gmainloop主事件循环机制的基础改造而来。

  • Glib事件循环机制提供了一套事件分发接口,使用这套接口注册事件源(source)和对应的回调,可以开发基于事件触发的应用。Glib的核心是poll机制,通过poll检查用户注册的事件源,并执行对应的回调,用户不需要关注其具体实现,只需要按照要求注册对应的事件源和回调
  • Glib事件循环机制管理所有注册的事件源,主要类型有:fdpipesockettimer。不同事件源可以在一个线程中处理,也可以在不同线程中处理,这取决于事件源所在的上下文(GMainContext),一个上下文只能运行在一个线程中,所以如果想要事件源在不同线程中并发被处理,可以将其放在不同的上下文。

Glib对一个事件源的处理分为4个阶段:初始化,准备,poll,和调度。用户可以在这4个处理阶段为每个事件源注册自己的回调处理函数。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// vl.c
static void main_loop(void)//在main函数里被调用
{
#ifdef CONFIG_PROFILER
int64_t ti;
#endif
while (!main_loop_should_exit()) {//判断是否应该退出QEMU
#ifdef CONFIG_PROFILER
ti = profile_getclock();
#endif
main_loop_wait(false);//主循环的主要处理过程
#ifdef CONFIG_PROFILER
dev_time += profile_getclock() - ti;
#endif
}
}

void main_loop_wait(int nonblocking)
{
int ret;
uint32_t timeout = UINT32_MAX;
int64_t timeout_ns;

if (nonblocking) {
timeout = 0;
}

/* poll any events */
g_array_set_size(gpollfds, 0); /* reset for new iteration */
/* XXX: separate device handlers from system ones */
slirp_pollfds_fill(gpollfds, &timeout);

if (timeout == UINT32_MAX) {
timeout_ns = -1;
} else {
timeout_ns = (uint64_t)timeout * (int64_t)(SCALE_MS);
}

timeout_ns = qemu_soonest_timeout(timeout_ns,
timerlistgroup_deadline_ns(
&main_loop_tlg));

ret = os_host_main_loop_wait(timeout_ns); //核心逻辑
slirp_pollfds_poll(gpollfds, (ret < 0));

/* CPU thread can infinitely wait for event after
missing the warp */
qemu_start_warp_timer();
qemu_clock_run_all_timers();
}

QEMU的主事件循环的主要处理过程是main_loop_wait(),它执行以下任务:

  1. 等待文件描述符变得可读或可写。 文件描述符起着至关重要的作用,因为fdsocketpipe各种其他资源都是文件描述符。 可以使用qemu_set_fd_handler()添加文件描述符。
  2. 运行过期的计时器。 可以使用qemu_mod_timer()添加计时器。
  3. 运行下半部分(BHs),就像定时器立即到期一样。BH用于避免重入和溢出调用堆栈。 可以使用qemu_bh_schedule()添加BH
  当文件描述符准备就绪、计时器到期、BH被调度时,事件循环将调用响应事件的回调。 回调有两个关于其环境的简单规则:

  • 没有其他核心代码同时执行,因此不需要同步。 回调相对于其他核心代码按顺序和原子方式执行。 在任何给定时间只有一个控制线程执行核心代码。
  • 不应执行阻塞系统调用或长时间运行的计算。 由于事件循环在继续其他事件之前等待回调返回,因此避免在回调中花费无限量的时间是很重要的。 违反此规则会导致guest虚拟机暂停并且监视器无响应。

monitor/QMP

用来实现虚拟机管理的模块,对于入门开发者来说,这部分内容知道怎么使用即可,代码方面我没去深究。

monitor的使用很简单,在qemu启动的命令行中不加-nographic时会弹出一个UI窗口,那个窗口就是monitor,我们可以与qemu交互来管理设备和查看设备状态,数据备份,访问Guest OS等操作,想重定向到标准输入终端加选项-monitor stdio即可,想重定向到网络的话,-monitor telnet:localhost:9000,server

QMP(QEMU monitor protocol),在很多情况下,我们希望在外部将控制命令输入到QEMU monitor中,但是原有的机制太麻烦(需要重定向等绕圈子的操作),QEMU为我们提供了一个叫QMP的东西。QMP全称QEMU monitor protocol顾名思义就是用于QEMU monitor的协议啦。QMPJSON格式传输命令与返回信息,许多基于QEMU的应用都使用了这个功能,比如著名的虚拟化中间件libvirt,它在对QEMU虚拟机做操作时,就是使用的QMP,可以通过telnet或者qmp-shell等方式使用QMP

参考

https://vmsplice.net/~stefan/qemu-code-overview.pdf

https://juniorprincewang.github.io/2018/11/15/QEMU%E5%AD%A6%E4%B9%A0/

https://blog.csdn.net/yearn520/article/details/6602182?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight

https://lists.gnu.org/archive/html/qemu-devel/2011-04/pdfhC5rVdz7U8.pdf

https://rickylss.github.io/qemu/2019/06/25/qemu-monitor/

https://blog.csdn.net/woai110120130/

Qemu的选项参数

复杂的命令行选项是Qemu的最大缺点之一,毕竟除了几个常用的选项外,没人会记得这么多偏门的选项,所以现查手册是比较快速的方法,这里把所有的选项记录下来(QEMU emulator version 2.3.93),供查阅方便,想自己生成的话,直接用./qemu-system-x86_64 --help

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
QEMU emulator version 2.3.93, Copyright (c) 2003-2008 Fabrice Bellard
usage: qemu-system-x86_64 [options] [disk_image]

'disk_image' is a raw hard disk image for IDE hard disk 0

Standard options:
-h or -help display this help and exit
-version display version information and exit
-machine [type=]name[,prop[=value][,...]]
selects emulated machine ('-machine help' for list)
property accel=accel1[:accel2[:...]] selects accelerator
supported accelerators are kvm, xen, tcg (default: tcg)
kernel_irqchip=on|off controls accelerated irqchip support
vmport=on|off|auto controls emulation of vmport (default: auto)
kvm_shadow_mem=size of KVM shadow MMU
dump-guest-core=on|off include guest memory in a core dump (default=on)
mem-merge=on|off controls memory merge support (default: on)
iommu=on|off controls emulated Intel IOMMU (VT-d) support (default=off)
aes-key-wrap=on|off controls support for AES key wrapping (default=on)
dea-key-wrap=on|off controls support for DEA key wrapping (default=on)
suppress-vmdesc=on|off disables self-describing migration (default=off)
-cpu cpu select CPU ('-cpu help' for list)
-smp [cpus=]n[,maxcpus=cpus][,cores=cores][,threads=threads][,sockets=sockets]
set the number of CPUs to 'n' [default=1]
maxcpus= maximum number of total cpus, including
offline CPUs for hotplug, etc
cores= number of CPU cores on one socket
threads= number of threads on one CPU core
sockets= number of discrete sockets in the system
-numa node[,mem=size][,cpus=cpu[-cpu]][,nodeid=node]
-numa node[,memdev=id][,cpus=cpu[-cpu]][,nodeid=node]
-add-fd fd=fd,set=set[,opaque=opaque]
Add 'fd' to fd 'set'
-set group.id.arg=value
set <arg> parameter for item <id> of type <group>
i.e. -set drive.$id.file=/path/to/image
-global driver.property=value
-global driver=driver,property=property,value=value
set a global default for a driver property
-boot [order=drives][,once=drives][,menu=on|off]
[,splash=sp_name][,splash-time=sp_time][,reboot-timeout=rb_time][,strict=on|off]
'drives': floppy (a), hard disk (c), CD-ROM (d), network (n)
'sp_name': the file's name that would be passed to bios as logo picture, if menu=on
'sp_time': the period that splash picture last if menu=on, unit is ms
'rb_timeout': the timeout before guest reboot when boot failed, unit is ms
-m[emory] [size=]megs[,slots=n,maxmem=size]
configure guest RAM
size: initial amount of guest memory
slots: number of hotplug slots (default: none)
maxmem: maximum amount of guest memory (default: none)
NOTE: Some architectures might enforce a specific granularity
-mem-path FILE provide backing storage for guest RAM
-mem-prealloc preallocate guest memory (use with -mem-path)
-k language use keyboard layout (for example 'fr' for French)
-audio-help print list of audio drivers and their options
-soundhw c1,... enable audio support
and only specified sound cards (comma separated list)
use '-soundhw help' to get the list of supported cards
use '-soundhw all' to enable all of them
-balloon none disable balloon device
-balloon virtio[,addr=str]
enable virtio balloon device (default)
-device driver[,prop[=value][,...]]
add device (based on driver)
prop=value,... sets driver properties
use '-device help' to print all possible drivers
use '-device driver,help' to print all possible properties
-name string1[,process=string2][,debug-threads=on|off]
set the name of the guest
string1 sets the window title and string2 the process name (on Linux)
When debug-threads is enabled, individual threads are given a separate name (on Linux)
NOTE: The thread names are for debugging and not a stable API.
-uuid %08x-%04x-%04x-%04x-%012x
specify machine UUID

Block device options:
-fda/-fdb file use 'file' as floppy disk 0/1 image
-hda/-hdb file use 'file' as IDE hard disk 0/1 image
-hdc/-hdd file use 'file' as IDE hard disk 2/3 image
-cdrom file use 'file' as IDE cdrom image (cdrom is ide1 master)
-drive [file=file][,if=type][,bus=n][,unit=m][,media=d][,index=i]
[,cyls=c,heads=h,secs=s[,trans=t]][,snapshot=on|off]
[,cache=writethrough|writeback|none|directsync|unsafe][,format=f]
[,serial=s][,addr=A][,rerror=ignore|stop|report]
[,werror=ignore|stop|report|enospc][,id=name][,aio=threads|native]
[,readonly=on|off][,copy-on-read=on|off]
[,discard=ignore|unmap][,detect-zeroes=on|off|unmap]
[[,bps=b]|[[,bps_rd=r][,bps_wr=w]]]
[[,iops=i]|[[,iops_rd=r][,iops_wr=w]]]
[[,bps_max=bm]|[[,bps_rd_max=rm][,bps_wr_max=wm]]]
[[,iops_max=im]|[[,iops_rd_max=irm][,iops_wr_max=iwm]]]
[[,iops_size=is]]
[[,group=g]]
use 'file' as a drive image
-mtdblock file use 'file' as on-board Flash memory image
-sd file use 'file' as SecureDigital card image
-pflash file use 'file' as a parallel flash image
-snapshot write to temporary files instead of disk image files
-hdachs c,h,s[,t]
force hard disk 0 physical geometry and the optional BIOS
translation (t=none or lba) (usually QEMU can guess them)
-fsdev fsdriver,id=id[,path=path,][security_model={mapped-xattr|mapped-file|passthrough|none}]
[,writeout=immediate][,readonly][,socket=socket|sock_fd=sock_fd]
-virtfs local,path=path,mount_tag=tag,security_model=[mapped-xattr|mapped-file|passthrough|none]
[,writeout=immediate][,readonly][,socket=socket|sock_fd=sock_fd]
-virtfs_synth Create synthetic file system image

USB options:
-usb enable the USB driver (will be the default soon)
-usbdevice name add the host or guest USB device 'name'

Display options:
-display sdl[,frame=on|off][,alt_grab=on|off][,ctrl_grab=on|off]
[,window_close=on|off]|curses|none|
gtk[,grab_on_hover=on|off]|
vnc=<display>[,<optargs>]
select display type
-nographic disable graphical output and redirect serial I/Os to console
-curses use a curses/ncurses interface instead of SDL
-no-frame open SDL window without a frame and window decorations
-alt-grab use Ctrl-Alt-Shift to grab mouse (instead of Ctrl-Alt)
-ctrl-grab use Right-Ctrl to grab mouse (instead of Ctrl-Alt)
-no-quit disable SDL window close capability
-sdl enable SDL
-spice [port=port][,tls-port=secured-port][,x509-dir=<dir>]
[,x509-key-file=<file>][,x509-key-password=<file>]
[,x509-cert-file=<file>][,x509-cacert-file=<file>]
[,x509-dh-key-file=<file>][,addr=addr][,ipv4|ipv6|unix]
[,tls-ciphers=<list>]
[,tls-channel=[main|display|cursor|inputs|record|playback]]
[,plaintext-channel=[main|display|cursor|inputs|record|playback]]
[,sasl][,password=<secret>][,disable-ticketing]
[,image-compression=[auto_glz|auto_lz|quic|glz|lz|off]]
[,jpeg-wan-compression=[auto|never|always]]
[,zlib-glz-wan-compression=[auto|never|always]]
[,streaming-video=[off|all|filter]][,disable-copy-paste]
[,disable-agent-file-xfer][,agent-mouse=[on|off]]
[,playback-compression=[on|off]][,seamless-migration=[on|off]]
enable spice
at least one of {port, tls-port} is mandatory
-portrait rotate graphical output 90 deg left (only PXA LCD)
-rotate <deg> rotate graphical output some deg left (only PXA LCD)
-vga [std|cirrus|vmware|qxl|xenfb|tcx|cg3|virtio|none]
select video card type
-full-screen start in full screen
-vnc display start a VNC server on display

i386 target only:
-win2k-hack use it when installing Windows 2000 to avoid a disk full bug
-no-fd-bootchk disable boot signature checking for floppy disks
-no-acpi disable ACPI
-no-hpet disable HPET
-acpitable [sig=str][,rev=n][,oem_id=str][,oem_table_id=str][,oem_rev=n][,asl_compiler_id=str][,asl_compiler_rev=n][,{data|file}=file1[:file2]...]
ACPI table description
-smbios file=binary
load SMBIOS entry from binary file
-smbios type=0[,vendor=str][,version=str][,date=str][,release=%d.%d]
[,uefi=on|off]
specify SMBIOS type 0 fields
-smbios type=1[,manufacturer=str][,product=str][,version=str][,serial=str]
[,uuid=uuid][,sku=str][,family=str]
specify SMBIOS type 1 fields
-smbios type=2[,manufacturer=str][,product=str][,version=str][,serial=str]
[,asset=str][,location=str]
specify SMBIOS type 2 fields
-smbios type=3[,manufacturer=str][,version=str][,serial=str][,asset=str]
[,sku=str]
specify SMBIOS type 3 fields
-smbios type=4[,sock_pfx=str][,manufacturer=str][,version=str][,serial=str]
[,asset=str][,part=str]
specify SMBIOS type 4 fields
-smbios type=17[,loc_pfx=str][,bank=str][,manufacturer=str][,serial=str]
[,asset=str][,part=str][,speed=%d]
specify SMBIOS type 17 fields

Network options:
-netdev user,id=str[,net=addr[/mask]][,host=addr][,restrict=on|off]
[,hostname=host][,dhcpstart=addr][,dns=addr][,dnssearch=domain][,tftp=dir]
[,bootfile=f][,hostfwd=rule][,guestfwd=rule][,smb=dir[,smbserver=addr]]
configure a user mode network backend with ID 'str',
its DHCP server and optional services
-netdev tap,id=str[,fd=h][,fds=x:y:...:z][,ifname=name][,script=file][,downscript=dfile]
[,helper=helper][,sndbuf=nbytes][,vnet_hdr=on|off][,vhost=on|off]
[,vhostfd=h][,vhostfds=x:y:...:z][,vhostforce=on|off][,queues=n]
configure a host TAP network backend with ID 'str'
use network scripts 'file' (default=/etc/qemu-ifup)
to configure it and 'dfile' (default=/etc/qemu-ifdown)
to deconfigure it
use '[down]script=no' to disable script execution
use network helper 'helper' (default=/usr/local/libexec/qemu-bridge-helper) to
configure it
use 'fd=h' to connect to an already opened TAP interface
use 'fds=x:y:...:z' to connect to already opened multiqueue capable TAP interfaces
use 'sndbuf=nbytes' to limit the size of the send buffer (the
default is disabled 'sndbuf=0' to enable flow control set 'sndbuf=1048576')
use vnet_hdr=off to avoid enabling the IFF_VNET_HDR tap flag
use vnet_hdr=on to make the lack of IFF_VNET_HDR support an error condition
use vhost=on to enable experimental in kernel accelerator
(only has effect for virtio guests which use MSIX)
use vhostforce=on to force vhost on for non-MSIX virtio guests
use 'vhostfd=h' to connect to an already opened vhost net device
use 'vhostfds=x:y:...:z to connect to multiple already opened vhost net devices
use 'queues=n' to specify the number of queues to be created for multiqueue TAP
-netdev bridge,id=str[,br=bridge][,helper=helper]
configure a host TAP network backend with ID 'str' that is
connected to a bridge (default=br0)
using the program 'helper (default=/usr/local/libexec/qemu-bridge-helper)
-netdev l2tpv3,id=str,src=srcaddr,dst=dstaddr[,srcport=srcport][,dstport=dstport]
[,rxsession=rxsession],txsession=txsession[,ipv6=on/off][,udp=on/off]
[,cookie64=on/off][,counter][,pincounter][,txcookie=txcookie]
[,rxcookie=rxcookie][,offset=offset]
configure a network backend with ID 'str' connected to
an Ethernet over L2TPv3 pseudowire.
Linux kernel 3.3+ as well as most routers can talk
L2TPv3. This transport allows connecting a VM to a VM,
VM to a router and even VM to Host. It is a nearly-universal
standard (RFC3391). Note - this implementation uses static
pre-configured tunnels (same as the Linux kernel).
use 'src=' to specify source address
use 'dst=' to specify destination address
use 'udp=on' to specify udp encapsulation
use 'srcport=' to specify source udp port
use 'dstport=' to specify destination udp port
use 'ipv6=on' to force v6
L2TPv3 uses cookies to prevent misconfiguration as
well as a weak security measure
use 'rxcookie=0x012345678' to specify a rxcookie
use 'txcookie=0x012345678' to specify a txcookie
use 'cookie64=on' to set cookie size to 64 bit, otherwise 32
use 'counter=off' to force a 'cut-down' L2TPv3 with no counter
use 'pincounter=on' to work around broken counter handling in peer
use 'offset=X' to add an extra offset between header and data
-netdev socket,id=str[,fd=h][,listen=[host]:port][,connect=host:port]
configure a network backend to connect to another network
using a socket connection
-netdev socket,id=str[,fd=h][,mcast=maddr:port[,localaddr=addr]]
configure a network backend to connect to a multicast maddr and port
use 'localaddr=addr' to specify the host address to send packets from
-netdev socket,id=str[,fd=h][,udp=host:port][,localaddr=host:port]
configure a network backend to connect to another network
using an UDP tunnel
-netdev vhost-user,id=str,chardev=dev[,vhostforce=on|off]
configure a vhost-user network, backed by a chardev 'dev'
-netdev hubport,id=str,hubid=n
configure a hub port on QEMU VLAN 'n'
-net nic[,vlan=n][,macaddr=mac][,model=type][,name=str][,addr=str][,vectors=v]
old way to create a new NIC and connect it to VLAN 'n'
(use the '-device devtype,netdev=str' option if possible instead)
-net dump[,vlan=n][,file=f][,len=n]
dump traffic on vlan 'n' to file 'f' (max n bytes per packet)
-net none use it alone to have zero network devices. If no -net option
is provided, the default is '-net nic -net user'
-net [user|tap|bridge|socket][,vlan=n][,option][,option][,...]
old way to initialize a host network interface
(use the -netdev option if possible instead)

Character device options:
-chardev null,id=id[,mux=on|off]
-chardev socket,id=id[,host=host],port=port[,to=to][,ipv4][,ipv6][,nodelay][,reconnect=seconds]
[,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off] (tcp)
-chardev socket,id=id,path=path[,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off] (unix)
-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]
[,localport=localport][,ipv4][,ipv6][,mux=on|off]
-chardev msmouse,id=id[,mux=on|off]
-chardev vc,id=id[[,width=width][,height=height]][[,cols=cols][,rows=rows]]
[,mux=on|off]
-chardev ringbuf,id=id[,size=size]
-chardev file,id=id,path=path[,mux=on|off]
-chardev pipe,id=id,path=path[,mux=on|off]
-chardev pty,id=id[,mux=on|off]
-chardev stdio,id=id[,mux=on|off][,signal=on|off]
-chardev serial,id=id,path=path[,mux=on|off]
-chardev tty,id=id,path=path[,mux=on|off]
-chardev parallel,id=id,path=path[,mux=on|off]
-chardev parport,id=id,path=path[,mux=on|off]

Device URL Syntax:
-iscsi [user=user][,password=password]
[,header-digest=CRC32C|CR32C-NONE|NONE-CRC32C|NONE
[,initiator-name=initiator-iqn][,id=target-iqn]
[,timeout=timeout]
iSCSI session parameters
Bluetooth(R) options:
-bt hci,null dumb bluetooth HCI - doesn't respond to commands
-bt hci,host[:id]
use host's HCI with the given name
-bt hci[,vlan=n]
emulate a standard HCI in virtual scatternet 'n'
-bt vhci[,vlan=n]
add host computer to virtual scatternet 'n' using VHCI
-bt device:dev[,vlan=n]
emulate a bluetooth device 'dev' in scatternet 'n'

TPM device options:
-tpmdev passthrough,id=id[,path=path][,cancel-path=path]
use path to provide path to a character device; default is /dev/tpm0
use cancel-path to provide path to TPM's cancel sysfs entry; if
not provided it will be searched for in /sys/class/misc/tpm?/device

Linux/Multiboot boot specific:
-kernel bzImage use 'bzImage' as kernel image
-append cmdline use 'cmdline' as kernel command line
-initrd file use 'file' as initial ram disk
-dtb file use 'file' as device tree image

Debug/Expert options:
-fw_cfg [name=]<name>,file=<file>
add named fw_cfg entry from file
-serial dev redirect the serial port to char device 'dev'
-parallel dev redirect the parallel port to char device 'dev'
-monitor dev redirect the monitor to char device 'dev'
-qmp dev like -monitor but opens in 'control' mode
-qmp-pretty dev like -qmp but uses pretty JSON formatting
-mon [chardev=]name[,mode=readline|control][,default]
-debugcon dev redirect the debug console to char device 'dev'
-pidfile file write PID to 'file'
-singlestep always run in singlestep mode
-S freeze CPU at startup (use 'c' to start execution)
-realtime [mlock=on|off]
run qemu with realtime features
mlock=on|off controls mlock support (default: on)
-gdb dev wait for gdb connection on 'dev'
-s shorthand for -gdb tcp::1234
-d item1,... enable logging of specified items (use '-d help' for a list of log items)
-D logfile output log to logfile (default stderr)
-L path set the directory for the BIOS, VGA BIOS and keymaps
-bios file set the filename for the BIOS
-enable-kvm enable KVM full virtualization support
-xen-domid id specify xen guest domain id
-xen-create create domain using xen hypercalls, bypassing xend
warning: should not be used when xend is in use
-xen-attach attach to existing xen domain
xend will use this when starting QEMU
-no-reboot exit instead of rebooting
-no-shutdown stop before shutdown
-loadvm [tag|id]
start right away with a saved state (loadvm in monitor)
-daemonize daemonize QEMU after initializing
-option-rom rom load a file, rom, into the option ROM space
-rtc [base=utc|localtime|date][,clock=host|rt|vm][,driftfix=none|slew]
set the RTC base and clock, enable drift fix for clock ticks (x86 only)
-icount [shift=N|auto][,align=on|off][,sleep=no]
enable virtual instruction counter with 2^N clock ticks per
instruction, enable aligning the host and virtual clocks
or disable real time cpu sleeping
-watchdog model
enable virtual hardware watchdog [default=none]
-watchdog-action reset|shutdown|poweroff|pause|debug|none
action when watchdog fires [default=reset]
-echr chr set terminal escape character instead of ctrl-a
-virtioconsole c
set virtio console
-show-cursor show cursor
-tb-size n set TB size
-incoming tcp:[host]:port[,to=maxport][,ipv4][,ipv6]
-incoming rdma:host:port[,ipv4][,ipv6]
-incoming unix:socketpath
prepare for incoming migration, listen on
specified protocol and socket address
-incoming fd:fd
-incoming exec:cmdline
accept incoming migration on given file descriptor
or from given external command
-incoming defer
wait for the URI to be specified via migrate_incoming
-nodefaults don't create default devices
-chroot dir chroot to dir just before starting the VM
-runas user change to user id user just before starting the VM
-sandbox <arg> Enable seccomp mode 2 system call filter (default 'off').
-readconfig <file>
-writeconfig <file>
read/write config file
-nodefconfig
do not load default config files at startup
-no-user-config
do not load user-provided config files at startup
-trace [events=<file>][,file=<file>]
specify tracing options
-enable-fips enable FIPS 140-2 compliance
-msg timestamp[=on|off]
change the format of messages
on|off controls leading timestamps (default:on)
-dump-vmstate <file>
Output vmstate information in JSON format to file.
Use the scripts/vmstate-static-checker.py file to
check for possible regressions in migration code
by comparing two such vmstate dumps.Generic object creation
-object TYPENAME[,PROP1=VALUE1,...]
create a new object of type TYPENAME setting properties
in the order they are specified. Note that the 'id'
property must be set. These objects are placed in the
'/objects' path.

During emulation, the following keys are useful:
ctrl-alt-f toggle full screen
ctrl-alt-n switch to virtual console 'n'
ctrl-alt toggle mouse and keyboard grab

When using -nographic, press 'ctrl-a h' to get some help.

Qemu的定时器

qemu中所有的与时间相关的模块都由timer.hqemu-timer.c文件实现,时钟的功能主要有二:记录时间和处理定时任务。

基本数据结构

QEMUClock Type

qemuclock一共有四种类型,分别是:QEMU_CLOCK_REALTIMEQEMU_CLOCK_VIRTUALQEMU_CLOCK_HOSTQEMU_CLOCK_VIRTUAL_RT

  • QEMU_CLOCK_REALTIME不受虚拟系统的影响,随时间流逝而累加计数
  • QEMU_CLOCK_VIRTUAL虚拟时钟,记录虚拟系统的时间滴答
  • QEMU_CLOCK_HOST这个类似墙上时钟,修改宿主机系统时间会改变这个时间
  • QEMU_CLOCK_VIRTUAL_RT在非icount模式下和QEMU_CLOCK_VIRTUAL,在icount模式下于QEMU_CLOCK_VIRTUAL不同的是在虚拟CPU休眠的时候该值也会累加
1
2
3
4
5
6
7
8
//timer.h
typedef enum {
QEMU_CLOCK_REALTIME = 0,
QEMU_CLOCK_VIRTUAL = 1,
QEMU_CLOCK_HOST = 2,
QEMU_CLOCK_VIRTUAL_RT = 3,
QEMU_CLOCK_MAX
} QEMUClockType;

QEMUClock Struct

1
2
3
4
5
6
7
8
9
10
typedef struct QEMUClock {
/* We rely on BQL to protect the timerlists */
QLIST_HEAD(, QEMUTimerList) timerlists;//QEMUTimerList用于存放定时器链表,注意定时器为每个滴答都会回调

NotifierList reset_notifiers;//用于时钟被重置时调用
int64_t last;//最后一次查询时间

QEMUClockType type;//时钟类型
bool enabled;//表示时钟是否被禁止
} QEMUClock;

QEMUTimer Struct

1
2
3
4
5
6
7
8
9
struct QEMUTimer {
int64_t expire_time; /* in nanoseconds */ //到期时间,绝对时间,单位ns
QEMUTimerList *timer_list; //所属的QEMUTimerList
QEMUTimerCB *cb; //定时器的到期回调函数
void *opaque; //回调函数的参数
QEMUTimer *next; //下一个定时器
int attributes;
int scale;
};

QEMUTimerList Struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* A QEMUTimerList is a list of timers attached to a clock. More
* than one QEMUTimerList can be attached to each clock, for instance
* used by different AioContexts / threads. Each clock also has
* a list of the QEMUTimerLists associated with it, in order that
* reenabling the clock can call all the notifiers.
*/

struct QEMUTimerList {
QEMUClock *clock; //所属的QEMUClock
QemuMutex active_timers_lock; //用于修改active_timers链表的锁
QEMUTimer *active_timers; //timer链表
QLIST_ENTRY(QEMUTimerList) list;
QEMUTimerListNotifyCB *notify_cb; //该队列有定时器到期后的回调函数
void *notify_opaque; //回调函数的参数

/* lightweight method to mark the end of timerlist's running */
QemuEvent timers_done_ev;
};

初始化流程

1
2
3
4
5
6
7
8
9
10
11
12
// util/qemu-timer.c
void init_clocks(QEMUTimerListNotifyCB *notify_cb) //初始化函数入口
{
QEMUClockType type;
for (type = 0; type < QEMU_CLOCK_MAX; type++) { //#define QEMU_CLOCK_MAX 4
qemu_clock_init(type, notify_cb); //初始化4个时钟
}

#ifdef CONFIG_PRCTL_PR_SET_TIMERSLACK
prctl(PR_SET_TIMERSLACK, 1, 0, 0, 0); //设置线程的定时器计数器为1
#endif
}

继续分析:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// util/qemu-timer.c
static void qemu_clock_init(QEMUClockType type, QEMUTimerListNotifyCB *notify_cb)
{
QEMUClock *clock = qemu_clock_ptr(type);

/* Assert that the clock of type TYPE has not been initialized yet. */
assert(main_loop_tlg.tl[type] == NULL);

clock->type = type;
clock->enabled = (type == QEMU_CLOCK_VIRTUAL ? false : true);
clock->last = INT64_MIN;
QLIST_INIT(&clock->timerlists);
notifier_list_init(&clock->reset_notifiers);
main_loop_tlg.tl[type] = timerlist_new(type, notify_cb, NULL);
}
//四个时钟使用类型作为索引放在qemu_clocks数组中
/**
* qemu_clock_ptr:
* @type: type of clock
*
* Translate a clock type into a pointer to QEMUClock object.
*
* Returns: a pointer to the QEMUClock object
*/
static inline QEMUClock *qemu_clock_ptr(QEMUClockType type)
{
return &qemu_clocks[type];
}

//用于初始化时钟的定时器链表()
QEMUTimerList *timerlist_new(QEMUClockType type,
QEMUTimerListNotifyCB *cb,
void *opaque)
{
QEMUTimerList *timer_list;
QEMUClock *clock = qemu_clock_ptr(type);

timer_list = g_malloc0(sizeof(QEMUTimerList));
qemu_event_init(&timer_list->timers_done_ev, true);
timer_list->clock = clock;
timer_list->notify_cb = cb;
timer_list->notify_opaque = opaque;
qemu_mutex_init(&timer_list->active_timers_lock);
QLIST_INSERT_HEAD(&clock->timerlists, timer_list, list);
return timer_list;
}

timerlist_new函数其实就是把一个定时器回调函数注册到了这个时钟的timer队列里面,由此可见主线程的定时器回调函数为qemu_timer->notify_cb,参数为qemu_timer->notify_opaque。另外主线程的定时器不但可以从定时器结构QEMUClock.timer_list中索引,还可以从main_loop_tlg中索引。
这样定时器就初始化工作完成了。

计时方法

qemu_clock_get_ns是用来获取时钟上的时间的。

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
int64_t qemu_clock_get_ns(QEMUClockType type)
{
int64_t now, last;
QEMUClock *clock = qemu_clock_ptr(type);

switch (type) {
case QEMU_CLOCK_REALTIME:
return get_clock();
default:
case QEMU_CLOCK_VIRTUAL:
if (use_icount) {
return cpu_get_icount();
} else {
return cpu_get_clock();
}
case QEMU_CLOCK_HOST:
now = REPLAY_CLOCK(REPLAY_CLOCK_HOST, get_clock_realtime());
last = clock->last;
clock->last = now;
if (now < last || now > (last + get_max_clock_jump())) {
notifier_list_notify(&clock->reset_notifiers, &now);
}
return now;
case QEMU_CLOCK_VIRTUAL_RT:
return REPLAY_CLOCK(REPLAY_CLOCK_VIRTUAL_RT, cpu_get_clock());
}
}

对于每种时钟使用不同时间获取函数,先来看QEMU_CLOCK_REALTIME, 这是一个不受vCPU影响的累加计数器,get_clock其实是使用clock_gettime(CLOCK_MONOTONIC, &ts)实现的,所以是获取的主机开机后经过的相对时间。

QEMU_CLOCK_VIRTUAL是从vCPU获取的时间或者icount
QEMU_CLOCK_HOST使用timeofday函数获取的真实时间,这是一个绝对时间。
QEMU_CLOCK_VIRTUAL_RT则获取vCPU时间。

参考

https://blog.csdn.net/woai110120130/article/details/99689645

https://rickylss.github.io/qemu/2019/05/20/qemu-timer/

打赏还是打残,这是个问题