@@ -72,6 +72,15 @@ namespace {
72
72
struct CachedBlock {
73
73
static constexpr u16 CacheIndexMax = UINT16_MAX;
74
74
static constexpr u16 InvalidEntry = CacheIndexMax;
75
+ // * MaxReleasedCachePages default is currently 4
76
+ // - We arrived at this value after noticing that mapping
77
+ // in larger memory regions performs better than releasing
78
+ // memory and forcing a cache hit. According to the data,
79
+ // it suggests that beyond 4 pages, the release execution time is
80
+ // longer than the map execution time. In this way, the default
81
+ // is dependent on the platform.
82
+ // TODO: set MaxReleasedCachePages back to 4U
83
+ static constexpr uptr MaxReleasedCachePages = 0U ;
75
84
76
85
uptr CommitBase = 0 ;
77
86
uptr CommitSize = 0 ;
@@ -90,8 +99,9 @@ struct CachedBlock {
90
99
template <typename Config> class MapAllocatorNoCache {
91
100
public:
92
101
void init (UNUSED s32 ReleaseToOsInterval) {}
93
- CachedBlock retrieve (UNUSED uptr Size, UNUSED uptr Alignment,
94
- UNUSED uptr HeadersSize, UNUSED uptr &EntryHeaderPos) {
102
+ CachedBlock retrieve (UNUSED uptr MaxAllowedFragmentedBytes, UNUSED uptr Size,
103
+ UNUSED uptr Alignment, UNUSED uptr HeadersSize,
104
+ UNUSED uptr &EntryHeaderPos) {
95
105
return {};
96
106
}
97
107
void store (UNUSED Options Options, UNUSED uptr CommitBase,
@@ -121,7 +131,7 @@ template <typename Config> class MapAllocatorNoCache {
121
131
}
122
132
};
123
133
124
- static const uptr MaxUnusedCachePages = 4U ;
134
+ static const uptr MaxUnreleasedCachePages = 4U ;
125
135
126
136
template <typename Config>
127
137
bool mapSecondary (const Options &Options, uptr CommitBase, uptr CommitSize,
@@ -151,9 +161,11 @@ bool mapSecondary(const Options &Options, uptr CommitBase, uptr CommitSize,
151
161
}
152
162
}
153
163
154
- const uptr MaxUnusedCacheBytes = MaxUnusedCachePages * PageSize;
155
- if (useMemoryTagging<Config>(Options) && CommitSize > MaxUnusedCacheBytes) {
156
- const uptr UntaggedPos = Max (AllocPos, CommitBase + MaxUnusedCacheBytes);
164
+ const uptr MaxUnreleasedCacheBytes = MaxUnreleasedCachePages * PageSize;
165
+ if (useMemoryTagging<Config>(Options) &&
166
+ CommitSize > MaxUnreleasedCacheBytes) {
167
+ const uptr UntaggedPos =
168
+ Max (AllocPos, CommitBase + MaxUnreleasedCacheBytes);
157
169
return MemMap.remap (CommitBase, UntaggedPos - CommitBase, " scudo:secondary" ,
158
170
MAP_MEMTAG | Flags) &&
159
171
MemMap.remap (UntaggedPos, CommitBase + CommitSize - UntaggedPos,
@@ -334,61 +346,114 @@ class MapAllocatorCache {
334
346
}
335
347
}
336
348
337
- CachedBlock retrieve (uptr Size, uptr Alignment, uptr HeadersSize,
338
- uptr &EntryHeaderPos) EXCLUDES(Mutex) {
349
+ CachedBlock retrieve (uptr MaxAllowedFragmentedPages, uptr Size,
350
+ uptr Alignment, uptr HeadersSize, uptr &EntryHeaderPos)
351
+ EXCLUDES(Mutex) {
339
352
const uptr PageSize = getPageSizeCached ();
340
353
// 10% of the requested size proved to be the optimal choice for
341
354
// retrieving cached blocks after testing several options.
342
355
constexpr u32 FragmentedBytesDivisor = 10 ;
343
- bool Found = false ;
344
356
CachedBlock Entry;
345
357
EntryHeaderPos = 0 ;
346
358
{
347
359
ScopedLock L (Mutex);
348
360
CallsToRetrieve++;
349
361
if (EntriesCount == 0 )
350
362
return {};
351
- u32 OptimalFitIndex = 0 ;
363
+ u16 RetrievedIndex = CachedBlock::InvalidEntry ;
352
364
uptr MinDiff = UINTPTR_MAX;
353
- for (u32 I = LRUHead; I != CachedBlock::InvalidEntry;
365
+
366
+ // Since allocation sizes don't always match cached memory chunk sizes
367
+ // we allow some memory to be unused (called fragmented bytes). The
368
+ // amount of unused bytes is exactly EntryHeaderPos - CommitBase.
369
+ //
370
+ // CommitBase CommitBase + CommitSize
371
+ // V V
372
+ // +---+------------+-----------------+---+
373
+ // | | | | |
374
+ // +---+------------+-----------------+---+
375
+ // ^ ^ ^
376
+ // Guard EntryHeaderPos Guard-page-end
377
+ // page-begin
378
+ //
379
+ // [EntryHeaderPos, CommitBase + CommitSize) contains the user data as
380
+ // well as the header metadata. If EntryHeaderPos - CommitBase exceeds
381
+ // MaxAllowedFragmentedPages * PageSize, the cached memory chunk is
382
+ // not considered valid for retrieval.
383
+ for (u16 I = LRUHead; I != CachedBlock::InvalidEntry;
354
384
I = Entries[I].Next ) {
355
385
const uptr CommitBase = Entries[I].CommitBase ;
356
386
const uptr CommitSize = Entries[I].CommitSize ;
357
387
const uptr AllocPos =
358
388
roundDown (CommitBase + CommitSize - Size, Alignment);
359
389
const uptr HeaderPos = AllocPos - HeadersSize;
390
+ const uptr MaxAllowedFragmentedBytes =
391
+ MaxAllowedFragmentedPages * PageSize;
360
392
if (HeaderPos > CommitBase + CommitSize)
361
393
continue ;
394
+ // TODO: Remove AllocPos > CommitBase + MaxAllowedFragmentedBytes
395
+ // and replace with Diff > MaxAllowedFragmentedBytes
362
396
if (HeaderPos < CommitBase ||
363
- AllocPos > CommitBase + PageSize * MaxUnusedCachePages ) {
397
+ AllocPos > CommitBase + MaxAllowedFragmentedBytes ) {
364
398
continue ;
365
399
}
366
- Found = true ;
367
- const uptr Diff = HeaderPos - CommitBase;
368
- // immediately use a cached block if it's size is close enough to the
369
- // requested size.
370
- const uptr MaxAllowedFragmentedBytes =
371
- (CommitBase + CommitSize - HeaderPos) / FragmentedBytesDivisor;
372
- if (Diff <= MaxAllowedFragmentedBytes) {
373
- OptimalFitIndex = I;
374
- EntryHeaderPos = HeaderPos;
375
- break ;
376
- }
377
- // keep track of the smallest cached block
400
+
401
+ const uptr Diff = roundDown (HeaderPos, PageSize) - CommitBase;
402
+
403
+ // Keep track of the smallest cached block
378
404
// that is greater than (AllocSize + HeaderSize)
379
- if (Diff > MinDiff)
405
+ if (Diff >= MinDiff)
380
406
continue ;
381
- OptimalFitIndex = I;
407
+
382
408
MinDiff = Diff;
409
+ RetrievedIndex = I;
383
410
EntryHeaderPos = HeaderPos;
411
+
412
+ // Immediately use a cached block if its size is close enough to the
413
+ // requested size
414
+ const uptr OptimalFitThesholdBytes =
415
+ (CommitBase + CommitSize - HeaderPos) / FragmentedBytesDivisor;
416
+ if (Diff <= OptimalFitThesholdBytes)
417
+ break ;
384
418
}
385
- if (Found ) {
386
- Entry = Entries[OptimalFitIndex ];
387
- remove (OptimalFitIndex );
419
+ if (RetrievedIndex != CachedBlock::InvalidEntry ) {
420
+ Entry = Entries[RetrievedIndex ];
421
+ remove (RetrievedIndex );
388
422
SuccessfulRetrieves++;
389
423
}
390
424
}
391
425
426
+ // The difference between the retrieved memory chunk and the request
427
+ // size is at most MaxAllowedFragmentedPages
428
+ //
429
+ // / MaxAllowedFragmentedPages * PageSize \
430
+ // +--------------------------+-------------+
431
+ // | | |
432
+ // +--------------------------+-------------+
433
+ // \ Bytes to be released / ^
434
+ // |
435
+ // (may or may not be committed)
436
+ //
437
+ // The maximum number of bytes released to the OS is capped by
438
+ // MaxReleasedCachePages
439
+ //
440
+ // TODO : Consider making MaxReleasedCachePages configurable since
441
+ // the release to OS API can vary across systems.
442
+ if (Entry.Time != 0 ) {
443
+ const uptr FragmentedBytes =
444
+ roundDown (EntryHeaderPos, PageSize) - Entry.CommitBase ;
445
+ const uptr MaxUnreleasedCacheBytes = MaxUnreleasedCachePages * PageSize;
446
+ if (FragmentedBytes > MaxUnreleasedCacheBytes) {
447
+ const uptr MaxReleasedCacheBytes =
448
+ CachedBlock::MaxReleasedCachePages * PageSize;
449
+ uptr BytesToRelease =
450
+ roundUp (Min<uptr>(MaxReleasedCacheBytes,
451
+ FragmentedBytes - MaxUnreleasedCacheBytes),
452
+ PageSize);
453
+ Entry.MemMap .releaseAndZeroPagesToOS (Entry.CommitBase , BytesToRelease);
454
+ }
455
+ }
456
+
392
457
return Entry;
393
458
}
394
459
@@ -659,8 +724,13 @@ MapAllocator<Config>::tryAllocateFromCache(const Options &Options, uptr Size,
659
724
FillContentsMode FillContents) {
660
725
CachedBlock Entry;
661
726
uptr EntryHeaderPos;
727
+ uptr MaxAllowedFragmentedPages = MaxUnreleasedCachePages;
728
+
729
+ if (UNLIKELY (useMemoryTagging<Config>(Options)))
730
+ MaxAllowedFragmentedPages += CachedBlock::MaxReleasedCachePages;
662
731
663
- Entry = Cache.retrieve (Size, Alignment, getHeadersSize (), EntryHeaderPos);
732
+ Entry = Cache.retrieve (MaxAllowedFragmentedPages, Size, Alignment,
733
+ getHeadersSize (), EntryHeaderPos);
664
734
if (!Entry.isValid ())
665
735
return nullptr ;
666
736
0 commit comments