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);
} else {
kvm_main_loop_wait(env, 1000);
}
}
pthread_mutex_unlock(&qemu_mutex);
return 0;
}
static void kvm_main_loop_wait(CPUState *env, int timeout)
{
struct timespec ts;
int r, e;
siginfo_t siginfo;
sigset_t waitset;
sigset_t chkset;
ts.tv_sec = timeout / 1000;
ts.tv_nsec = (timeout % 1000) * 1000000;
sigemptyset(&waitset);
sigaddset(&waitset, SIG_IPI);
sigaddset(&waitset, SIGBUS);
do {
pthread_mutex_unlock(&qemu_mutex);
r = sigtimedwait(&waitset, &siginfo, &ts);
e = errno;
pthread_mutex_lock(&qemu_mutex);
if (r == -1 && !(e == EAGAIN || e == EINTR)) {
printf("sigtimedwait: %s\n", strerror(e));
exit(1);
}
switch (r) {
case SIGBUS:
kvm_on_sigbus(env, &siginfo);
break;
default:
break;
}
r = sigpending(&chkset);
if (r == -1) {
printf("sigpending: %s\n", strerror(e));
exit(1);
}
} while (sigismember(&chkset, SIG_IPI) || sigismember(&chkset, SIGBUS));
cpu_single_env = env;
flush_queued_work(env);
if (env->stop) {
env->stop = 0;
env->stopped = 1;
pthread_cond_signal(&qemu_pause_cond);
}
env->kvm_cpu_state.signalled = 0;
}
3.异步io文件操作线程
创建io操作线程,进行读写操作。可以通过gdb跟踪,验证查看io线程
static void spawn_thread(void)
{
sigset_t set, oldset;
cur_threads++;
idle_threads++;
/* block all signals */
if (sigfillset(&set)) die("sigfillset");
if (sigprocmask(SIG_SETMASK, &set, &oldset)) die("sigprocmask");
thread_create(&thread_id, &attr, aio_thread, NULL);
if (sigprocmask(SIG_SETMASK, &oldset, NULL)) die("sigprocmask restore");
}
static void qemu_paio_submit(struct qemu_paiocb *aiocb)
{
aiocb->ret = -EINPROGRESS;
aiocb->active = 0;
mutex_lock(&lock);
if (idle_threads == 0 && cur_threads < max_threads)
spawn_thread();
QTAILQ_INSERT_TAIL(&request_list, aiocb, node);
mutex_unlock(&lock);
cond_signal(&cond);
}
可见启动一次bdrv_aio_readv或者raw_aio_writev操作,创建一个aio_thread线程。
static BlockDriverAIOCB *raw_aio_readv(BlockDriverState *bs,
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
BlockDriverCompletionFunc *cb, void *opaque)
{
return raw_aio_submit(bs, sector_num, qiov, nb_sectors,
cb, opaque, QEMU_AIO_READ);
}
static BlockDriverAIOCB *raw_aio_writev(BlockDriverState *bs,
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
BlockDriverCompletionFunc *cb, void *opaque)
{
return raw_aio_submit(bs, sector_num, qiov, nb_sectors,
cb, opaque, QEMU_AIO_WRITE);
}
4.主线程与执行客户机代码线程,主线程与异步io文件操作线程同步----qemu_global_mutex
select阻塞(主线程)和执行客户机代码(客户机线程)不需要同步锁,这在qemu运行过程占的时间比例较大。
但是执行异步io文件操作时,占用qemu_global_mutex锁的。
select阻塞这里,实际不需要锁定。
void main_loop_wait(int timeout)
{
qemu_mutex_unlock_iothread();//开锁
ret = select(nfds + 1, &rfds, &wfds, &xfds, &tv);
qemu_mutex_lock_iothread(); //锁住
}
执行客户机代码是不要锁定
int kvm_cpu_exec(CPUState *env)
{
qemu_mutex_unlock_iothread();//开锁
ret = kvm_vcpu_ioctl(env, KVM_RUN, 0);
qemu_mutex_lock_iothread(); //锁住
}
http://blog.csdn.net/zhuriyuxiao/article/details/8835593
阅读(1137) | 评论(0) | 转发(0) |