基本介绍
在kvm虚拟机正常的运行的过程中,guest os正常执行,就好像它在一个物理机上执行一样。当guest执行到敏感指令时,就需要KVM来进行敏感指令的模拟和IO的处理了。因此不严格的可以说KVM的工作(对系统进行虚拟化)主要就是通过对guest的退出进行处理来完成的。本文就讲述ARM64平台KVM对guest的退出处理,主要集中在内核KVM模块的代码分析,涉及到应用层qemu的IO处理,则本文没有涉及。
ARM64 KVM的guest的退出大概有三种情况:
- guest执行WFI/WFE指令。
- guest访问特权级的系统寄存器。
- 内存相关地址访问退出,这又分两种情况。一种是相关内存地址未分配物理内存,需要进入host分配内存。另一种是该地址是一个IO地址,这种情况要退出到host进行IO模拟。
上述三种情况guest都会执行敏感指令,从而产生EL2异常,CPU执行模式自动切换到EL2,程序跳转到EL2的el1_sync异常入口,从而KVM开始执行。在CPU切换到EL2模式时,CPU的相关寄存器比如ESR_EL2会记录异常产生的原因,如果是地址访问异常,寄存器还会记录产生异常的地址及要访问的寄存器,KVM可以查询相关的寄存器从而进行分别处理。
WFI/WFE指令模拟
当操作系统空闲,没有任务要处理时,idle线程就会被调度执行,一般情况下idle线程都会调用WFI/WFI指令进入一个低功耗状态。在物理环境下可以让CPU进入低功耗状态,但是在虚拟环境下,显然不能直接让CPU进入低功耗状态,因为可能host或其他虚拟机还有任务需要处理。所以KVM要对WFI/WFE指令进行模拟,主要是阻塞guest进程一段时间,时间允许的情况下调度其他vcpu执行。下面进行代码分析。
KVM处理退出的函数是handle_exit函数,该函数中如果判断退出原因是WFI/WFE指令,则调用kvm_handle_wfx函数进行模拟,该函数代码如下:
74 static int kvm_handle_wfx(struct kvm_vcpu *vcpu, struct kvm_run *run)
75 {
76 if (kvm_vcpu_get_hsr(vcpu) & HSR_WFI_IS_WFE) {
77 trace_kvm_wfx(*vcpu_pc(vcpu), true);
78 vcpu->stat.wfe_exit_stat++;
79 kvm_vcpu_on_spin(vcpu, vcpu_mode_priv(vcpu)); //WFE模拟函数
80 } else {
81 trace_kvm_wfx(*vcpu_pc(vcpu), false);
82 vcpu->stat.wfi_exit_stat++;
83 kvm_vcpu_block(vcpu); //WFI模拟函数
84 kvm_clear_request(KVM_REQ_UNHALT, vcpu);
85 }
86
87 kvm_skip_instr(vcpu, kvm_vcpu_trap_il_is32bit(vcpu));
88
89 return 1;
90 }
WFE指令的模拟(kvm_vcpu_on_spin函数,该函数较简单,没有贴出代码一行行分析),是把guest进程挂起直到下次再调度到guest进程,则进入guest执行。为了优化实现,具体的做法并不是将该vcpu进程直接挂起到进程队列,而是把CPU让给同一虚拟机的其他CPU(yield_to函数),为了公平起见,会按照顺序让渡给vm中的vcpu,下次再让渡时从上次停止让渡的vcpu向下遍历。并不是每个CPU都能被yield_to,必须是之前被调度走的(vcpu->preempted=true)且runable的。
WFI指令模拟,调用kvm_vcpu_block直到接收到一个到guest的IRQ或FIQ。
2149 void kvm_vcpu_block(struct kvm_vcpu *vcpu)
2150 {
2151 ktime_t start, cur;
2152 DECLARE_SWAITQUEUE(wait);
2153 bool waited = false;
2154 u64 block_ns;
2155
2156 start = cur = ktime_get();
2157 if (vcpu->halt_poll_ns) {
2158 ktime_t stop = ktime_add_ns(ktime_get(), vcpu->halt_poll_ns);
2159
2160 ++vcpu->stat.halt_attempted_poll;
2161 do {
2162 /*
2163 * This sets KVM_REQ_UNHALT if an interrupt
2164 * arrives.
2165 */
2166 if (kvm_vcpu_check_block(vcpu) < 0) {
2167 ++vcpu->stat.halt_successful_poll;
2168 if (!vcpu_valid_wakeup(vcpu))
2169 ++vcpu->stat.halt_poll_invalid;
2170 goto out;
2171 }
2172 cur = ktime_get();
2173 } while (single_task_running() && ktime_before(cur, stop));
2174 }
2176 kvm_arch_vcpu_blocking(vcpu);
2177
2178 for (;;) {
2179 prepare_to_swait(&vcpu->wq, &wait, TASK_INTERRUPTIBLE);
2180
2181 if (kvm_vcpu_check_block(vcpu) < 0)
2182 break;
2183
2184 waited = true;
2185 schedule();
2186 }
2187
2188 finish_swait(&vcpu->wq, &wait);
2189 cur = ktime_get();
2190
2191 kvm_arch_vcpu_unblocking(vcpu);
2192 out:
2193 block_ns = ktime_to_ns(cur) - ktime_to_ns(start);
2194
2195 if (!vcpu_valid_wakeup(vcpu))
2196 shrink_halt_poll_ns(vcpu);
2197 else if (halt_poll_ns) {
2198 if (block_ns <= vcpu->halt_poll_ns)
2199 ;
2200 /* we had a long block, shrink polling */
2201 else if (vcpu->halt_poll_ns && block_ns > halt_poll_ns)
2202 shrink_halt_poll_ns(vcpu);
2203 /* we had a short halt and our poll time is too small */
2204 else if (vcpu->halt_poll_ns < halt_poll_ns &&
2205 block_ns < halt_poll_ns)
2206 grow_halt_poll_ns(vcpu);
2207 } else
2208 vcpu->halt_poll_ns = 0;
2209
2210 trace_kvm_vcpu_wakeup(block_ns, waited, vcpu_valid_wakeup(vcpu));
2211 kvm_arch_vcpu_block_finish(vcpu);
2212 }
2213 EXPORT_SYMBOL_GPL(kvm_vcpu_block);`C
kvm_vcpu_block的功能是让vcpu线程阻塞,简单的做法就是调用schedule程序让渡CPU直到下次调度回来检查有没有达到VCPU的唤醒条件(vcpu接收到IRQ/FIQ/singal)。但是kvm在调度出去后再调度回来可能会经历一个较长的时间,如果要等待的事件很快到来,kvm_vcpu_block其实应该等待较少的时间。这里有个优化实现,kvm_vpu_block在调用schedule调度走之前,先用一小段时间(vcpu->halt_poll_ns)循环poll事件是否到来,如果到来就结束block,然后返回,否则就schedule.
2157~2176,循环探测事件是否到来,如果到来就结束block,然后返回。
2178~2186,schedule让出CPU
2195~2210,poll的时间长短不是固定的,而是根据block时间的长短来动态伸缩,最开始设为0。这段代码就是根据每次block的时间伸缩poll的时间的代码。
guest os访问系统寄存器模拟
guest os访问CPU的系统寄存器(非通用寄存器)的指令也是特权指令,CPU捕获到该指令后会自动切换到EL2,从而进入KVM执行。KVM的handle_exit函数判断guest退出的原因是访问系统寄存器,则会调用kvm_handle_sys_reg函数来处理。
1912 int kvm_handle_sys_reg(struct kvm_vcpu *vcpu, struct kvm_run *run)
1913 {
1914 struct sys_reg_params params;
1915 unsigned long esr = kvm_vcpu_get_hsr(vcpu);
1916 int Rt = kvm_vcpu_sys_get_rt(vcpu);
1917 int ret;
1918
1919 trace_kvm_handle_sys_reg(esr);
1920
1921 params.is_aarch32 = false;
1922 params.is_32bit = false;
1923 params.Op0 = (esr >> 20) & 3;
1924 params.Op1 = (esr >> 14) & 0x7;
1925 params.CRn = (esr >> 10) & 0xf;
1926 params.CRm = (esr >> 1) & 0xf;
1927 params.Op2 = (esr >> 17) & 0x7;
1928 params.regval = vcpu_get_reg(vcpu, Rt);
1929 params.is_write = !(esr & 1);
1930
1931 ret = emulate_sys_reg(vcpu, ¶ms);
1932
1933 if (!params.is_write)
1934 vcpu_set_reg(vcpu, Rt, params.regval);
1935 return ret;
1936 }
guest访问系统寄存器的指令是MRS(读)指令和MSR(写)指令,在guest执行MRS、MSR访问系统寄存器时会触发EL2的sync异常(0x18),并且ESR寄存器的ISS域保存了具体的指令信息。
1916行,kvm_vcpu_sys_get_rt从guest退出原因信息中取出ESR.ISS.Rt域(bit5~9)的值,该值为该触发异常的指令要中使用的通用寄存器。
这段代码主要是初始化一个struct sys_reg_params 结构体,该结构体记录要模拟的指令信息(比如是否是读或写,指令的操作码等),该结构体作为参数传递给指令模拟函数。其中1928行,regval是从vcpu结构体中取出当前vcpu中Rt寄存器的值。
1931行,调用 emulate_sys_reg函数进行指令模拟。
1934行,指令模拟完后,如果是要读寄存器,则把模拟的寄存器的值存入vcpu结构体中,当进入guest时,该值就会被恢复到目标寄存器,从而模拟完成。
1873 static int emulate_sys_reg(struct kvm_vcpu *vcpu,
1874 struct sys_reg_params *params)
1875 {
1876 size_t num;
1877 const struct sys_reg_desc *table, *r;
1878
1879 table = get_target_table(vcpu->arch.target, true, &num);
1880
1881 /* Search target-specific then generic table. */
1882 r = find_reg(params, table, num);
1883 if (!r)
1884 r = find_reg(params, sys_reg_descs, ARRAY_SIZE(sys_reg_descs));
1885
1886 if (likely(r)) {
1887 perform_access(vcpu, params, r);
1888 } else {
1889 kvm_err("Unsupported guest sys_reg access at: %lx\n",
1890 *vcpu_pc(vcpu));
1891 print_sys_reg_instr(params);
1892 kvm_inject_undefined(vcpu);
1893 }
1894 return 1;
1895 }
struct sys_reg_desc结构体里存储了怎么模拟一个指令的信息:
struct sys_reg_desc {
u8 Op0;
u8 Op1;
u8 CRn;
u8 CRm;
u8 Op2;
bool (*access)(struct kvm_vcpu *, struct sys_reg_params *, const struct sys_reg_desc *);
void (*reset)(struct kvm_vcpu *, const struct sys_reg_desc *);
int reg;
u64 val;
int (*get_user)(struct kvm_vcpu *vcpu, const struct sys_reg_desc *rd, const struct kvm_one_reg *reg, void __user *uaddr);
int (*set_user)(struct kvm_vcpu *vcpu, const struct sys_reg_desc *rd, const struct kvm_one_reg *reg, void __user *uaddr);
};
前面几个字段(Op0,Op1,CRn,CRm,Op2)存放了要模拟的指令的相关编码。
access字段是具体的模拟函数。
reset字段是vcpu初始化时调用的函数。
reg是该指令访问的寄存器在寄存器表中的索引。
value是一个寄存器值,一般是初始值。
,get_user/set_user函数是应用层程序调用ioctl获取和设置寄存器值时调用的函数。
每个待模拟的指令都有一个sys_reg_desc结构体,他们放在数组(table)中。系统中有两个sys_reg_desc表,一个是系统启动时调用core_initcall初始化的,每个不同的CPU target类型都有一个表,另外一个是通用的sys_reg_descs表。
1882行,先查找CPU target相关的sys_reg_desc表,如果找不到该指令的模拟程序,则跳入通用sys_reg_descs表查找。
1887行,如果找到相关模拟指令的sys_reg_desc,则perform_access是调用具体执行指令模拟的access函数执行具体模拟操作。
1892行,如果没有找到模拟函数,则向guest注入一个未定义指令异常,这是通过向vcpu中的相关寄存器赋值来实现的。vcpu.elr_el1=vcpu.pc, vcpu.pc=异常向量地址(是通过vcpu.vbar计算得到),esr_el1为计算出的异常向量掩码,vcpu.spsr=vcpu.cpsr。vcpu.cpsr=PSTATE_FAULT_BITS_64。当切换回guest时,el2.elr和el2.esr则直接恢复的是异常向量的地址和状态,然后跳入guest的el1异常向量执行。
由此可见访问每个系统寄存器都有特定的模拟函数,上述分析过程主要是跳入特定指令的模拟函数执行。由于每个特定系统寄存器都有特定函数模拟,代码比较多就不一一列举了。大部分的系统寄存器的模拟是从vcpu结构体中取出或保存一个虚拟的系统寄存器的值。
IO访问、内存访问模拟
guest os访问IO的指令(访问某一个IO地址)是敏感指令,会产生异常切换到EL2模式,访问普通内存空间的指令分两种情况,一种是已经未其分配了物理内存,则非敏感指令,guest正常访问内存执行程序,但是当guest访问的内存地址未映射到物理内存时就变成了敏感指令,需要跳入到KVM去分配物理内存。KVM判断guest退出的原因是地址访问时,会调用kvm_handle_guest_abort函数就行执行。那么怎么区分是普通内存访问还是IO内存访问呢,这是通过产生异常的IPA地址来确定的,guest的内存(IPA)地址范围是由qemu分配的,他们都会被注册进KVM,KVM通过查找IPA是否在注册的地址范围内来确定是否是普通内存的。
1467 int kvm_handle_guest_abort(struct kvm_vcpu *vcpu, struct kvm_run *run)
1468 {
1469 unsigned long fault_status;
1470 phys_addr_t fault_ipa;
1471 struct kvm_memory_slot *memslot;
1472 unsigned long hva;
1473 bool is_iabt, write_fault, writable;
1474 gfn_t gfn;
1475 int ret, idx;
1476
1477 fault_status = kvm_vcpu_trap_get_fault_type(vcpu);
1478
1479 fault_ipa = kvm_vcpu_get_fault_ipa(vcpu);
1480 is_iabt = kvm_vcpu_trap_is_iabt(vcpu);
1481
1482 /* Synchronous External Abort? */
1483 if (kvm_vcpu_dabt_isextabt(vcpu)) {
1484 /*
1485 * For RAS the host kernel may handle this abort.
1486 * There is no need to pass the error into the guest.
1487 */
1488 if (!handle_guest_sea(fault_ipa, kvm_vcpu_get_hsr(vcpu)))
1489 return 1;
1490
1491 if (unlikely(!is_iabt)) {
1492 kvm_inject_vabt(vcpu);
1493 return 1;
1494 }
1495 }
1496
1497 trace_kvm_guest_fault(*vcpu_pc(vcpu), kvm_vcpu_get_hsr(vcpu),
1498 kvm_vcpu_get_hfar(vcpu), fault_ipa);
1499
1500 /* Check the stage-2 fault is trans. fault or write fault */
1501 if (fault_status != FSC_FAULT && fault_status != FSC_PERM &&
1502 fault_status != FSC_ACCESS) {
1503 kvm_err("Unsupported FSC: EC=%#x xFSC=%#lx ESR_EL2=%#lx\n",
1504 kvm_vcpu_trap_get_class(vcpu),
1505 (unsigned long)kvm_vcpu_trap_get_fault(vcpu),
1506 (unsigned long)kvm_vcpu_get_hsr(vcpu));
1507 return -EFAULT;
1508 }
1509
1510 idx = srcu_read_lock(&vcpu->kvm->srcu);
1511
1512 gfn = fault_ipa >> PAGE_SHIFT;
1513 memslot = gfn_to_memslot(vcpu->kvm, gfn);
1514 hva = gfn_to_hva_memslot_prot(memslot, gfn, &writable);
1515 write_fault = kvm_is_write_fault(vcpu);
1516 if (kvm_is_error_hva(hva) || (write_fault && !writable)) {
1517 if (is_iabt) {
1518 /* Prefetch Abort on I/O address */
1519 kvm_inject_pabt(vcpu, kvm_vcpu_get_hfar(vcpu));
1520 ret = 1;
1521 goto out_unlock;
1522 }
1523
1524 /*
1525 * Check for a cache maintenance operation. Since we
1526 * ended-up here, we know it is outside of any memory
1527 * slot. But we can't find out if that is for a device,
1528 * or if the guest is just being stupid. The only thing
1529 * we know for sure is that this range cannot be cached.
1530 *
1531 * So let's assume that the guest is just being
1532 * cautious, and skip the instruction.
1533 */
1534 if (kvm_vcpu_dabt_is_cm(vcpu)) {
1535 kvm_skip_instr(vcpu, kvm_vcpu_trap_il_is32bit(vcpu));
1536 ret = 1;
1537 goto out_unlock;
1538 }
1539
1540 /*
1541 * The IPA is reported as [MAX:12], so we need to
1542 * complement it with the bottom 12 bits from the
1543 * faulting VA. This is always 12 bits, irrespective
1544 * of the page size.
1545 */
1546 fault_ipa |= kvm_vcpu_get_hfar(vcpu) & ((1 << 12) - 1);
1547 ret = io_mem_abort(vcpu, run, fault_ipa);
1548 goto out_unlock;
1549 }
1550
1551 /* Userspace should not be able to register out-of-bounds IPAs */
1552 VM_BUG_ON(fault_ipa >= KVM_PHYS_SIZE);
1553
1554 if (fault_status == FSC_ACCESS) {
1555 handle_access_fault(vcpu, fault_ipa);
1556 ret = 1;
1557 goto out_unlock;
1558 }
1559
1560 ret = user_mem_abort(vcpu, fault_ipa, memslot, hva, fault_status);
1561 if (ret == 0)
1562 ret = 1;
1563 out_unlock:
1564 srcu_read_unlock(&vcpu->kvm->srcu, idx);
1565 return ret;
1566 }
当guest产生ABORT异常到EL2时,host处理这两个异常的函数是kvm_handle_guest_abort函数。
当guest产生的el2异常是ABORT异常几乎可以肯定是stag2阶段地址转换表项缺失引起的,这意味着要么guest需要更多内存(host要为其分配一个内存页,类似页故障);要么意味着guest访问IO memory,这一般交给userspace的qemu来模拟IO操作。究竟是哪种情况是通过引起异常的IPA地址来区分的,如果该IPA地址内存范围被userspace的qemu注册为stand RAM的,则说明不是访问IO memory,我们需为guest分配更多内存。
1477行,从vcpu.fault.esr_el2寄存器中取出故障状态,也就是ISS域,里面存储ARBORT异常的细节信息。
1479行,从vcpu.fault.hpfar中获取引起异常的IPA地址。
1480行,判断是指令异常还是数据异常。
1483~1495行,如果是外部同步异常,如果支持RAS,host能够处理该异常,则不需要注入该异常给guest,否则,为guest注入VSE异常。这是通过简单设置HCR寄存器的VSE位来实现的。
1500~1508,错误处理,如果出现异常错误掩码(不是CPU设计引入的),则出错返回。
1512~1515,根据IPA地址的页帧号找到其对应的memory_slot。然后通过该槽把IPA转换为qemu进程的虚拟地址(hv),注意如果是stand ram,则可以顺利转换为hv。否则,则为IO memory访问,hv被赋值为KVM_HVA_ERR_BAD。
1516~1549,如果hv=KVM_HVA_ERR_BAD或者在只读地址上写操作,则要么是指令ABORT,要么是I/O memory访问。对应指令ABORT,则向guest注入一个指令ABORT异常(通过给vcpu相关寄存器赋值)。如果是I/Omemory访问,则分两种情况,如果指令是cache维护指令,则直接跳过该条指令,什么也不做(I/Omeoory是非cache的,这里是假定guest有些愚蠢的代码,才会出现这种情况);剩下的就是正常的IO操作指令了,调用io_mem_abort函数进行模拟。第1546行是把IPA与guest va的低12位相与,得到完整的IPA地址。
1554~1558行,handle_access_fault处理Access Flag异常。Access Flag标识该内存页是否允许访问,如果设为0,则不被允许访问。当Access Flag为0(stag2页表)而guest访问该页时,会触发Access Flag异常,简单的调用mkyoung函数把Access Flag设为1,以后就可以正常访问了。
1550行,到这里说明guest需要分配更多的内存,user_mem_abort函数处理该要求。
155 int io_mem_abort(struct kvm_vcpu *vcpu, struct kvm_run *run,
156 phys_addr_t fault_ipa)
157 {
158 unsigned long data;
159 unsigned long rt;
160 int ret;
161 bool is_write;
162 int len;
163 u8 data_buf[8];
164
165 /*
166 * Prepare MMIO operation. First decode the syndrome data we get
167 * from the CPU. Then try if some in-kernel emulation feels
168 * responsible, otherwise let user space do its magic.
169 */
170 if (kvm_vcpu_dabt_isvalid(vcpu)) {
171 ret = decode_hsr(vcpu, &is_write, &len);
172 if (ret)
173 return ret;
174 } else {
175 kvm_err("load/store instruction decoding not implemented\n");
176 return -ENOSYS;
177 }
178
179 rt = vcpu->arch.mmio_decode.rt;
180
181 if (is_write) {
182 data = vcpu_data_guest_to_host(vcpu, vcpu_get_reg(vcpu, rt),
183 len);
184
185 trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, len, fault_ipa, &data);
186 kvm_mmio_write_buf(data_buf, len, data);
187
188 ret = kvm_io_bus_write(vcpu, KVM_MMIO_BUS, fault_ipa, len,
189 data_buf);
190 } else {
191 trace_kvm_mmio(KVM_TRACE_MMIO_READ_UNSATISFIED, len,
192 fault_ipa, NULL);
193
194 ret = kvm_io_bus_read(vcpu, KVM_MMIO_BUS, fault_ipa, len,
195 data_buf);
196 }
197
198 /* Now prepare kvm_run for the potential return to userland. */
199 run->mmio.is_write = is_write;
200 run->mmio.phys_addr = fault_ipa;
201 run->mmio.len = len;
202
203 if (!ret) {
204 /* We handled the access successfully in the kernel. */
205 if (!is_write)
206 memcpy(run->mmio.data, data_buf, len);
207 vcpu->stat.mmio_exit_kernel++;
208 kvm_handle_mmio_return(vcpu, run);
209 return 1;
210 }
211
212 if (is_write)
213 memcpy(run->mmio.data, data_buf, len);
214 vcpu->stat.mmio_exit_user++;
215 run->exit_reason = KVM_EXIT_MMIO;
216 return 0;
217 }
170~177行,通过ABORT异常的esr_el2.iss.HSR_ISV位判断是否记录下了该次访问的细节信息,如果没有记录下来,则无法模拟,报错退出。否则调用decode_hsr解码具体的细节信息。
179~196行,尝试在内核层模拟该IO请求(内核模拟的设备),内核会对一些设备进行模拟,这些设备按照模拟的地址空间存放在不同的数组中MMIO设备存放在KVM_MMIO_BUS数组,每个设备有读写模拟函数,这里通过kvm_io_bus_read/write函数调用具体的读写模拟接口。
198~216,如果内核没有模拟该IO操作,则返回应用层(return 0)模拟,把相关的操作参数存放在kvm_run.mmio结构体中。如果内核模拟完,则不进入应用层(return 1),直接再此进入guest循环。
123 static int decode_hsr(struct kvm_vcpu *vcpu, bool *is_write, int *len)
124 {
125 unsigned long rt;
126 int access_size;
127 bool sign_extend;
128
129 if (kvm_vcpu_dabt_iss1tw(vcpu)) {
130 /* page table accesses IO mem: tell guest to fix its TTBR */
131 kvm_inject_dabt(vcpu, kvm_vcpu_get_hfar(vcpu));
132 return 1;
133 }
134
135 access_size = kvm_vcpu_dabt_get_as(vcpu);
136 if (unlikely(access_size < 0))
137 return access_size;
138
139 *is_write = kvm_vcpu_dabt_iswrite(vcpu);
140 sign_extend = kvm_vcpu_dabt_issext(vcpu);
141 rt = kvm_vcpu_dabt_get_rd(vcpu);
142
143 *len = access_size;
144 vcpu->arch.mmio_decode.sign_extend = sign_extend;
145 vcpu->arch.mmio_decode.rt = rt;
146
147 /*
148 * The MMIO instruction is emulated and should not be re-executed
149 * in the guest.
150 */
151 kvm_skip_instr(vcpu, kvm_vcpu_trap_il_is32bit(vcpu));
152 return 0;
153 }
129~133行,如果是由S1PTW引起的异常,说明使用的stag1的页表地址不对,给guest注入异常,告诉guest修改其TTBR。
剩下的代码记录访问细节信息,包括访问大小,是写还是读,以及访问的寄存器,其中sign_extend代表byte或半字访问是否是符号扩展的。细节信息保存在vcpu.arch.mmio_decode中。
最后一行是vcpu的pc寄存器跳过一个指令,因为该次的访问指令将被模拟,不需要再执行。
1282 static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
1283 struct kvm_memory_slot *memslot, unsigned long hva,
1284 unsigned long fault_status)
1285 {
1286 int ret;
1287 bool write_fault, writable, hugetlb = false, force_pte = false;
1288 unsigned long mmu_seq;
1289 gfn_t gfn = fault_ipa >> PAGE_SHIFT;
1290 struct kvm *kvm = vcpu->kvm;
1291 struct kvm_mmu_memory_cache *memcache = &vcpu->arch.mmu_page_cache;
1292 struct vm_area_struct *vma;
1293 kvm_pfn_t pfn;
1294 pgprot_t mem_type = PAGE_S2;
1295 bool logging_active = memslot_is_logging(memslot);
1296 unsigned long flags = 0;
1297
1298 write_fault = kvm_is_write_fault(vcpu);
1299 if (fault_status == FSC_PERM && !write_fault) {
1300 kvm_err("Unexpected L2 read permission error\n");
1301 return -EFAULT;
1302 }
1303
1304 /* Let's check if we will get back a huge page backed by hugetlbfs */
1305 down_read(¤t->mm->mmap_sem);
1306 vma = find_vma_intersection(current->mm, hva, hva + 1);
1307 if (unlikely(!vma)) {
1308 kvm_err("Failed to find VMA for hva 0x%lx\n", hva);
1309 up_read(¤t->mm->mmap_sem);
1310 return -EFAULT;
1311 }
1312
1313 if (vma_kernel_pagesize(vma) == PMD_SIZE && !logging_active) {
1314 hugetlb = true;
1315 gfn = (fault_ipa & PMD_MASK) >> PAGE_SHIFT;
1316 } else {
1326 if ((memslot->userspace_addr & ~PMD_MASK) !=
1327 ((memslot->base_gfn << PAGE_SHIFT) & ~PMD_MASK))
1328 force_pte = true;
1329 }
1330 up_read(¤t->mm->mmap_sem);
1331
1332 /* We need minimum second+third level pages */
1333 ret = mmu_topup_memory_cache(memcache, KVM_MMU_CACHE_MIN_PAGES,
1334 KVM_NR_MEM_OBJS);
1335 if (ret)
1336 return ret;
1337
1338 mmu_seq = vcpu->kvm->mmu_notifier_seq;
1348 smp_rmb();
1349
1350 pfn = gfn_to_pfn_prot(kvm, gfn, write_fault, &writable);
1351 if (pfn == KVM_PFN_ERR_HWPOISON) {
1352 kvm_send_hwpoison_signal(hva, vma);
1353 return 0;
1354 }
1355 if (is_error_noslot_pfn(pfn))
1356 return -EFAULT;
1357
1358 if (kvm_is_device_pfn(pfn)) {
1359 mem_type = PAGE_S2_DEVICE;
1360 flags |= KVM_S2PTE_FLAG_IS_IOMAP;
1361 } else if (logging_active) {
1362 /*
1363 * Faults on pages in a memslot with logging enabled
1364 * should not be mapped with huge pages (it introduces churn
1365 * and performance degradation), so force a pte mapping.
1366 */
1367 force_pte = true;
1368 flags |= KVM_S2_FLAG_LOGGING_ACTIVE;
1369
1370 /*
1371 * Only actually map the page as writable if this was a write
1372 * fault.
1373 */
1374 if (!write_fault)
1375 writable = false;
1376 }
1377
1378 spin_lock(&kvm->mmu_lock);
1379 if (mmu_notifier_retry(kvm, mmu_seq))
1380 goto out_unlock;
1381
1382 if (!hugetlb && !force_pte)
1383 hugetlb = transparent_hugepage_adjust(&pfn, &fault_ipa);
1385 if (hugetlb) {
1386 pmd_t new_pmd = pfn_pmd(pfn, mem_type);
1387 new_pmd = pmd_mkhuge(new_pmd);
1388 if (writable) {
1389 new_pmd = kvm_s2pmd_mkwrite(new_pmd);
1390 kvm_set_pfn_dirty(pfn);
1391 }
1392 coherent_cache_guest_page(vcpu, pfn, PMD_SIZE);
1393 ret = stage2_set_pmd_huge(kvm, memcache, fault_ipa, &new_pmd);
1394 } else {
1395 pte_t new_pte = pfn_pte(pfn, mem_type);
1396
1397 if (writable) {
1398 new_pte = kvm_s2pte_mkwrite(new_pte);
1399 kvm_set_pfn_dirty(pfn);
1400 mark_page_dirty(kvm, gfn);
1401 }
1402 coherent_cache_guest_page(vcpu, pfn, PAGE_SIZE);
1403 ret = stage2_set_pte(kvm, memcache, fault_ipa, &new_pte, flags);
1404 }
1405
1406 out_unlock:
1407 spin_unlock(&kvm->mmu_lock);
1408 kvm_set_pfn_accessed(pfn);
1409 kvm_release_pfn_clean(pfn);
1410 return ret;
1411 }
1306~1310,根据guest访问的IPA地址对应于qemu进程hv地址找到管理该内存段的vma结构。
1312~1315,判断是否使用hugepage,如果host使用hugepage,且目前没有正在记录脏页,则hugetlb=true。
1333行, 该行主要是扩充kvm_mmu_memory_cache以保证在后面设置stage2页表时有足够的页来建立页表。
1350行,gfn_to_pfn_prot函数从host的qemu进程的页表中找到hv对应的物理页帧号,并返回该页帧号。(这个函数保证被换出的页必定被换入?)
1383行,判断能否使用透明巨页功能。
1385~1391行,如果使用巨页或透明巨页,则设置stage2页表PMD页表项为block页
1394~1404行,设置普通页的stag2页表。
可以看出普通内存模拟主要就是找到物理页帧号设置stag2阶段的页表为虚拟机分配物理内存。其中分配物理内存找出物理页帧号主要是由gfn_to_pfn_prot函数处理的。