VirtualBox

source: vbox/trunk/src/VBox/VMM/PDMAsyncCompletionFileCache.cpp@ 22309

Last change on this file since 22309 was 22309, checked in by vboxsync, 15 years ago

PDMAsyncCompletion: Add first part of the cache for file I/O

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 39.4 KB
Line 
1/* $Id: PDMAsyncCompletionFileCache.cpp 22309 2009-08-17 20:59:28Z vboxsync $ */
2/** @file
3 * PDM Async I/O - Transport data asynchronous in R3 using EMT.
4 * File data cache.
5 */
6
7/*
8 * Copyright (C) 2006-2008 Sun Microsystems, Inc.
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 *
18 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
19 * Clara, CA 95054 USA or visit http://www.sun.com if you need
20 * additional information or have any questions.
21 */
22#define LOG_GROUP LOG_GROUP_PDM_ASYNC_COMPLETION
23#include <iprt/types.h>
24#include <iprt/mem.h>
25#include <VBox/log.h>
26#include <VBox/stam.h>
27
28#include "PDMAsyncCompletionFileInternal.h"
29
30static void pdmacFileCacheTaskCompleted(PPDMACTASKFILE pTask, void *pvUser);
31
32static void pdmacFileCacheCheckList(PPDMACFILELRULIST pList, PPDMACFILECACHEENTRY pNotInList)
33{
34#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
35 PPDMACFILECACHEENTRY pCurr = pList->pHead;
36
37 /* Check that there are no double entries and no cycles in the list. */
38 while (pCurr)
39 {
40 PPDMACFILECACHEENTRY pNext = pCurr->pNext;
41
42 while (pNext)
43 {
44 AssertMsg(pCurr != pNext,
45 ("Entry %#p is at least two times in list %#p or there is a cycle in the list\n",
46 pCurr, pList));
47 pNext = pNext->pNext;
48 }
49
50 AssertMsg(pCurr != pNotInList, ("Not allowed entry %#p is in list\n", pCurr));
51
52 if (!pCurr->pNext)
53 AssertMsg(pCurr == pList->pTail, ("End of list reached but last element is not list tail\n"));
54
55 pCurr = pCurr->pNext;
56 }
57#endif
58}
59
60static void pdmacFileCacheEntryRemoveFromList(PPDMACFILECACHEENTRY pEntry)
61{
62 PPDMACFILELRULIST pList = pEntry->pList;
63 PPDMACFILECACHEENTRY pPrev, pNext;
64
65 LogFlowFunc((": Deleting entry %#p from list %#p\n", pEntry, pList));
66
67 AssertPtr(pList);
68 pdmacFileCacheCheckList(pList, NULL);
69
70 pPrev = pEntry->pPrev;
71 pNext = pEntry->pNext;
72
73 AssertMsg(pEntry != pPrev, ("Entry links to itself as previous element\n"));
74 AssertMsg(pEntry != pNext, ("Entry links to itself as next element\n"));
75
76 if (pPrev)
77 pPrev->pNext = pNext;
78 else
79 {
80 pList->pHead = pNext;
81
82 if (pNext)
83 pNext->pPrev = NULL;
84 }
85
86 if (pNext)
87 pNext->pPrev = pPrev;
88 else
89 {
90 pList->pTail = pPrev;
91
92 if (pPrev)
93 pPrev->pNext = NULL;
94 }
95
96 pEntry->pList = NULL;
97 pEntry->pPrev = NULL;
98 pEntry->pNext = NULL;
99 pList->cbCached -= pEntry->cbData;
100 pdmacFileCacheCheckList(pList, pEntry);
101}
102
103static void pdmacFileCacheEntryAddToList(PPDMACFILELRULIST pList, PPDMACFILECACHEENTRY pEntry)
104{
105 LogFlowFunc((": Adding entry %#p to list %#p\n", pEntry, pList));
106 pdmacFileCacheCheckList(pList, NULL);
107
108 /* Remove from old list if needed */
109 if (pEntry->pList)
110 pdmacFileCacheEntryRemoveFromList(pEntry);
111
112 pEntry->pNext = pList->pHead;
113 if (pList->pHead)
114 pList->pHead->pPrev = pEntry;
115 else
116 {
117 Assert(!pList->pTail);
118 pList->pTail = pEntry;
119 }
120
121 pEntry->pPrev = NULL;
122 pList->pHead = pEntry;
123 pList->cbCached += pEntry->cbData;
124 pEntry->pList = pList;
125 pdmacFileCacheCheckList(pList, NULL);
126}
127
128/**
129 * Destroys a LRU list freeing all entries.
130 *
131 * @returns nothing
132 * @param pList Pointer to the LRU list to destroy.
133 *
134 * @note The caller must own the critical section of the cache.
135 */
136static void pdmacFileCacheDestroyList(PPDMACFILELRULIST pList)
137{
138 while (pList->pHead)
139 {
140 PPDMACFILECACHEENTRY pEntry = pList->pHead;
141
142 pList->pHead = pEntry->pNext;
143
144 AssertMsg(!(pEntry->fFlags & (PDMACFILECACHE_ENTRY_IO_IN_PROGRESS | PDMACFILECACHE_ENTRY_IS_DIRTY)),
145 ("Entry is dirty and/or still in progress fFlags=%#x\n", pEntry->fFlags));
146
147 RTMemPageFree(pEntry->pbData);
148 RTMemFree(pEntry);
149 }
150}
151
152static size_t pdmacFileCacheEvictPagesFrom(PPDMACFILECACHEGLOBAL pCache, size_t cbData,
153 PPDMACFILELRULIST pListSrc, PPDMACFILELRULIST pGhostListDst)
154{
155 size_t cbEvicted = 0;
156
157 AssertMsg(cbData > 0, ("Evicting 0 bytes not possible\n"));
158 AssertMsg( !pGhostListDst
159 || (pGhostListDst == &pCache->LruRecentlyGhost)
160 || (pGhostListDst == &pCache->LruFrequentlyGhost),
161 ("Destination list must be NULL or one of the ghost lists\n"));
162
163 /* Start deleting from the tail. */
164 PPDMACFILECACHEENTRY pEntry = pListSrc->pTail;
165
166 while ((cbEvicted < cbData) && pEntry)
167 {
168 PPDMACFILECACHEENTRY pCurr = pEntry;
169
170 pEntry = pEntry->pPrev;
171
172 /* We can't evict pages which are currently in progress */
173 if (!(pCurr->fFlags & PDMACFILECACHE_ENTRY_IO_IN_PROGRESS))
174 {
175 LogFlow(("Evicting entry %#p (%u bytes)\n", pCurr, pCurr->cbData));
176 if (pCurr->pbData)
177 {
178 RTMemPageFree(pCurr->pbData);
179 pCurr->pbData = NULL;
180 }
181
182 cbEvicted += pCurr->cbData;
183
184 if (pGhostListDst)
185 {
186 pdmacFileCacheEntryAddToList(pGhostListDst, pCurr);
187 }
188 else
189 {
190 /* Delete the entry from the AVL tree it is assigned to. */
191 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
192 RTAvlrFileOffsetRemove(pCurr->pEndpoint->DataCache.pTree, pCurr->Core.Key);
193 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
194
195 pdmacFileCacheEntryRemoveFromList(pCurr);
196 pCache->cbCached -= pCurr->cbData;
197 RTMemFree(pCurr);
198 }
199 }
200 else
201 LogFlow(("Entry %#p (%u bytes) is still in progress and can't be evicted\n", pCurr, pCurr->cbData));
202 }
203
204 return cbEvicted;
205}
206
207static size_t pdmacFileCacheReplace(PPDMACFILECACHEGLOBAL pCache, size_t cbData, PPDMACFILELRULIST pEntryList)
208{
209 if ( (pCache->LruRecentlyUsed.cbCached)
210 && ( (pCache->LruRecentlyUsed.cbCached > pCache->uAdaptVal)
211 || ( (pEntryList == &pCache->LruFrequentlyGhost)
212 && (pCache->LruRecentlyUsed.cbCached == pCache->uAdaptVal))))
213 {
214 /* We need to remove entry size pages from T1 and move the entries to B1 */
215 return pdmacFileCacheEvictPagesFrom(pCache, cbData,
216 &pCache->LruRecentlyUsed,
217 &pCache->LruRecentlyGhost);
218 }
219 else
220 {
221 /* We need to remove entry size pages from T2 and move the entries to B2 */
222 return pdmacFileCacheEvictPagesFrom(pCache, cbData,
223 &pCache->LruFrequentlyUsed,
224 &pCache->LruFrequentlyGhost);
225 }
226}
227
228static size_t pdmacFileCacheEvict(PPDMACFILECACHEGLOBAL pCache, size_t cbData)
229{
230 size_t cbRemoved = ~0;
231
232 if ((pCache->LruRecentlyUsed.cbCached + pCache->LruRecentlyGhost.cbCached) >= pCache->cbMax)
233 {
234 /* Delete desired pages from the cache. */
235 if (pCache->LruRecentlyUsed.cbCached < pCache->cbMax)
236 {
237 cbRemoved = pdmacFileCacheEvictPagesFrom(pCache, cbData,
238 &pCache->LruRecentlyGhost,
239 NULL);
240 }
241 else
242 {
243 cbRemoved = pdmacFileCacheEvictPagesFrom(pCache, cbData,
244 &pCache->LruRecentlyUsed,
245 NULL);
246 }
247 }
248 else
249 {
250 uint32_t cbUsed = pCache->LruRecentlyUsed.cbCached + pCache->LruRecentlyGhost.cbCached +
251 pCache->LruFrequentlyUsed.cbCached + pCache->LruFrequentlyGhost.cbCached;
252
253 if (cbUsed >= pCache->cbMax)
254 {
255 if (cbUsed == 2*pCache->cbMax)
256 cbRemoved = pdmacFileCacheEvictPagesFrom(pCache, cbData,
257 &pCache->LruFrequentlyGhost,
258 NULL);
259
260 if (cbRemoved >= cbData)
261 cbRemoved = pdmacFileCacheReplace(pCache, cbData, NULL);
262 }
263 }
264
265 return cbRemoved;
266}
267
268static void pdmacFileCacheUpdate(PPDMACFILECACHEGLOBAL pCache, PPDMACFILECACHEENTRY pEntry)
269{
270 int32_t uUpdateVal = 0;
271
272 /* Update parameters */
273 if (pEntry->pList == &pCache->LruRecentlyGhost)
274 {
275 if (pCache->LruRecentlyGhost.cbCached >= pCache->LruFrequentlyGhost.cbCached)
276 uUpdateVal = 1;
277 else
278 uUpdateVal = pCache->LruFrequentlyGhost.cbCached / pCache->LruRecentlyGhost.cbCached;
279
280 pCache->uAdaptVal = RT_MIN(pCache->uAdaptVal + uUpdateVal, pCache->cbMax);
281 }
282 else if (pEntry->pList == &pCache->LruFrequentlyGhost)
283 {
284 if (pCache->LruFrequentlyGhost.cbCached >= pCache->LruRecentlyGhost.cbCached)
285 uUpdateVal = 1;
286 else
287 uUpdateVal = pCache->LruRecentlyGhost.cbCached / pCache->LruFrequentlyGhost.cbCached;
288
289 pCache->uAdaptVal = RT_MIN(pCache->uAdaptVal - uUpdateVal, 0);
290 }
291 else
292 AssertMsgFailed(("Invalid list type\n"));
293}
294
295static void pdmacFileCacheReadFromEndpoint(PPDMACFILECACHEENTRY pEntry)
296{
297 /* Make sure no one evicts the entry while it is accessed. */
298 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IO_IN_PROGRESS;
299
300 PPDMACTASKFILE pIoTask = pdmacFileTaskAlloc(pEntry->pEndpoint);
301 AssertPtr(pIoTask);
302
303 AssertMsg(pEntry->pbData, ("Entry is in ghost state\n"));
304
305 pIoTask->pEndpoint = pEntry->pEndpoint;
306 pIoTask->enmTransferType = PDMACTASKFILETRANSFER_READ;
307 pIoTask->Off = pEntry->Core.Key;
308 pIoTask->DataSeg.cbSeg = pEntry->cbData;
309 pIoTask->DataSeg.pvSeg = pEntry->pbData;
310 pIoTask->pvUser = pEntry;
311 pIoTask->pfnCompleted = pdmacFileCacheTaskCompleted;
312
313 /* Send it off to the I/O manager. */
314 pdmacFileEpAddTask(pEntry->pEndpoint, pIoTask);
315}
316
317static void pdmacFileCacheWriteToEndpoint(PPDMACFILECACHEENTRY pEntry)
318{
319 /* Make sure no one evicts the entry while it is accessed. */
320 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IO_IN_PROGRESS;
321
322 PPDMACTASKFILE pIoTask = pdmacFileTaskAlloc(pEntry->pEndpoint);
323 AssertPtr(pIoTask);
324
325 AssertMsg(pEntry->pbData, ("Entry is in ghost state\n"));
326
327 pIoTask->pEndpoint = pEntry->pEndpoint;
328 pIoTask->enmTransferType = PDMACTASKFILETRANSFER_WRITE;
329 pIoTask->Off = pEntry->Core.Key;
330 pIoTask->DataSeg.cbSeg = pEntry->cbData;
331 pIoTask->DataSeg.pvSeg = pEntry->pbData;
332 pIoTask->pvUser = pEntry;
333 pIoTask->pfnCompleted = pdmacFileCacheTaskCompleted;
334
335 /* Send it off to the I/O manager. */
336 pdmacFileEpAddTask(pEntry->pEndpoint, pIoTask);
337}
338
339static void pdmacFileCacheTaskCompleted(PPDMACTASKFILE pTask, void *pvUser)
340{
341 PPDMACFILECACHEENTRY pEntry = (PPDMACFILECACHEENTRY)pvUser;
342 PPDMACFILECACHEGLOBAL pCache = pEntry->pCache;
343
344 RTCritSectEnter(&pCache->CritSect);
345
346 pEntry->fFlags &= ~PDMACFILECACHE_ENTRY_IO_IN_PROGRESS;
347
348 if (pTask->enmTransferType == PDMACTASKFILETRANSFER_WRITE)
349 {
350 pEntry->fFlags &= ~PDMACFILECACHE_ENTRY_IS_DIRTY;
351
352 /* Process waiting segment list. The data in entry might have changed inbetween. */
353 PPDMACFILETASKSEG pCurr = pEntry->pHead;
354
355 while (pCurr)
356 {
357 AssertMsg(pCurr->fWrite, ("Completed write entries should never have read tasks attached\n"));
358
359 memcpy(pEntry->pbData + pCurr->uBufOffset, pCurr->pvBuf, pCurr->cbTransfer);
360 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IS_DIRTY;
361
362 uint32_t uOld = ASMAtomicSubU32(&pCurr->pTask->cbTransferLeft, pCurr->cbTransfer);
363 AssertMsg(uOld >= pCurr->cbTransfer, ("New value would overflow\n"));
364 if (!(uOld - pCurr->cbTransfer)
365 && !ASMAtomicXchgBool(&pCurr->pTask->fCompleted, true))
366 pdmR3AsyncCompletionCompleteTask(&pCurr->pTask->Core);
367
368 PPDMACFILETASKSEG pFree = pCurr;
369 pCurr = pCurr->pNext;
370
371 RTMemFree(pFree);
372 }
373 }
374 else
375 {
376 AssertMsg(pTask->enmTransferType == PDMACTASKFILETRANSFER_READ, ("Invalid transfer type\n"));
377 AssertMsg(!(pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY),("Invalid flags set\n"));
378
379 /* Process waiting segment list. */
380 PPDMACFILETASKSEG pCurr = pEntry->pHead;
381
382 while (pCurr)
383 {
384 if (pCurr->fWrite)
385 {
386 memcpy(pEntry->pbData + pCurr->uBufOffset, pCurr->pvBuf, pCurr->cbTransfer);
387 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IS_DIRTY;
388 }
389 else
390 memcpy(pCurr->pvBuf, pEntry->pbData + pCurr->uBufOffset, pCurr->cbTransfer);
391
392 uint32_t uOld = ASMAtomicSubU32(&pCurr->pTask->cbTransferLeft, pCurr->cbTransfer);
393 AssertMsg(uOld >= pCurr->cbTransfer, ("New value would overflow\n"));
394 if (!(uOld - pCurr->cbTransfer)
395 && !ASMAtomicXchgBool(&pCurr->pTask->fCompleted, true))
396 pdmR3AsyncCompletionCompleteTask(&pCurr->pTask->Core);
397
398 PPDMACFILETASKSEG pFree = pCurr;
399 pCurr = pCurr->pNext;
400
401 RTMemFree(pFree);
402 }
403 }
404
405 pEntry->pHead = NULL;
406
407 if (pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY)
408 pdmacFileCacheWriteToEndpoint(pEntry);
409
410 RTCritSectLeave(&pCache->CritSect);
411}
412
413int pdmacFileCacheInit(PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile, PCFGMNODE pCfgNode)
414{
415 int rc = VINF_SUCCESS;
416 PPDMACFILECACHEGLOBAL pCache = &pClassFile->Cache;
417
418 /* Initialize members */
419 pCache->LruRecentlyUsed.pHead = NULL;
420 pCache->LruRecentlyUsed.pTail = NULL;
421 pCache->LruRecentlyUsed.cbCached = 0;
422
423 pCache->LruFrequentlyUsed.pHead = NULL;
424 pCache->LruFrequentlyUsed.pTail = NULL;
425 pCache->LruFrequentlyUsed.cbCached = 0;
426
427 pCache->LruRecentlyGhost.pHead = NULL;
428 pCache->LruRecentlyGhost.pTail = NULL;
429 pCache->LruRecentlyGhost.cbCached = 0;
430
431 pCache->LruFrequentlyGhost.pHead = NULL;
432 pCache->LruFrequentlyGhost.pTail = NULL;
433 pCache->LruFrequentlyGhost.cbCached = 0;
434
435 rc = CFGMR3QueryU32Def(pCfgNode, "CacheSize", &pCache->cbMax, 5 * _1M);
436 AssertLogRelRCReturn(rc, rc);
437
438 pCache->cbCached = 0;
439 pCache->uAdaptVal = 0;
440 LogFlowFunc((": Maximum number of bytes cached %u\n", pCache->cbCached));
441
442 STAMR3Register(pClassFile->Core.pVM, &pCache->cbMax,
443 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
444 "/PDM/AsyncCompletion/File/cbMax",
445 STAMUNIT_BYTES,
446 "Maximum cache size");
447 STAMR3Register(pClassFile->Core.pVM, &pCache->cbCached,
448 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
449 "/PDM/AsyncCompletion/File/cbCached",
450 STAMUNIT_BYTES,
451 "Currently used cache");
452 STAMR3Register(pClassFile->Core.pVM, &pCache->LruRecentlyUsed.cbCached,
453 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
454 "/PDM/AsyncCompletion/File/cbCachedMru",
455 STAMUNIT_BYTES,
456 "Number of bytes cached in Mru list");
457 STAMR3Register(pClassFile->Core.pVM, &pCache->LruFrequentlyUsed.cbCached,
458 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
459 "/PDM/AsyncCompletion/File/cbCachedFru",
460 STAMUNIT_BYTES,
461 "Number of bytes cached in Fru list");
462 STAMR3Register(pClassFile->Core.pVM, &pCache->LruRecentlyGhost.cbCached,
463 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
464 "/PDM/AsyncCompletion/File/cbCachedMruGhost",
465 STAMUNIT_BYTES,
466 "Number of bytes cached in Mru ghost list");
467 STAMR3Register(pClassFile->Core.pVM, &pCache->LruFrequentlyGhost.cbCached,
468 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
469 "/PDM/AsyncCompletion/File/cbCachedFruGhost",
470 STAMUNIT_BYTES, "Number of bytes cached in Fru ghost list");
471
472#ifdef VBOX_WITH_STATISTICS
473 STAMR3Register(pClassFile->Core.pVM, &pCache->cHits,
474 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
475 "/PDM/AsyncCompletion/File/CacheHits",
476 STAMUNIT_COUNT, "Number of hits in the cache");
477 STAMR3Register(pClassFile->Core.pVM, &pCache->cPartialHits,
478 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
479 "/PDM/AsyncCompletion/File/CachePartialHits",
480 STAMUNIT_COUNT, "Number of partial hits in the cache");
481 STAMR3Register(pClassFile->Core.pVM, &pCache->cMisses,
482 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
483 "/PDM/AsyncCompletion/File/CacheMisses",
484 STAMUNIT_COUNT, "Number of misses when accessing the cache");
485 STAMR3Register(pClassFile->Core.pVM, &pCache->StatRead,
486 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
487 "/PDM/AsyncCompletion/File/CacheRead",
488 STAMUNIT_BYTES, "Number of bytes read from the cache");
489 STAMR3Register(pClassFile->Core.pVM, &pCache->StatWritten,
490 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
491 "/PDM/AsyncCompletion/File/CacheWritten",
492 STAMUNIT_BYTES, "Number of bytes written to the cache");
493 STAMR3Register(pClassFile->Core.pVM, &pCache->StatTreeGet,
494 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
495 "/PDM/AsyncCompletion/File/CacheTreeGet",
496 STAMUNIT_TICKS_PER_CALL, "Time taken to access an entry in the tree");
497 STAMR3Register(pClassFile->Core.pVM, &pCache->StatTreeInsert,
498 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
499 "/PDM/AsyncCompletion/File/CacheTreeInsert",
500 STAMUNIT_TICKS_PER_CALL, "Time taken to insert an entry in the tree");
501 STAMR3Register(pClassFile->Core.pVM, &pCache->StatTreeRemove,
502 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
503 "/PDM/AsyncCompletion/File/CacheTreeRemove",
504 STAMUNIT_TICKS_PER_CALL, "Time taken to remove an entry an the tree");
505#endif
506
507 /* Initialize the critical section */
508 rc = RTCritSectInit(&pCache->CritSect);
509 return rc;
510}
511
512void pdmacFileCacheDestroy(PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile)
513{
514 PPDMACFILECACHEGLOBAL pCache = &pClassFile->Cache;
515
516 /* Make sure no one else uses the cache now */
517 RTCritSectEnter(&pCache->CritSect);
518
519 /* Cleanup deleting all cache entries waiting for in progress entries to finish. */
520 pdmacFileCacheDestroyList(&pCache->LruRecentlyUsed);
521 pdmacFileCacheDestroyList(&pCache->LruFrequentlyUsed);
522 pdmacFileCacheDestroyList(&pCache->LruRecentlyGhost);
523 pdmacFileCacheDestroyList(&pCache->LruFrequentlyGhost);
524
525 RTCritSectLeave(&pCache->CritSect);
526
527 RTCritSectDelete(&pCache->CritSect);
528}
529
530int pdmacFileEpCacheInit(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile)
531{
532 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
533
534 pEndpointCache->pTree = (PAVLRFOFFTREE)RTMemAllocZ(sizeof(AVLRFOFFTREE));
535 pEndpointCache->pCache = &pClassFile->Cache;
536
537 return VINF_SUCCESS;
538}
539
540static int pdmacFileEpCacheEntryDestroy(PAVLRFOFFNODECORE pNode, void *pvUser)
541{
542 PPDMACFILECACHEENTRY pEntry = (PPDMACFILECACHEENTRY)pNode;
543 PPDMACFILECACHEGLOBAL pCache = (PPDMACFILECACHEGLOBAL)pvUser;
544
545 AssertMsg(!(pEntry->fFlags & (PDMACFILECACHE_ENTRY_IO_IN_PROGRESS | PDMACFILECACHE_ENTRY_IS_DIRTY)),
546 ("Entry is dirty and/or still in progress fFlags=%#x\n", pEntry->fFlags));
547
548 pdmacFileCacheEntryRemoveFromList(pEntry);
549 pCache->cbCached -= pEntry->cbData;
550
551 RTMemPageFree(pEntry->pbData);
552 RTMemFree(pEntry);
553
554 return VINF_SUCCESS;
555}
556
557void pdmacFileEpCacheDestroy(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint)
558{
559 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
560 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
561
562 /* Make sure nobody is accessing the cache while we delete the tree. */
563 RTCritSectEnter(&pCache->CritSect);
564 RTAvlrFileOffsetDestroy(pEndpointCache->pTree, pdmacFileEpCacheEntryDestroy, pCache);
565 RTCritSectLeave(&pCache->CritSect);
566}
567
568/**
569 * Advances the current segment buffer by the number of bytes transfered
570 * or gets the next segment.
571 */
572#define ADVANCE_SEGMENT_BUFFER(BytesTransfered) \
573 do \
574 { \
575 cbSegLeft -= BytesTransfered; \
576 if (!cbSegLeft) \
577 { \
578 iSegCurr++; \
579 cbSegLeft = paSegments[iSegCurr].cbSeg; \
580 pbSegBuf = (uint8_t *)paSegments[iSegCurr].pvSeg; \
581 } \
582 else \
583 pbSegBuf += BytesTransfered; \
584 } \
585 while (0);
586
587int pdmacFileEpCacheRead(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONTASKFILE pTask,
588 RTFOFF off, PCPDMDATASEG paSegments, size_t cSegments,
589 size_t cbRead)
590{
591 int rc = VINF_SUCCESS;
592 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
593 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
594 PPDMACFILECACHEENTRY pEntry;
595
596 LogFlowFunc((": pEndpoint=%#p{%s} pTask=%#p off=%RTfoff paSegments=%#p cSegments=%u cbRead=%u\n",
597 pEndpoint, pEndpoint->Core.pszUri, pTask, off, paSegments, cSegments, cbRead));
598
599 pTask->cbTransferLeft = cbRead;
600 /* Set to completed to make sure that the task is valid while we access it. */
601 ASMAtomicWriteBool(&pTask->fCompleted, true);
602
603 RTCritSectEnter(&pCache->CritSect);
604
605 int iSegCurr = 0;
606 uint8_t *pbSegBuf = (uint8_t *)paSegments[iSegCurr].pvSeg;
607 size_t cbSegLeft = paSegments[iSegCurr].cbSeg;
608
609 while (cbRead)
610 {
611 size_t cbToRead;
612
613 STAM_PROFILE_ADV_START(&pCache->StatTreeGet, Cache);
614 pEntry = (PPDMACFILECACHEENTRY)RTAvlrFileOffsetRangeGet(pEndpointCache->pTree, off);
615 STAM_PROFILE_ADV_STOP(&pCache->StatTreeGet, Cache);
616
617 /*
618 * If there is no entry we try to create a new one eviciting unused pages
619 * if the cache is full. If this is not possible we will pass the request through
620 * and skip the caching (all entries may be still in progress so they can't
621 * be evicted)
622 * If we have an entry it can be in one of the LRU lists where the entry
623 * contains data (recently used or frequently used LRU) so we can just read
624 * the data we need and put the entry at the head of the frequently used LRU list.
625 * In case the entry is in one of the ghost lists it doesn't contain any data.
626 * We have to fetch it again evicting pages from either T1 or T2 to make room.
627 */
628 if (pEntry)
629 {
630 RTFOFF OffDiff = off - pEntry->Core.Key;
631
632 AssertMsg(off >= pEntry->Core.Key,
633 ("Overflow in calculation off=%RTfoff OffsetAligned=%RTfoff\n",
634 off, pEntry->Core.Key));
635
636 AssertPtr(pEntry->pList);
637
638 cbToRead = RT_MIN(pEntry->cbData - OffDiff, cbRead);
639 cbRead -= cbToRead;
640
641 if (!cbRead)
642 STAM_COUNTER_INC(&pCache->cHits);
643 else
644 STAM_COUNTER_INC(&pCache->cPartialHits);
645
646 STAM_COUNTER_ADD(&pCache->StatRead, cbToRead);
647
648 /* Ghost lists contain no data. */
649 if ( (pEntry->pList == &pCache->LruRecentlyUsed)
650 || (pEntry->pList == &pCache->LruFrequentlyUsed))
651 {
652 if ( (pEntry->fFlags & PDMACFILECACHE_ENTRY_IO_IN_PROGRESS)
653 && !(pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY))
654 {
655 /* Entry didn't completed yet. Append to the list */
656 while (cbToRead)
657 {
658 PPDMACFILETASKSEG pSeg = (PPDMACFILETASKSEG)RTMemAllocZ(sizeof(PDMACFILETASKSEG));
659
660 pSeg->pTask = pTask;
661 pSeg->uBufOffset = OffDiff;
662 pSeg->cbTransfer = RT_MIN(cbToRead, cbSegLeft);
663 pSeg->pvBuf = pbSegBuf;
664 pSeg->fWrite = false;
665
666 ADVANCE_SEGMENT_BUFFER(pSeg->cbTransfer);
667
668 pSeg->pNext = pEntry->pHead;
669 pEntry->pHead = pSeg;
670
671 off += pSeg->cbTransfer;
672 cbToRead -= pSeg->cbTransfer;
673 OffDiff += pSeg->cbTransfer;
674 }
675 }
676 else
677 {
678 /* Read as much as we can from the entry. */
679 while (cbToRead)
680 {
681 size_t cbCopy = RT_MIN(cbSegLeft, cbToRead);
682
683 memcpy(pbSegBuf, pEntry->pbData + OffDiff, cbCopy);
684
685 ADVANCE_SEGMENT_BUFFER(cbCopy);
686
687 cbToRead -= cbCopy;
688 off += cbCopy;
689 OffDiff += cbCopy;
690 ASMAtomicSubS32(&pTask->cbTransferLeft, cbCopy);
691 }
692 }
693
694 /* Move this entry to the top position */
695 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
696 }
697 else
698 {
699 pdmacFileCacheUpdate(pCache, pEntry);
700 pdmacFileCacheReplace(pCache, pEntry->cbData, pEntry->pList);
701
702 /* Move the entry to T2 and fetch it to the cache. */
703 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
704
705 pEntry->pbData = (uint8_t *)RTMemPageAlloc(pEntry->cbData);
706 AssertPtr(pEntry->pbData);
707
708 while (cbToRead)
709 {
710 PPDMACFILETASKSEG pSeg = (PPDMACFILETASKSEG)RTMemAllocZ(sizeof(PDMACFILETASKSEG));
711
712 AssertMsg(off >= pEntry->Core.Key,
713 ("Overflow in calculation off=%RTfoff OffsetAligned=%RTfoff\n",
714 off, pEntry->Core.Key));
715
716 pSeg->pTask = pTask;
717 pSeg->uBufOffset = OffDiff;
718 pSeg->cbTransfer = RT_MIN(cbToRead, cbSegLeft);
719 pSeg->pvBuf = pbSegBuf;
720
721 ADVANCE_SEGMENT_BUFFER(pSeg->cbTransfer);
722
723 pSeg->pNext = pEntry->pHead;
724 pEntry->pHead = pSeg;
725
726 off += pSeg->cbTransfer;
727 OffDiff += pSeg->cbTransfer;
728 cbToRead -= pSeg->cbTransfer;
729 }
730
731 pdmacFileCacheReadFromEndpoint(pEntry);
732 }
733 }
734 else
735 {
736 /* No entry found for this offset. Get best fit entry and fetch the data to the cache. */
737 STAM_PROFILE_ADV_START(&pCache->StatTreeGet, Cache);
738 PPDMACFILECACHEENTRY pEntryBestFit = (PPDMACFILECACHEENTRY)RTAvlrFileOffsetGetBestFit(pEndpointCache->pTree, off, true);
739 STAM_PROFILE_ADV_STOP(&pCache->StatTreeGet, Cache);
740
741 LogFlow(("%sbest fit entry for off=%RTfoff (BestFit=%RTfoff BestFitEnd=%RTfoff BestFitSize=%u)\n",
742 pEntryBestFit ? "" : "No ",
743 off,
744 pEntryBestFit ? pEntryBestFit->Core.Key : 0,
745 pEntryBestFit ? pEntryBestFit->Core.KeyLast : 0,
746 pEntryBestFit ? pEntryBestFit->cbData : 0));
747
748 if (pEntryBestFit && ((off + (RTFOFF)cbRead) > pEntryBestFit->Core.Key))
749 cbToRead = pEntryBestFit->Core.Key - off;
750 else
751 cbToRead = cbRead;
752
753 cbRead -= cbToRead;
754
755 if (!cbRead)
756 STAM_COUNTER_INC(&pCache->cMisses);
757 else
758 STAM_COUNTER_INC(&pCache->cPartialHits);
759
760 size_t cbRemoved = pdmacFileCacheEvict(pCache, cbToRead);
761
762 if (cbRemoved >= cbToRead)
763 {
764 LogFlow(("Evicted %u bytes (%u requested). Creating new cache entry\n", cbRemoved, cbToRead));
765 PPDMACFILECACHEENTRY pEntryNew = (PPDMACFILECACHEENTRY)RTMemAllocZ(sizeof(PDMACFILECACHEENTRY));
766 AssertPtr(pEntryNew);
767
768 pEntryNew->Core.Key = off;
769 pEntryNew->Core.KeyLast = off + cbToRead - 1;
770 pEntryNew->pEndpoint = pEndpoint;
771 pEntryNew->pCache = pCache;
772 pEntryNew->fFlags = 0;
773 pEntryNew->pList = NULL;
774 pEntryNew->cbData = cbToRead;
775 pEntryNew->pHead = NULL;
776 pEntryNew->pbData = (uint8_t *)RTMemPageAlloc(cbToRead);
777 AssertPtr(pEntryNew->pbData);
778 pdmacFileCacheEntryAddToList(&pCache->LruRecentlyUsed, pEntryNew);
779
780 STAM_PROFILE_ADV_START(&pCache->StatTreeInsert, Cache);
781 bool fInserted = RTAvlrFileOffsetInsert(pEndpoint->DataCache.pTree, &pEntryNew->Core);
782 AssertMsg(fInserted, ("Node was not inserted into tree\n"));
783 STAM_PROFILE_ADV_STOP(&pCache->StatTreeInsert, Cache);
784
785 uint32_t uBufOffset = 0;
786
787 pCache->cbCached += cbToRead;
788
789 while (cbToRead)
790 {
791 PPDMACFILETASKSEG pSeg = (PPDMACFILETASKSEG)RTMemAllocZ(sizeof(PDMACFILETASKSEG));
792
793 pSeg->pTask = pTask;
794 pSeg->uBufOffset = uBufOffset;
795 pSeg->cbTransfer = RT_MIN(cbToRead, cbSegLeft);
796 pSeg->pvBuf = pbSegBuf;
797
798 ADVANCE_SEGMENT_BUFFER(pSeg->cbTransfer);
799
800 pSeg->pNext = pEntryNew->pHead;
801 pEntryNew->pHead = pSeg;
802
803 off += pSeg->cbTransfer;
804 cbToRead -= pSeg->cbTransfer;
805 uBufOffset += pSeg->cbTransfer;
806 }
807
808 pdmacFileCacheReadFromEndpoint(pEntryNew);
809 }
810 else
811 {
812 /*
813 * There is not enough free space in the cache.
814 * Pass the request directly to the I/O manager.
815 */
816 LogFlow(("Couldn't evict %u bytes from the cache (%u actually removed). Remaining request will be passed through\n", cbToRead, cbRemoved));
817
818 while (cbToRead)
819 {
820 PPDMACTASKFILE pIoTask = pdmacFileTaskAlloc(pEndpoint);
821 AssertPtr(pIoTask);
822
823 pIoTask->pEndpoint = pEndpoint;
824 pIoTask->enmTransferType = PDMACTASKFILETRANSFER_READ;
825 pIoTask->Off = off;
826 pIoTask->DataSeg.cbSeg = RT_MIN(cbToRead, cbSegLeft);
827 pIoTask->DataSeg.pvSeg = pbSegBuf;
828 pIoTask->pvUser = pTask;
829 pIoTask->pfnCompleted = pdmacFileEpTaskCompleted;
830
831 off += pIoTask->DataSeg.cbSeg;
832 cbToRead -= pIoTask->DataSeg.cbSeg;
833
834 ADVANCE_SEGMENT_BUFFER(pIoTask->DataSeg.cbSeg);
835
836 /* Send it off to the I/O manager. */
837 pdmacFileEpAddTask(pEndpoint, pIoTask);
838 }
839 }
840 }
841 }
842
843 ASMAtomicWriteBool(&pTask->fCompleted, false);
844
845 if (ASMAtomicReadS32(&pTask->cbTransferLeft) == 0
846 && !ASMAtomicXchgBool(&pTask->fCompleted, true))
847 pdmR3AsyncCompletionCompleteTask(&pTask->Core);
848
849 RTCritSectLeave(&pCache->CritSect);
850
851 return rc;
852}
853
854int pdmacFileEpCacheWrite(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONTASKFILE pTask,
855 RTFOFF off, PCPDMDATASEG paSegments, size_t cSegments,
856 size_t cbWrite)
857{
858 int rc = VINF_SUCCESS;
859 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
860 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
861 PPDMACFILECACHEENTRY pEntry;
862
863 LogFlowFunc((": pEndpoint=%#p{%s} pTask=%#p off=%RTfoff paSegments=%#p cSegments=%u cbWrite=%u\n",
864 pEndpoint, pEndpoint->Core.pszUri, pTask, off, paSegments, cSegments, cbWrite));
865
866 pTask->cbTransferLeft = cbWrite;
867 /* Set to completed to make sure that the task is valid while we access it. */
868 ASMAtomicWriteBool(&pTask->fCompleted, true);
869
870 RTCritSectEnter(&pCache->CritSect);
871
872 int iSegCurr = 0;
873 uint8_t *pbSegBuf = (uint8_t *)paSegments[iSegCurr].pvSeg;
874 size_t cbSegLeft = paSegments[iSegCurr].cbSeg;
875
876 while (cbWrite)
877 {
878 size_t cbToWrite;
879
880 STAM_PROFILE_ADV_START(&pCache->StatTreeGet, Cache);
881 pEntry = (PPDMACFILECACHEENTRY)RTAvlrFileOffsetRangeGet(pEndpointCache->pTree, off);
882 STAM_PROFILE_ADV_STOP(&pCache->StatTreeGet, Cache);
883
884 if (pEntry)
885 {
886 /* Write the data into the entry and mark it as dirty */
887 AssertPtr(pEntry->pList);
888
889 RTFOFF OffDiff = off - pEntry->Core.Key;
890
891 AssertMsg(off >= pEntry->Core.Key,
892 ("Overflow in calculation off=%RTfoff OffsetAligned=%RTfoff\n",
893 off, pEntry->Core.Key));
894
895 cbToWrite = RT_MIN(pEntry->cbData - OffDiff, cbWrite);
896 cbWrite -= cbToWrite;
897
898 if (!cbWrite)
899 STAM_COUNTER_INC(&pCache->cHits);
900 else
901 STAM_COUNTER_INC(&pCache->cPartialHits);
902
903 STAM_COUNTER_ADD(&pCache->StatWritten, cbToWrite);
904
905 /* Ghost lists contain no data. */
906 if ( (pEntry->pList == &pCache->LruRecentlyUsed)
907 || (pEntry->pList == &pCache->LruFrequentlyUsed))
908 {
909 if (pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY)
910 {
911 AssertMsg(pEntry->fFlags & PDMACFILECACHE_ENTRY_IO_IN_PROGRESS,
912 ("Entry is dirty but not in progress\n"));
913
914 /* The data isn't written to the file yet */
915 while (cbToWrite)
916 {
917 PPDMACFILETASKSEG pSeg = (PPDMACFILETASKSEG)RTMemAllocZ(sizeof(PDMACFILETASKSEG));
918
919 pSeg->pTask = pTask;
920 pSeg->uBufOffset = OffDiff;
921 pSeg->cbTransfer = RT_MIN(cbToWrite, cbSegLeft);
922 pSeg->pvBuf = pbSegBuf;
923 pSeg->fWrite = true;
924
925 ADVANCE_SEGMENT_BUFFER(pSeg->cbTransfer);
926
927 pSeg->pNext = pEntry->pHead;
928 pEntry->pHead = pSeg;
929
930 off += pSeg->cbTransfer;
931 OffDiff += pSeg->cbTransfer;
932 cbToWrite -= pSeg->cbTransfer;
933 }
934 }
935 else
936 {
937 AssertMsg(!(pEntry->fFlags & PDMACFILECACHE_ENTRY_IO_IN_PROGRESS),
938 ("Entry is not dirty but in progress\n"));
939
940 /* Write as much as we can into the entry and update the file. */
941 while (cbToWrite)
942 {
943 size_t cbCopy = RT_MIN(cbSegLeft, cbToWrite);
944
945 memcpy(pEntry->pbData + OffDiff, pbSegBuf, cbCopy);
946
947 ADVANCE_SEGMENT_BUFFER(cbCopy);
948
949 cbToWrite-= cbCopy;
950 off += cbCopy;
951 OffDiff += cbCopy;
952 ASMAtomicSubS32(&pTask->cbTransferLeft, cbCopy);
953 }
954
955 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IS_DIRTY;
956 pdmacFileCacheWriteToEndpoint(pEntry);
957 }
958
959 /* Move this entry to the top position */
960 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
961 }
962 else
963 {
964 pdmacFileCacheUpdate(pCache, pEntry);
965 pdmacFileCacheReplace(pCache, pEntry->cbData, pEntry->pList);
966
967 /* Move the entry to T2 and fetch it to the cache. */
968 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
969
970 pEntry->pbData = (uint8_t *)RTMemPageAlloc(pEntry->cbData);
971 AssertPtr(pEntry->pbData);
972
973 while (cbToWrite)
974 {
975 PPDMACFILETASKSEG pSeg = (PPDMACFILETASKSEG)RTMemAllocZ(sizeof(PDMACFILETASKSEG));
976
977 AssertMsg(off >= pEntry->Core.Key,
978 ("Overflow in calculation off=%RTfoff OffsetAligned=%RTfoff\n",
979 off, pEntry->Core.Key));
980
981 pSeg->pTask = pTask;
982 pSeg->uBufOffset = OffDiff;
983 pSeg->cbTransfer = RT_MIN(cbToWrite, cbSegLeft);
984 pSeg->pvBuf = pbSegBuf;
985 pSeg->fWrite = true;
986
987 ADVANCE_SEGMENT_BUFFER(pSeg->cbTransfer);
988
989 pSeg->pNext = pEntry->pHead;
990 pEntry->pHead = pSeg;
991
992 off += pSeg->cbTransfer;
993 OffDiff += pSeg->cbTransfer;
994 cbToWrite -= pSeg->cbTransfer;
995 }
996
997 pdmacFileCacheReadFromEndpoint(pEntry);
998 }
999 }
1000 else
1001 {
1002 /*
1003 * No entry found. Write directly into file.
1004 */
1005 STAM_PROFILE_ADV_START(&pCache->StatTreeGet, Cache);
1006 PPDMACFILECACHEENTRY pEntryBestFit = (PPDMACFILECACHEENTRY)RTAvlrFileOffsetGetBestFit(pEndpointCache->pTree, off, true);
1007 STAM_PROFILE_ADV_STOP(&pCache->StatTreeGet, Cache);
1008
1009 LogFlow(("%sbest fit entry for off=%RTfoff (BestFit=%RTfoff BestFitEnd=%RTfoff BestFitSize=%u)\n",
1010 pEntryBestFit ? "" : "No ",
1011 off,
1012 pEntryBestFit ? pEntryBestFit->Core.Key : 0,
1013 pEntryBestFit ? pEntryBestFit->Core.KeyLast : 0,
1014 pEntryBestFit ? pEntryBestFit->cbData : 0));
1015
1016 if (pEntryBestFit && ((off + (RTFOFF)cbWrite) > pEntryBestFit->Core.Key))
1017 cbToWrite = pEntryBestFit->Core.Key - off;
1018 else
1019 cbToWrite = cbWrite;
1020
1021 cbWrite -= cbToWrite;
1022
1023 while (cbToWrite)
1024 {
1025 PPDMACTASKFILE pIoTask = pdmacFileTaskAlloc(pEndpoint);
1026 AssertPtr(pIoTask);
1027
1028 pIoTask->pEndpoint = pEndpoint;
1029 pIoTask->enmTransferType = PDMACTASKFILETRANSFER_WRITE;
1030 pIoTask->Off = off;
1031 pIoTask->DataSeg.cbSeg = RT_MIN(cbToWrite, cbSegLeft);
1032 pIoTask->DataSeg.pvSeg = pbSegBuf;
1033 pIoTask->pvUser = pTask;
1034 pIoTask->pfnCompleted = pdmacFileEpTaskCompleted;
1035
1036 off += pIoTask->DataSeg.cbSeg;
1037 cbToWrite -= pIoTask->DataSeg.cbSeg;
1038
1039 ADVANCE_SEGMENT_BUFFER(pIoTask->DataSeg.cbSeg);
1040
1041 /* Send it off to the I/O manager. */
1042 pdmacFileEpAddTask(pEndpoint, pIoTask);
1043 }
1044 }
1045 }
1046
1047 ASMAtomicWriteBool(&pTask->fCompleted, false);
1048
1049 if (ASMAtomicReadS32(&pTask->cbTransferLeft) == 0
1050 && !ASMAtomicXchgBool(&pTask->fCompleted, true))
1051 pdmR3AsyncCompletionCompleteTask(&pTask->Core);
1052
1053 RTCritSectLeave(&pCache->CritSect);
1054
1055 return VINF_SUCCESS;
1056}
1057
1058#undef ADVANCE_SEGMENT_BUFFER
1059
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette