日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

kvm 监控内存,替换页表(linux版的win VT晶核)(这个整复杂了,不用小内核也可以实现,留着吧,主要记录了bootLoad的启动过程)

發布時間:2024/1/8 linux 31 豆豆

?kvm 監控內存,替換頁表等問題

一、如何利用kvm 監控整個linux系統

?不過kvm似乎只能監控自己的虛擬機,自己的主機監控不了。那么,只能利用kvm重新啟動linux內核。kvm啟動可以簡化為小內核啟動的方式。

利用完整內核:

?????????1.舊內核有漏洞,引入風險。但是我們只需把完整的內核簡化為小內核,不需要完整內核。

?????????2.目的是啟動kvm,?,甚至不需要頁表轉換,分配器初始化。

?????????3.應該保留那些內核必要的功能。

選一個較簡單的內核作為載體:

sudo apt-get install -y gcc-4.7 sudo 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++ #同gcc sudo 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加電后被掛起。-sShorthand for -gdb tcp::1234, i.e. open a gdbserver on TCP port 1234 (see gdb_usage). -SDo 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.新建虛擬CPU vcpu->vcpu_fd = ioctl(kvm->vm_fd, KVM_CREATE_VCPU, i); 5.運行虛擬機 ioctl(vcpu->fd, KVM_RUN, 0) < 0) 6.接管中斷和其他異常

https://github.com/dpw/kvm-hello-world

找到一個簡單的虛擬機實例。總之,我的理解是利用接口的原理,直接在內核中創建虛擬機,啟動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, andmmaps 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移動到內存起始位置處。

? ? ??

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中的程序讀取邏輯期盼中的引導扇區,這里是引導程序。

?

  • bootsect.s 引導扇區源碼,編譯后不得超過512字節。
  • setup.S 輔助程序
  • 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.新建虛擬CPU vcpu->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寄存器

    http://oenhan.com/kvm-src-3-cpu

    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 controlsDetermines what operations?cause VM exitsCR0, CR3, CR4, Exceptions, IO?Ports, Interrupts, Pin Events, etc
    Guest-state areaSaved on VM exits,Reloaded on VM entryEIP, ESP, EFLAGS, IDTR, Segment?Regs, Exit info, etc
    Host-state areaLoaded on VM exitsCR3, EIP set to monitor entry point,?EFLAGS hardcoded, etc
    VM-exit controlsDetermines which state to?save, load, how to transitionExample: MSR save-load list
    VM-entry controlsDetermines which state to?load, how to transitionIncluding 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_dbgbreak_handler>(vmexit_handler_->handlers));//// Example: Enable tracing of I/O instructions.//std::get<vmexit_stats_handler>(vmexit_handler_->handlers).trace_bitmap().set(int(vmx::exit_reason::execute_io_instruction));if (auto err = hvpp::hypervisor::start(*vmexit_handler_)){destroy();return err;}

    ?

    總結

    以上是生活随笔為你收集整理的kvm 监控内存,替换页表(linux版的win VT晶核)(这个整复杂了,不用小内核也可以实现,留着吧,主要记录了bootLoad的启动过程)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。