天天看點

KVM源代碼分析

1.KVM模型結構

為什麼有OS虛拟化?随着CPU計算能力的提高,單獨的OS已不能充分利用CPU的計算能力,1.很多應用的執行需要單獨占用一個OS環境,如安全測試等;2.而IAAS雲計算廠商也是以OS為範圍銷售計算能力。那麼在所有虛拟化方案中,都是由hypervisor取代原生的OS去控制具體硬體資源,而同時hypervisor将資源配置設定具體的VM,VM中運作的是沒有修改過的OS,如果讓VM中的OS能正常運作,hypervisor的任務就是模拟具體的硬體資源,讓OS不能識别出是真是假。

KVM源代碼分析

當然上面的模型是Xen示例,OS對應用而言是硬體資源管理中心,那麼hypervisor就是具體VM的OS了,KVM是就利用了這一點,利用現有的kernel代碼,建構了一個hypervisor,這個樣子記憶體配置設定,程序排程等就無需重寫代碼,如此hypervisor就是所謂的host,VM中的OS就是guest。

guest OS保證具體運作場景中的程式正常執行,而KVM的代碼則部署在HOST上,Userspace對應的是QEMU,Kernel對應的是KVM Driver,KVM Driver負責模拟虛拟機的CPU運作,記憶體管理,裝置管理等;QEMU則模拟虛拟機的IO裝置接口以及使用者态控制接口。QEMU通過KVM等fd進行IOCTL控制KVM驅動的運作過程。

KVM源代碼分析

如上圖所示,guest自身有自己的使用者模式和核心模式;guest是在host中是作為一個使用者态程序存在的,這個程序就是qemu,qemu本身就是一個虛拟化程式,隻是純軟體虛拟化效率很低,它被KVM進行改造後,作為KVM的前端存在,用來進行建立程序或者IO互動等;而KVM Driver則是Linux核心模式,它提供KVM fd給qemu調用,用來進行cpu虛拟化,記憶體虛拟化等。QEMU通KVM提供的fd接口,通過ioctl系統調用建立和運作虛拟機。KVM Driver使得整個Linux成為一個虛拟機監控器,負責接收qemu模拟效率很低的指令。

2.KVM工作原理

KVM源代碼分析

上圖是一個執行過程圖,首先啟動一個虛拟化管理軟體qemu,開始啟動一個虛拟機,通過ioctl等系統調用向核心中申請指定的資源,搭建好虛拟環境,啟動虛拟機内的OS,執行 VMLAUCH 指令,即進入了guest代碼執行過程。如果 Guest OS 發生外部中斷或者影子頁表缺頁之類的事件,暫停 Guest OS 的執行,退出QEMU即guest VM-exit,進行一些必要的處理,然後重新進入客戶模式,執行guest代碼;這個時候如果是io請求,則送出給使用者态下的qemu處理,qemu處理後再次通過IOCTL回報給KVM驅動。

3.CPU虛拟化

X86體系結構CPU虛拟化技術的稱為 Intel VT-x 技術,引入了VMX,提供了兩種處理器的工作環境。 VMCS 結構實作兩種環境之間的切換。 VM Entry 使虛拟機進去guest模式,VM Exit 使虛拟機退出guest模式。

VMM排程guest執行時,qemu 通過 ioctl 系統調用進入核心模式,在 KVM Driver中獲得目前實體 CPU的引用。之後将guest狀态從VMCS中讀出, 并裝入實體CPU中。執行 VMLAUCH 指令使得實體處理器進入非根操作環境,運作guest OS代碼。

當 guest OS 執行一些特權指令或者外部事件時, 比如I/O通路,對控制寄存器的操作,MSR的讀寫等, 都會導緻實體CPU發生 VMExit, 停止運作 Guest OS,将 Guest OS儲存到VMCS中, Host 狀态裝入實體處理器中, 處理器進入根操作環境,KVM取得控制權,通過讀取 VMCS 中 VM_EXIT_REASON 字段得到引起 VM Exit 的原因。 進而調用kvm_exit_handler 處理函數。 如果由于 I/O 獲得信号到達,則退出到userspace模式的 Qemu 處理。處理完畢後,重新進入guest模式運作虛拟 CPU。

4.Mem虛拟化

OS對于實體記憶體主要有兩點認識:1.實體位址從0開始;2.記憶體位址是連續的。VMM接管了所有記憶體,但guest OS的對記憶體的使用就存在這兩點沖突了,除此之外,一個guest對記憶體的操作很有可能影響到另外一個guest乃至host的運作。VMM的記憶體虛拟化就要解決這些問題。

在OS代碼中,應用也是占用所有的邏輯位址,同時不影響其他應用的關鍵點在于有線性位址這個中間層;解決方法則是添加了一個中間層:guest實體位址空間;guest看到是從0開始的guest實體位址空間(類比從0開始的線性位址),而且是連續的,雖然有些位址沒有映射;同時guest實體位址映射到不同的host邏輯位址,如此保證了VM之間的安全性要求。

這樣MEM虛拟化就是GVA->GPA->HPA的尋址過程,傳統軟體方法有影子頁表,硬體虛拟化提供了EPT支援。

總體描述到此,後面代碼裡面見真相。

前段時間挖了一個坑,KVM源代碼分析1:基本工作原理,準備寫一下kvm的代碼機制,結果一直沒時間填土,現在還一下舊賬,争取能溫故而知新。 基本原理裡面提到kvm虛拟化由使用者态程式Qemu和核心态驅動kvm配合完成,qemu負責HOST使用者态層面程序管理,IO處理等,KVM負責把qemu的部分指令在硬體上直接實作,從虛拟機的建立和運作上看,qemu的代碼占了流程上的主要部分。下面的代碼主要主要針對與qemu,KVM部分另外開篇再說。

代碼:

QEMU:git://git.qemu.org/qemu.git v2.4.0

KVM:https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git v4.2

QEMU和KVM是通過IOCTL進行配合的,直接抓住這個線看有kvm_ioctl、kvm_vm_ioctl、kvm_vcpu_ioctl、kvm_device_ioctl等,他們還都在一個C檔案裡面。

使用kvm_ioctl很少了,直接看調用的代碼,有KVM_GET_VCPU_MMAP_SIZE,KVM_CHECK_EXTENSION,KVM_GET_API_VERSION,KVM_CREATE_VM,KVM_GET_SUPPORTED_CPUID等等,需要記住隻有KVM_CREATE_VM。

而調用kvm_vm_ioctl的函數真是海了去了,需要看的是KVM_SET_USER_MEMORY_REGION,KVM_CREATE_VCPU,KVM_CREATE_DEVICE。

所有寄存器的交換資訊都是通過kvm_vcpu_ioctl,需要記住的操作隻有,KVM_RUN。

所有看QEMU和KVM的配合流程如下:

KVM源代碼分析

接下來參考上圖分析qemu代碼流程: 從vl.c代碼的main函數開始。 atexit(qemu_run_exit_notifiers)注冊了qemu的退出處理函數,後面在具體看qemu_run_exit_notifiers函數。 module_call_init則開始初始化qemu的各個子產品,陸陸續續的有以下參數:

typedef enum {

MODULE_INIT_BLOCK,

MODULE_INIT_MACHINE,

MODULE_INIT_QAPI,

MODULE_INIT_QOM,

MODULE_INIT_MAX

} module_init_type;

1 2 3 4 5 6 7 typedef enum {      MODULE_INIT_BLOCK ,      MODULE_INIT_MACHINE ,      MODULE_INIT_QAPI ,      MODULE_INIT_QOM ,      MODULE_INIT _MAX } module_init_type ;

最開始初始化的MODULE_INIT_QOM,QOM是qemu實作的一種模拟裝置,具體可以參考http://wiki.qemu.org/Features/QOM,代碼下面的不遠處就MODULE_INIT_MACHINE的初始化,這兩條語句放到一起看,直接說一下module_call_init的機制。 module_call_init實際設計的一個函數連結清單,ModuleTypeList ,連結清單關系如下圖

KVM源代碼分析

它把相關的函數注冊到對應的數組連結清單上,通過執行init項目完成所有裝置的初始化。module_call_init就是執行e->init()完成功能的,而e->init是什麼時候通過register_module_init注冊到ModuleTypeList上的ModuleEntry,是module_init注冊的,而調用module_init的有

#define block_init(function) module_init(function, MODULE_INIT_BLOCK)

#define machine_init(function) module_init(function, MODULE_INIT_MACHINE)

#define qapi_init(function) module_init(function, MODULE_INIT_QAPI)

#define type_init(function) module_init(function, MODULE_INIT_QOM)

1 2 3 4 #define block_init(function) module_init(function, MODULE_INIT_BLOCK) #define machine_init(function) module_init(function, MODULE_INIT_MACHINE) #define qapi_init(function) module_init(function, MODULE_INIT_QAPI) #define type_init(function) module_init(function, MODULE_INIT_QOM)

那麼執行machine_init則是挂到了MODULE_INIT_MACHINE,type_init則将函數挂載了MODULE_INIT_QOM。那麼排查一下是,我們隻關注PC的注冊,那麼就是machine_init(pc_machine_init_##suffix),源自DEFINE_PC_MACHINE(suffix, namestr, initfn, optsfn)宏,而DEFINE_I440FX_MACHINE有

#define DEFINE_I440FX_MACHINE(suffix, name, compatfn, optionfn)

static void pc_init_##suffix(MachineState *machine)

{

void (*compat)(MachineState *m) = (compatfn);

if (compat) {

compat(machine);

}

pc_init1(machine);

}

DEFINE_PC_MACHINE(suffix, name, pc_init_##suffix, optionfn)

#define DEFINE_PC_MACHINE(suffix, namestr, initfn, optsfn)

static void pc_machine_##suffix##_class_init(ObjectClass *oc, void *data)

{

MachineClass *mc = MACHINE_CLASS(oc);

optsfn(mc);

mc->name = namestr;

mc->init = initfn;

}

static const TypeInfo pc_machine_type_##suffix = {

.name = namestr TYPE_MACHINE_SUFFIX,

.parent = TYPE_PC_MACHINE,

.class_init = pc_machine_##suffix##_class_init,

};

static void pc_machine_init_##suffix(void)

{

type_register(&pc_machine_type_##suffix);

}

machine_init(pc_machine_init_##suffix)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #define DEFINE_I440FX_MACHINE(suffix, name, compatfn, optionfn)      static void pc_init_ ##suffix(MachineState *machine)      {          void ( * compat ) ( MachineState * m ) = ( compatfn ) ;          if ( compat ) {              compat ( machine ) ;          }          pc_init1 ( machine ) ;      }      DEFINE_PC_MACHINE ( suffix , name , pc_init_ ##suffix, optionfn)   #define DEFINE_PC_MACHINE(suffix, namestr, initfn, optsfn)      static void pc_machine_ ##suffix##_class_init(ObjectClass *oc, void *data)      {          MachineClass * mc = MACHINE_CLASS ( oc ) ;          optsfn ( mc ) ;          mc -> name = namestr ;          mc -> init = initfn ;      }      static const TypeInfo pc_machine_type_ ##suffix = {          . name        = namestr TYPE_MACHINE_SUFFIX ,          . parent      = TYPE_PC_MACHINE ,          . class_init = pc_machine_ ##suffix##_class_init,      } ;      static void pc_machine_init_ ##suffix(void)      {          type_register ( & pc_machine_type_ ##suffix);      }      machine_init ( pc_machine_init_ ##suffix)

DEFINE_PC_MACHINE注冊的函數pc_init_##suffix在DEFINE_I440FX_MACHINE中定義,怎麼組合都無關,pc_init1(machine)函數一定要執行,本質就是pc_init1指派給了mc->init,其他愛看不看吧。

而module_init的宏是

#define module_init(function, type)

static void __attribute__((constructor)) do_qemu_init_ ## function(void)

{

register_dso_module_init(function, type);

}

#else

/* This should not be used directly. Use block_init etc. instead. */

#define module_init(function, type)

static void __attribute__((constructor)) do_qemu_init_ ## function(void)

{

register_module_init(function, type);

}

1 2 3 4 5 6 7 8 9 10 11 12 #define module_init(function, type)                                         static void __attribute__ ( ( constructor ) ) do_qemu_init_ ## function(void)     {                                                                                 register_dso_module_init ( function , type ) ;                                } #else #define module_init(function, type)                                         static void __attribute__ ( ( constructor ) ) do_qemu_init_ ## function(void)     {                                                                                 register_module_init ( function , type ) ;                                    }

它前面的修飾是__attribute__((constructor)),這個導緻machine_init或者type_init等會在main()之前就被執行。所有type_init(kvm_type_init)-> kvm_accel_type -> kvm_accel_class_init -> kvm_init依次完成了函數注冊,所有說module_call_init(MODULE_INIT_QOM)函數已經完成了kvm_init的執行,所有這樣就清楚KVM調用關系了。

如此就先去看kvm_init函數,前面主要幹了一件事,填充KVMState *s結構體,然後通過kvm_ioctl(s, KVM_GET_API_VERSION, 0)判斷核心KVM驅動和目前QEMU版本是否相容,下面則是執行kvm_ioctl(s, KVM_CREATE_VM, type)進行虛拟機的建立活動,建立了KVM虛拟機,擷取虛拟機句柄。具體KVM_CREATE_VM在核心态做了什麼,ioctl的工作等另外再說,現在假定KVM_CREATE_VM所代表的虛拟機建立成功,下面通過檢查kvm_check_extension結果填充KVMState,kvm_arch_init初始化KVMState,其中有IDENTITY_MAP_ADDR,TSS_ADDR,NR_MMU_PAGES等,cpu_register_phys_memory_client注冊qemu對記憶體管理的函數集,kvm_create_irqchip建立kvm中斷管理内容,通過kvm_vm_ioctl(s, KVM_CREATE_IRQCHIP)實作,具體核心态的工作内容後面分析。到此kvm_init的工作就完成了,最主要的工作就是建立的虛拟機。

這樣繞了這麼大圈,重新回到vl.c上面來,前面剛說了module_call_init(MODULE_INIT_MACHINE)本質就是把pc_init1指派給了mc->init,然後machine_class = find_default_machine(),如此可以看到machine_class的init函數一定會執行pc_init1。

下面涉及對OPT入參的解析過程略過不提。 qemu準備模拟的機器的類型從下面語句獲得:

current_machine = MACHINE(object_new(object_class_get_name(

OBJECT_CLASS(machine_class))));

1 2      current_machine = MACHINE ( object_new ( object_class_get_name (                            OBJECT_CLASS ( machine_class ) ) ) ) ;

machine_class則是通過入參傳入的

case QEMU_OPTION_machine:

olist = qemu_find_opts("machine");

opts = qemu_opts_parse_noisily(olist, optarg, true);

if (!opts) {

exit(1);

}

break;

1 2 3 4 5 6 7              case QEMU_OPTION_machine :                  olist = qemu_find_opts ( "machine" ) ;                  opts = qemu_opts_parse_noisily ( olist , optarg , true ) ;                  if ( ! opts ) {                      exit ( 1 ) ;                  }                  break ;

man qemu

-machine [type=]name[,prop=value[,...]]

Select the emulated machine by name.

Use "-machine help" to list available machines

1 2 3        - machine [ type = ] name [ , prop = value [ , . . . ] ]            Select the emulated machine by name .            Use "-machine help" to list available machines

下面有cpu_exec_init_all就是執行了qemu的記憶體結構體的初始化而已,cpudef_init則提供了VCPU的不同型号的模拟,qemu_set_log設定日志輸出,kvm對外的日志是從這裡配置的。中間的亂七八糟的就忽略掉即可,然後直接到了machine_class->init(current_machine)函數,其實就是執行了pc_init1。暫且記下來,先看下面的,cpu_synchronize_all_post_init就是核心和qemu資料不一緻同步一下。下面的函數沒有重要的了,隻有vm_start()函數需要記一下,後面會用到。

現在進入pc_init1函數:

在pc_init1中重點看兩個函數,pc_cpus_init和pc_memory_init,顧名思義,CPU和記憶體的初始化,中斷,vga等函數的初始化先忽略掉,先看這兩個。

pc_cpus_init入參是cpu_model,前面說過這是具體的CPU模型,所有X86的CPU模型都在builtin_x86_defs中定義,取其中一個看看

{

.name = "SandyBridge",

.level = 0xd,

.vendor = CPUID_VENDOR_INTEL,

.family = 6,

.model = 42,

.stepping = 1,

.features[FEAT_1_EDX] =

CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX |

CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA |

CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 |

CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE |

CPUID_DE | CPUID_FP87,

.features[FEAT_1_ECX] =

CPUID_EXT_AVX | CPUID_EXT_XSAVE | CPUID_EXT_AES |

CPUID_EXT_TSC_DEADLINE_TIMER | CPUID_EXT_POPCNT |

CPUID_EXT_X2APIC | CPUID_EXT_SSE42 | CPUID_EXT_SSE41 |

CPUID_EXT_CX16 | CPUID_EXT_SSSE3 | CPUID_EXT_PCLMULQDQ |

CPUID_EXT_SSE3,

.features[FEAT_8000_0001_EDX] =

CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_NX |

CPUID_EXT2_SYSCALL,

.features[FEAT_8000_0001_ECX] =

CPUID_EXT3_LAHF_LM,

.features[FEAT_XSAVE] =

CPUID_XSAVE_XSAVEOPT,

.features[FEAT_6_EAX] =

CPUID_6_EAX_ARAT,

.xlevel = 0x80000008,

.model_id = "Intel Xeon E312xx (Sandy Bridge)",

},

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31      {          . name = "SandyBridge" ,          . level = 0xd ,          . vendor = CPUID_VENDOR_INTEL ,          . family = 6 ,          . model = 42 ,          . stepping = 1 ,          . features [ FEAT_1_EDX ] =              CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX |              CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA |              CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 |              CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE |              CPUID_DE | CPUID_FP87 ,          . features [ FEAT_1_ECX ] =              CPUID_EXT_AVX | CPUID_EXT_XSAVE | CPUID_EXT_AES |              CPUID_EXT_TSC_DEADLINE_TIMER | CPUID_EXT_POPCNT |              CPUID_EXT_X2APIC | CPUID_EXT_SSE42 | CPUID_EXT_SSE41 |              CPUID_EXT_CX16 | CPUID_EXT_SSSE3 | CPUID_EXT_PCLMULQDQ |              CPUID_EXT_SSE3 ,          . features [ FEAT_8000_0001_EDX ] =              CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_NX |              CPUID_EXT2_SYSCALL ,          . features [ FEAT_8000_0001_ECX ] =              CPUID_EXT3_LAHF_LM ,          . features [ FEAT_XSAVE ] =              CPUID_XSAVE_XSAVEOPT ,          . features [ FEAT_6_EAX ] =              CPUID_6_EAX_ARAT ,          . xlevel = 0x80000008 ,          . model_id = "Intel Xeon E312xx (Sandy Bridge)" ,      } ,

你可以cat一個本地的/proc/cpuinfo,builtin_x86_defs定義的就是這些參數。

然後是for循環中針對每個CPU初始化,即pc_new_cpu,直接進入cpu_x86_create函數,

主要就是把CPUX86State填充了一下,涉及到CPUID和其他的feature。下面是x86_cpu_realize,即喚醒CPU,重點是qemu_init_vcpu,MCE忽略掉,走到qemu_kvm_start_vcpu,qemu建立VCPU,如下:

//建立VPU對于的qemu線程,線程函數是qemu_kvm_cpu_thread_fn

qemu_thread_create(cpu->thread, thread_name, qemu_kvm_cpu_thread_fn,

cpu, QEMU_THREAD_JOINABLE);

//如果線程沒有建立成功,則一直在此處循環阻塞。說明多核vcpu的建立是順序的

while (!cpu->created) {

qemu_cond_wait(&qemu_cpu_cond, &qemu_global_mutex);

}

1 2 3 4 5 6 7      //建立VPU對于的qemu線程,線程函數是qemu_kvm_cpu_thread_fn      qemu_thread_create ( cpu -> thread , thread_name , qemu_kvm_cpu_thread_fn ,                        cpu , QEMU_THREAD_JOINABLE ) ;      //如果線程沒有建立成功,則一直在此處循環阻塞。說明多核vcpu的建立是順序的      while ( ! cpu -> created ) {          qemu_cond_wait ( & qemu_cpu_cond , & qemu_global_mutex ) ;      }

線程建立完成,具體任務支線提,回到主流程上,qemu_init_vcpu執行完成後,下面就是cpu_reset,此處的作用是什麼呢?答案是無用,本質是一個空函數,它的主要功能就是CPUClass的reset函數,reset在cpu_class_init裡面注冊的,注冊的是cpu_common_reset,這是一個空函數,沒有任何作用。cpu_class_init則是被cpu_type_info即TYPE_CPU使用,而cpu_type_info則由type_init(cpu_register_types)完成,type_init則是前面提到的和machine_init對應的注冊關系。根據下句完成工作

#define type_init(function) module_init(function, MODULE_INIT_QOM)

1 #define type_init(function) module_init(function, MODULE_INIT_QOM)

從上面看,pc_cpus_init函數過程已經理順了,下面看一下,vcpu所在的線程對應的qemu_kvm_cpu_thread_fn中:

//初始化VCPU

r = kvm_init_vcpu(env);

//初始化KVM中斷

qemu_kvm_init_cpu_signals(env);

//标志VCPU建立完成,和上面判斷是對應的

cpu->created = true;

qemu_cond_signal(&qemu_cpu_cond);

while (1) {

if (cpu_can_run(env)) {

//CPU進入執行狀态

r = kvm_cpu_exec(env);

if (r == EXCP_DEBUG) {

cpu_handle_guest_debug(env);

}

}

qemu_kvm_wait_io_event(env);

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 //初始化VCPU      r = kvm_init_vcpu ( env ) ; //初始化KVM中斷      qemu_kvm_init_cpu_signals ( env ) ;   //标志VCPU建立完成,和上面判斷是對應的      cpu -> created = true ;      qemu_cond_signal ( & qemu_cpu_cond ) ;      while ( 1 ) {          if ( cpu_can_run ( env ) ) {            //CPU進入執行狀态              r = kvm_cpu_exec ( env ) ;              if ( r == EXCP_DEBUG ) {                  cpu_handle_guest_debug ( env ) ;              }          }          qemu_kvm_wait_io_event ( env ) ;      }

CPU進入執行狀态的時候我們看到其他的VCPU包括記憶體可能還沒有初始化,關鍵是此處有一個開關,qemu_cpu_cond,打開這個開關才能進入到CPU執行狀态,誰來打開這個開關,後面再說。先看kvm_init_vcpu,通過kvm_vm_ioctl,KVM_CREATE_VCPU建立VCPU,用KVM_GET_VCPU_MMAP_SIZE擷取env->kvm_run對應的記憶體映射,kvm_arch_init_vcpu則填充對應的kvm_arch内容,具體核心部分,後面單獨寫。kvm_init_vcpu就是擷取了vcpu,将相關内容填充了env。

qemu_kvm_init_cpu_signals則是将中斷組合掩碼傳遞給kvm_set_signal_mask,最終給核心KVM_SET_SIGNAL_MASK。kvm_cpu_exec此時還在阻塞過程中,先挂起來,看記憶體的初始化。

記憶體初始化函數是pc_memory_init,memory_region_init_ram傳入了高端記憶體和低端記憶體的值,memory_region_init負責填充mr,重點在qemu_ram_alloc,即qemu_ram_alloc_from_ptr,首先有RAMBlock,ram_list,那就直接借助find_ram_offset函數一起看一下qemu的記憶體分布模型。

KVM源代碼分析

qemu模拟了普通記憶體分布模型,記憶體的線性也是分塊被使用的,每個塊稱為RAMBlock,由ram_list統領,RAMBlock.offset則是區塊的線性位址,即相對于開始的偏移位,RAMBlock.length(size)則是區塊的大小,find_ram_offset則是線上性區間内找到沒有使用的一段空間,可以完全容納新申請的ramblock length大小,代碼就是進行了所有區塊的周遊,找到滿足新申請length的最小區間,把ramblock安插進去即可,傳回的offset即是新配置設定區間的開始位址。

而RAMBlock的實體則是在RAMBlock.host,由kvm_vmalloc(size)配置設定真正實體記憶體,内部qemu_vmalloc使用qemu_memalign頁對齊配置設定記憶體。後續的都是對RAMBlock的插入等處理。

從上面看,memory_region_init_ram已經将qemu記憶體模型和實際的實體記憶體初始化了。

vmstate_register_ram_global這個函數則是負責将前面提到的ramlist中的ramblock和memory region的初始位址對應一下,将mr->name填充到ramblock的idstr裡面,就是讓二者有确定的對應關系,如此mr就有了實體記憶體使用。

後面則是subregion的處理,memory_region_init_alias初始化,其中将ram傳遞給mr->owner确定了隸屬關系,memory_region_add_subregion則是大頭,memory_region_add_subregion_common前面的判斷忽略,QTAILQ_INSERT_TAIL(&mr->subregions, subregion, subregions_link)就是插入了連結清單而已,主要内容在memory_region_transaction_commit。

memory_region_transaction_commit中引入了新的結構address_spaces(AS),注釋裡面提到“AddressSpace: describes a mapping of addresses to #MemoryRegion objects”,就是記憶體位址的映射關系,因為記憶體有不同的應用類型,address_spaces以連結清單形式存在,commit函數則是對所有AS執行address_space_update_topology,先看AS在哪裡注冊的,就是前面提到的kvm_init裡面,執行memory_listener_register,注冊了address_space_memory和address_space_io兩個,涉及的另外一個結構體則是MemoryListener,有kvm_memory_listener和kvm_io_listener,就是用于監控記憶體映射關系發生變化之後執行回調函數。

下面進入到address_space_update_topology函數,FlatView則是“Flattened global view of current active memory hierarchy”,address_space_get_flatview直接擷取目前的,generate_memory_topology則根據前面已經變化的mr重新生成FlatView,然後通過address_space_update_topology_pass比較,簡單說address_space_update_topology_pass就是兩個FlatView逐條的FlatRange進行對比,以後一個FlatView為準,如果前面FlatView的FlatRange和後面的不一樣,則對前面的FlatView的這條FlatRange進行處理,差别就是3種情況,如代碼:

while (iold < old_view->nr || inew < new_view->nr) {

if (iold < old_view->nr) {

frold = &old_view->ranges[iold];

} else {

frold = NULL;

}

if (inew < new_view->nr) {

frnew = &new_view->ranges[inew];

} else {

frnew = NULL;

}

if (frold

&& (!frnew

|| int128_lt(frold->addr.start, frnew->addr.start)

|| (int128_eq(frold->addr.start, frnew->addr.start)

&& !flatrange_equal(frold, frnew)))) {

/* In old but not in new, or in both but attributes changed. */

if (!adding) { //這個判斷代碼添加的無用,可以直接删除,

//address_space_update_topology裡面的兩個pass也可以删除一個

MEMORY_LISTENER_UPDATE_REGION(frold, as, Reverse, region_del);

}

++iold;

} else if (frold && frnew && flatrange_equal(frold, frnew)) {

/* In both and unchanged (except logging may have changed) */

if (adding) {

MEMORY_LISTENER_UPDATE_REGION(frnew, as, Forward, region_nop);

if (frold->dirty_log_mask && !frnew->dirty_log_mask) {

MEMORY_LISTENER_UPDATE_REGION(frnew, as, Reverse, log_stop);

} else if (frnew->dirty_log_mask && !frold->dirty_log_mask) {

MEMORY_LISTENER_UPDATE_REGION(frnew, as, Forward, log_start);

}

}

++iold;

++inew;

} else {

/* In new */

if (adding) {

MEMORY_LISTENER_UPDATE_REGION(frnew, as, Forward, region_add);

}

++inew;

}

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 while ( iold < old_view -> nr || inew < new_view -> nr ) {          if ( iold < old_view -> nr ) {              frold = & old_view -> ranges [ iold ] ;          } else {              frold = NULL ;          }          if ( inew < new_view -> nr ) {              frnew = & new_view -> ranges [ inew ] ;          } else {              frnew = NULL ;          }            if ( frold              && ( ! frnew                  || int128_lt ( frold -> addr . start , frnew -> addr . start )                  || ( int128_eq ( frold -> addr . start , frnew -> addr . start )                      && ! flatrange_equal ( frold , frnew ) ) ) ) {                             if ( ! adding ) { //這個判斷代碼添加的無用,可以直接删除,                  //address_space_update_topology裡面的兩個pass也可以删除一個                  MEMORY_LISTENER_UPDATE_REGION ( frold , as , Reverse , region_del ) ;              }                ++ iold ;          } else if ( frold && frnew && flatrange_equal ( frold , frnew ) ) {                             if ( adding ) {                  MEMORY_LISTENER_UPDATE_REGION ( frnew , as , Forward , region_nop ) ;                  if ( frold -> dirty_log_mask && ! frnew -> dirty_log_mask ) {                      MEMORY_LISTENER_UPDATE_REGION ( frnew , as , Reverse , log_stop ) ;                  } else if ( frnew -> dirty_log_mask && ! frold -> dirty_log_mask ) {                      MEMORY_LISTENER_UPDATE_REGION ( frnew , as , Forward , log_start ) ;                  }              }                ++ iold ;              ++ inew ;          } else {                             if ( adding ) {                  MEMORY_LISTENER_UPDATE_REGION ( frnew , as , Forward , region_add ) ;              }                ++ inew ;          }      }

重點在MEMORY_LISTENER_UPDATE_REGION函數上,将變化的FlatRange構造一個MemoryRegionSection,然後周遊所有的memory_listeners,如果memory_listeners監控的記憶體區域和MemoryRegionSection一樣,則執行第四個入參函數,如region_del函數,即kvm_region_del函數,這個是在kvm_init中初始化的。kvm_region_del主要是kvm_set_phys_mem函數,主要是将MemoryRegionSection有效值轉換成KVMSlot形式,在kvm_set_user_memory_region中使用kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION, &mem)傳遞給kernel。

我們看記憶體初始化真正需要做的是什麼?就是qemu申請記憶體,把申請實體位址傳遞給kernel進行映射,那我們直接就可以KVMSlot申請記憶體,然後傳遞給kvm_vm_ioctl,這樣也是OK的,之是以有這麼多代碼,因為qemu本身是一個軟體虛拟機,mr涉及的位址已經是vm的位址,對于KVM是多餘的,隻是友善函數複用而已。

記憶體初始化之後還是pci等處理先跳過,如此pc_init就完成了,但是前面VM線程已經初始化成功,在qemu_kvm_cpu_thread_fn函數中等待運作:

while (1) {

if (cpu_can_run(cpu)) {

r = kvm_cpu_exec(cpu);

if (r == EXCP_DEBUG) {

cpu_handle_guest_debug(cpu);

}

}

qemu_kvm_wait_io_event(cpu);

}

1 2 3 4 5 6 7 8 9      while ( 1 ) {          if ( cpu_can_run ( cpu ) ) {              r = kvm_cpu_exec ( cpu ) ;              if ( r == EXCP_DEBUG ) {                  cpu_handle_guest_debug ( cpu ) ;              }          }          qemu_kvm_wait_io_event ( cpu ) ;      }

判斷條件就是cpu_can_run函數,即cpu->stop && cpu->stopped && current_run_state != running 都是false,而這幾個參數都是由vm_start函數決定的

void vm_start(void)

{

if (!runstate_is_running()) {

cpu_enable_ticks();

runstate_set(RUN_STATE_RUNNING);

vm_state_notify(1, RUN_STATE_RUNNING);

resume_all_vcpus();

monitor_protocol_event(QEVENT_RESUME, NULL);

}

}

1 2 3 4 5 6 7 8 9 10 void vm_start ( void ) {      if ( ! runstate_is_running ( ) ) {          cpu_enable_ticks ( ) ;          runstate_set ( RUN_STATE_RUNNING ) ;          vm_state_notify ( 1 , RUN_STATE_RUNNING ) ;          resume_all_vcpus ( ) ;          monitor_protocol_event ( QEVENT_RESUME , NULL ) ;      } }

如此kvm_cpu_exec就真正進入執行階段,即通過kvm_vcpu_ioctl傳遞KVM_RUN給核心。

在虛拟機的建立與運作章節裡面籠統的介紹了KVM在qemu中的建立和運作,基本的qemu代碼流程已經梳理清楚,後續主要寫一些硬體虛拟化的原理和代碼流程,主要寫原理和qemu控制KVM運作的的ioctl接口,後續對核心代碼的梳理也從這些接口下手。

QEMU:git://git.qemu.org/qemu.git v2.4.0

KVM:https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git v4.2

1.VT-x 技術

Intel處理器支援的虛拟化技術即是VT-x,之是以CPU支援硬體虛拟化是因為軟體虛拟化的效率太低。

處理器虛拟化的本質是分時共享,主要展現在狀态恢複和資源隔離,實際上每個VM對于VMM看就是一個task麼,之前Intel處理器在虛拟化上沒有提供預設的硬體支援,傳統 x86 處理器有4個特權級,Linux使用了0,3級别,0即核心,3即使用者态,(更多參考CPU的運作環、特權級與保護)而在虛拟化架構上,虛拟機監控器的運作級别需要核心态特權級,而CPU特權級被傳統OS占用,是以Intel設計了VT-x,提出了VMX模式,即VMX root operation 和 VMX non-root operation,虛拟機監控器運作在VMX root operation,虛拟機運作在VMX non-root operation。每個模式下都有相對應的0~3特權級。

為什麼引入這兩種特殊模式,在傳統x86的系統中,CPU有不同的特權級,是為了劃分不同的權限指令,某些指令隻能由系統軟體操作,稱為特權指令,這些指令隻能在最高特權級上才能正确執行,反之則會觸發異常,處理器會陷入到最高特權級,由系統軟體處理。還有一種需要操作特權資源(如通路中斷寄存器)的指令,稱為敏感指令。OS運作在特權級上,屏蔽掉使用者态直接執行的特權指令,達到控制所有的硬體資源目的;而在虛拟化環境中,VMM控制所有所有硬體資源,VM中的OS隻能占用一部分資源,OS執行的很多特權指令是不能真正對硬體生效的,是以原特權級下有了root模式,OS指令不需要修改就可以正常執行在特權級上,但這個特權級的所有敏感指令都會傳遞到root模式處理,這樣達到了VMM的目的。

在KVM源代碼分析1:基本工作原理章節中也說了kvm分3個模式,對應到VT-x 中即是客戶模式對應vmx非root模式,核心模式對應VMX root模式下的0特權級,使用者模式對應vmx root模式下的3特權級。

如下圖

KVM源代碼分析

在非根模式下敏感指令引發的陷入稱為VM-Exit,VM-Exit發生後,CPU從非根模式切換到根模式;對應的,VM-Entry則是從根模式到非根模式,通常意味着調用VM進入運作态。VMLAUCH/VMRESUME指令則是用來發起VM-Entry。

2.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

關于具體控制域的細節,還是翻Intel手冊吧。

3.VM-Entry/VM-Exit

VM-Entry是從根模式切換到非根模式,即VMM切換到guest上,這個狀态由VMM發起,發起之前先儲存VMM中的關鍵寄存器内容到VMCS中,然後進入到VM-Entry,VM-Entry附帶參數主要有3個:1.guest是否處于64bit模式,2.MSR VM-Entry控制,3.注入事件。1應該隻在VMLAUCH有意義,3更多是在VMRESUME,而VMM發起VM-Entry更多是因為3,2主要用來每次更新MSR。

VM-Exit是CPU從非根模式切換到根模式,從guest切換到VMM的操作,VM-Exit觸發的原因就很多了,執行敏感指令,發生中斷,模拟特權資源等。

運作在非根模式下的敏感指令一般分為3個方面:

1.行為沒有變化的,也就是說該指令能夠正确執行。

2.行為有變化的,直接産生VM-Exit。

3.行為有變化的,但是是否産生VM-Exit受到VM-Execution控制域控制。

主要說一下”受到VM-Execution控制域控制”的敏感指令,這個就是針對性的硬體優化了,一般是1.産生VM-Exit;2.不産生VM-Exit,同時調用優化函數完成功能。典型的有“RDTSC指令”。除了大部分是優化性能的,還有一小部分是直接VM-Exit執行指令結果是異常的,或者說在虛拟化場景下是不适用的,典型的就是TSC offset了。

VM-Exit發生時退出的相關資訊,如退出原因、觸發中斷等,這些内容儲存在VM-Exit資訊域中。

4.KVM_CREATE_VM

建立VM就寫這裡吧,kvm_dev_ioctl_create_vm函數是主幹,在kvm_create_vm中,主要有兩個函數,kvm_arch_init_vm和hardware_enable_all,需要注意,但是更先一步的是KVM結構體,下面的struct是精簡後的版本。

struct kvm {

struct mm_struct *mm; /* userspace tied to this vm */

struct kvm_memslots *memslots; /*qemu模拟的記憶體條模型*/

struct kvm_vcpu *vcpus[KVM_MAX_VCPUS]; /* 模拟的CPU */

atomic_t online_vcpus;

int last_boosted_vcpu;

struct list_head vm_list; //HOST上VM管理連結清單,

struct kvm_io_bus *buses[KVM_NR_BUSES];

struct kvm_vm_stat stat;

struct kvm_arch arch; //這個是host的arch的一些參數

atomic_t users_count;

long tlbs_dirty;

struct list_head devices;

};

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct kvm {      struct mm_struct * mm ;      struct kvm_memslots * memslots ;         struct kvm_vcpu * vcpus [ KVM_MAX_VCPUS ] ;      atomic_t online_vcpus ;      int last_boosted_vcpu ;      struct list_head vm_list ;    //HOST上VM管理連結清單,      struct kvm_io_bus * buses [ KVM_NR_BUSES ] ;      struct kvm_vm_stat stat ;      struct kvm_arch arch ; //這個是host的arch的一些參數      atomic_t users_count ;        long tlbs_dirty ;      struct list_head devices ; } ;

kvm_arch_init_vm基本沒有特别動作,初始化了KVM->arch,以及更新了kvmclock函數,這個另外再說。

而hardware_enable_all,針對于每個CPU執行“on_each_cpu(hardware_enable_nolock, NULL, 1)”,在hardware_enable_nolock中先把cpus_hardware_enabled置位,進入到kvm_arch_hardware_enable中,有hardware_enable和TSC初始化規則,主要看hardware_enable,crash_enable_local_vmclear清理位圖,判斷MSR_IA32_FEATURE_CONTROL寄存器是否滿足虛拟環境,不滿足則将條件寫入到寄存器内,CR4将X86_CR4_VMXE置位,另外還有kvm_cpu_vmxon打開VMX操作模式,外層包了vmm_exclusive的判斷,它是kvm_intel.ko的外置參數,預設唯一,可以讓使用者強制不使用VMM硬體支援。

5.KVM_CREATE_VCPU

kvm_vm_ioctl_create_vcpu主要有三部分,kvm_arch_vcpu_create,kvm_arch_vcpu_setup和kvm_arch_vcpu_postcreate,重點自然是kvm_arch_vcpu_create。老樣子,在這之前先看一下VCPU的結構體。

struct kvm_vcpu {

struct kvm *kvm; //歸屬的KVM

#ifdef CONFIG_PREEMPT_NOTIFIERS

struct preempt_notifier preempt_notifier;

#endif

int cpu;

int vcpu_id;

int srcu_idx;

int mode;

unsigned long requests;

unsigned long guest_debug;

struct mutex mutex;

struct kvm_run *run; //運作時的狀态

int fpu_active;

int guest_fpu_loaded, guest_xcr0_loaded;

wait_queue_head_t wq; //隊列

struct pid *pid;

int sigset_active;

sigset_t sigset;

struct kvm_vcpu_stat stat; //一些資料

#ifdef CONFIG_HAS_IOMEM

int mmio_needed;

int mmio_read_completed;

int mmio_is_write;

int mmio_cur_fragment;

int mmio_nr_fragments;

struct kvm_mmio_fragment mmio_fragments[KVM_MAX_MMIO_FRAGMENTS];

#endif

#ifdef CONFIG_KVM_ASYNC_PF

struct {

u32 queued;

struct list_head queue;

struct list_head done;

spinlock_t lock;

} async_pf;

#endif

#ifdef CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT

/*

* Cpu relax intercept or pause loop exit optimization

* in_spin_loop: set when a vcpu does a pause loop exit

* or cpu relax intercepted.

* dy_eligible: indicates whether vcpu is eligible for directed yield.

*/

struct {

bool in_spin_loop;

bool dy_eligible;

} spin_loop;

#endif

bool preempted;

struct kvm_vcpu_arch arch; //目前VCPU虛拟的架構,預設介紹X86

};

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 struct kvm_vcpu {      struct kvm * kvm ;    //歸屬的KVM #ifdef CONFIG_PREEMPT_NOTIFIERS      struct preempt_notifier preempt_notifier ; #endif      int cpu ;      int vcpu_id ;      int srcu_idx ;      int mode ;      unsigned long requests ;      unsigned long guest_debug ;        struct mutex mutex ;      struct kvm_run * run ;    //運作時的狀态        int fpu_active ;      int guest_fpu_loaded , guest_xcr0_loaded ;      wait_queue_head_t wq ; //隊列      struct pid * pid ;      int sigset_active ;      sigset_t sigset ;      struct kvm_vcpu_stat stat ; //一些資料   #ifdef CONFIG_HAS_IOMEM      int mmio_needed ;      int mmio_read_completed ;      int mmio_is_write ;      int mmio_cur_fragment ;      int mmio_nr_fragments ;      struct kvm_mmio_fragment mmio_fragments [ KVM_MAX_MMIO_FRAGMENTS ] ; #endif   #ifdef CONFIG_KVM_ASYNC_PF      struct {          u32 queued ;          struct list_head queue ;          struct list_head done ;          spinlock_t lock ;      } async_pf ; #endif   #ifdef CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT           struct {          bool in_spin_loop ;          bool dy_eligible ;      } spin_loop ; #endif      bool preempted ;      struct kvm_vcpu_arch arch ;    //目前VCPU虛拟的架構,預設介紹X86 } ;

借着看kvm_arch_vcpu_create,它借助kvm_x86_ops->vcpu_create即vmx_create_vcpu完成任務,vmx是X86硬體虛拟化層,從代碼看,qemu使用者态是一層,kernel 中KVM通用代碼是一層,類似kvm_x86_ops是一層,針對各個不同硬體架構,而vcpu_vmx則是具體架構的虛拟化方案一層。首先是kvm_vcpu_init初始化,主要是填充結構體,可以注意的是vcpu->run分派了一頁記憶體,下面有kvm_arch_vcpu_init負責填充x86 CPU結構體,下面就是kvm_vcpu_arch:

struct kvm_vcpu_arch {

/*

* rip and regs accesses must go through

* kvm_{register,rip}_{read,write} functions.

*/

unsigned long regs[NR_VCPU_REGS];

u32 regs_avail;

u32 regs_dirty;

//類似這些寄存器就是就是用來緩存真正的CPU值的

unsigned long cr0;

unsigned long cr0_guest_owned_bits;

unsigned long cr2;

unsigned long cr3;

unsigned long cr4;

unsigned long cr4_guest_owned_bits;

unsigned long cr8;

u32 hflags;

u64 efer;

u64 apic_base;

struct kvm_lapic *apic; /* kernel irqchip context */

unsigned long apic_attention;

int32_t apic_arb_prio;

int mp_state;

u64 ia32_misc_enable_msr;

bool tpr_access_reporting;

u64 ia32_xss;

/*

* Paging state of the vcpu

*

* If the vcpu runs in guest mode with two level paging this still saves

* the paging mode of the l1 guest. This context is always used to

* handle faults.

*/

struct kvm_mmu mmu; //記憶體管理,更多的是附帶了直接操作函數

/*

* Paging state of an L2 guest (used for nested npt)

*

* This context will save all necessary information to walk page tables

* of the an L2 guest. This context is only initialized for page table

* walking and not for faulting since we never handle l2 page faults on

* the host.

*/

struct kvm_mmu nested_mmu;

/*

* Pointer to the mmu context currently used for

* gva_to_gpa translations.

*/

struct kvm_mmu *walk_mmu;

struct kvm_mmu_memory_cache mmu_pte_list_desc_cache;

struct kvm_mmu_memory_cache mmu_page_cache;

struct kvm_mmu_memory_cache mmu_page_header_cache;

struct fpu guest_fpu;

u64 xcr0;

u64 guest_supported_xcr0;

u32 guest_xstate_size;

struct kvm_pio_request pio;

void *pio_data;

u8 event_exit_inst_len;

struct kvm_queued_exception {

bool pending;

bool has_error_code;

bool reinject;

u8 nr;

u32 error_code;

} exception;

struct kvm_queued_interrupt {

bool pending;

bool soft;

u8 nr;

} interrupt;

int halt_request; /* real mode on Intel only */

int cpuid_nent;

struct kvm_cpuid_entry2 cpuid_entries[KVM_MAX_CPUID_ENTRIES];

int maxphyaddr;

/* emulate context */

//下面是KVM的軟體模拟模式,也就是沒有vmx的情況,估計也沒人用這一套

struct x86_emulate_ctxt emulate_ctxt;

bool emulate_regs_need_sync_to_vcpu;

bool emulate_regs_need_sync_from_vcpu;

int (*complete_userspace_io)(struct kvm_vcpu *vcpu);

gpa_t time;

struct pvclock_vcpu_time_info hv_clock;

unsigned int hw_tsc_khz;

struct gfn_to_hva_cache pv_time;

bool pv_time_enabled;

/* set guest stopped flag in pvclock flags field */

bool pvclock_set_guest_stopped_request;

struct {

u64 msr_val;

u64 last_steal;

u64 accum_steal;

struct gfn_to_hva_cache stime;

struct kvm_steal_time steal;

} st;

u64 last_guest_tsc;

u64 last_host_tsc;

u64 tsc_offset_adjustment;

u64 this_tsc_nsec;

u64 this_tsc_write;

u64 this_tsc_generation;

bool tsc_catchup;

bool tsc_always_catchup;

s8 virtual_tsc_shift;

u32 virtual_tsc_mult;

u32 virtual_tsc_khz;

s64 ia32_tsc_adjust_msr;

atomic_t nmi_queued; /* unprocessed asynchronous NMIs */

unsigned nmi_pending; /* NMI queued after currently running handler */

bool nmi_injected; /* Trying to inject an NMI this entry */

struct mtrr_state_type mtrr_state;

u64 pat;

unsigned switch_db_regs;

unsigned long db[KVM_NR_DB_REGS];

unsigned long dr6;

unsigned long dr7;

unsigned long eff_db[KVM_NR_DB_REGS];

unsigned long guest_debug_dr7;

u64 mcg_cap;

u64 mcg_status;

u64 mcg_ctl;

u64 *mce_banks;

/* Cache MMIO info */

u64 mmio_gva;

unsigned access;

gfn_t mmio_gfn;

u64 mmio_gen;

struct kvm_pmu pmu;

/* used for guest single stepping over the given code position */

unsigned long singlestep_rip;

/* fields used by HYPER-V emulation */

u64 hv_vapic;

cpumask_var_t wbinvd_dirty_mask;

unsigned long last_retry_eip;

unsigned long last_retry_addr;

struct {

bool halted;

gfn_t gfns[roundup_pow_of_two(ASYNC_PF_PER_VCPU)];

struct gfn_to_hva_cache data;

u64 msr_val;

u32 id;

bool send_user_only;

} apf;

/* OSVW MSRs (AMD only) */

struct {

u64 length;

u64 status;

} osvw;

struct {

u64 msr_val;

struct gfn_to_hva_cache data;

} pv_eoi;

/*

* Indicate whether the access faults on its page table in guest

* which is set when fix page fault and used to detect unhandeable

* instruction.

*/

bool write_fault_to_shadow_pgtable;

/* set at EPT violation at this point */

unsigned long exit_qualification;

/* pv related host specific info */

struct {

bool pv_unhalted;

} pv;

};

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 struct kvm_vcpu_arch {           unsigned long regs [ NR_VCPU_REGS ] ;      u32 regs_avail ;      u32 regs_dirty ; //類似這些寄存器就是就是用來緩存真正的CPU值的      unsigned long cr0 ;      unsigned long cr0_guest_owned_bits ;      unsigned long cr2 ;      unsigned long cr3 ;      unsigned long cr4 ;      unsigned long cr4_guest_owned_bits ;      unsigned long cr8 ;      u32 hflags ;      u64 efer ;      u64 apic_base ;      struct kvm_lapic * apic ;           unsigned long apic_attention ;      int32_t apic_arb_prio ;      int mp_state ;      u64 ia32_misc_enable_msr ;      bool tpr_access_reporting ;      u64 ia32_xss ;             struct kvm_mmu mmu ; //記憶體管理,更多的是附帶了直接操作函數             struct kvm_mmu nested_mmu ;             struct kvm_mmu * walk_mmu ;        struct kvm_mmu_memory_cache mmu_pte_list_desc_cache ;      struct kvm_mmu_memory_cache mmu_page_cache ;      struct kvm_mmu_memory_cache mmu_page_header_cache ;        struct fpu guest_fpu ;      u64 xcr0 ;      u64 guest_supported_xcr0 ;      u32 guest_xstate_size ;        struct kvm_pio_request pio ;      void * pio_data ;        u8 event_exit_inst_len ;        struct kvm_queued_exception {          bool pending ;          bool has_error_code ;          bool reinject ;          u8 nr ;          u32 error_code ;      } exception ;        struct kvm_queued_interrupt {          bool pending ;          bool soft ;          u8 nr ;      } interrupt ;        int halt_request ;        int cpuid_nent ;      struct kvm_cpuid_entry2 cpuid_entries [ KVM_MAX_CPUID_ENTRIES ] ;        int maxphyaddr ;        //下面是KVM的軟體模拟模式,也就是沒有vmx的情況,估計也沒人用這一套      struct x86_emulate_ctxt emulate_ctxt ;      bool emulate_regs_need_sync_to_vcpu ;      bool emulate_regs_need_sync_from_vcpu ;      int ( * complete_userspace_io ) ( struct kvm_vcpu * vcpu ) ;        gpa_t time ;      struct pvclock_vcpu_time_info hv_clock ;      unsigned int hw_tsc_khz ;      struct gfn_to_hva_cache pv_time ;      bool pv_time_enabled ;           bool pvclock_set_guest_stopped_request ;        struct {          u64 msr_val ;          u64 last_steal ;          u64 accum_steal ;          struct gfn_to_hva_cache stime ;          struct kvm_steal_time steal ;      } st ;        u64 last_guest_tsc ;      u64 last_host_tsc ;      u64 tsc_offset_adjustment ;      u64 this_tsc_nsec ;      u64 this_tsc_write ;      u64 this_tsc_generation ;      bool tsc_catchup ;      bool tsc_always_catchup ;      s8 virtual_tsc_shift ;      u32 virtual_tsc_mult ;      u32 virtual_tsc_khz ;      s64 ia32_tsc_adjust_msr ;        atomic_t nmi_queued ;         unsigned nmi_pending ;      bool nmi_injected ;             struct mtrr_state_type mtrr_state ;      u64 pat ;        unsigned switch_db_regs ;      unsigned long db [ KVM_NR_DB_REGS ] ;      unsigned long dr6 ;      unsigned long dr7 ;      unsigned long eff_db [ KVM_NR_DB_REGS ] ;      unsigned long guest_debug_dr7 ;        u64 mcg_cap ;      u64 mcg_status ;      u64 mcg_ctl ;      u64 * mce_banks ;             u64 mmio_gva ;      unsigned access ;      gfn_t mmio_gfn ;      u64 mmio_gen ;        struct kvm_pmu pmu ;             unsigned long singlestep_rip ;             u64 hv_vapic ;        cpumask_var_t wbinvd_dirty_mask ;        unsigned long last_retry_eip ;      unsigned long last_retry_addr ;        struct {          bool halted ;          gfn_t gfns [ roundup_pow_of_two ( ASYNC_PF_PER_VCPU ) ] ;          struct gfn_to_hva_cache data ;          u64 msr_val ;          u32 id ;          bool send_user_only ;      } apf ;             struct {          u64 length ;          u64 status ;      } osvw ;        struct {          u64 msr_val ;          struct gfn_to_hva_cache data ;      } pv_eoi ;             bool write_fault_to_shadow_pgtable ;             unsigned long exit_qualification ;             struct {          bool pv_unhalted ;      } pv ; } ;

整個arch結構真是長,很适合湊篇幅,很多結構其他過程涉及到的再提吧,反正我也不知道。

kvm_arch_vcpu_init初始化了x86在虛拟化底層的實作函數,首先是pv和emulate_ctxt,這些不支援VMX下的模拟虛拟化,尤其是vcpu->arch.emulate_ctxt.ops = &emulate_ops,emulate_ops初始化虛拟化模拟的對象函數。

static struct x86_emulate_ops emulate_ops = {

.read_std = kvm_read_guest_virt_system,

.write_std = kvm_write_guest_virt_system,

.fetch = kvm_fetch_guest_virt,

.read_emulated = emulator_read_emulated,

.write_emulated = emulator_write_emulated,

.cmpxchg_emulated = emulator_cmpxchg_emulated,

.invlpg = emulator_invlpg,

.pio_in_emulated = emulator_pio_in_emulated,

.pio_out_emulated = emulator_pio_out_emulated,

.get_segment = emulator_get_segment,

.set_segment = emulator_set_segment,

.get_cached_segment_base = emulator_get_cached_segment_base,

.get_gdt = emulator_get_gdt,

.get_idt = emulator_get_idt,

.set_gdt = emulator_set_gdt,

.set_idt = emulator_set_idt,

.get_cr = emulator_get_cr,

.set_cr = emulator_set_cr,

.cpl = emulator_get_cpl,

.get_dr = emulator_get_dr,

.set_dr = emulator_set_dr,

.set_msr = emulator_set_msr,

.get_msr = emulator_get_msr,

.halt = emulator_halt,

.wbinvd = emulator_wbinvd,

.fix_hypercall = emulator_fix_hypercall,

.get_fpu = emulator_get_fpu,

.put_fpu = emulator_put_fpu,

.intercept = emulator_intercept,

.get_cpuid = emulator_get_cpuid,

};

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 static struct x86_emulate_ops emulate_ops = {      . read_std              = kvm_read_guest_virt_system ,      . write_std            = kvm_write_guest_virt_system ,      . fetch                = kvm_fetch_guest_virt ,      . read_emulated        = emulator_read_emulated ,      . write_emulated        = emulator_write_emulated ,      . cmpxchg_emulated      = emulator_cmpxchg_emulated ,      . invlpg                = emulator_invlpg ,      . pio_in_emulated      = emulator_pio_in_emulated ,      . pio_out_emulated      = emulator_pio_out_emulated ,      . get_segment          = emulator_get_segment ,      . set_segment          = emulator_set_segment ,      . get_cached_segment_base = emulator_get_cached_segment_base ,      . get_gdt              = emulator_get_gdt ,      . get_idt          = emulator_get_idt ,      . set_gdt              = emulator_set_gdt ,      . set_idt          = emulator_set_idt ,      . get_cr                = emulator_get_cr ,      . set_cr                = emulator_set_cr ,      . cpl                  = emulator_get_cpl ,      . get_dr                = emulator_get_dr ,      . set_dr                = emulator_set_dr ,      . set_msr              = emulator_set_msr ,      . get_msr              = emulator_get_msr ,      . halt                  = emulator_halt ,      . wbinvd                = emulator_wbinvd ,      . fix_hypercall        = emulator_fix_hypercall ,      . get_fpu              = emulator_get_fpu ,      . put_fpu              = emulator_put_fpu ,      . intercept            = emulator_intercept ,      . get_cpuid            = emulator_get_cpuid , } ;

x86_emulate_ops函數看看就好,實際上也很少有人放棄vmx直接軟體模拟。後面又有mp_state,給pio_data配置設定了一個page,kvm_set_tsc_khz設定TSC,kvm_mmu_create則是初始化MMU的函數,裡面的函數都是位址轉換的重點,在記憶體虛拟化重點提到。kvm_create_lapic初始化lapic,初始化mce_banks結構,還有pv_time,xcr0,xstat,pmu等,類似x86硬體結構上需要存在的,OS底層需要看到的硬體名稱都要有對應的軟體結構。

回到vmx_create_vcpu,vmx的guest_msrs配置設定得到一個page,後面是vmcs的配置設定,vmx->loaded_vmcs->vmcs = alloc_vmcs(),alloc_vmcs為目前cpu執行alloc_vmcs_cpu,alloc_vmcs_cpu中alloc_pages_exact_node配置設定給vmcs,alloc_pages_exact_node調用__alloc_pages實作,原來以為vmcs占用了一個page,但此處從夥伴系統申請了2^vmcs_config.order頁,此處vmcs_config在setup_vmcs_config中初始化,vmcs_conf->order = get_order(vmcs_config.size),而vmcs_conf->size = vmx_msr_high & 0x1fff,又rdmsr(MSR_IA32_VMX_BASIC, vmx_msr_low, vmx_msr_high),此處size由于與0x1fff與運算,大小必然小于4k,order則為0,然來繞去還是一個page大小。這麼做估計是為了相容vmcs_config中的size計算。

下面根據vmm_exclusive進行kvm_cpu_vmxon,進入vmx模式,初始化loaded_vmcs,然後用kvm_cpu_vmxoff退出vmx模式。

vmx_vcpu_load加載VCPU的資訊,切換到指定cpu,進入到vmx模式,将loaded_vmcs的vmcs和目前cpu的vmcs綁定到一起。vmx_vcpu_setup則是初始化vmcs内容,主要是指派計算,下面的vmx_vcpu_put則是vmx_vcpu_load的反運算。下面還有一些apic,nested,pml就不說了。

vmx_create_vcpu結束就直接回到kvm_vm_ioctl_create_vcpu函數,下面是kvm_arch_vcpu_setup,整個就一條線到kvm_arch_vcpu_load函數,主要有kvm_x86_ops->vcpu_load(vcpu, cpu)和tsc處理,vcpu_load就是vmx_vcpu_load,剛說了,就是進入vcpu模式下準備工作。

kvm_arch_vcpu_setup後面是create_vcpu_fd為proc建立控制fd,讓qemu使用。kvm_arch_vcpu_postcreate則是馬後炮般,重新vcpu_load,寫msr,tsc。

如此整個vcpu就建立完成了。

6.KVM_RUN

KVM run涉及内容也不少,先寫完記憶體虛拟化之後再開篇專門寫RUN流程。

下一篇:

KVM源代碼分析4:記憶體虛拟化

———-完———-

——————–下面未編輯的留存————————————-

給vmcs配置設定空間并初始化,在alloc_vmcs_cpu配置設定一個頁大小記憶體,用來儲存vm和vmm資訊。

vmx-&gt;vmcs = alloc_vmcs();

if (!vmx-&gt;vmcs)

goto free_msrs;

vmcs_init(vmx-&gt;vmcs);

1 2 3 4 5      vmx - & gt ; vmcs = alloc_vmcs ( ) ;      if ( ! vmx - & gt ; vmcs )          goto free_msrs ;        vmcs_init ( vmx - & gt ; vmcs ) ;

執行vm entry的時候将vmm狀态儲存到vmcs的host area,并加載對應vm的vmcs guest area資訊到CPU中,vm exit的時候則反之,vmcs具體結構配置設定由硬體實作,程式員隻需要通過VMWRITE和VMREAD指令去通路。

vmx執行完後,回到kvm_vm_ioctl_create_vcpu函數。kvm_arch_vcpu_reset對vcpu的結構進行初始化,後面一些就是檢查vcpu的合法性,最後和kvm串接到一起。

vcpu的建立到此結束,下面說一下vcpu的運作。

VCPU一旦建立成功,後續的控制基本上從kvm_vcpu_ioctl開始,控制開關有KVM_RUN,KVM_GET_REGS,KVM_SET_REGS,KVM_GET_SREGS,KVM_SET_SREGS,KVM_GET_MP_STATE,KVM_SET_MP_STATE,KVM_TRANSLATE,KVM_SET_GUEST_DEBUG,KVM_SET_SIGNAL_MASK等,如果不清楚具體開關作用,可以直接到qemu搜尋對應開關代碼,一目了然。

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-&gt;prepare_guest_switch(vcpu);

if (vcpu-&gt;fpu_active)

kvm_load_guest_fpu(vcpu);

kvm_load_guest_xcr0(vcpu);

vcpu-&gt;mode = IN_GUEST_MODE;

/* We should set -&gt;mode before check -&gt;requests,

* see the comment in make_all_cpus_request.

*/

smp_mb();

local_irq_disable();

1 2 3 4 5 6 7 8 9 10 11 12 13 kvm_x86_ops - & gt ; prepare_guest_switch ( vcpu ) ;      if ( vcpu - & gt ; fpu_active )          kvm_load_guest_fpu ( vcpu ) ;      kvm_load_guest_xcr0 ( vcpu ) ;        vcpu - & gt ; mode = IN_GUEST_MODE ;             smp_mb ( ) ;        local_irq_disable ( ) ;

然後加載guest的寄存器等資訊,fpu,xcr0,将vcpu模式設定為guest狀态,屏蔽中斷響應,準備進入guest。但仍進行一次檢查,vcpu->mode和vcpu->requests等,如果有問題,則恢複host狀态。

kvm_guest_enter做了兩件事:account_system_vtime計算虛拟機系統時間;rcu_virt_note_context_switch對rcu鎖資料進行保護,完成上下文切換。

準備工作搞定,kvm_x86_ops->run(vcpu),開始運作guest,由vmx_vcpu_run實作。

if (vmx-&gt;emulation_required &amp;&amp; emulate_invalid_guest_state)

return;

if (test_bit(VCPU_REGS_RSP, (unsigned long *)&amp;vcpu-&gt;arch.regs_dirty))

vmcs_writel(GUEST_RSP, vcpu-&gt;arch.regs[VCPU_REGS_RSP]);

if (test_bit(VCPU_REGS_RIP, (unsigned long *)&amp;vcpu-&gt;arch.regs_dirty))

vmcs_writel(GUEST_RIP, vcpu-&gt;arch.regs[VCPU_REGS_RIP]);

1 2 3 4 5 6 7      if ( vmx - & gt ; emulation_required & amp ; & amp ; emulate_invalid_guest_state )          return ;        if ( test_bit ( VCPU_REGS_RSP , ( unsigned long * ) & amp ; vcpu - & gt ; arch . regs_dirty ) )          vmcs_writel ( GUEST_RSP , vcpu - & gt ; arch . regs [ VCPU_REGS_RSP ] ) ;      if ( test_bit ( VCPU_REGS_RIP , ( unsigned long * ) & amp ; vcpu - & gt ; arch . regs_dirty ) )          vmcs_writel ( GUEST_RIP , vcpu - & gt ; arch . regs [ VCPU_REGS_RIP ] ) ;

判斷模拟器,RSP,RIP寄存器值。

主要功能在這段内聯彙編上

asm(

/* Store host registers */

&quot;push %%&quot;R&quot;dx; push %%&quot;R&quot;bp;&quot;

&quot;push %%&quot;R&quot;cx nt&quot; /* placeholder for guest rcx */

&quot;push %%&quot;R&quot;cx nt&quot;

//如果vcpu host rsp和環境不等,則将其拷貝到vpu上

&quot;cmp %%&quot;R&quot;sp, %c[host_rsp](%0) nt&quot;

&quot;je 1f nt&quot;

&quot;mov %%&quot;R&quot;sp, %c[host_rsp](%0) nt&quot;

__ex(ASM_VMX_VMWRITE_RSP_RDX) &quot;nt&quot;//__kvm_handle_fault_on_reboot write host rsp

&quot;1: nt&quot;

/* Reload cr2 if changed */

&quot;mov %c[cr2](%0), %%&quot;R&quot;ax nt&quot;

&quot;mov %%cr2, %%&quot;R&quot;dx nt&quot;

//環境上cr2值和vpu上的值不同,則将vpu上值拷貝到環境上

&quot;cmp %%&quot;R&quot;ax, %%&quot;R&quot;dx nt&quot;

&quot;je 2f nt&quot;

&quot;mov %%&quot;R&quot;ax, %%cr2 nt&quot;

&quot;2: nt&quot;

/* Check if vmlaunch of vmresume is needed */

&quot;cmpl $0, %c[launched](%0) nt&quot;

/* Load guest registers. Don't clobber flags. */

&quot;mov %c[rax](%0), %%&quot;R&quot;ax nt&quot;

&quot;mov %c[rbx](%0), %%&quot;R&quot;bx nt&quot;

&quot;mov %c[rdx](%0), %%&quot;R&quot;dx nt&quot;

&quot;mov %c[rsi](%0), %%&quot;R&quot;si nt&quot;

&quot;mov %c[rdi](%0), %%&quot;R&quot;di nt&quot;

&quot;mov %c[rbp](%0), %%&quot;R&quot;bp nt&quot;

#ifdef CONFIG_X86_64

&quot;mov %c[r8](%0), %%r8 nt&quot;

&quot;mov %c[r9](%0), %%r9 nt&quot;

&quot;mov %c[r10](%0), %%r10 nt&quot;

&quot;mov %c[r11](%0), %%r11 nt&quot;

&quot;mov %c[r12](%0), %%r12 nt&quot;

&quot;mov %c[r13](%0), %%r13 nt&quot;

&quot;mov %c[r14](%0), %%r14 nt&quot;

&quot;mov %c[r15](%0), %%r15 nt&quot;

#endif

&quot;mov %c[rcx](%0), %%&quot;R&quot;cx nt&quot; /* kills %0 (ecx) */

/* Enter guest mode */

//此處和cmpl $0, %c[launched](%0)是對應的,此處選擇進入guest的兩種模式

//RESUME和LAUNCH,通過__ex __kvm_handle_fault_on_reboot執行

&quot;jne .Llaunched nt&quot;

__ex(ASM_VMX_VMLAUNCH) &quot;nt&quot;

&quot;jmp .Lkvm_vmx_return nt&quot;

&quot;.Llaunched: &quot; __ex(ASM_VMX_VMRESUME) &quot;nt&quot;

//退出vmx,儲存guest資訊,加載host資訊

&quot;.Lkvm_vmx_return: &quot;

/* Save guest registers, load host registers, keep flags */

&quot;mov %0, %c[wordsize](%%&quot;R&quot;sp) nt&quot;

&quot;pop %0 nt&quot;

&quot;mov %%&quot;R&quot;ax, %c[rax](%0) nt&quot;

&quot;mov %%&quot;R&quot;bx, %c[rbx](%0) nt&quot;

&quot;pop&quot;Q&quot; %c[rcx](%0) nt&quot;

&quot;mov %%&quot;R&quot;dx, %c[rdx](%0) nt&quot;

&quot;mov %%&quot;R&quot;si, %c[rsi](%0) nt&quot;

&quot;mov %%&quot;R&quot;di, %c[rdi](%0) nt&quot;

&quot;mov %%&quot;R&quot;bp, %c[rbp](%0) nt&quot;

#ifdef CONFIG_X86_64

&quot;mov %%r8, %c[r8](%0) nt&quot;

&quot;mov %%r9, %c[r9](%0) nt&quot;

&quot;mov %%r10, %c[r10](%0) nt&quot;

&quot;mov %%r11, %c[r11](%0) nt&quot;

&quot;mov %%r12, %c[r12](%0) nt&quot;

&quot;mov %%r13, %c[r13](%0) nt&quot;

&quot;mov %%r14, %c[r14](%0) nt&quot;

&quot;mov %%r15, %c[r15](%0) nt&quot;

#endif

&quot;mov %%cr2, %%&quot;R&quot;ax nt&quot;

&quot;mov %%&quot;R&quot;ax, %c[cr2](%0) nt&quot;

&quot;pop %%&quot;R&quot;bp; pop %%&quot;R&quot;dx nt&quot;

&quot;setbe %c[fail](%0) nt&quot;

: : &quot;c&quot;(vmx), &quot;d&quot;((unsigned long)HOST_RSP),

//下面加了前面寄存器的指針值,對應具體結構的值

[launched]&quot;i&quot;(offsetof(struct vcpu_vmx, launched)),

[fail]&quot;i&quot;(offsetof(struct vcpu_vmx, fail)),

[host_rsp]&quot;i&quot;(offsetof(struct vcpu_vmx, host_rsp)),

[rax]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RAX])),

[rbx]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RBX])),

[rcx]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RCX])),

[rdx]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RDX])),

[rsi]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RSI])),

[rdi]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RDI])),

[rbp]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RBP])),

#ifdef CONFIG_X86_64

[r8]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R8])),

[r9]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R9])),

[r10]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R10])),

[r11]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R11])),

[r12]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R12])),

[r13]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R13])),

[r14]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R14])),

[r15]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R15])),

#endif

[cr2]&quot;i&quot;(offsetof(struct vcpu_vmx, vcpu.arch.cr2)),

[wordsize]&quot;i&quot;(sizeof(ulong))

: &quot;cc&quot;, &quot;memory&quot;

, R&quot;ax&quot;, R&quot;bx&quot;, R&quot;di&quot;, R&quot;si&quot;

#ifdef CONFIG_X86_64

, &quot;r8&quot;, &quot;r9&quot;, &quot;r10&quot;, &quot;r11&quot;, &quot;r12&quot;, &quot;r13&quot;, &quot;r14&quot;, &quot;r15&quot;

#endif

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 asm (                           & quot ; push % % & quot ; R & quot ; dx ; push % % & quot ; R & quot ; bp ; & quot ;          & quot ; push % % & quot ; R & quot ; cx nt & quot ;          & quot ; push % % & quot ; R & quot ; cx nt & quot ;                  //如果vcpu host rsp和環境不等,則将其拷貝到vpu上          & quot ; cmp % % & quot ; R & quot ; sp , % c [ host_rsp ] ( % 0 ) nt & quot ;          & quot ; je 1f nt & quot ;          & quot ; mov % % & quot ; R & quot ; sp , % c [ host_rsp ] ( % 0 ) nt & quot ;          __ex ( ASM_VMX_VMWRITE_RSP_RDX ) & quot ; nt & quot ; //__kvm_handle_fault_on_reboot write host rsp          & quot ; 1 : nt & quot ;                   & quot ; mov % c [ cr2 ] ( % 0 ) , % % & quot ; R & quot ; ax nt & quot ;          & quot ; mov % % cr2 , % % & quot ; R & quot ; dx nt & quot ;                  //環境上cr2值和vpu上的值不同,則将vpu上值拷貝到環境上          & quot ; cmp % % & quot ; R & quot ; ax , % % & quot ; R & quot ; dx nt & quot ;          & quot ; je 2f nt & quot ;          & quot ; mov % % & quot ; R & quot ; ax , % % cr2 nt & quot ;          & quot ; 2 : nt & quot ;                   & quot ; cmpl $ 0 , % c [ launched ] ( % 0 ) nt & quot ;                   & quot ; mov % c [ rax ] ( % 0 ) , % % & quot ; R & quot ; ax nt & quot ;          & quot ; mov % c [ rbx ] ( % 0 ) , % % & quot ; R & quot ; bx nt & quot ;          & quot ; mov % c [ rdx ] ( % 0 ) , % % & quot ; R & quot ; dx nt & quot ;          & quot ; mov % c [ rsi ] ( % 0 ) , % % & quot ; R & quot ; si nt & quot ;          & quot ; mov % c [ rdi ] ( % 0 ) , % % & quot ; R & quot ; di nt & quot ;          & quot ; mov % c [ rbp ] ( % 0 ) , % % & quot ; R & quot ; bp nt & quot ; #ifdef CONFIG_X86_64          & quot ; mov % c [ r8 ] ( % 0 ) ,    % % r8   nt & quot ;          & quot ; mov % c [ r9 ] ( % 0 ) ,    % % r9   nt & quot ;          & quot ; mov % c [ r10 ] ( % 0 ) , % % r10 nt & quot ;          & quot ; mov % c [ r11 ] ( % 0 ) , % % r11 nt & quot ;          & quot ; mov % c [ r12 ] ( % 0 ) , % % r12 nt & quot ;          & quot ; mov % c [ r13 ] ( % 0 ) , % % r13 nt & quot ;          & quot ; mov % c [ r14 ] ( % 0 ) , % % r14 nt & quot ;          & quot ; mov % c [ r15 ] ( % 0 ) , % % r15 nt & quot ; #endif          & quot ; mov % c [ rcx ] ( % 0 ) , % % & quot ; R & quot ; cx nt & quot ;                             //此處和cmpl $0, %c[launched](%0)是對應的,此處選擇進入guest的兩種模式                  //RESUME和LAUNCH,通過__ex  __kvm_handle_fault_on_reboot執行          & quot ; jne . Llaunched nt & quot ;          __ex ( ASM_VMX_VMLAUNCH ) & quot ; nt & quot ;          & quot ; jmp . Lkvm_vmx_return nt & quot ;          & quot ; . Llaunched : & quot ; __ex ( ASM_VMX_VMRESUME ) & quot ; nt & quot ;                  //退出vmx,儲存guest資訊,加載host資訊          & quot ; . Lkvm_vmx_return : & quot ;                   & quot ; mov % 0 , % c [ wordsize ] ( % % & quot ; R & quot ; sp ) nt & quot ;          & quot ; pop % 0 nt & quot ;          & quot ; mov % % & quot ; R & quot ; ax , % c [ rax ] ( % 0 ) nt & quot ;          & quot ; mov % % & quot ; R & quot ; bx , % c [ rbx ] ( % 0 ) nt & quot ;          & quot ; pop & quot ; Q & quot ; % c [ rcx ] ( % 0 ) nt & quot ;          & quot ; mov % % & quot ; R & quot ; dx , % c [ rdx ] ( % 0 ) nt & quot ;          & quot ; mov % % & quot ; R & quot ; si , % c [ rsi ] ( % 0 ) nt & quot ;          & quot ; mov % % & quot ; R & quot ; di , % c [ rdi ] ( % 0 ) nt & quot ;          & quot ; mov % % & quot ; R & quot ; bp , % c [ rbp ] ( % 0 ) nt & quot ; #ifdef CONFIG_X86_64          & quot ; mov % % r8 ,    % c [ r8 ] ( % 0 ) nt & quot ;          & quot ; mov % % r9 ,    % c [ r9 ] ( % 0 ) nt & quot ;          & quot ; mov % % r10 , % c [ r10 ] ( % 0 ) nt & quot ;          & quot ; mov % % r11 , % c [ r11 ] ( % 0 ) nt & quot ;          & quot ; mov % % r12 , % c [ r12 ] ( % 0 ) nt & quot ;          & quot ; mov % % r13 , % c [ r13 ] ( % 0 ) nt & quot ;          & quot ; mov % % r14 , % c [ r14 ] ( % 0 ) nt & quot ;          & quot ; mov % % r15 , % c [ r15 ] ( % 0 ) nt & quot ; #endif          & quot ; mov % % cr2 , % % & quot ; R & quot ; ax   nt & quot ;          & quot ; mov % % & quot ; R & quot ; ax , % c [ cr2 ] ( % 0 ) nt & quot ;            & quot ; pop    % % & quot ; R & quot ; bp ; pop    % % & quot ; R & quot ; dx nt & quot ;          & quot ; setbe % c [ fail ] ( % 0 ) nt & quot ;            : : & quot ; c & quot ; ( vmx ) , & quot ; d & quot ; ( ( unsigned long ) HOST_RSP ) ,   //下面加了前面寄存器的指針值,對應具體結構的值          [ launched ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , launched ) ) ,          [ fail ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , fail ) ) ,          [ host_rsp ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , host_rsp ) ) ,          [ rax ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_RAX ] ) ) ,          [ rbx ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_RBX ] ) ) ,          [ rcx ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_RCX ] ) ) ,          [ rdx ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_RDX ] ) ) ,          [ rsi ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_RSI ] ) ) ,          [ rdi ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_RDI ] ) ) ,          [ rbp ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_RBP ] ) ) , #ifdef CONFIG_X86_64          [ r8 ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_R8 ] ) ) ,          [ r9 ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_R9 ] ) ) ,          [ r10 ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_R10 ] ) ) ,          [ r11 ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_R11 ] ) ) ,          [ r12 ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_R12 ] ) ) ,          [ r13 ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_R13 ] ) ) ,          [ r14 ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_R14 ] ) ) ,          [ r15 ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . regs [ VCPU_REGS_R15 ] ) ) , #endif          [ cr2 ] & quot ; i & quot ; ( offsetof ( struct vcpu_vmx , vcpu . arch . cr2 ) ) ,          [ wordsize ] & quot ; i & quot ; ( sizeof ( ulong ) )            : & quot ; cc & quot ; , & quot ; memory & quot ;          , R & quot ; ax & quot ; , R & quot ; bx & quot ; , R & quot ; di & quot ; , R & quot ; si & quot ; #ifdef CONFIG_X86_64          , & quot ; r8 & quot ; , & quot ; r9 & quot ; , & quot ; r10 & quot ; , & quot ; r11 & quot ; , & quot ; r12 & quot ; , & quot ; r13 & quot ; , & quot ; r14 & quot ; , & quot ; r15 & quot ; #endif

以上代碼相對容易了解的,根據注釋大緻清楚了具體作用。

然後就是恢複系統NMI等中斷:

vmx_complete_atomic_exit(vmx);

vmx_recover_nmi_blocking(vmx);

vmx_complete_interrupts(vmx);

1 2 3 vmx_complete_atomic_exit ( vmx ) ; vmx_recover_nmi_blocking ( vmx ) ; vmx_complete_interrupts ( vmx ) ;

回到vcpu_enter_guest,通過hw_breakpoint_restore恢複硬體斷點。

if (hw_breakpoint_active())

hw_breakpoint_restore();

kvm_get_msr(vcpu, MSR_IA32_TSC, &amp;vcpu-&gt;arch.last_guest_tsc);

//設定vcpu模式,恢複host相關内容

vcpu-&gt;mode = OUTSIDE_GUEST_MODE;

smp_wmb();

local_irq_enable();

++vcpu-&gt;stat.exits;

/*

* We must have an instruction between local_irq_enable() and

* kvm_guest_exit(), so the timer interrupt isn't delayed by

* the interrupt shadow. The stat.exits increment will do nicely.

* But we need to prevent reordering, hence this barrier():

*/

barrier();

//重新整理系統時間

kvm_guest_exit();

preempt_enable();

vcpu-&gt;srcu_idx = srcu_read_lock(&amp;vcpu-&gt;kvm-&gt;srcu);

/*

* Profile KVM exit RIPs:

*/

if (unlikely(prof_on == KVM_PROFILING)) {

unsigned long rip = kvm_rip_read(vcpu);

profile_hit(KVM_PROFILING, (void *)rip);

}

kvm_lapic_sync_from_vapic(vcpu);

//處理vmx退出

r = kvm_x86_ops-&gt;handle_exit(vcpu);

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37      if ( hw_breakpoint_active ( ) )          hw_breakpoint_restore ( ) ;        kvm_get_msr ( vcpu , MSR_IA32_TSC , & amp ; vcpu - & gt ; arch . last_guest_tsc ) ;   //設定vcpu模式,恢複host相關内容      vcpu - & gt ; mode = OUTSIDE_GUEST_MODE ;      smp_wmb ( ) ;      local_irq_enable ( ) ;        ++ vcpu - & gt ; stat . exits ;             barrier ( ) ; //重新整理系統時間      kvm_guest_exit ( ) ;        preempt_enable ( ) ;        vcpu - & gt ; srcu_idx = srcu_read_lock ( & amp ; vcpu - & gt ; kvm - & gt ; srcu ) ;             if ( unlikely ( prof_on == KVM_PROFILING ) ) {          unsigned long rip = kvm_rip_read ( vcpu ) ;          profile_hit ( KVM_PROFILING , ( void * ) rip ) ;      }        kvm_lapic_sync_from_vapic ( vcpu ) ; //處理vmx退出      r = kvm_x86_ops - & gt ; handle_exit ( vcpu ) ;

handle_exit退出函數由vmx_handle_exit實作,主要設定vcpu->run->exit_reason,讓外部感覺退出原因,并對應處理。對于vpu而言,handle_exit隻是意味着一個傳統linux一個時間片的結束,後續的工作都是由handle完成的,handle_exit對應的函數集如下:

static int (*kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = {

[EXIT_REASON_EXCEPTION_NMI] = handle_exception,

[EXIT_REASON_EXTERNAL_INTERRUPT] = handle_external_interrupt,

[EXIT_REASON_TRIPLE_FAULT] = handle_triple_fault,

[EXIT_REASON_NMI_WINDOW] = handle_nmi_window,

[EXIT_REASON_IO_INSTRUCTION] = handle_io,

[EXIT_REASON_CR_ACCESS] = handle_cr,

[EXIT_REASON_DR_ACCESS] = handle_dr,

[EXIT_REASON_CPUID] = handle_cpuid,

[EXIT_REASON_MSR_READ] = handle_rdmsr,

[EXIT_REASON_MSR_WRITE] = handle_wrmsr,

[EXIT_REASON_PENDING_INTERRUPT] = handle_interrupt_window,

[EXIT_REASON_HLT] = handle_halt,

[EXIT_REASON_INVD] = handle_invd,

[EXIT_REASON_INVLPG] = handle_invlpg,

[EXIT_REASON_VMCALL] = handle_vmcall,

[EXIT_REASON_VMCLEAR] = handle_vmx_insn,

[EXIT_REASON_VMLAUNCH] = handle_vmx_insn,

[EXIT_REASON_VMPTRLD] = handle_vmx_insn,

[EXIT_REASON_VMPTRST] = handle_vmx_insn,

[EXIT_REASON_VMREAD] = handle_vmx_insn,

[EXIT_REASON_VMRESUME] = handle_vmx_insn,

[EXIT_REASON_VMWRITE] = handle_vmx_insn,

[EXIT_REASON_VMOFF] = handle_vmx_insn,

[EXIT_REASON_VMON] = handle_vmx_insn,

[EXIT_REASON_TPR_BELOW_THRESHOLD] = handle_tpr_below_threshold,

[EXIT_REASON_APIC_ACCESS] = handle_apic_access,

[EXIT_REASON_WBINVD] = handle_wbinvd,

[EXIT_REASON_XSETBV] = handle_xsetbv,

[EXIT_REASON_TASK_SWITCH] = handle_task_switch,

[EXIT_REASON_MCE_DURING_VMENTRY] = handle_machine_check,

[EXIT_REASON_EPT_VIOLATION] = handle_ept_violation,

[EXIT_REASON_EPT_MISCONFIG] = handle_ept_misconfig,

[EXIT_REASON_PAUSE_INSTRUCTION] = handle_pause,

[EXIT_REASON_MWAIT_INSTRUCTION] = handle_invalid_op,

[EXIT_REASON_MONITOR_INSTRUCTION] = handle_invalid_op,

};

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 static int ( * kvm_vmx_exit_handlers [ ] ) ( struct kvm_vcpu * vcpu ) = {      [ EXIT_REASON_EXCEPTION_NMI ]            = handle_exception ,      [ EXIT_REASON_EXTERNAL_INTERRUPT ]        = handle_external_interrupt ,      [ EXIT_REASON_TRIPLE_FAULT ]              = handle_triple_fault ,      [ EXIT_REASON_NMI_WINDOW ]            = handle_nmi_window ,      [ EXIT_REASON_IO_INSTRUCTION ]            = handle_io ,      [ EXIT_REASON_CR_ACCESS ]                = handle_cr ,      [ EXIT_REASON_DR_ACCESS ]                = handle_dr ,      [ EXIT_REASON_CPUID ]                    = handle_cpuid ,      [ EXIT_REASON_MSR_READ ]                  = handle_rdmsr ,      [ EXIT_REASON_MSR_WRITE ]                = handle_wrmsr ,      [ EXIT_REASON_PENDING_INTERRUPT ]        = handle_interrupt_window ,      [ EXIT_REASON_HLT ]                      = handle_halt ,      [ EXIT_REASON_INVD ]                = handle_invd ,      [ EXIT_REASON_INVLPG ]                = handle_invlpg ,      [ EXIT_REASON_VMCALL ]                    = handle_vmcall ,      [ EXIT_REASON_VMCLEAR ]                    = handle_vmx_insn ,      [ EXIT_REASON_VMLAUNCH ]                  = handle_vmx_insn ,      [ EXIT_REASON_VMPTRLD ]                  = handle_vmx_insn ,      [ EXIT_REASON_VMPTRST ]                  = handle_vmx_insn ,      [ EXIT_REASON_VMREAD ]                    = handle_vmx_insn ,      [ EXIT_REASON_VMRESUME ]                  = handle_vmx_insn ,      [ EXIT_REASON_VMWRITE ]                  = handle_vmx_insn ,      [ EXIT_REASON_VMOFF ]                    = handle_vmx_insn ,      [ EXIT_REASON_VMON ]                      = handle_vmx_insn ,      [ EXIT_REASON_TPR_BELOW_THRESHOLD ]      = handle_tpr_below_threshold ,      [ EXIT_REASON_APIC_ACCESS ]              = handle_apic_access ,      [ EXIT_REASON_WBINVD ]                    = handle_wbinvd ,      [ EXIT_REASON_XSETBV ]                    = handle_xsetbv ,      [ EXIT_REASON_TASK_SWITCH ]              = handle_task_switch ,      [ EXIT_REASON_MCE_DURING_VMENTRY ]        = handle_machine_check ,      [ EXIT_REASON_EPT_VIOLATION ]            = handle_ept_violation ,      [ EXIT_REASON_EPT_MISCONFIG ]            = handle_ept_misconfig ,      [ EXIT_REASON_PAUSE_INSTRUCTION ]        = handle_pause ,      [ EXIT_REASON_MWAIT_INSTRUCTION ]            = handle_invalid_op ,      [ EXIT_REASON_MONITOR_INSTRUCTION ]      = handle_invalid_op , } ;

有handle_task_switch進行任務切換,handle_io處理qemu的外部模拟IO等,具體處理内容後面在寫。

再次退回到__vcpu_run函數,在while (r > 0)中,循環受vcpu_enter_guest傳回值控制,隻有運作異常的時候才退出循環,否則通過kvm_resched一直運作下去。

if (need_resched()) {

srcu_read_unlock(&amp;kvm-&gt;srcu, vcpu-&gt;srcu_idx);

kvm_resched(vcpu);

vcpu-&gt;srcu_idx = srcu_read_lock(&amp;kvm-&gt;srcu);

}

1 2 3 4 5          if ( need_resched ( ) ) {              srcu_read_unlock ( & amp ; kvm - & gt ; srcu , vcpu - & gt ; srcu_idx ) ;              kvm_resched ( vcpu ) ;              vcpu - & gt ; srcu_idx = srcu_read_lock ( & amp ; kvm - & gt ; srcu ) ;          }

再退就到了kvm_arch_vcpu_ioctl_run函數,此時kvm run的執行也結束。

KVM cpu虛拟化的了解基本如上,涉及到的具體細節有時間後開篇另說。

KVM源代碼分析未完待續

終于把KVM源代碼分析3:CPU虛拟化寫完了,雖然還有run的部分另外在寫,還是先看一下記憶體虛拟化部分。

代碼版本:https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git v4.5

在虛拟機的建立與運作中pc_init_pci負責在qemu中初始化虛拟機,記憶體初始化也是在這裡完成的,還是一步步從qemu說起,在vl.c的main函數中有ram_size參數,由qemu入參辨別QEMU_OPTION_m設定,顧名思義就是虛拟機記憶體的大小,通過machine->init一步步傳遞給pc_init1函數。在這裡分出了above_4g_mem_size和below_4g_mem_size,即高低端記憶體(也不一定是32bit機器..),然後開始初始化記憶體,即pc_memory_init,記憶體通過memory_region_init_ram下面的qemu_ram_alloc配置設定,使用qemu_ram_alloc_from_ptr。

插播qemu對記憶體條的模拟管理,是通過RAMBlock和ram_list管理的,RAMBlock就是每次申請的記憶體池,ram_list則是RAMBlock的連結清單,他們結構如下:

typedef struct RAMBlock {

//對應宿主的記憶體位址

uint8_t *host;

//block在ramlist中的偏移

ram_addr_t offset;

//block長度

ram_addr_t length;

uint32_t flags;

//block名字

char idstr[256];

QLIST_ENTRY(RAMBlock) next;

#if defined(__linux__) && !defined(TARGET_S390X)

int fd;

#endif

} RAMBlock;

typedef struct RAMList {

//看代碼了解就是list的head,但是不知道為啥叫dirty...

uint8_t *phys_dirty;

QLIST_HEAD(ram, RAMBlock) blocks;

} RAMList;

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 typedef struct RAMBlock { //對應宿主的記憶體位址      uint8_t * host ; //block在ramlist中的偏移      ram_addr_t offset ; //block長度      ram_addr_t length ;      uint32_t flags ; //block名字      char idstr [ 256 ] ;      QLIST_ENTRY ( RAMBlock ) next ; #if defined(__linux__) && !defined(TARGET_S390X)      int fd ; #endif } RAMBlock ;   typedef struct RAMList { //看代碼了解就是list的head,但是不知道為啥叫dirty...      uint8_t * phys_dirty ;      QLIST_HEAD ( ram , RAMBlock ) blocks ; } RAMList ;

下面再回到qemu_ram_alloc_from_ptr函數,使用find_ram_offset指派給new block的offset,find_ram_offset具體工作模型已經在KVM源代碼分析2:虛拟機的建立與運作中提到了,不贅述。然後是一串判斷,在kvm_enabled的情況下使用new_block->host = kvm_vmalloc(size),最終記憶體是qemu_vmalloc配置設定的,使用qemu_memalign幹活。

void *qemu_memalign(size_t alignment, size_t size)

{

void *ptr;

//使用posix進行記憶體針對頁大小對齊

#if defined(_POSIX_C_SOURCE) && !defined(__sun__)

int ret;

ret = posix_memalign(&ptr, alignment, size);

if (ret != 0) {

fprintf(stderr, "Failed to allocate %zu B: %sn",

size, strerror(ret));

abort();

}

#elif defined(CONFIG_BSD)

ptr = qemu_oom_check(valloc(size));

#else

//所謂檢查oom就是看memalign對應malloc申請記憶體是否成功

ptr = qemu_oom_check(memalign(alignment, size));

#endif

trace_qemu_memalign(alignment, size, ptr);

return ptr;

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void * qemu_memalign ( size_t alignment , size_t size ) {      void * ptr ; //使用posix進行記憶體針對頁大小對齊 #if defined(_POSIX_C_SOURCE) && !defined(__sun__)      int ret ;      ret = posix_memalign ( & ptr , alignment , size ) ;      if ( ret != 0 ) {          fprintf ( stderr , "Failed to allocate %zu B: %sn" ,                  size , strerror ( ret ) ) ;          abort ( ) ;      } #elif defined(CONFIG_BSD)      ptr = qemu_oom_check ( valloc ( size ) ) ; #else //所謂檢查oom就是看memalign對應malloc申請記憶體是否成功      ptr = qemu_oom_check ( memalign ( alignment , size ) ) ; #endif      trace_qemu_memalign ( alignment , size , ptr ) ;      return ptr ; }

以上qemu_vmalloc進行記憶體申請就結束了。在qemu_ram_alloc_from_ptr函數末尾則是将block添加到連結清單,realloc整個ramlist,用memset初始化整個ramblock,madvise對記憶體使用限定。

然後一層層的退回到pc_memory_init函數。

此時pc.ram已經配置設定完成,ram_addr已經拿到了配置設定的記憶體位址,MemoryRegion ram初始化完成。下面則是對已有的ram進行分段,即ram-below-4g和ram-above-4g,也就是高端記憶體和低端記憶體。用memory_region_init_alias初始化子MemoryRegion,然後将memory_region_add_subregion添加關聯起來,memory_region_add_subregion具體細節“KVM源碼分析2”中已經說了,參考對照着看吧,中間很多映射代碼過程也隻是qemu遺留的軟體實作,沒看到具體存在的意義,直接看到kvm_set_user_memory_region函數,核心真正需要kvm_vm_ioctl傳遞過去的參數是什麼, struct kvm_userspace_memory_region mem而已,也就是

struct kvm_userspace_memory_region {

__u32 slot;

__u32 flags;

__u64 guest_phys_addr;

__u64 memory_size; /* bytes */

__u64 userspace_addr; /* start of the userspace allocated memory */

};

1 2 3 4 5 6 7 struct kvm_userspace_memory_region { __u32 slot ; __u32 flags ; __u64 guest_phys_addr ; __u64 memory_size ; __u64 userspace_addr ; } ;

kvm_vm_ioctl進入到核心是在KVM_SET_USER_MEMORY_REGION參數中,即執行kvm_vm_ioctl_set_memory_region,然後一直向下,到__kvm_set_memory_region函數,check_memory_region_flags檢查mem->flags是否合法,而目前flag也就使用了兩位,KVM_MEM_LOG_DIRTY_PAGES和KVM_MEM_READONLY,從qemu傳遞過來隻能是KVM_MEM_LOG_DIRTY_PAGES,下面是對mem中各參數的合規檢查,(mem->memory_size & (PAGE_SIZE – 1))要求以頁為機關,(mem->guest_phys_addr & (PAGE_SIZE – 1))要求guest_phys_addr頁對齊,而((mem->userspace_addr & (PAGE_SIZE – 1)) || !access_ok(VERIFY_WRITE,(void __user *)(unsigned long)mem->userspace_addr,mem->memory_size))則保證host的線性位址頁對齊而且該位址域有寫權限。

id_to_memslot則是根據qemu的記憶體槽号得到kvm結構下的記憶體槽号,轉換關系來自id_to_index數組,那映射關系怎麼來的,映射關系是一一對應的,在kvm_create_vm虛拟機建立過程中,kvm_init_memslots_id初始化對應關系,即slots->id_to_index[i] = slots->memslots[i].id = i,目前映射是沒有意義的,估計是為了後續擴充而存在的。

擴充了new的kvm_memory_slot,下面直接在代碼中注釋更友善:

//映射記憶體有大小,不是删除記憶體條

if (npages) {

//記憶體槽号沒有虛拟記憶體條,意味記憶體新建立

if (!old.npages)

change = KVM_MR_CREATE;

else { /* Modify an existing slot. */

//修改已存在的記憶體修改标志或者平移映射位址

//下面是不能處理的狀态(記憶體條大小不能變,實體位址不能變,不能修改隻讀)

if ((mem->userspace_addr != old.userspace_addr) ||

(npages != old.npages) ||

((new.flags ^ old.flags) & KVM_MEM_READONLY))

goto out;

//guest位址不同,記憶體條平移

if (base_gfn != old.base_gfn)

change = KVM_MR_MOVE;

else if (new.flags != old.flags)

//修改屬性

change = KVM_MR_FLAGS_ONLY;

else { /* Nothing to change. */

r = 0;

goto out;

}

}

} else if (old.npages) {

//申請插入的記憶體為0,而記憶體槽上有記憶體,意味删除

change = KVM_MR_DELETE;

} else /* Modify a non-existent slot: disallowed. */

goto out;

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 //映射記憶體有大小,不是删除記憶體條 if ( npages ) { //記憶體槽号沒有虛拟記憶體條,意味記憶體新建立      if ( ! old . npages )          change = KVM_MR_CREATE ;      else { //修改已存在的記憶體修改标志或者平移映射位址 //下面是不能處理的狀态(記憶體條大小不能變,實體位址不能變,不能修改隻讀)          if ( ( mem -> userspace_addr != old . userspace_addr ) ||              ( npages != old . npages ) ||              ( ( new . flags ^ old . flags ) & KVM_MEM_READONLY ) )              goto out ; //guest位址不同,記憶體條平移          if ( base_gfn != old . base_gfn )              change = KVM_MR_MOVE ;          else if ( new . flags != old . flags ) //修改屬性              change = KVM_MR_FLAGS_ONLY ;          else {              r = 0 ;              goto out ;          }      } } else if ( old . npages ) { //申請插入的記憶體為0,而記憶體槽上有記憶體,意味删除      change = KVM_MR_DELETE ; } else      goto out ;

另外看kvm_mr_change就知道memslot的變動值了:

enum kvm_mr_change {

KVM_MR_CREATE,

KVM_MR_DELETE,

KVM_MR_MOVE,

KVM_MR_FLAGS_ONLY,

};

1 2 3 4 5 6 enum kvm_mr_change {      KVM_MR_CREATE ,      KVM_MR_DELETE ,      KVM_MR_MOVE ,      KVM_MR_FLAGS_ONLY , } ;

在往下是一段檢查

if ((change == KVM_MR_CREATE) || (change == KVM_MR_MOVE)) {

/* Check for overlaps */

r = -EEXIST;

kvm_for_each_memslot(slot, kvm->memslots) {

if ((slot->id >= KVM_USER_MEM_SLOTS) ||

//下面排除掉準備操作的記憶體條,在KVM_MR_MOVE中是有交集的

(slot->id == mem->slot))

continue;

//下面就是目前已有的slot與new在guest線性區間上有交集

if (!((base_gfn + npages <= slot->base_gfn) ||

(base_gfn >= slot->base_gfn + slot->npages)))

goto out;

//out錯誤碼就是EEXIST

}

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if ( ( change == KVM_MR_CREATE ) || ( change == KVM_MR_MOVE ) ) {           r = - EEXIST ;      kvm_for_each_memslot ( slot , kvm -> memslots ) {          if ( ( slot -> id >= KVM_USER_MEM_SLOTS ) || //下面排除掉準備操作的記憶體條,在KVM_MR_MOVE中是有交集的              ( slot -> id == mem -> slot ) )              continue ; //下面就是目前已有的slot與new在guest線性區間上有交集          if ( ! ( ( base_gfn + npages <= slot -> base_gfn ) ||                ( base_gfn >= slot -> base_gfn + slot -> npages ) ) )              goto out ; //out錯誤碼就是EEXIST      } }

如果是新插入記憶體條,代碼則走入kvm_arch_create_memslot函數,裡面主要是一個循環,KVM_NR_PAGE_SIZES是分頁的級數,此處是3,第一次循環,lpages = gfn_to_index(slot->base_gfn + npages – 1,slot->base_gfn, level) + 1,lpages就是一級頁表所需要的page數,大緻是npages>>0*9,然後為slot->arch.rmap[i]申請了記憶體空間,此處可以猜想,rmap就是一級頁表了,繼續看,lpages約為npages>>1*9,此處又多為lpage_info申請了同等空間,然後對lpage_info初始化指派,現在看不到lpage_info的具體作用,看到後再補上。整體上看kvm_arch_create_memslot做了一個3級的軟體頁表。

如果有髒頁,并且髒頁位圖為空,則配置設定髒頁位圖, kvm_create_dirty_bitmap實際就是”頁數/8″.

if ((new.flags & KVM_MEM_LOG_DIRTY_PAGES) && !new.dirty_bitmap) {

if (kvm_create_dirty_bitmap(&new) < 0)

goto out_free;

}

1 2 3 4 if ( ( new . flags & KVM_MEM_LOG_DIRTY_PAGES ) && ! new . dirty_bitmap ) {          if ( kvm_create_dirty_bitmap ( & new ) < 0 )              goto out_free ;      }

當記憶體條的改變是KVM_MR_DELETE或者KVM_MR_MOVE,先申請一個slots,把kvm->memslots暫存到這裡,首先通過id_to_memslot擷取準備插入的記憶體條對應到kvm的插槽是slot,無論删除還是移動,将其先标記為KVM_MEMSLOT_INVALID,然後是install_new_memslots,其實就是更新了一下slots->generation的值,

——–待編輯———–

繼續閱讀