-
Notifications
You must be signed in to change notification settings - Fork 7
Description
Background
PVM is a software-based virtualization solution and does not have new hardware features like VMX (VPID) or SVM (ASID) to add tags for TLB entries to reduce TLB misses. However, the Linux kernel previously utilized only PCIDs 0 to 6 before AMD broadcast TLB invalidation was introduced. Therefore, PVM uses the unused PCID range [32~64) for the guest. The allocation algorithm is similar to the host PCID allocation algorithm, as it simply chooses an unused host PCID for one guest address space.
Goal
To improve the chances of direct switching for the guest.
After the implementation of the code in this pull request, the guest user CR3 is preloaded. However, host PCID allocation may still lead to the disabling of direct switching. In fact, we always maintain synchronization between the guest kernel and user TLBs, i.e., synchronizing the shadow page table results in flushing both the guest kernel and user TLBs together. Therefore, there is no need to perform host PCID allocation for the guest user CR3.
New Design
ASID:
PVM would utilize all the unused PCIDs for the guest and divide them into groups. The group size is 8 (since the guest Linux may only use 7 PCIDs), which we refer to as ASID. The ASID allocation is scoped to each vCPU, meaning that each vCPU receives an ASID when it is loaded onto a pCPU. The overall ASID allocation process is nearly identical to ASID allocation in SVM.
host PCID:
The host PCID for one guest address space corresponding to the guest kernel CR3 is calculated as ASID | guest PCID. For the user CR3, it is ASID | guest PCID | X86_CR3_PTI_PCID_USER_BIT. Since the guest may use PCIDs greater than 7, we reserve index 0 for such guest PCIDs and perform a flush whenever index 0 is used.
#define PVM_ASID_SHIFT 3
#define NUM_PVM_GUEST_PCID_INDEX (1U << PVM_ASID_SHIFT)
#define PVM_GUEST_PTI_PCID_BIT 11
#define PVM_GUEST_PTI_PCID_MASK (1U << PVM_GUEST_PTI_PCID_BIT)
#define PVM_GUEST_PCID_INDEX_MASK (NUM_PVM_GUEST_PCID_INDEX - 1)
#define PVM_GUEST_PCID_MASK (PVM_GUEST_PCID_INDEX_MASK | PVM_GUEST_PTI_PCID_MASK)
static inline u32 guest_pcid_to_host_pcid(struct vcpu_pvm *pvm, u32 guest_pcid, bool is_smod)
{
u32 pcid;
/*
* if guest PCID is bigger than 7, use the fallback guest PCID
* 0, which is assumed to always be force flushed.
*/
if (guest_pcid & ~PVM_GUEST_PCID_MASK)
guest_pcid = 0;
pcid = (pvm->asid << PVM_ASID_SHIFT) | (guest_pcid & PVM_GUEST_PCID_MASK);
if (!is_smod)
pcid |= PVM_GUEST_PTI_PCID_MASK;
return pcid;
}Result
In our internal PVM implementation for lower kernel versions, combined with user CR3 preloading, we observed an improvement of about 5% in UnixBench, especially in the multi-process test cases.
Problem
As pointed out in the background, after the global ASID is introduced in the mainline, we need the kernel to provide an interface for allocating a range of PCIDs for PVM.