PDMBlkCache.cpp revision bdb12dc9eb596286686c3a9d29680ce054a2afd0
/* $Id$ */
/** @file
* PDM Block Cache.
*/
/*
* Copyright (C) 2006-2008 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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.
*/
/** @page pg_pdm_block_cache PDM Block Cache - The I/O cache
* This component implements an I/O cache based on the 2Q cache algorithm.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_PDM_BLK_CACHE
#include "PDMInternal.h"
#include "PDMBlkCacheInternal.h"
#ifdef VBOX_STRICT
# define PDMACFILECACHE_IS_CRITSECT_OWNER(Cache) \
do \
{ \
("Thread does not own critical section\n"));\
} while(0)
# define PDMACFILECACHE_EP_IS_SEMRW_WRITE_OWNER(pEpCache) \
do \
{ \
("Thread is not exclusive owner of the per endpoint RW semaphore\n")); \
} while(0)
# define PDMACFILECACHE_EP_IS_SEMRW_READ_OWNER(pEpCache) \
do \
{ \
("Thread is not read owner of the per endpoint RW semaphore\n")); \
} while(0)
#else
# define PDMACFILECACHE_IS_CRITSECT_OWNER(Cache) do { } while(0)
# define PDMACFILECACHE_EP_IS_SEMRW_WRITE_OWNER(pEpCache) do { } while(0)
# define PDMACFILECACHE_EP_IS_SEMRW_READ_OWNER(pEpCache) do { } while(0)
#endif
#define PDM_BLK_CACHE_SAVED_STATE_VERSION 1
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
/**
* Decrement the reference counter of the given cache entry.
*
* @returns nothing.
* @param pEntry The entry to release.
*/
{
}
/**
* Increment the reference counter of the given cache entry.
*
* @returns nothing.
* @param pEntry The entry to reference.
*/
{
}
#ifdef DEBUG
{
/* Amount of cached data should never exceed the maximum amount. */
("Current amount of cached data exceeds maximum\n"));
/* The amount of cached data in the LRU and FRU list should match cbCached */
AssertMsg(pCache->LruRecentlyUsedIn.cbCached + pCache->LruFrequentlyUsed.cbCached == pCache->cbCached,
("Amount of cached data doesn't match\n"));
("Paged out list exceeds maximum\n"));
}
#endif
{
#ifdef DEBUG
#endif
}
{
#ifdef DEBUG
#endif
}
{
}
{
}
{
}
{
}
/**
* Checks consistency of a LRU list.
*
* @returns nothing
* @param pList The LRU list to check.
* @param pNotInList Element which is not allowed to occur in the list.
*/
{
/* Check that there are no double entries and no cycles in the list. */
while (pCurr)
{
while (pNext)
{
("Entry %#p is at least two times in list %#p or there is a cycle in the list\n",
}
}
}
#endif
/**
* Unlinks a cache entry from the LRU list it is assigned to.
*
* @returns nothing.
* @param pEntry The entry to unlink.
*/
{
#endif
if (pPrev)
else
{
if (pNext)
}
if (pNext)
else
{
if (pPrev)
}
#endif
}
/**
* Adds a cache entry to the given LRU list unlinking it from the currently
* assigned list if needed.
*
* @returns nothing.
* @param pList List to the add entry to.
* @param pEntry Entry to add.
*/
{
#endif
/* Remove from old list if needed */
else
{
}
#endif
}
/**
* Destroys a LRU list freeing all entries.
*
* @returns nothing
* @param pList Pointer to the LRU list to destroy.
*
* @note The caller must own the critical section of the cache.
*/
{
{
}
}
/**
* Tries to remove the given amount of bytes from a given list in the cache
* moving the entries to one of the given ghosts lists
*
* @returns Amount of data which could be freed.
* @param pCache Pointer to the global cache data.
* @param cbData The amount of the data to free.
* @param pListSrc The source list to evict data from.
* @param pGhostListSrc The ghost list removed entries should be moved to
* NULL if the entry should be freed.
* @param fReuseBuffer Flag whether a buffer should be reused if it has the same size
* @param ppbBuf Where to store the address of the buffer if an entry with the
* same size was found and fReuseBuffer is true.
*
* @note This function may return fewer bytes than requested because entries
* may be marked as non evictable if they are used for I/O at the
* moment.
*/
{
("Destination list must be NULL or the recently used but paged out list\n"));
if (fReuseBuffer)
{
}
/* Start deleting from the tail. */
{
/* We can't evict pages which are currently in progress or dirty but not in progress */
{
/* Ok eviction candidate. Grab the endpoint semaphore and check again
* because somebody else might have raced us. */
{
{
}
if (pGhostListDst)
{
/* We have to remove the last entries from the paged out list. */
&& pGhostEntFree)
{
{
}
}
{
/* Couldn't remove enough entries. Delete */
}
else
}
else
{
/* Delete the entry from the AVL tree it is assigned to. */
}
}
}
else
LogFlow(("Entry %#p (%u bytes) is still in progress and can't be evicted\n", pCurr, pCurr->cbData));
}
return cbEvicted;
}
static bool pdmBlkCacheReclaim(PPDMBLKCACHEGLOBAL pCache, size_t cbData, bool fReuseBuffer, uint8_t **ppbBuffer)
{
return true;
{
/* Try to evict as many bytes as possible from A1in */
/*
* If it was not possible to remove enough entries
* try the frequently accessed cache.
*/
{
Assert(!fReuseBuffer || !*ppbBuffer); /* It is not possible that we got a buffer with the correct size but we didn't freed enough data. */
/*
* If we removed something we can't pass the reuse buffer flag anymore because
* we don't need to evict that much data
*/
if (!cbRemoved)
else
}
}
else
{
/* We have to remove entries from frequently access list. */
}
}
DECLINLINE(int) pdmBlkCacheEnqueue(PPDMBLKCACHE pBlkCache, uint64_t off, size_t cbXfer, PPDMBLKCACHEIOXFER pIoXfer)
{
int rc = VINF_SUCCESS;
LogFlowFunc(("%s: Enqueuing hIoXfer=%#p enmXferDir=%d\n",
{
case PDMBLKCACHETYPE_DEV:
{
break;
}
case PDMBLKCACHETYPE_DRV:
{
break;
}
case PDMBLKCACHETYPE_USB:
{
break;
}
case PDMBLKCACHETYPE_INTERNAL:
{
break;
}
default:
AssertMsgFailed(("Unknown block cache type!\n"));
}
return rc;
}
/**
* Initiates a read I/O task for the given entry.
*
* @returns VBox status code.
* @param pEntry The entry to fetch the data to.
*/
{
/* Make sure no one evicts the entry while it is accessed. */
if (RT_UNLIKELY(!pIoXfer))
return VERR_NO_MEMORY;
}
/**
* Initiates a write I/O task for the given entry.
*
* @returns nothing.
* @param pEntry The entry to read the data from.
*/
{
/* Make sure no one evicts the entry while it is accessed. */
if (RT_UNLIKELY(!pIoXfer))
return VERR_NO_MEMORY;
}
/**
* Passthrough a part of a request directly to the I/O manager
* handling the endpoint.
*
* @returns VBox status code.
* @param pEndpoint The endpoint.
* @param pTask The task.
* @param pIoMemCtx The I/O memory context to use.
* @param offStart Offset to start transfer from.
* @param cbData Amount of data to transfer.
*/
{
if (RT_UNLIKELY(!pIoXfer))
return VERR_NO_MEMORY;
if (pSgBuf)
{
}
}
/**
* Commit a single dirty entry to the endpoint
*
* @returns nothing
* @param pEntry The entry to commit.
*/
{
("Invalid flags set for entry %#p\n", pEntry));
}
/**
* Commit all dirty entries for a single endpoint.
*
* @returns nothing.
* @param pBlkCache The endpoint cache to commit.
*/
{
uint32_t cbCommitted = 0;
/* Return if the cache was suspended. */
if (pBlkCache->fSuspended)
return;
/* The list is moved to a new header to reduce locking overhead. */
if (!RTListIsEmpty(&ListDirtyNotCommitted))
{
PPDMBLKCACHEENTRY pEntry = RTListGetFirst(&ListDirtyNotCommitted, PDMBLKCACHEENTRY, NodeNotCommitted);
{
}
/* Commit the last endpoint */
("Committed all entries but list is not empty\n"));
}
("Number of committed bytes exceeds number of dirty bytes\n"));
/* Reset the commit timer if we don't have any dirty bits. */
if ( !(cbDirtyOld - cbCommitted)
}
/**
* Commit all dirty entries in the cache.
*
* @returns nothing.
* @param pCache The global cache instance.
*/
{
if (!fCommitInProgress)
{
{
}
/* Commit the last endpoint */
}
}
/**
* Adds the given entry as a dirty to the cache.
*
* @returns Flag whether the amount of dirty bytes in the cache exceeds the threshold
* @param pBlkCache The endpoint cache the entry belongs to.
* @param pEntry The entry to add.
*/
{
bool fDirtyBytesExceeded = false;
/* If the commit timer is disabled we commit right away. */
if (pCache->u32CommitTimeoutMs == 0)
{
}
{
/* Prevent committing if the VM was suspended. */
{
/* Arm the commit timer. */
}
}
return fDirtyBytesExceeded;
}
{
bool fFound = false;
{
{
fFound = true;
break;
}
}
}
/**
* Commit timer callback.
*/
{
LogFlowFunc(("Commit interval expired, commiting dirty entries\n"));
LogFlowFunc(("Entries committed, going to sleep\n"));
}
{
/* Go through the list and save all dirty entries. */
{
/* Count the number of entries to safe. */
{
cEntries++;
}
/* Walk the list of all dirty entries and save them. */
{
/* A few sanity checks. */
("Invalid list\n"));
("Size and range do not match\n"));
/* Save */
}
}
/* Terminator */
}
static DECLCALLBACK(int) pdmR3BlkCacheLoadExec(PVM pVM, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
{
int rc = VINF_SUCCESS;
{
while ( cRefs > 0
&& RT_SUCCESS(rc))
{
cbId++; /* Include terminator */
if (!pszId)
{
rc = VERR_NO_MEMORY;
break;
}
/* Search for the block cache with the provided id. */
if (!pBlkCache)
{
N_("The VM is missing a block device. Please make sure the source and target VMs have compatible storage configurations"));
break;
}
/* Get the entries */
while (cEntries > 0)
{
if (!pEntry)
{
rc = VERR_NO_MEMORY;
break;
}
if (RT_FAILURE(rc))
{
break;
}
/* Insert into the tree. */
/* Add to the dirty list. */
cEntries--;
}
cRefs--;
}
if (pszId)
}
else
N_("The VM is missing a block device. Please make sure the source and target VMs have compatible storage configurations"));
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
}
return rc;
}
{
int rc = VINF_SUCCESS;
if (!pBlkCacheGlobal)
return VERR_NO_MEMORY;
pBlkCacheGlobal->cRefs = 0;
pBlkCacheGlobal->cbCached = 0;
pBlkCacheGlobal->fCommitInProgress = false;
/* Initialize members */
do
{
pBlkCacheGlobal->cbRecentlyUsedInMax = (pBlkCacheGlobal->cbMax / 100) * 25; /* 25% of the buffer size */
pBlkCacheGlobal->cbRecentlyUsedOutMax = (pBlkCacheGlobal->cbMax / 100) * 50; /* 50% of the buffer size */
LogFlowFunc(("cbRecentlyUsedInMax=%u cbRecentlyUsedOutMax=%u\n",
/** @todo r=aeichner: Experiment to find optimal default values */
rc = CFGMR3QueryU32Def(pCfgBlkCache, "CacheCommitIntervalMs", &pBlkCacheGlobal->u32CommitTimeoutMs, 10000 /* 10sec */);
rc = CFGMR3QueryU32Def(pCfgBlkCache, "CacheCommitThreshold", &pBlkCacheGlobal->cbCommitDirtyThreshold, pBlkCacheGlobal->cbMax / 2);
} while (0);
if (RT_SUCCESS(rc))
{
"Maximum cache size");
"Currently used cache");
"/PDM/BlkCache/cbCachedMruIn",
"Number of bytes cached in MRU list");
"Number of bytes cached in FRU list");
"/PDM/BlkCache/cbCachedFru",
"Number of bytes cached in FRU ghost list");
#ifdef VBOX_WITH_STATISTICS
STAMUNIT_COUNT, "Number of hits in the cache");
STAMUNIT_COUNT, "Number of partial hits in the cache");
"/PDM/BlkCache/CacheMisses",
STAMUNIT_COUNT, "Number of misses when accessing the cache");
STAMUNIT_BYTES, "Number of bytes read from the cache");
"/PDM/BlkCache/CacheWritten",
STAMUNIT_BYTES, "Number of bytes written to the cache");
"/PDM/BlkCache/CacheTreeGet",
STAMUNIT_TICKS_PER_CALL, "Time taken to access an entry in the tree");
STAMUNIT_TICKS_PER_CALL, "Time taken to insert an entry in the tree");
STAMUNIT_TICKS_PER_CALL, "Time taken to remove an entry an the tree");
STAMUNIT_COUNT, "Number of times a buffer could be reused");
#endif
/* Initialize the critical section */
}
if (RT_SUCCESS(rc))
{
/* Create the commit timer */
if (pBlkCacheGlobal->u32CommitTimeoutMs > 0)
"BlkCache-Commit",
if (RT_SUCCESS(rc))
{
/* Register saved state handler. */
rc = SSMR3RegisterInternal(pVM, "pdmblkcache", 0, PDM_BLK_CACHE_SAVED_STATE_VERSION, pBlkCacheGlobal->cbMax,
if (RT_SUCCESS(rc))
{
LogRel(("BlkCache: Cache successfully initialised. Cache size is %u bytes\n", pBlkCacheGlobal->cbMax));
LogRel(("BlkCache: Cache commit threshold is %u bytes\n", pBlkCacheGlobal->cbCommitDirtyThreshold));
return VINF_SUCCESS;
}
}
}
if (pBlkCacheGlobal)
return rc;
}
{
if (pBlkCacheGlobal)
{
/* Make sure no one else uses the cache now */
/* Cleanup deleting all cache entries waiting for in progress entries to finish. */
}
}
{
if ( pBlkCacheGlobal
{
/* The VM was suspended because of an I/O error, commit all dirty entries. */
}
return VINF_SUCCESS;
}
{
int rc = VINF_SUCCESS;
if (!pBlkCacheGlobal)
return VERR_NOT_SUPPORTED;
/*
* Check that no other user cache has the same id first,
* Unique id's are necessary in case the state is saved.
*/
if (!pBlkCache)
{
if (pBlkCache)
if ( pBlkCache
{
pBlkCache->fSuspended = false;
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
{
#ifdef VBOX_WITH_STATISTICS
STAMUNIT_COUNT, "Number of deferred writes",
#endif
/* Add to the list of users. */
pBlkCacheGlobal->cRefs++;
*ppBlkCache = pBlkCache;
LogFlowFunc(("returns success\n"));
return VINF_SUCCESS;
}
else
rc = VERR_NO_MEMORY;
}
}
}
else
rc = VERR_NO_MEMORY;
if (pBlkCache)
}
else
return rc;
}
const char *pcszId)
{
int rc = VINF_SUCCESS;
if (RT_SUCCESS(rc))
{
*ppBlkCache = pBlkCache;
}
return rc;
}
const char *pcszId)
{
int rc = VINF_SUCCESS;
if (RT_SUCCESS(rc))
{
*ppBlkCache = pBlkCache;
}
return rc;
}
const char *pcszId)
{
int rc = VINF_SUCCESS;
if (RT_SUCCESS(rc))
{
*ppBlkCache = pBlkCache;
}
return rc;
}
const char *pcszId)
{
int rc = VINF_SUCCESS;
if (RT_SUCCESS(rc))
{
*ppBlkCache = pBlkCache;
}
return rc;
}
/**
* Callback for the AVL destroy routine. Frees a cache entry for this endpoint.
*
* @returns IPRT status code.
* @param pNode The node to destroy.
* @param pvUser Opaque user data.
*/
{
{
/* Leave the locks to let the I/O thread make progress but reference the entry to prevent eviction. */
RTThreadSleep(250);
/* Re-enter all locks */
}
if (fUpdateCache)
return VINF_SUCCESS;
}
/**
* Destroys all cache resources used by the given endpoint.
*
* @returns nothing.
* @param pEndpoint The endpoint to the destroy.
*/
{
/*
* Commit all dirty entries now (they are waited on for completion during the
* destruction of the AVL tree below).
* The exception is if the VM was paused because of an I/O error before.
*/
/* Make sure nobody is accessing the cache while we delete the tree. */
#ifdef VBOX_WITH_STATISTICS
#endif
}
{
/*
* Validate input.
*/
if (!pDevIns)
return;
/* Return silently if not supported. */
if (!pBlkCacheGlobal)
return;
RTListForEachSafe(&pBlkCacheGlobal->ListUsers, pBlkCache, pBlkCacheNext, PDMBLKCACHE, NodeCacheUser)
{
}
}
{
/*
* Validate input.
*/
if (!pDrvIns)
return;
/* Return silently if not supported. */
if (!pBlkCacheGlobal)
return;
RTListForEachSafe(&pBlkCacheGlobal->ListUsers, pBlkCache, pBlkCacheNext, PDMBLKCACHE, NodeCacheUser)
{
}
}
{
/*
* Validate input.
*/
if (!pUsbIns)
return;
/* Return silently if not supported. */
if (!pBlkCacheGlobal)
return;
RTListForEachSafe(&pBlkCacheGlobal->ListUsers, pBlkCache, pBlkCacheNext, PDMBLKCACHE, NodeCacheUser)
{
}
}
{
if (pEntry)
return pEntry;
}
/**
* Return the best fit cache entries for the given offset.
*
* @returns nothing.
* @param pBlkCache The endpoint cache.
* @param off The offset.
* @param pEntryAbove Where to store the pointer to the best fit entry above the
* the given offset. NULL if not required.
*/
{
if (ppEntryAbove)
{
if (*ppEntryAbove)
}
}
{
}
/**
* Allocates and initializes a new entry for the cache.
* The entry has a reference count of 1.
*
* @returns Pointer to the new cache entry or NULL if out of memory.
* @param pBlkCache The cache the entry belongs to.
* @param off Start offset.
* @param cbData Size of the cache entry.
* @param pbBuffer Pointer to the buffer to use.
* NULL if a new buffer should be allocated.
* The buffer needs to have the same size of the entry.
*/
{
if (RT_UNLIKELY(!pEntryNew))
return NULL;
if (pbBuffer)
else
{
return NULL;
}
return pEntryNew;
}
/**
* in exclusive mode.
*
* @returns true if the flag in fSet is set and the one in fClear is clear.
* false otherwise.
* The R/W semaphore is only held if true is returned.
*
* @param pBlkCache The endpoint cache instance data.
* @param pEntry The entry to check the flags for.
* @param fSet The flag which is tested to be set.
* @param fClear The flag which is tested to be clear.
*/
{
if (fPassed)
{
/* Acquire the lock and check again because the completion callback might have raced us. */
/* Drop the lock if we didn't passed the test. */
if (!fPassed)
}
return fPassed;
}
/**
* Adds a segment to the waiting list for a cache entry
* which is currently in progress.
*
* @returns nothing.
* @param pEntry The cache entry to add the segment to.
* @param pSeg The segment to add.
*/
{
if (pEntry->pWaitingHead)
{
}
else
{
}
}
/**
* Add a buffer described by the I/O memory context
* to the entry waiting for completion.
*
* @returns VBox status code.
* @param pEntry The entry to add the buffer to.
* @param pTask Task associated with the buffer.
* @param pIoMemCtx The memory context to use.
* @param offDiff Offset from the start of the buffer
* in the entry.
* @param cbData Amount of data to wait for onthis entry.
* @param fWrite Flag whether the task waits because it wants to write
* to the cache entry.
*/
{
if (!pWaiter)
return VERR_NO_MEMORY;
return VINF_SUCCESS;
}
/**
* Calculate aligned offset and size for a new cache entry
* which do not intersect with an already existing entry and the
* file end.
*
* @returns The number of bytes the entry can hold of the requested amount
* of byte.
* @param pEndpoint The endpoint.
* @param pBlkCache The endpoint cache.
* @param off The start offset.
* @param cb The number of bytes the entry needs to hold at least.
* @param uAlignment Alignment of the boundary sizes.
* @param poffAligned Where to store the aligned offset.
* @param pcbAligned Where to store the aligned size of the entry.
*/
unsigned uAlignment,
{
/* Get the best fit entries around the offset */
/* Log the info */
LogFlow(("%sest fit entry above off=%llu (BestFit=%llu BestFitEnd=%llu BestFitSize=%u)\n",
off,
offAligned = off;
if ( pEntryAbove
{
}
else
{
}
/* A few sanity checks */
("Aligned size intersects with another cache entry\n"));
if (pEntryAbove)
*pcbAligned = cbAligned;
return cbInEntry;
}
/**
* Create a new cache entry evicting data from the cache if required.
*
* @returns Pointer to the new cache entry or NULL
* if not enough bytes could be evicted from the cache.
* @param pEndpoint The endpoint.
* @param pBlkCache The endpoint cache.
* @param off The offset.
* @param cb Number of bytes the cache entry should have.
* @param uAlignment Alignment the size of the entry should have.
* @param pcbData Where to store the number of bytes the new
* entry can hold. May be lower than actually requested
* due to another entry intersecting the access range.
*/
unsigned uAlignment,
{
if (fEnough)
{
{
("Overflow in calculation off=%llu OffsetAligned=%llu\n",
}
else
}
else
return pEntryNew;
}
{
{
pReq->cXfersPending = 0;
}
return pReq;
}
{
{
case PDMBLKCACHETYPE_DEV:
{
break;
}
case PDMBLKCACHETYPE_DRV:
{
break;
}
case PDMBLKCACHETYPE_USB:
{
break;
}
case PDMBLKCACHETYPE_INTERNAL:
{
break;
}
default:
AssertMsgFailed(("Unknown block cache type!\n"));
}
}
int rcReq, bool fCallHandler)
{
if (RT_FAILURE(rcReq))
if (!cXfersPending)
{
if (fCallHandler)
else
return true;
}
return false;
}
{
int rc = VINF_SUCCESS;
LogFlowFunc((": pBlkCache=%#p{%s} off=%llu pcSgBuf=%#p cbRead=%u pvUser=%#p\n",
/* Allocate new request structure. */
if (RT_UNLIKELY(!pReq))
return VERR_NO_MEMORY;
/* Increment data transfer counter to keep the request valid while we access it. */
while (cbRead)
{
/*
* If there is no entry we try to create a new one eviciting unused pages
* if the cache is full. If this is not possible we will pass the request through
* and skip the caching (all entries may be still in progress so they can't
* be evicted)
* If we have an entry it can be in one of the LRU lists where the entry
* contains data (recently used or frequently used LRU) so we can just read
* the data we need and put the entry at the head of the frequently used LRU list.
* In case the entry is in one of the ghost lists it doesn't contain any data.
* We have to fetch it again evicting pages from either T1 or T2 to make room.
*/
if (pEntry)
{
("Overflow in calculation off=%llu OffsetAligned=%llu\n",
("Buffer of cache entry exceeded off=%llu cbToRead=%d\n",
if (!cbRead)
else
/* Ghost lists contain no data. */
{
{
/* Entry didn't completed yet. Append to the list */
false /* fWrite */);
}
else
{
/* Read as much as we can from the entry. */
}
/* Move this entry to the top position */
{
}
/* Release the entry */
}
else
{
pdmBlkCacheEntryRemoveFromList(pEntry); /* Remove it before we remove data, otherwise it may get freed when evicting data. */
/* Move the entry to Am and fetch it to the cache. */
if (fEnough)
{
if (pbBuffer)
else
false /* fWrite */);
/* Release the entry */
}
else
{
}
}
}
else
{
#ifdef VBOX_WITH_IO_READ_CACHE
/* No entry found for this offset. Create a new entry and fetch the data to the cache. */
&cbToRead);
if (pEntryNew)
{
if (!cbRead)
else
&SgBuf,
false /* fWrite */);
}
else
{
/*
* There is not enough free space in the cache.
* Pass the request directly to the I/O manager.
*/
LogFlow(("Couldn't evict %u bytes from the cache. Remaining request will be passed through\n", cbToRead));
}
#else
/* Clip read size if necessary. */
if (pEntryAbove)
{
else
}
else
#endif
}
}
return rc;
}
{
int rc = VINF_SUCCESS;
LogFlowFunc((": pBlkCache=%#p{%s} off=%llu pcSgBuf=%#p cbWrite=%u pvUser=%#p\n",
/* Allocate new request structure. */
if (RT_UNLIKELY(!pReq))
return VERR_NO_MEMORY;
/* Increment data transfer counter to keep the request valid while we access it. */
while (cbWrite)
{
if (pEntry)
{
/* Write the data into the entry and mark it as dirty */
("Overflow in calculation off=%llu OffsetAligned=%llu\n",
if (!cbWrite)
else
/* Ghost lists contain no data. */
{
/* Check if the entry is dirty. */
0))
{
/* If it is already dirty but not in progress just update the data. */
{
}
else
{
/* The data isn't written to the file yet */
true /* fWrite */);
}
}
else /* Dirty bit not set */
{
/*
* Check if a read is in progress for this entry.
* We have to defer processing in that case.
*/
0))
{
true /* fWrite */);
}
else /* I/O in progress flag not set */
{
/* Write as much as we can into the entry and update the file. */
if (fCommit)
}
} /* Dirty bit not set */
/* Move this entry to the top position */
{
}
}
else /* Entry is on the ghost list */
{
pdmBlkCacheEntryRemoveFromList(pEntry); /* Remove it before we remove data, otherwise it may get freed when evicting data. */
if (fEnough)
{
/* Move the entry to Am and fetch it to the cache. */
if (pbBuffer)
else
true /* fWrite */);
/* Release the reference. If it is still needed the I/O in progress flag should protect it now. */
}
else
{
}
}
}
else /* No entry found */
{
/*
* No entry found. Try to create a new cache entry to store the data in and if that fails
* write directly to the file.
*/
512, &cbToWrite);
if (pEntryNew)
{
/*
* Check if it is possible to just write the data without waiting
* for it to get fetched first.
*/
{
if (fCommit)
}
else
{
/* Defer the write and fetch the data from the endpoint. */
true /* fWrite */);
}
}
else
{
/*
* There is not enough free space in the cache.
* Pass the request directly to the I/O manager.
*/
LogFlow(("Couldn't evict %u bytes from the cache. Remaining request will be passed through\n", cbToWrite));
}
}
}
return rc;
}
{
int rc = VINF_SUCCESS;
/* Commit dirty entries in the cache. */
/* Allocate new request structure. */
if (RT_UNLIKELY(!pReq))
return VERR_NO_MEMORY;
return VINF_AIO_TASK_PENDING;
}
/**
* Completes a task segment freeing all resources and completes the task handle
* if everything was transferred.
*
* @returns Next task segment handle.
* @param pTaskSeg Task segment to complete.
* @param rc Status code to set.
*/
int rc)
{
return pNext;
}
static void pdmBlkCacheIoXferCompleteEntry(PPDMBLKCACHE pBlkCache, PPDMBLKCACHEIOXFER hIoXfer, int rcIoXfer)
{
/* Reference the entry now as we are clearing the I/O in progress flag
* which protected the entry till now. */
/* Process waiting segment list. The data in entry might have changed in-between. */
bool fDirty = false;
("The list tail was not updated correctly\n"));
{
/*
* An error here is difficult to handle as the original request completed already.
* The error is logged for now and the VM is paused.
* If the user continues the entry is written again in the hope
* the user fixed the problem and the next write succeeds.
*/
if (RT_FAILURE(rcIoXfer))
{
LogRel(("I/O cache: Error while writing entry at offset %llu (%u bytes) to medium \"%s\" (rc=%Rrc)\n",
{
int rc = VMSetRuntimeError(pCache->pVM, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "BLKCACHE_IOERR",
N_("The I/O cache encountered an error while updating data in medium \"%s\" (rc=%Rrc). "
"Make sure there is enough free space on the disk and that the disk is working properly. "
"Operation can be resumed afterwards"),
}
/* Mark the entry as dirty again to get it added to the list later on. */
fDirty = true;
}
while (pCurr)
{
fDirty = true;
}
}
else
{
("Invalid flags set\n"));
while (pCurr)
{
{
fDirty = true;
}
else
}
}
bool fCommit = false;
if (fDirty)
/* Dereference so that it isn't protected anymore except we issued anyother write for it. */
if (fCommit)
/* Complete waiters now. */
while (pComplete)
}
VMMR3DECL(void) PDMR3BlkCacheIoXferComplete(PPDMBLKCACHE pBlkCache, PPDMBLKCACHEIOXFER hIoXfer, int rcIoXfer)
{
else
}
/**
* Callback for the AVL do with all routine. Waits for a cachen entry to finish any pending I/O.
*
* @returns IPRT status code.
* @param pNode The node to destroy.
* @param pvUser Opaque user data.
*/
{
{
/* Leave the locks to let the I/O thread make progress but reference the entry to prevent eviction. */
RTThreadSleep(1);
/* Re-enter all locks and drop the reference. */
}
return VINF_SUCCESS;
}
{
int rc = VINF_SUCCESS;
/* Wait for all I/O to complete. */
return rc;
}
{
return VINF_SUCCESS;
}