| Directory: | cvmfs/ |
|---|---|
| File: | cvmfs/malloc_arena.cc |
| Date: | 2026-07-19 02:35:15 |
| Exec | Total | Coverage | |
|---|---|---|---|
| Lines: | 121 | 121 | 100.0% |
| Branches: | 39 | 54 | 72.2% |
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | * This file is part of the CernVM File System. | ||
| 3 | */ | ||
| 4 | |||
| 5 | #include "malloc_arena.h" | ||
| 6 | |||
| 7 | #include <cassert> | ||
| 8 | #include <cstddef> | ||
| 9 | #include <cstring> | ||
| 10 | #include <new> | ||
| 11 | |||
| 12 | #include "util/smalloc.h" | ||
| 13 | |||
| 14 | using namespace std; // NOLINT | ||
| 15 | |||
| 16 | |||
| 17 | /** | ||
| 18 | * Walks through the free list starting at rover_ and looks for the first block | ||
| 19 | * larger than block_size. Returns NULL if no such block exists. | ||
| 20 | */ | ||
| 21 | 130035912 | MallocArena::AvailBlockCtl *MallocArena::FindAvailBlock( | |
| 22 | const int32_t block_size) { | ||
| 23 | 130035912 | bool wrapped = false; | |
| 24 | // Generally: p = LINK(q) | ||
| 25 | 130035912 | AvailBlockCtl *q = rover_; | |
| 26 | AvailBlockCtl *p; | ||
| 27 | do { | ||
| 28 | 48092321447 | p = q->GetNextPtr(arena_); | |
| 29 |
2/2✓ Branch 0 taken 95750339 times.
✓ Branch 1 taken 47996571108 times.
|
48092321447 | if (p->size >= block_size) { |
| 30 | 95750339 | rover_ = p->GetNextPtr(arena_); | |
| 31 | 95750339 | return p; | |
| 32 | } | ||
| 33 |
2/2✓ Branch 0 taken 73223928 times.
✓ Branch 1 taken 47923347180 times.
|
47996571108 | if (p == head_avail_) { |
| 34 |
2/2✓ Branch 0 taken 34285573 times.
✓ Branch 1 taken 38938355 times.
|
73223928 | if (wrapped) |
| 35 | 34285573 | return NULL; | |
| 36 | 38938355 | wrapped = true; | |
| 37 | } | ||
| 38 | 47962285535 | q = p; | |
| 39 | } while (true); | ||
| 40 | } | ||
| 41 | |||
| 42 | |||
| 43 | /** | ||
| 44 | * Creates a free block at the place of the reserved block ptr points into. | ||
| 45 | * The free block might need to be merged with adjacent lower and/or upper | ||
| 46 | * blocks. In these cases, the corresponding blocks are removed from the list | ||
| 47 | * of available blocks. Every allocated block has a predecessor and a | ||
| 48 | * successor in the arena. The newly created free block is added to the end of | ||
| 49 | * the list of available blocks. | ||
| 50 | */ | ||
| 51 | 94183458 | void MallocArena::Free(void *ptr) { | |
| 52 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 94183458 times.
|
94183458 | assert(Contains(ptr)); |
| 53 | |||
| 54 | 94183458 | no_reserved_--; | |
| 55 | |||
| 56 | 94183458 | ReservedBlockCtl *block_ctl = reinterpret_cast<ReservedBlockCtl *>( | |
| 57 | reinterpret_cast<char *>(ptr) - sizeof(ReservedBlockCtl)); | ||
| 58 | 94183458 | const char prior_tag = *(reinterpret_cast<char *>(block_ctl) - 1); | |
| 59 |
3/4✓ Branch 0 taken 60546902 times.
✓ Branch 1 taken 33636556 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 60546902 times.
|
94183458 | assert((prior_tag == kTagAvail) || (prior_tag == kTagReserved)); |
| 60 | |||
| 61 | 94183458 | int32_t new_size = block_ctl->size(); | |
| 62 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 94183458 times.
|
94183458 | assert(new_size > 0); |
| 63 | 94183458 | AvailBlockCtl *new_avail = reinterpret_cast<AvailBlockCtl *>(block_ctl); | |
| 64 | |||
| 65 |
2/2✓ Branch 0 taken 33636556 times.
✓ Branch 1 taken 60546902 times.
|
94183458 | if (prior_tag == kTagAvail) { |
| 66 | // Merge with block before and remove the block from the list | ||
| 67 | 33636556 | const int32_t prior_size = reinterpret_cast<AvailBlockTag *>( | |
| 68 | reinterpret_cast<char *>(block_ctl) | ||
| 69 | - sizeof(AvailBlockTag)) | ||
| 70 | ->size; | ||
| 71 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 33636556 times.
|
33636556 | assert(prior_size > 0); |
| 72 | 33636556 | new_size += prior_size; | |
| 73 | 33636556 | new_avail = reinterpret_cast<AvailBlockCtl *>( | |
| 74 | 33636556 | reinterpret_cast<char *>(block_ctl) - prior_size); | |
| 75 | // new_avail points now to the prior block | ||
| 76 | 33636556 | UnlinkAvailBlock(new_avail); | |
| 77 |
2/2✓ Branch 0 taken 433084 times.
✓ Branch 1 taken 33203472 times.
|
33636556 | if (rover_ == new_avail) |
| 78 | 433084 | rover_ = head_avail_; | |
| 79 | } | ||
| 80 | |||
| 81 | 94183458 | const int32_t succ_size = *reinterpret_cast<int32_t *>( | |
| 82 | 94183458 | reinterpret_cast<char *>(new_avail) + new_size); | |
| 83 |
2/2✓ Branch 0 taken 55077770 times.
✓ Branch 1 taken 39105688 times.
|
94183458 | if (succ_size >= 0) { |
| 84 | // Merge with succeeding block and remove the block from the list | ||
| 85 | 55077770 | AvailBlockCtl *succ_avail = reinterpret_cast<AvailBlockCtl *>( | |
| 86 | 55077770 | reinterpret_cast<char *>(new_avail) + new_size); | |
| 87 | 55077770 | UnlinkAvailBlock(succ_avail); | |
| 88 | 55077770 | new_size += succ_size; | |
| 89 |
2/2✓ Branch 0 taken 728399 times.
✓ Branch 1 taken 54349371 times.
|
55077770 | if (rover_ == succ_avail) |
| 90 | 728399 | rover_ = head_avail_; | |
| 91 | } | ||
| 92 | |||
| 93 | // Set new free block's boundaries | ||
| 94 | 94183458 | new_avail->size = new_size; | |
| 95 | 94183458 | new (AvailBlockTag::GetTagLocation(new_avail)) AvailBlockTag(new_size); | |
| 96 | |||
| 97 | 94183458 | EnqueueAvailBlock(new_avail); | |
| 98 | 94183458 | } | |
| 99 | |||
| 100 | |||
| 101 | /** | ||
| 102 | * Inserts an available block at the end of the free list. | ||
| 103 | */ | ||
| 104 | 94183458 | void MallocArena::EnqueueAvailBlock(AvailBlockCtl *block) { | |
| 105 | 94183458 | AvailBlockCtl *next = head_avail_; | |
| 106 | 94183458 | AvailBlockCtl *prev = head_avail_->GetPrevPtr(arena_); | |
| 107 | 94183458 | next->link_prev = block->ConvertToLink(arena_); | |
| 108 | 94183458 | prev->link_next = block->ConvertToLink(arena_); | |
| 109 | 94183458 | block->link_next = head_avail_->ConvertToLink(arena_); | |
| 110 | 94183458 | block->link_prev = prev->ConvertToLink(arena_); | |
| 111 | 94183458 | } | |
| 112 | |||
| 113 | |||
| 114 | /** | ||
| 115 | * The ptr points to the result of Malloc(). The size of the area is stored | ||
| 116 | * a few bytes before ptr. | ||
| 117 | */ | ||
| 118 | 177691191 | uint32_t MallocArena::GetSize(void *ptr) const { | |
| 119 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 177691191 times.
|
177691191 | assert(Contains(ptr)); |
| 120 | |||
| 121 | 177691191 | ReservedBlockCtl *block_ctl = reinterpret_cast<ReservedBlockCtl *>( | |
| 122 | reinterpret_cast<char *>(ptr) - sizeof(ReservedBlockCtl)); | ||
| 123 | 177691191 | const int32_t size = block_ctl->size(); | |
| 124 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 177691191 times.
|
177691191 | assert(size > 1); |
| 125 | 177691191 | return size - sizeof(ReservedBlockCtl) - 1; | |
| 126 | } | ||
| 127 | |||
| 128 | |||
| 129 | /** | ||
| 130 | * Walks the list of available blocks starting from rover and allocates the | ||
| 131 | * first available spot that's large enough. Puts the reserved block at the end | ||
| 132 | * of the available one and, if necessary, removes the available one from the | ||
| 133 | * list of free blocks. | ||
| 134 | */ | ||
| 135 | 130035912 | void *MallocArena::Malloc(const uint32_t size) { | |
| 136 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 130035912 times.
|
130035912 | assert(size > 0); |
| 137 | |||
| 138 | // Control word first, block type tag last | ||
| 139 | 130035912 | int32_t total_size = sizeof(ReservedBlockCtl) + size + 1; | |
| 140 | 130035912 | total_size = RoundUp8(total_size); | |
| 141 |
2/2✓ Branch 0 taken 571982 times.
✓ Branch 1 taken 129463930 times.
|
130035912 | if (total_size < kMinBlockSize) |
| 142 | 571982 | total_size = kMinBlockSize; | |
| 143 | |||
| 144 | 130035912 | AvailBlockCtl *p = FindAvailBlock(total_size); | |
| 145 |
2/2✓ Branch 0 taken 34285573 times.
✓ Branch 1 taken 95750339 times.
|
130035912 | if (p == NULL) |
| 146 | 34285573 | return NULL; | |
| 147 | |||
| 148 | 95750339 | no_reserved_++; | |
| 149 | 95750339 | return ReserveBlock(p, total_size); | |
| 150 | } | ||
| 151 | |||
| 152 | |||
| 153 | /** | ||
| 154 | * The arena starts with a pointer to this followed by the AvailBlockCtl of | ||
| 155 | * head_avail_, followed by a reserved tag to prevent it from being merged, | ||
| 156 | * followed by a free block spanning the arena until the end tag. The end tag | ||
| 157 | * is a single negative int, which mimics another reserved block. | ||
| 158 | */ | ||
| 159 | 3614 | MallocArena::MallocArena(unsigned arena_size) | |
| 160 | 3614 | : arena_(reinterpret_cast<char *>(sxmmap_align(arena_size))) | |
| 161 | 3614 | , head_avail_(reinterpret_cast<AvailBlockCtl *>(arena_ + sizeof(uint64_t))) | |
| 162 | 3614 | , rover_(head_avail_) | |
| 163 | 3614 | , no_reserved_(0) | |
| 164 | 3614 | , arena_size_(arena_size) { | |
| 165 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3614 times.
|
3614 | assert(arena_size_ > 0); |
| 166 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3614 times.
|
3614 | assert((arena_size_ % (2 * 1024 * 1024)) == 0); // Multiple of 2MB |
| 167 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3614 times.
|
3614 | assert(arena_size_ <= (512 * 1024 * 1024)); // <= 512MB |
| 168 | |||
| 169 | 3614 | const unsigned char padding = 7; | |
| 170 | // Size of the initial free block: everything minus arena boundaries | ||
| 171 | 3614 | const int32_t usable_size = arena_size_ | |
| 172 | 3614 | - (sizeof(uint64_t) + sizeof(AvailBlockCtl) | |
| 173 | + padding + 1 + sizeof(int32_t)); | ||
| 174 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3614 times.
|
3614 | assert((usable_size % 8) == 0); |
| 175 | |||
| 176 | // First 8 bytes of arena: this pointer (occupies only 4 bytes on 32bit | ||
| 177 | // architectures, in which case the second 4 bytes are unused.) | ||
| 178 | 3614 | *reinterpret_cast<MallocArena **>(arena_) = this; | |
| 179 | |||
| 180 | // The initial large free block | ||
| 181 | 3614 | AvailBlockCtl *free_block = new (arena_ + sizeof(uint64_t) | |
| 182 | + sizeof(AvailBlockCtl) + padding + 1) | ||
| 183 | 3614 | AvailBlockCtl(); | |
| 184 | 3614 | free_block->size = usable_size; | |
| 185 | 3614 | free_block->link_next = free_block->link_prev = head_avail_->ConvertToLink( | |
| 186 | arena_); | ||
| 187 | 3614 | new (AvailBlockTag::GetTagLocation(free_block)) AvailBlockTag(usable_size); | |
| 188 | |||
| 189 | 3614 | head_avail_->size = 0; | |
| 190 | 3614 | head_avail_->link_next = head_avail_->link_prev = free_block->ConvertToLink( | |
| 191 | arena_); | ||
| 192 | |||
| 193 | // Prevent succeeding blocks from merging | ||
| 194 | 3614 | *(reinterpret_cast<char *>(free_block) - 1) = kTagReserved; | |
| 195 | // Final tag: reserved block marker | ||
| 196 | 3614 | *reinterpret_cast<int32_t *>(arena_ + arena_size_ - sizeof(int32_t)) = -1; | |
| 197 | 3614 | } | |
| 198 | |||
| 199 | |||
| 200 | /** | ||
| 201 | * Initializes the arena with repeated copies of the given pattern. Used for | ||
| 202 | * testing. | ||
| 203 | */ | ||
| 204 | 44 | MallocArena *MallocArena::CreateInitialized(unsigned arena_size, | |
| 205 | unsigned char pattern) { | ||
| 206 | 44 | MallocArena *result = new MallocArena(arena_size); | |
| 207 | // At this point, there is one big free block linked to by head_avail_ | ||
| 208 | 44 | AvailBlockCtl *free_block = result->head_avail_->GetNextPtr(result->arena_); | |
| 209 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
|
44 | assert(free_block != result->head_avail_); |
| 210 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
|
44 | assert(free_block->size > 0); |
| 211 | // Strip control information at both ends of the block | ||
| 212 | 44 | const int usable_size = free_block->size | |
| 213 | 44 | - (sizeof(AvailBlockCtl) + sizeof(AvailBlockTag)); | |
| 214 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
|
44 | assert(usable_size > 0); |
| 215 | 44 | memset(free_block + 1, pattern, usable_size); | |
| 216 | 44 | return result; | |
| 217 | } | ||
| 218 | |||
| 219 | |||
| 220 | 3609 | MallocArena::~MallocArena() { sxunmap(arena_, arena_size_); } | |
| 221 | |||
| 222 | |||
| 223 | /** | ||
| 224 | * Given the free block "block", cuts out a new reserved block of size | ||
| 225 | * block_size at the end of the free block. Returns a pointer usable by the | ||
| 226 | * application. | ||
| 227 | */ | ||
| 228 | 95750339 | void *MallocArena::ReserveBlock(AvailBlockCtl *block, int32_t block_size) { | |
| 229 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 95750339 times.
|
95750339 | assert(block->size >= block_size); |
| 230 | |||
| 231 | 95750339 | int32_t remaining_size = block->size - block_size; | |
| 232 | // Avoid creation of very small blocks | ||
| 233 |
2/2✓ Branch 0 taken 5020605 times.
✓ Branch 1 taken 90729734 times.
|
95750339 | if (remaining_size < kMinBlockSize) { |
| 234 | 5020605 | block_size += remaining_size; | |
| 235 | 5020605 | remaining_size = 0; | |
| 236 | } | ||
| 237 | |||
| 238 | // Update the list of available blocks | ||
| 239 |
2/2✓ Branch 0 taken 5020605 times.
✓ Branch 1 taken 90729734 times.
|
95750339 | if (remaining_size == 0) { |
| 240 | // Remove free block p from the list of available blocks | ||
| 241 | 5020605 | UnlinkAvailBlock(block); | |
| 242 | } else { | ||
| 243 | 90729734 | block->ShrinkTo(remaining_size); | |
| 244 | } | ||
| 245 | |||
| 246 | // Place the new allocation, which also sets the block type tag at the end | ||
| 247 | 95750339 | char *new_block = reinterpret_cast<char *>(block) + remaining_size; | |
| 248 | 95750339 | new (new_block) ReservedBlockCtl(block_size); | |
| 249 | 95750339 | return new_block + sizeof(ReservedBlockCtl); | |
| 250 | } | ||
| 251 | |||
| 252 | |||
| 253 | /** | ||
| 254 | * Removes the given block from the doubly linked free block list. This happens | ||
| 255 | * when two adjacent free blocks are created in Free() and then merged. Or if | ||
| 256 | * a block gets fully used in Malloc(). | ||
| 257 | */ | ||
| 258 | 93734931 | void MallocArena::UnlinkAvailBlock(AvailBlockCtl *block) { | |
| 259 | 93734931 | AvailBlockCtl *next = block->GetNextPtr(arena_); | |
| 260 | 93734931 | AvailBlockCtl *prev = block->GetPrevPtr(arena_); | |
| 261 | 93734931 | prev->link_next = block->link_next; | |
| 262 | 93734931 | next->link_prev = block->link_prev; | |
| 263 | 93734931 | } | |
| 264 |