qemu-kvm 线程事件模型
1.主(父)线程。
主线程执行循环,主要做三件事情
1).执行select操作,查询文件描述符有无读写操作
2).执行定时器回调函数
3).执行下半部(BH)回调函数。为什么要采用BH,资料说主要避免可重入性和调用栈溢出。
2.执行客户机代码的线程
只讨论kvm执行客户机代码情况(不考虑TCG,TCG采用动态翻译技术),如果有多个vcpu,就意味着存在多个线程。
3.异步io文件操作线程
提交i/o操作请求到队列中, 该线程从队列取请求,并进行处理。
4.主线程与执行客户机代码线程同步
主线程与执行客户机代码线程不能同时运行,主要通过一个全局互斥锁实现。
代码分析
1.主(父)线程。
下面函数是主线程主要执行函数:当文件描述符,定时器,下半部分触发相应事件后,将执行相应回调函数。
void main_loop_wait(int timeout){
ret = select(nfds + 1, &rfds, &wfds, &xfds, &tv);
if (ret > 0) {
IOHandlerRecord *pioh;
QLIST_FOREACH(ioh, &io_handlers, next) {
if (!ioh->deleted && ioh->fd_read && FD_ISSET(ioh->fd, &rfds)) {
ioh->fd_read(ioh->opaque);
if (!(ioh->fd_read_poll && ioh->fd_read_poll(ioh->opaque)))
FD_CLR(ioh->fd, &rfds);
}
if (!ioh->deleted && ioh->fd_write && FD_ISSET(ioh->fd, &wfds)) {
ioh->fd_write(ioh->opaque);
}
}
}
qemu_run_timers(&active_timers[QEMU_CLOCK_HOST],
qemu_get_clock(host_clock));
/* Check bottom-halves last in case any of the earlier events triggered
them. */
qemu_bh_poll();
}
对于select函数轮循文件描述符,以及对于该描述执行操作函数,主要通过qemu_set_fd_handler()和qemu_set_fd_handler2函数添加完成的。
int qemu_set_fd_handler(int fd,
IOHandler *fd_read,
IOHandler *fd_write,
void *opaque);
int qemu_set_fd_handler2(int fd,
IOCanRWHandler *fd_read_poll,
IOHandler *fd_read,
IOHandler *fd_write,
void *opaque)
对于到期执行的定时器函数,回调函数由qemu_new_time函数添加的,触发时间qemu_mod_timer函数修改的
EMUTimer *qemu_new_timer(QEMUClock *clock, QEMUTimerCB *cb, void *opaque)
void qemu_mod_timer(QEMUTimer *ts, int64_t expire_time)
下半部要添加调度函数由qemu_bh_new 和qemu_bh_schedule完成的。
EMUBH *qemu_bh_new(QEMUBHFunc *cb, void *opaque)
void qemu_bh_schedule(QEMUBH *bh)
2.执行客户机代码的线程
当初始化客户机硬件时,对于每个cpu创建一个线程,每个线程执行ap_main_loop函数,该函数运行kvm_run函数,运行客户机代码。
/* PC hardware initialisation */
static void pc_init1(ram_addr_t ram_size,
const char *boot_device,
const char *kernel_filename,
const char *kernel_cmdline,
const char *initrd_filename,
const char *cpu_model,
int pci_enabled)
{
for (i = 0; i < smp_cpus; i++) {
env = pc_new_cpu(cpu_model);
}
}
void kvm_init_vcpu(CPUState *env)
{
pthread_create(&env->kvm_cpu_state.thread, NULL, ap_main_loop, env);
while (env->created == 0)
qemu_cond_wait(&qemu_vcpu_cond);
}
执行客户机线程调用函数ap_main_loop,该函数最终调用函数kvm_main_loop_cpu, 该函数工作过程如下:
1.注入中断,执行客户机代码,解决客户机退出原因,例如 KVM_EXIT_MMIO, KVM_EXIT_IO。如果解决成功,继续运行。失败话,进入步骤2
2.该步骤如果vcpu存在着,已传递但是还没有处理里信号SIG_IPI,SIGBUS,该线程阻塞,也就意味着暂停处理器客户机代码,直到处理相应信号。
3.如果上述过程完成后,继续允许执行客户机代码。
static int kvm_main_loop_cpu(CPUState *env)
{
while (1) {
int run_cpu = !is_cpu_stopped(env);
if (run_cpu && !kvm_irqchip_in_kernel()) {
process_irqchip_events(env);
run_cpu = !env->halted;
}
if (run_cpu) {
kvm_cpu_exec(env);
kvm_main_loop_wait(env, 0);
1.主(父)线程。
主线程执行循环,主要做三件事情
1).执行select操作,查询文件描述符有无读写操作
2).执行定时器回调函数
3).执行下半部(BH)回调函数。为什么要采用BH,资料说主要避免可重入性和调用栈溢出。
2.执行客户机代码的线程
只讨论kvm执行客户机代码情况(不考虑TCG,TCG采用动态翻译技术),如果有多个vcpu,就意味着存在多个线程。
3.异步io文件操作线程
提交i/o操作请求到队列中, 该线程从队列取请求,并进行处理。
4.主线程与执行客户机代码线程同步
主线程与执行客户机代码线程不能同时运行,主要通过一个全局互斥锁实现。
代码分析
1.主(父)线程。
下面函数是主线程主要执行函数:当文件描述符,定时器,下半部分触发相应事件后,将执行相应回调函数。
void main_loop_wait(int timeout){
ret = select(nfds + 1, &rfds, &wfds, &xfds, &tv);
if (ret > 0) {
IOHandlerRecord *pioh;
QLIST_FOREACH(ioh, &io_handlers, next) {
if (!ioh->deleted && ioh->fd_read && FD_ISSET(ioh->fd, &rfds)) {
ioh->fd_read(ioh->opaque);
if (!(ioh->fd_read_poll && ioh->fd_read_poll(ioh->opaque)))
FD_CLR(ioh->fd, &rfds);
}
if (!ioh->deleted && ioh->fd_write && FD_ISSET(ioh->fd, &wfds)) {
ioh->fd_write(ioh->opaque);
}
}
}
qemu_run_timers(&active_timers[QEMU_CLOCK_HOST],
qemu_get_clock(host_clock));
/* Check bottom-halves last in case any of the earlier events triggered
them. */
qemu_bh_poll();
}
对于select函数轮循文件描述符,以及对于该描述执行操作函数,主要通过qemu_set_fd_handler()和qemu_set_fd_handler2函数添加完成的。
int qemu_set_fd_handler(int fd,
IOHandler *fd_read,
IOHandler *fd_write,
void *opaque);
int qemu_set_fd_handler2(int fd,
IOCanRWHandler *fd_read_poll,
IOHandler *fd_read,
IOHandler *fd_write,
void *opaque)
对于到期执行的定时器函数,回调函数由qemu_new_time函数添加的,触发时间qemu_mod_timer函数修改的
EMUTimer *qemu_new_timer(QEMUClock *clock, QEMUTimerCB *cb, void *opaque)
void qemu_mod_timer(QEMUTimer *ts, int64_t expire_time)
下半部要添加调度函数由qemu_bh_new 和qemu_bh_schedule完成的。
EMUBH *qemu_bh_new(QEMUBHFunc *cb, void *opaque)
void qemu_bh_schedule(QEMUBH *bh)
2.执行客户机代码的线程
当初始化客户机硬件时,对于每个cpu创建一个线程,每个线程执行ap_main_loop函数,该函数运行kvm_run函数,运行客户机代码。
/* PC hardware initialisation */
static void pc_init1(ram_addr_t ram_size,
const char *boot_device,
const char *kernel_filename,
const char *kernel_cmdline,
const char *initrd_filename,
const char *cpu_model,
int pci_enabled)
{
for (i = 0; i < smp_cpus; i++) {
env = pc_new_cpu(cpu_model);
}
}
void kvm_init_vcpu(CPUState *env)
{
pthread_create(&env->kvm_cpu_state.thread, NULL, ap_main_loop, env);
while (env->created == 0)
qemu_cond_wait(&qemu_vcpu_cond);
}
执行客户机线程调用函数ap_main_loop,该函数最终调用函数kvm_main_loop_cpu, 该函数工作过程如下:
1.注入中断,执行客户机代码,解决客户机退出原因,例如 KVM_EXIT_MMIO, KVM_EXIT_IO。如果解决成功,继续运行。失败话,进入步骤2
2.该步骤如果vcpu存在着,已传递但是还没有处理里信号SIG_IPI,SIGBUS,该线程阻塞,也就意味着暂停处理器客户机代码,直到处理相应信号。
3.如果上述过程完成后,继续允许执行客户机代码。
static int kvm_main_loop_cpu(CPUState *env)
{
while (1) {
int run_cpu = !is_cpu_stopped(env);
if (run_cpu && !kvm_irqchip_in_kernel()) {
process_irqchip_events(env);
run_cpu = !env->halted;
}
if (run_cpu) {
kvm_cpu_exec(env);
kvm_main_loop_wait(env, 0);