VirtualBox

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

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

PDMAsyncCompletion: Documentation for the cache

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