PGMSharedPage.cpp revision 63a23b6d96aca4c8545d3c3e89cc454af7ba3da6
/* $Id$ */
/** @file
* PGM - Page Manager and Monitor, Shared page handling
*/
/*
* Copyright (C) 2006-2011 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_PGM_SHARED
#include <VBox/vmm/pgm.h>
#include <VBox/vmm/stam.h>
#include "PGMInternal.h"
#include <VBox/vmm/vm.h>
#include <VBox/sup.h>
#include <VBox/param.h>
#include <VBox/err.h>
#include <VBox/log.h>
#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/mem.h>
#include <iprt/string.h>
#include "PGMInline.h"
#ifdef VBOX_WITH_PAGE_SHARING
/*******************************************************************************
* Global Variables *
*******************************************************************************/
# ifdef VBOX_STRICT
/** Keep a copy of all registered shared modules for the .pgmcheckduppages debugger command. */
static PGMMREGISTERSHAREDMODULEREQ g_apSharedModules[512] = {0};
static unsigned g_cSharedModules = 0;
# endif /* VBOX_STRICT */
/**
* Registers a new shared module for the VM
*
* @returns VBox status code.
* @param pVM Pointer to the VM.
* @param enmGuestOS Guest OS type.
* @param pszModuleName Module name.
* @param pszVersion Module version.
* @param GCBaseAddr Module base address.
* @param cbModule Module size.
* @param cRegions Number of shared region descriptors.
* @param paRegions Shared region(s).
*
* @todo This should be a GMMR3 call. No need to involve GMM here.
*/
VMMR3DECL(int) PGMR3SharedModuleRegister(PVM pVM, VBOXOSFAMILY enmGuestOS, char *pszModuleName, char *pszVersion,
RTGCPTR GCBaseAddr, uint32_t cbModule, uint32_t cRegions,
VMMDEVSHAREDREGIONDESC const *paRegions)
{
Log(("PGMR3SharedModuleRegister family=%d name=%s version=%s base=%RGv size=%x cRegions=%d\n",
enmGuestOS, pszModuleName, pszVersion, GCBaseAddr, cbModule, cRegions));
/*
* Sanity check.
*/
AssertReturn(cRegions <= VMMDEVSHAREDREGIONDESC_MAX, VERR_INVALID_PARAMETER);
/*
* Allocate and initialize a GMM request.
*/
PGMMREGISTERSHAREDMODULEREQ pReq;
pReq = (PGMMREGISTERSHAREDMODULEREQ)RTMemAllocZ(RT_OFFSETOF(GMMREGISTERSHAREDMODULEREQ, aRegions[cRegions]));
AssertReturn(pReq, VERR_NO_MEMORY);
pReq->enmGuestOS = enmGuestOS;
pReq->GCBaseAddr = GCBaseAddr;
pReq->cbModule = cbModule;
pReq->cRegions = cRegions;
for (uint32_t i = 0; i < cRegions; i++)
pReq->aRegions[i] = paRegions[i];
int rc = RTStrCopy(pReq->szName, sizeof(pReq->szName), pszModuleName);
if (RT_SUCCESS(rc))
{
rc = RTStrCopy(pReq->szVersion, sizeof(pReq->szVersion), pszVersion);
if (RT_SUCCESS(rc))
{
/*
* Issue the request. In strict builds, do some local tracking.
*/
rc = GMMR3RegisterSharedModule(pVM, pReq);
if (RT_SUCCESS(rc))
rc = pReq->rc;
AssertMsg(rc == VINF_SUCCESS || rc == VINF_GMM_SHARED_MODULE_ALREADY_REGISTERED, ("%Rrc\n", rc));
# ifdef VBOX_STRICT
if ( rc == VINF_SUCCESS
&& g_cSharedModules < RT_ELEMENTS(g_apSharedModules))
{
unsigned i;
for (i = 0; i < RT_ELEMENTS(g_apSharedModules); i++)
if (g_apSharedModules[i] == NULL)
{
size_t const cbSharedModule = RT_OFFSETOF(GMMREGISTERSHAREDMODULEREQ, aRegions[cRegions]);
g_apSharedModules[i] = (PGMMREGISTERSHAREDMODULEREQ)RTMemDup(pReq, cbSharedModule);
g_cSharedModules++;
break;
}
Assert(i < RT_ELEMENTS(g_apSharedModules));
}
# endif /* VBOX_STRICT */
if (RT_SUCCESS(rc))
rc = VINF_SUCCESS;
}
}
RTMemFree(pReq);
return rc;
}
/**
* Unregisters a shared module for the VM
*
* @returns VBox status code.
* @param pVM Pointer to the VM.
* @param pszModuleName Module name.
* @param pszVersion Module version.
* @param GCBaseAddr Module base address.
* @param cbModule Module size.
*
* @todo This should be a GMMR3 call. No need to involve GMM here.
*/
VMMR3DECL(int) PGMR3SharedModuleUnregister(PVM pVM, char *pszModuleName, char *pszVersion, RTGCPTR GCBaseAddr, uint32_t cbModule)
{
Log(("PGMR3SharedModuleUnregister name=%s version=%s base=%RGv size=%x\n", pszModuleName, pszVersion, GCBaseAddr, cbModule));
AssertMsgReturn(cbModule > 0 && cbModule < _1G, ("%u\n", cbModule), VERR_OUT_OF_RANGE);
/*
* Forward the request to GMM (ring-0).
*/
PGMMUNREGISTERSHAREDMODULEREQ pReq = (PGMMUNREGISTERSHAREDMODULEREQ)RTMemAlloc(sizeof(*pReq));
AssertReturn(pReq, VERR_NO_MEMORY);
pReq->GCBaseAddr = GCBaseAddr;
pReq->u32Alignment = 0;
pReq->cbModule = cbModule;
int rc = RTStrCopy(pReq->szName, sizeof(pReq->szName), pszModuleName);
if (RT_SUCCESS(rc))
{
rc = RTStrCopy(pReq->szVersion, sizeof(pReq->szVersion), pszVersion);
if (RT_SUCCESS(rc))
{
rc = GMMR3UnregisterSharedModule(pVM, pReq);
# ifdef VBOX_STRICT
/*
* Update our local tracking.
*/
for (unsigned i = 0; i < g_cSharedModules; i++)
{
if ( g_apSharedModules[i]
&& !strcmp(g_apSharedModules[i]->szName, pszModuleName)
&& !strcmp(g_apSharedModules[i]->szVersion, pszVersion))
{
RTMemFree(g_apSharedModules[i]);
g_apSharedModules[i] = NULL;
g_cSharedModules--;
break;
}
}
# endif /* VBOX_STRICT */
}
}
RTMemFree(pReq);
return rc;
}
/**
* Rendezvous callback that will be called once.
*
* @returns VBox strict status code.
* @param pVM Pointer to the VM.
* @param pVCpu Pointer to the VMCPU of the calling EMT.
* @param pvUser Pointer to a VMCPUID with the requester's ID.
*/
static DECLCALLBACK(VBOXSTRICTRC) pgmR3SharedModuleRegRendezvous(PVM pVM, PVMCPU pVCpu, void *pvUser)
{
VMCPUID idCpu = *(VMCPUID *)pvUser;
/* Execute on the VCPU that issued the original request to make sure we're in the right cr3 context. */
if (pVCpu->idCpu != idCpu)
{
Assert(pVM->cCpus > 1);
return VINF_SUCCESS;
}
/* Flush all pending handy page operations before changing any shared page assignments. */
int rc = PGMR3PhysAllocateHandyPages(pVM);
AssertRC(rc);
/* Lock it here as we can't deal with busy locks in this ring-0 path. */
pgmLock(pVM);
rc = GMMR3CheckSharedModules(pVM);
pgmUnlock(pVM);
AssertLogRelRC(rc);
return rc;
}
/**
* Shared module check helper (called on the way out).
*
* @param pVM Pointer to the VM.
* @param VMCPUID VCPU id
*/
static DECLCALLBACK(void) pgmR3CheckSharedModulesHelper(PVM pVM, VMCPUID idCpu)
{
/* We must stall other VCPUs as we'd otherwise have to send IPI flush commands for every single change we make. */
STAM_REL_PROFILE_START(&pVM->pgm.s.StatShModCheck, a);
int rc = VMMR3EmtRendezvous(pVM, VMMEMTRENDEZVOUS_FLAGS_TYPE_ALL_AT_ONCE, pgmR3SharedModuleRegRendezvous, &idCpu);
AssertRCSuccess(rc);
STAM_REL_PROFILE_STOP(&pVM->pgm.s.StatShModCheck, a);
}
/**
* Check all registered modules for changes.
*
* @returns VBox status code.
* @param pVM Pointer to the VM
*/
VMMR3DECL(int) PGMR3SharedModuleCheckAll(PVM pVM)
{
/* Queue the actual registration as we are under the IOM lock right now. Perform this operation on the way out. */
return VMR3ReqCallNoWait(pVM, VMCPUID_ANY_QUEUE, (PFNRT)pgmR3CheckSharedModulesHelper, 2, pVM, VMMGetCpuId(pVM));
}
# ifdef DEBUG
/**
* Query the state of a page in a shared module
*
* @returns VBox status code.
* @param pVM Pointer to the VM.
* @param GCPtrPage Page address.
* @param pfShared Shared status (out).
* @param pfPageFlags Page flags (out).
*/
VMMR3DECL(int) PGMR3SharedModuleGetPageState(PVM pVM, RTGCPTR GCPtrPage, bool *pfShared, uint64_t *pfPageFlags)
{
/* Debug only API for the page fusion testcase. */
RTGCPHYS GCPhys;
uint64_t fFlags;
pgmLock(pVM);
int rc = PGMGstGetPage(VMMGetCpu(pVM), GCPtrPage, &fFlags, &GCPhys);
switch (rc)
{
case VINF_SUCCESS:
{
PPGMPAGE pPage = pgmPhysGetPage(pVM, GCPhys);
if (pPage)
{
*pfShared = PGM_PAGE_IS_SHARED(pPage);
*pfPageFlags = fFlags;
}
else
rc = VERR_PGM_INVALID_GC_PHYSICAL_ADDRESS;
break;
}
case VERR_PAGE_NOT_PRESENT:
case VERR_PAGE_TABLE_NOT_PRESENT:
case VERR_PAGE_MAP_LEVEL4_NOT_PRESENT:
case VERR_PAGE_DIRECTORY_PTR_NOT_PRESENT:
*pfShared = false;
*pfPageFlags = 0;
rc = VINF_SUCCESS;
break;
default:
break;
}
pgmUnlock(pVM);
return rc;
}
# endif /* DEBUG */
# ifdef VBOX_STRICT
/**
* The '.pgmcheckduppages' command.
*
* @returns VBox status.
* @param pCmd Pointer to the command descriptor (as registered).
* @param pCmdHlp Pointer to command helper functions.
* @param pVM Pointer to the current VM (if any).
* @param paArgs Pointer to (readonly) array of arguments.
* @param cArgs Number of arguments in the array.
*/
DECLCALLBACK(int) pgmR3CmdCheckDuplicatePages(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PVM pVM, PCDBGCVAR paArgs, unsigned cArgs)
{
unsigned cBallooned = 0;
unsigned cShared = 0;
unsigned cZero = 0;
unsigned cUnique = 0;
unsigned cDuplicate = 0;
unsigned cAllocZero = 0;
unsigned cPages = 0;
NOREF(pCmd); NOREF(paArgs); NOREF(cArgs);
pgmLock(pVM);
for (PPGMRAMRANGE pRam = pVM->pgm.s.pRamRangesXR3; pRam; pRam = pRam->pNextR3)
{
PPGMPAGE pPage = &pRam->aPages[0];
RTGCPHYS GCPhys = pRam->GCPhys;
uint32_t cLeft = pRam->cb >> PAGE_SHIFT;
while (cLeft-- > 0)
{
if (PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_RAM)
{
switch (PGM_PAGE_GET_STATE(pPage))
{
case PGM_PAGE_STATE_ZERO:
cZero++;
break;
case PGM_PAGE_STATE_BALLOONED:
cBallooned++;
break;
case PGM_PAGE_STATE_SHARED:
cShared++;
break;
case PGM_PAGE_STATE_ALLOCATED:
case PGM_PAGE_STATE_WRITE_MONITORED:
{
/* Check if the page was allocated, but completely zero. */
PGMPAGEMAPLOCK PgMpLck;
const void *pvPage;
int rc = pgmPhysGCPhys2CCPtrInternalReadOnly(pVM, pPage, GCPhys, &pvPage, &PgMpLck);
if ( RT_SUCCESS(rc)
&& ASMMemIsZeroPage(pvPage))
cAllocZero++;
else if (GMMR3IsDuplicatePage(pVM, PGM_PAGE_GET_PAGEID(pPage)))
cDuplicate++;
else
cUnique++;
if (RT_SUCCESS(rc))
pgmPhysReleaseInternalPageMappingLock(pVM, &PgMpLck);
break;
}
default:
AssertFailed();
break;
}
}
/* next */
pPage++;
GCPhys += PAGE_SIZE;
cPages++;
/* Give some feedback for every processed megabyte. */
if ((cPages & 0x7f) == 0)
pCmdHlp->pfnPrintf(pCmdHlp, NULL, ".");
}
}
pgmUnlock(pVM);
pCmdHlp->pfnPrintf(pCmdHlp, NULL, "\nNumber of zero pages %08x (%d MB)\n", cZero, cZero / 256);
pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Number of alloczero pages %08x (%d MB)\n", cAllocZero, cAllocZero / 256);
pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Number of ballooned pages %08x (%d MB)\n", cBallooned, cBallooned / 256);
pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Number of shared pages %08x (%d MB)\n", cShared, cShared / 256);
pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Number of unique pages %08x (%d MB)\n", cUnique, cUnique / 256);
pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Number of duplicate pages %08x (%d MB)\n", cDuplicate, cDuplicate / 256);
return VINF_SUCCESS;
}
/**
* The '.pgmsharedmodules' command.
*
* @returns VBox status.
* @param pCmd Pointer to the command descriptor (as registered).
* @param pCmdHlp Pointer to command helper functions.
* @param pVM Pointer to the current VM (if any).
* @param paArgs Pointer to (readonly) array of arguments.
* @param cArgs Number of arguments in the array.
*/
DECLCALLBACK(int) pgmR3CmdShowSharedModules(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PVM pVM, PCDBGCVAR paArgs, unsigned cArgs)
{
NOREF(pCmd); NOREF(paArgs); NOREF(cArgs);
pgmLock(pVM);
for (unsigned i = 0; i < RT_ELEMENTS(g_apSharedModules); i++)
{
if (g_apSharedModules[i])
{
pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Shared module %s (%s):\n", g_apSharedModules[i]->szName, g_apSharedModules[i]->szVersion);
for (unsigned j = 0; j < g_apSharedModules[i]->cRegions; j++)
pCmdHlp->pfnPrintf(pCmdHlp, NULL, "--- Region %d: base %RGv size %x\n", j, g_apSharedModules[i]->aRegions[j].GCRegionAddr, g_apSharedModules[i]->aRegions[j].cbRegion);
}
}
pgmUnlock(pVM);
return VINF_SUCCESS;
}
# endif /* VBOX_STRICT*/
#endif /* VBOX_WITH_PAGE_SHARING */