博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
kvm 监控内存,替换页表(linux版的win VT晶核)(这个整复杂了,不用小内核也可以实现,留着吧,主要记录了bootLoad的启动过程)
阅读量:2344 次
发布时间:2019-05-10

本文共 9044 字,大约阅读时间需要 30 分钟。

 kvm 监控内存,替换页表等问题

一、如何利用kvm 监控整个linux系统

 不过kvm似乎只能监控自己的虚拟机,自己的主机监控不了。那么,只能利用kvm重新启动linux内核。kvm启动可以简化为小内核启动的方式。

利用完整内核:

         1.旧内核有漏洞,引入风险。但是我们只需把完整的内核简化为小内核,不需要完整内核。

         2.目的是启动kvm, ,甚至不需要页表转换,分配器初始化。

         3.应该保留那些内核必要的功能。

选一个较简单的内核作为载体:

sudo apt-get install -y gcc-4.7sudo apt-get install -y g++-4.7# 重新建立软连接cd /usr/bin    #进入/usr/bin文件夹下sudo rm -r gcc  #移除之前的软连接sudo ln -sf gcc-4.7 gcc #建立gcc4.7的软连接sudo rm -r g++  #同gccsudo ln -sf g++-4.7 g++

 编译需要降低gcc版本,如上。以linux-2.6.38为例。(gcc 发布时间最好和内核发布时间同步,否则错误比较多)

KVM 是 Linux 的一部分。Linux 2.6.20 或更新版本包括 KVM。KVM 于 2006 年首次公布,并在一年后合并到主流 Linux 内核版本中。

  • gpio – GPIO驱动,与处理器相关
  • gpu – 包括DRM图形渲染架构,访问图形界面的DMA引擎,IMX的IPU图像处理单元等
  •   media  video

以上部分是driver中较上层的驱动,和我们加载kvm无关删掉吧。

 

二、调试内核启动过程

因为我们大幅度修改内核的启动流程,所以记录下内核调试的方法。当然是利用qemu

qemu-system-i386 -s -S -vnc :2 boot_image
其中-s是设置gdbserver的监听端口,-S则是让cpu加电后被挂起。-s        Shorthand for -gdb tcp::1234, i.e. open a gdbserver on TCP port 1234 (see gdb_usage).-S        Do not start CPU at startup (you must type ’c’ in the monitor).
输入gdb -q连接gdb server target remote :1234设置被调试环境架构 set architecture i8086
qemu的 -S选项相当于将断点打在CPU加电后要执行的第一条指令处,也就是BIOS程序的第一条指令。

 

三、kvm原始设计流程

一般意义上的kvm需要通过用户态到内核态交互来实现虚拟机的创建和操控。由于我们设计直接用小内核启动kvm,  启动linux原本的内核。所以这里可以简化直接调用内核程序。

1.查询KVM版本的请求操作ioctl(kvm_fd, KVM_GET_API_VERSION,0)2.创建虚拟机文件描述符的操作vm_fd = ioctl(kvm_fd, KVM_CREATE_VM, 0)3.设置虚拟机内存struct kvm_userspace_memory_region region={        .slot = 0,        .guest_phys_addr = 0,        .memory_size = ram_size,        .userspace_addr = (u64)ram_start    };    ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION, &region);4.新建虚拟CPUvcpu->vcpu_fd = ioctl(kvm->vm_fd, KVM_CREATE_VCPU, i);5.运行虚拟机ioctl(vcpu->fd, KVM_RUN, 0) < 0)6.接管中断和其他异常

找到一个简单的虚拟机实例。总之,我的理解是利用接口的原理,直接在内核中创建虚拟机,启动linux就可以了。

* Opens `/dev/kvm` and checks the version.* Makes a `KVM_CREATE_VM` call to creates a VM.* Uses mmap to allocate some memory for the VM.* Makes a `KVM_CREATE_VCPU` call to creates a VCPU within the VM, and  mmaps its control area.* Sets the FLAGS and CS:IP registers of the VCPU.* Copies a few bytes of code into the VM memory.* Makes a `KVM_RUN` call to execute the VCPU.* Checks that the VCPU execution had the expected result.

这一段是实现的流程,简单讲就是把要执行的代码二进制数据拷贝进虚拟机,设置相应的vcpu寄存器,启动。

四、启动原生内核

有了上面的基础,我们其实就可以实现kvm控制的linux内核的启动。

1.正常的引导程序

上电--->实模式-->执行ROM-BIOS中的地址(0xffff0)-->读入磁盘第一扇区(512B)到绝对地址0x7c00-->跳转到0x7c00执行。

bootloader boot/bootsec.S将位于0x7c00(31kb这估计要比这个大)执行。之后将自己移动到0x90000(576KB)执行。boot/setup.s将读入0x90200处(2KB)。内核其他部分被读入0x10000(64KB)处。setup.s将会把system移动到内存起始位置处。

      setup结束后内存布局

head.s位于system模块的最前面,system模块一般被放置在磁盘上setup模块之后的扇区中。(0.11内核中约为120KB,占240扇区)执行完后的内存布局如下如:

 整个过程不断的设置idt和gdt的位置和定义方式。这里内存页目录和页表的设置完成。

32位保护模式:

当CPU运行在保护模式下时,会将实模式下的段地址当作保护模式下段描述符的指针被使用,此时段寄存器中存放的是一个描述符在描述符表中的偏移地址。

而当前描述符表的基地址将会被存放在描述符表寄存器中,如全局描述符表寄存器gdtr,中断们描述符表寄存器idtr.并使用专门的指令lgdt/lidt加载。

以上过程基于0.11内核实现,但对硬盘的描述不够完善,再次参考内核源码情景分析(内核为2.6)。

  • 一块硬盘可以有多个分区,每个分区都可以都可以由引导扇区,但属于逻辑硬盘。
  • bios默认只是别主引导扇区(MBR),MBR中有磁盘划分的信息,还有一段简短程序,但不是引导程序(512B)。
  • MBR中的程序读取逻辑期盼中的引导扇区,这里是引导程序。

 

  1. bootsect.s 引导扇区源码,编译后不得超过512字节。
  2. setup.S 辅助程序
  3. vidie.S 引导过程的显示。

PC体系中,线性地址0xA0000上,即640KB以上都用于图形接口以即bios本身。而0xA0000以下的640KB为基本内存。如配备更多内存,则从0x100000处为高内存。CPU在bootsec时尚处于16位实地址模式,然后利用setup转入32位保护模式段式寻址。

bzimage为压缩大内核,解压在基本内存中放不下,所以解压后一般放在0x100000地址处。setup为内核的执行作好了准备(包括解压),然后跳转到0x100000处执行。cpu执行内核映像的入口startup_32就在内核映像的开头的地方,因此其物理地址为0x100000 。此时的系统空间地址映射时线性的VA=PA+0xC0000000; 所以startup_32地址为0xC0100000;

2.如何启动原生内核 

我们目前小内核已经完全实现了页表查询的地址转换过程,我们这里称为HPA-->HVA的转换。我们需要启动的内核将会实现另一套GPA-->GVA的地址转换。bootsect和setup会加载解压内核到0x100000的物理地址处。这里我们可以直接令HVA=GPA。将解压的bzImage放到任意的HVA(ip_hva), 虚拟机直接执行ip_hva。理论上我们将启动我们的新内核,新世界的大门也会打开。

0x90000处的代码将会被setup程序修改为启动内核的参数,所以HVA不能等于GPA. 因为system也就是内核本身会在0x90000处查找参数。“console=ttyS0 root=/dev/ram rdinit=/sbin/init”

startup_32入口运行与保护模式下的段式寻址方式。段描述表中与_KERNEL_CS和__KENREL_DS相对应的描述想所提供的基地址都是0,所以此时地址为线性地址。CPU的中断已经在进入startup_32之前关闭。

虽然代码段寄存器CS已经设置成__KERNEL_CS, startup_32的地址为0xC0100000。但是在转入这个入口时使用的指令时ljmp 0x100000, 而不是 “ljmp  startup_32”,所以装入cup中的寄存器ip的地址是物理地址0x100000而不是虚拟地址。

分段:

描述符表(全局、局部)+ 选择子(选择符)= 描述符

 

3.关于磁盘和文件系统虚拟的问题

另一个比较棘手的问题是,原生内核在执行前会有个简易的文件系统,但此时我们的虚拟机内核没有。操作硬盘将会是一个问题。换句话说就是虚拟机此时没有虚拟硬盘。理论上可以直接转换到主机的硬盘操作,这个走一步看一步吧。

五、内核中直接接管跟模式

kvm_arch_vcpu_init负责填充x86 CPU结构体,kvm_arch_vcpu_init

1.查询KVM版本的请求操作ioctl(kvm_fd, KVM_GET_API_VERSION,0)2.创建虚拟机文件描述符的操作vm_fd = ioctl(kvm_fd, KVM_CREATE_VM, 0)3.设置虚拟机内存struct kvm_userspace_memory_region region={        .slot = 0,        .guest_phys_addr = 0,        .memory_size = ram_size,        .userspace_addr = (u64)ram_start    };    ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION, &region);4.新建虚拟CPUvcpu->vcpu_fd = ioctl(kvm->vm_fd, KVM_CREATE_VCPU, i);5.运行虚拟机ioctl(vcpu->fd, KVM_RUN, 0) < 0)6.接管中断和其他异常vmx->vmcs = alloc_vmcs();if (!vmx->vmcs)    goto free_msrs;vmcs_init(vmx->vmcs);

执行vm entry的时候将vmm状态保存到vmcs的host area,并加载对应vm的vmcs guest area信息到CPU中,vm exit的时候则反之,vmcs具体结构分配由硬件实现,程序员只需要通过VMWRITE和VMREAD指令去访问。

KVM_RUN的实现函数是kvm_arch_vcpu_ioctl_run,进行安全检查之后进入__vcpu_run中,在while循环里面调用vcpu_enter_guest进入guest模式,首先处理vcpu->requests,对应的request做处理,kvm_mmu_reload加载mmu,通过kvm_x86_ops->prepare_guest_switch(vcpu)准备陷入到guest,prepare_guest_switch实现是vmx_save_host_state,顾名思义,就是保存host的当前状态。

准备工作搞定,kvm_x86_ops->run(vcpu),开始运行guest,由vmx_vcpu_run实现。

handle_exit退出函数由vmx_handle_exit实现,主要设置vcpu->run->exit_reason,让外部感知退出原因,并对应处理。

VMCS寄存器

VMCS保存虚拟机的相关CPU状态,每个VCPU都有一个VMCS(内存的),每个物理CPU都有VMCS对应的(物理的),当CPU发生VM-Entry时,CPU则从VCPU指定的内存中读取VMCS加载到物理CPU上执行,当发生VM-Exit时,CPU则将当前的CPU状态保存到VCPU指定的内存中,即VMCS,以备下次VMRESUME。

VMLAUCH指VM的第一次VM-Entry,VMRESUME则是VMLAUCH之后后续的VM-Entry。VMCS下有一些控制域:

VM-execution controls Determines what operations cause VM exits CR0, CR3, CR4, Exceptions, IO Ports, Interrupts, Pin Events, etc
Guest-state area Saved on VM exits,Reloaded on VM entry EIP, ESP, EFLAGS, IDTR, Segment Regs, Exit info, etc
Host-state area Loaded on VM exits CR3, EIP set to monitor entry point, EFLAGS hardcoded, etc
VM-exit controls Determines which state to save, load, how to transition Example: MSR save-load list
VM-entry controls Determines which state to load, how to transition Including injecting events (interrupts, exceptions) on entry

 

bluepill

这一段是关于win vt 的启动跟模式的代码VmxInitialize

Cpu->Vmx.MSRBitmap = MmAllocateContiguousPages (VMX_MSRBitmap_SIZE_IN_PAGES, &Cpu->Vmx.MSRBitmapPA);  if (!Cpu->Vmx.MSRBitmap) {    _KdPrint (("VmxInitialize(): Failed to allocate memory for  MSRBitmap\n"));    return STATUS_INSUFFICIENT_RESOURCES;  }  RtlZeroMemory (Cpu->Vmx.MSRBitmap, PAGE_SIZE);  _KdPrint (("VmxInitialize(): MSRBitmap VA: 0x%p\n", Cpu->Vmx.MSRBitmap));  _KdPrint (("VmxInitialize(): MSRBitmap PA: 0x%llx\n", Cpu->Vmx.MSRBitmapPA.QuadPart));  if (!NT_SUCCESS (VmxEnable (Cpu->Vmx.OriginaVmxonR))) {    _KdPrint (("VmxInitialize(): Failed to enable Vmx\n"));    return STATUS_UNSUCCESSFUL;  }  *((ULONG64 *) (Cpu->Vmx.OriginalVmcs)) = (MsrRead (MSR_IA32_VMX_BASIC) & 0xffffffff); //set up vmcs_revision_id        if (!NT_SUCCESS (Status = VmxSetupVMCS (Cpu, GuestRip, GuestRsp))) {    _KdPrint (("Vmx(): VmxSetupVMCS() failed with status 0x%08hX\n", Status));    VmxDisable ();    return Status;  }

VmxEnable启动cr4虚拟化的功能。

既然可以直接启动跟模式,那么那么核心问题就变成了host 和 guest的状态的切换和保存了。虚拟化驱动处于内核状态,host的状态就是我们驱动的状态,guest的状态反而变成了整个内核。内核要继续运行,host要接管整个内核的各种行为。

 

int run_vm(struct vm *vm, struct vcpu *vcpu, size_t sz){	struct kvm_regs regs;	uint64_t memval = 0;	for (;;) {		if (ioctl(vcpu->fd, KVM_RUN, 0) < 0) {			perror("KVM_RUN");			exit(1);		}		switch (vcpu->kvm_run->exit_reason) {		case KVM_EXIT_HLT:			goto check;		case KVM_EXIT_IO:			if (vcpu->kvm_run->io.direction == KVM_EXIT_IO_OUT			    && vcpu->kvm_run->io.port == 0xE9) {				char *p = (char *)vcpu->kvm_run;				fwrite(p + vcpu->kvm_run->io.data_offset,				       vcpu->kvm_run->io.size, 1, stdout);				fflush(stdout);				continue;			}			/* fall through */		default:			fprintf(stderr,	"Got exit_reason %d,"				" expected KVM_EXIT_HLT (%d)\n",				vcpu->kvm_run->exit_reason, KVM_EXIT_HLT);			exit(1);		}	} check:	if (ioctl(vcpu->fd, KVM_GET_REGS, &regs) < 0) {		perror("KVM_GET_REGS");		exit(1);	}	if (regs.rax != 42) {		printf("Wrong result: {E,R,}AX is %lld\n", regs.rax);		return 0;	}	memcpy(&memval, &vm->mem[0x400], sz);	if (memval != 42) {		printf("Wrong result: memory at 0x400 is %lld\n",		       (unsigned long long)memval);		return 0;	}	return 1;}

从这段代码看,vmm要陷入死循环。然后监控虚拟机状态,进行相应的处理。那也就是说,我们的VMM驱动也要陷入死循环。然后让虚拟机继续内核的运行。

要处理的问题,进入我们VMM的时刻,有多少cpu的状态需要恢复。

又看了下hyperviser的代码,启动所有vcpu后,cpu自然被占用,不需要VMM死循环。需要的是设置vm_exit的各种回调,这样更方便。

mp::ipi_call([&start_err]() {      mm::allocator_guard _;      const auto idx = mp::cpu_index();      const auto err = global.vcpu_list[idx].start();      auto expected = error_code_t{};      start_err.compare_exchange_strong(expected, err);    });
// Create VM-exit handler instance.    //    vmexit_handler_ = new vmexit_handler_t();    device_->handler(std::get
(vmexit_handler_->handlers)); // // Example: Enable tracing of I/O instructions. // std::get
(vmexit_handler_->handlers) .trace_bitmap().set(int(vmx::exit_reason::execute_io_instruction)); if (auto err = hvpp::hypervisor::start(*vmexit_handler_)) { destroy(); return err; }

 

转载地址:http://lnjvb.baihongyu.com/

你可能感兴趣的文章
PHP页面纯静态化与伪静态化
查看>>
分享网页到微信朋友圈,显示缩略图的方法
查看>>
PHP参数类型限制
查看>>
IOS博客项目搭建-12-刷新数据-显示最新的微博数提示
查看>>
Laravel5 Markdown 编辑器使用教程
查看>>
php文件上传与下载
查看>>
Python3学习教程
查看>>
Python3学习笔记01-第一个Python程序
查看>>
Laravel5学生成绩管理系统-01-安装-建表-填充数据
查看>>
Mac OSX下使用apt-get命令
查看>>
Mac下安装PHP的mcrypt扩展的方法(自己总结的)
查看>>
关于html_entity_decode、空格 以及乱码
查看>>
Box2d no gravity
查看>>
mario collision
查看>>
tiled 地图工具
查看>>
小游戏
查看>>
旋转关节绳子
查看>>
射箭box2d
查看>>
cocos2d iphone-wax cocowax
查看>>
angribird level editor
查看>>