Chinaunix首页 | 论坛 | 博客
  • 博客访问: 132988
  • 博文数量: 76
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 513
  • 用 户 组: 普通用户
  • 注册时间: 2013-08-23 00:06
个人简介

展示自己、证明自己

文章分类

全部博文(76)

文章存档

2018年(1)

2014年(55)

2013年(20)

我的朋友

分类: C/C++

2014-07-07 10:50:36

Kvm利用了qemu的外设(irqchip在kmod中有实现,为了提高中断处理的效率),所以,在kmod中,如果guest发生了io事件,我们需要返回到qemu中来处理。具体的处理流程如下:

Guest执行io指令 -> 发生vmexit-> 返回qemu -> 处理io

 

下面,我们将具体的结合代码来分析(在使用vmx的情况下)。

 

首先,我们从发生vmexit之后开始:

vmx.c中的handle_io :

static int handle_io(struct kvm_vcpu *vcpu)
{
	unsigned long exit_qualification;
	int size, in, string;
	unsigned port;

	exit_qualification = vmcs_readl(EXIT_QUALIFICATION);  //获取exit qualification
	string = (exit_qualification & 16) != 0; //判断是否为string io (ins, outs)
	in = (exit_qualification & 8) != 0; //判断io方向,是in  还是out

	++vcpu->stat.io_exits;

	if (string || in) //如果是输入类的指令,或者是string io,就进入emulator处理
		return emulate_instruction(vcpu, 0) == EMULATE_DONE;

	port = exit_qualification >> 16; //得到端口号
	size = (exit_qualification & 7) + 1; //大小
	skip_emulated_instruction(vcpu); //跳过这个指令

	return kvm_fast_pio_out(vcpu, size, port); //进行out操作
}

这里我们可以看到,根据io的方向,以及是否为string io,处理方式是不同的。下面我们先看非string类型的 out操作是如何处理的:

X86.c:

int kvm_fast_pio_out(struct kvm_vcpu *vcpu, int size, unsigned short port)
{
	unsigned long val = kvm_register_read(vcpu, VCPU_REGS_RAX); //获得rax的值
	int ret = emulator_pio_out_emulated(&vcpu->arch.emulate_ctxt,
					    size, port, &val, 1); //进入emulator
	/* do not return to emulator after return from userspace */
	vcpu->arch.pio.count = 0; //因为非string类型的out操作一步就能完成,所以,从qemu进入kmod的时候不用再进入emulator了
	return ret;
}

static int emulator_pio_out_emulated(struct x86_emulate_ctxt *ctxt,
				     int size, unsigned short port,
				     const void *val, unsigned int count)
{
	struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt); //根据emulator的上下文获取vcpu

	memcpy(vcpu->arch.pio_data, val, size * count); //将io的数据复制到kmod和qemu共享的buffer中
	return emulator_pio_in_out(vcpu, size, port, (void *)val, count, false);
}

static int emulator_pio_in_out(struct kvm_vcpu *vcpu, int size,
			       unsigned short port, void *val,
			       unsigned int count, bool in)
{
	trace_kvm_pio(!in, port, size, count);

	vcpu->arch.pio.port = port;
	vcpu->arch.pio.in = in;
	vcpu->arch.pio.count  = count;
	vcpu->arch.pio.size = size;

	if (!kernel_pio(vcpu, vcpu->arch.pio_data)) { //如果在kmod中能完成io的话,就完成处理,不需要再返回qemu了
		vcpu->arch.pio.count = 0;
		return 1;
	}

	//将相应的数据保存到kvm_run中。
	vcpu->run->exit_reason = KVM_EXIT_IO;
	vcpu->run->io.direction = in ? KVM_EXIT_IO_IN : KVM_EXIT_IO_OUT;
	vcpu->run->io.size = size;
	vcpu->run->io.data_offset = KVM_PIO_PAGE_OFFSET * PAGE_SIZE;
	vcpu->run->io.count = count;
	vcpu->run->io.port = port;

	//返回qemu中进行处理。
	return 0;
}

返回qemu之后,在qemu的 kvm_all.c中:

int kvm_cpu_exec(CPUArchState *env)
{
    struct kvm_run *run = env->kvm_run;
int ret, run_ret;
…
…
switch (run->exit_reason) { //根据kvm_run中存放的退出原因来选择处理方式
        case KVM_EXIT_IO:
            DPRINTF("handle_io\n");
            kvm_handle_io(run->io.port,
                          (uint8_t *)run + run->io.data_offset,
                          run->io.direction,
                          run->io.size,
                          run->io.count);

			//这里可以看到,qemu会根据kvm_run中存放的数据来进行处理了
            ret = 0;
            break;

…
…
}

static void kvm_handle_io(uint16_t port, void *data, int direction, int size,
                          uint32_t count)
{
	//在这个函数中,我们就可以看到,最终 是调用了cpu_inb、cpu_outb这些函数和具体的设备进行交互。
    int i;
    uint8_t *ptr = data;

    for (i = 0; i < count; i++) {
        if (direction == KVM_EXIT_IO_IN) {
            switch (size) {
            case 1:
                stb_p(ptr, cpu_inb(port));
                break;
            case 2:
                stw_p(ptr, cpu_inw(port));
                break;
            case 4:
                stl_p(ptr, cpu_inl(port));
                break;
            }
        } else {
            switch (size) {
            case 1:
                cpu_outb(port, ldub_p(ptr));
                break;
            case 2:
                cpu_outw(port, lduw_p(ptr));
                break;
            case 4:
                cpu_outl(port, ldl_p(ptr));
                break;
            }
        }

        ptr += size;
    }
}


这样,kvm就完成了一次对guest执行out指令的虚拟。

在处理string io和in的时候,会有所不同。从上述流程可以看到,如果虚拟单个out指令,在kmod中可以直接把out的数据返回给qemu,qemu完成out操 作,但是,如果是in指令,qemu只能把得到的数据写到kvm_run中,kmod必须在下一次vmentry的时候,将qemu得到的数据放到相应的 位置,所以,在handle_io中,如果是in或者string指令,没有调用skip_emulated_instruction,这样,在qemu 完成in或者一次out之后,还会在同样的地方发生vmexit,这样再由emulator完成相应的处理,针对string类型的指 令,emulator会进行解码等操作,确认io的次数和源操作数、目的操作数等。

下面,我们再来看一下string io,和in指令是如何虚拟的:

调用 emulate_instruction时,针对x86架构,该函数调用了x86_emulate_instruction:

Asm-x86/kvm_host.h

static inline int emulate_instruction(struct kvm_vcpu *vcpu,
			int emulation_type)
{
	return x86_emulate_instruction(vcpu, 0, emulation_type, NULL, 0);
}

X86.c:

int x86_emulate_instruction(struct kvm_vcpu *vcpu,
			    unsigned long cr2,
			    int emulation_type,
			    void *insn,
			    int insn_len)
{
	int r;
	…
	…
	//这个函数做了对指令的decode等等过程,在这里省略,在过程中会调用到em_in和em_out
	…
	…
	if (ctxt->have_exception) {
		inject_emulated_exception(vcpu);
		r = EMULATE_DONE;
	} else if (vcpu->arch.pio.count) {
		if (!vcpu->arch.pio.in) //如果是out,不用再下次进入kmod时直接进入emulator
			vcpu->arch.pio.count = 0;
		else {
			writeback = false; //如果是in,则需要在下次qemu进入kmod的时候,完成io,实际上就是将qemu得到的数据写到正确的位置
			vcpu->arch.complete_userspace_io = complete_emulated_pio;
		}
		r = EMULATE_USER_EXIT;
	…
	…
}


下面的逻辑是进入kmod时,如果要完成in指令,进行的流程


int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run)
{
	…
	…
	//在这里我们可以看到,如果需要完成qemu的io,在这里调用刚才注册的complete_emulated_pio。
	if (unlikely(vcpu->arch.complete_userspace_io)) {
		int (*cui)(struct kvm_vcpu *) = vcpu->arch.complete_userspace_io;
		vcpu->arch.complete_userspace_io = NULL;
		r = cui(vcpu);
		if (r <= 0)
			goto out;
	} else
		WARN_ON(vcpu->arch.pio.count || vcpu->mmio_needed);

	r = __vcpu_run(vcpu);
	…
	…
}

static int complete_emulated_pio(struct kvm_vcpu *vcpu)
{
	BUG_ON(!vcpu->arch.pio.count);

	return complete_emulated_io(vcpu);
}

static inline int complete_emulated_io(struct kvm_vcpu *vcpu)
{
	int r;
	vcpu->srcu_idx = srcu_read_lock(&vcpu->kvm->srcu);
	r = emulate_instruction(vcpu, EMULTYPE_NO_DECODE);//再次进入emulator,不过这次不用decode了,直接进行相应处理。
	srcu_read_unlock(&vcpu->kvm->srcu, vcpu->srcu_idx);
	if (r != EMULATE_DONE)
		return 0;
	return 1;
}



上述过程讲述了in指令在进入kmod时,将数据写到正确位置的流程,下面,我们将讲述刚才x86_emulate_instruction时,走到em_in和em_out之后的流程:

em_out:

Emulate.c


static int em_out(struct x86_emulate_ctxt *ctxt)
{
	ctxt->ops->pio_out_emulated(ctxt, ctxt->src.bytes, ctxt->dst.val,
				    &ctxt->src.val, 1);//实际上调用了emulator_pio_out_emulated,将相应的信息写到kvm_run,但是这里并没有返回
	/* Disable writeback. */
	ctxt->dst.type = OP_NONE;
	return X86EMUL_CONTINUE; //注意这里返回的是continue,返回emulator之后,会再进行处理,如果是string操作,会再次回到本函数,emulator_pio_out_emulated也就会一点点把数据写到buffer里,最后再返回qemu做out处理,这里分两个逻辑,具体可以在emulator代码中看到
}
int x86_emulate_insn(struct x86_emulate_ctxt *ctxt)
{
	…
	…
	if (ctxt->rep_prefix && (ctxt->d & String)) {//如果是串操作
		unsigned int count;
		struct read_cache *r = &ctxt->io_read;
		if ((ctxt->d & SrcMask) == SrcSI)
			count = ctxt->src.count;
		else
			count = ctxt->dst.count;
		register_address_increment(ctxt, reg_rmw(ctxt, VCPU_REGS_RCX),
				-count); //rcx减少相应的count

		if (!string_insn_completed(ctxt)) {//如果串操作没完成
			/*
			 * Re-enter guest when pio read ahead buffer is empty
			 * or, if it is not used, after each 1024 iteration.
			 */
			//每1024次或者cache满了就goto done,rip不变,下次vmentry之后还会发生同样的vmexit,走同样的流程,只不过rcx、rdi变了
			if ((r->end != 0 || reg_read(ctxt, VCPU_REGS_RCX) & 0x3ff) &&
			    (r->end == 0 || r->end != r->pos)) {
				/*
				 * Reset read cache. Usually happens before
				 * decode, but since instruction is restarted
				 * we have to do it here.
				 */
				ctxt->mem_read.end = 0;
				writeback_registers(ctxt);
				return EMULATION_RESTART;//这里会再去调用em_out,具体流程在emulator的代码中,在以后的连载中会再解析emulator的代码,这里只针对串操作来说明
			}
			goto done; /* skip rip writeback *///如果串操作完成或者cache满,就返回qemu处理一次。
		}
	}
	…
	…
}


下面是em_in:

emulate.c:


static int em_in(struct x86_emulate_ctxt *ctxt)
{
	if (!pio_in_emulated(ctxt, ctxt->dst.bytes, ctxt->src.val,
			     &ctxt->dst.val))
		return X86EMUL_IO_NEEDED; //如果是第一次进入本函数,就会返回qemu return X86EMUL_CONTINUE; //是第二次进入本函数,数据已经写入正确位置 }

static int pio_in_emulated(struct x86_emulate_ctxt *ctxt,
			   unsigned int size, unsigned short port,
			   void *dest)
{
	struct read_cache *rc = &ctxt->io_read;

	if (rc->pos == rc->end) { /* refill pio read ahead */
		unsigned int in_page, n;
		unsigned int count = ctxt->rep_prefix ?
			address_mask(ctxt, reg_read(ctxt, VCPU_REGS_RCX)) : 1;
		//这里可以看到,如果有rep前缀,即发生了string的io,这里count即为rcx的值。
		in_page = (ctxt->eflags & EFLG_DF) ?
			offset_in_page(reg_read(ctxt, VCPU_REGS_RDI)) :
			PAGE_SIZE - offset_in_page(reg_read(ctxt, VCPU_REGS_RDI));
		n = min(min(in_page, (unsigned int)sizeof(rc->data)) / size,
			count);
		if (n == 0)
			n = 1;
		rc->pos = rc->end = 0;
		if (!ctxt->ops->pio_in_emulated(ctxt, size, port, rc->data, n))
			return 0;//这里实际上走到了emulator_pio_in_emulated
		rc->end = n * size;
	}

	if (ctxt->rep_prefix && !(ctxt->eflags & EFLG_DF)) {
		ctxt->dst.data = rc->data + rc->pos;
		ctxt->dst.type = OP_MEM_STR;
		ctxt->dst.count = (rc->end - rc->pos) / size;
		rc->pos = rc->end;
	} else {
		memcpy(dest, rc->data + rc->pos, size);
		rc->pos += size;
	}
	return 1;
}

X86.c
static int emulator_pio_in_emulated(struct x86_emulate_ctxt *ctxt,
				    int size, unsigned short port, void *val,
				    unsigned int count)
{
	struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt);
	int ret;

	if (vcpu->arch.pio.count) //count不为0表示需要把数据放到相应位置,而且这时意味着这时第二次进入这里,数据已经就位,可以写到正确的地方了。
		goto data_avail;

	ret = emulator_pio_in_out(vcpu, size, port, val, count, true); //如果是第一次进入本函数
 
	if (ret) {
data_avail:
		memcpy(val, vcpu->arch.pio_data, size * count);//这里才把数据写到了正确的位置
		vcpu->arch.pio.count = 0;
		return 1;
	}

	return 0;
}

总结起来,kvm对于io的处理逻辑如下:

1、 对于string类型的in,会进入emulator,然后把count等值设置好,然后返回qemu处理,qemu将数据写到buffer之后,下一次 进入kmod的时候,在__vcpu_run之前,调用complete_userspace_io会再次进入emulator,这次进入 emulator不用解码,会把相应数据写到正确的位置,如此循环直到串操作完成。

2、 对于string类型的out,会进入emulator,不断的把需要输出的数据写入buffer,待buffer满或者rcx到0之后,会返回qemu直接输出数据。如此循环直到串操作完成。

3、 对于非string的out操作,会直接返回qemu进行一次out操作,下次vmentry的时候,直接跳过这个指令。



http://blog.csdn.net/fanwenyi/article/details/12748613
阅读(881) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册