| Directory: | cvmfs/ |
|---|---|
| File: | cvmfs/malloc_arena.cc |
| Date: | 2026-05-24 02:35:55 |
| 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 | 101514750 | MallocArena::AvailBlockCtl *MallocArena::FindAvailBlock( | |
| 22 | const int32_t block_size) { | ||
| 23 | 101514750 | bool wrapped = false; | |
| 24 | // Generally: p = LINK(q) | ||
| 25 | 101514750 | AvailBlockCtl *q = rover_; | |
| 26 | AvailBlockCtl *p; | ||
| 27 | do { | ||
| 28 | 35856010380 | p = q->GetNextPtr(arena_); | |
| 29 |
2/2✓ Branch 0 taken 75771520 times.
✓ Branch 1 taken 35780238860 times.
|
35856010380 | if (p->size >= block_size) { |
| 30 | 75771520 | rover_ = p->GetNextPtr(arena_); | |
| 31 | 75771520 | return p; | |
| 32 | } | ||
| 33 |
2/2✓ Branch 0 taken 55177887 times.
✓ Branch 1 taken 35725060973 times.
|
35780238860 | if (p == head_avail_) { |
| 34 |
2/2✓ Branch 0 taken 25743230 times.
✓ Branch 1 taken 29434657 times.
|
55177887 | if (wrapped) |
| 35 | 25743230 | return NULL; | |
| 36 | 29434657 | wrapped = true; | |
| 37 | } | ||
| 38 | 35754495630 | 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 | 74916803 | void MallocArena::Free(void *ptr) { | |
| 52 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 74916803 times.
|
74916803 | assert(Contains(ptr)); |
| 53 | |||
| 54 | 74916803 | no_reserved_--; | |
| 55 | |||
| 56 | 74916803 | ReservedBlockCtl *block_ctl = reinterpret_cast<ReservedBlockCtl *>( | |
| 57 | reinterpret_cast<char *>(ptr) - sizeof(ReservedBlockCtl)); | ||
| 58 | 74916803 | const char prior_tag = *(reinterpret_cast<char *>(block_ctl) - 1); | |
| 59 |
3/4✓ Branch 0 taken 47350162 times.
✓ Branch 1 taken 27566641 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 47350162 times.
|
74916803 | assert((prior_tag == kTagAvail) || (prior_tag == kTagReserved)); |
| 60 | |||
| 61 | 74916803 | int32_t new_size = block_ctl->size(); | |
| 62 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 74916803 times.
|
74916803 | assert(new_size > 0); |
| 63 | 74916803 | AvailBlockCtl *new_avail = reinterpret_cast<AvailBlockCtl *>(block_ctl); | |
| 64 | |||
| 65 |
2/2✓ Branch 0 taken 27566641 times.
✓ Branch 1 taken 47350162 times.
|
74916803 | if (prior_tag == kTagAvail) { |
| 66 | // Merge with block before and remove the block from the list | ||
| 67 | 27566641 | 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 27566641 times.
|
27566641 | assert(prior_size > 0); |
| 72 | 27566641 | new_size += prior_size; | |
| 73 | 27566641 | new_avail = reinterpret_cast<AvailBlockCtl *>( | |
| 74 | 27566641 | reinterpret_cast<char *>(block_ctl) - prior_size); | |
| 75 | // new_avail points now to the prior block | ||
| 76 | 27566641 | UnlinkAvailBlock(new_avail); | |
| 77 |
2/2✓ Branch 0 taken 335759 times.
✓ Branch 1 taken 27230882 times.
|
27566641 | if (rover_ == new_avail) |
| 78 | 335759 | rover_ = head_avail_; | |
| 79 | } | ||
| 80 | |||
| 81 | 74916803 | const int32_t succ_size = *reinterpret_cast<int32_t *>( | |
| 82 | 74916803 | reinterpret_cast<char *>(new_avail) + new_size); | |
| 83 |
2/2✓ Branch 0 taken 41903264 times.
✓ Branch 1 taken 33013539 times.
|
74916803 | if (succ_size >= 0) { |
| 84 | // Merge with succeeding block and remove the block from the list | ||
| 85 | 41903264 | AvailBlockCtl *succ_avail = reinterpret_cast<AvailBlockCtl *>( | |
| 86 | 41903264 | reinterpret_cast<char *>(new_avail) + new_size); | |
| 87 | 41903264 | UnlinkAvailBlock(succ_avail); | |
| 88 | 41903264 | new_size += succ_size; | |
| 89 |
2/2✓ Branch 0 taken 532823 times.
✓ Branch 1 taken 41370441 times.
|
41903264 | if (rover_ == succ_avail) |
| 90 | 532823 | rover_ = head_avail_; | |
| 91 | } | ||
| 92 | |||
| 93 | // Set new free block's boundaries | ||
| 94 | 74916803 | new_avail->size = new_size; | |
| 95 | 74916803 | new (AvailBlockTag::GetTagLocation(new_avail)) AvailBlockTag(new_size); | |
| 96 | |||
| 97 | 74916803 | EnqueueAvailBlock(new_avail); | |
| 98 | 74916803 | } | |
| 99 | |||
| 100 | |||
| 101 | /** | ||
| 102 | * Inserts an available block at the end of the free list. | ||
| 103 | */ | ||
| 104 | 74916803 | void MallocArena::EnqueueAvailBlock(AvailBlockCtl *block) { | |
| 105 | 74916803 | AvailBlockCtl *next = head_avail_; | |
| 106 | 74916803 | AvailBlockCtl *prev = head_avail_->GetPrevPtr(arena_); | |
| 107 | 74916803 | next->link_prev = block->ConvertToLink(arena_); | |
| 108 | 74916803 | prev->link_next = block->ConvertToLink(arena_); | |
| 109 | 74916803 | block->link_next = head_avail_->ConvertToLink(arena_); | |
| 110 | 74916803 | block->link_prev = prev->ConvertToLink(arena_); | |
| 111 | 74916803 | } | |
| 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 | 133651194 | uint32_t MallocArena::GetSize(void *ptr) const { | |
| 119 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 133651194 times.
|
133651194 | assert(Contains(ptr)); |
| 120 | |||
| 121 | 133651194 | ReservedBlockCtl *block_ctl = reinterpret_cast<ReservedBlockCtl *>( | |
| 122 | reinterpret_cast<char *>(ptr) - sizeof(ReservedBlockCtl)); | ||
| 123 | 133651194 | const int32_t size = block_ctl->size(); | |
| 124 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 133651194 times.
|
133651194 | assert(size > 1); |
| 125 | 133651194 | 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 | 101514750 | void *MallocArena::Malloc(const uint32_t size) { | |
| 136 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 101514750 times.
|
101514750 | assert(size > 0); |
| 137 | |||
| 138 | // Control word first, block type tag last | ||
| 139 | 101514750 | int32_t total_size = sizeof(ReservedBlockCtl) + size + 1; | |
| 140 | 101514750 | total_size = RoundUp8(total_size); | |
| 141 |
2/2✓ Branch 0 taken 488985 times.
✓ Branch 1 taken 101025765 times.
|
101514750 | if (total_size < kMinBlockSize) |
| 142 | 488985 | total_size = kMinBlockSize; | |
| 143 | |||
| 144 | 101514750 | AvailBlockCtl *p = FindAvailBlock(total_size); | |
| 145 |
2/2✓ Branch 0 taken 25743230 times.
✓ Branch 1 taken 75771520 times.
|
101514750 | if (p == NULL) |
| 146 | 25743230 | return NULL; | |
| 147 | |||
| 148 | 75771520 | no_reserved_++; | |
| 149 | 75771520 | 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 | 3594 | MallocArena::MallocArena(unsigned arena_size) | |
| 160 | 3594 | : arena_(reinterpret_cast<char *>(sxmmap_align(arena_size))) | |
| 161 | 3594 | , head_avail_(reinterpret_cast<AvailBlockCtl *>(arena_ + sizeof(uint64_t))) | |
| 162 | 3594 | , rover_(head_avail_) | |
| 163 | 3594 | , no_reserved_(0) | |
| 164 | 3594 | , arena_size_(arena_size) { | |
| 165 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3594 times.
|
3594 | assert(arena_size_ > 0); |
| 166 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3594 times.
|
3594 | assert((arena_size_ % (2 * 1024 * 1024)) == 0); // Multiple of 2MB |
| 167 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3594 times.
|
3594 | assert(arena_size_ <= (512 * 1024 * 1024)); // <= 512MB |
| 168 | |||
| 169 | 3594 | const unsigned char padding = 7; | |
| 170 | // Size of the initial free block: everything minus arena boundaries | ||
| 171 | 3594 | const int32_t usable_size = arena_size_ | |
| 172 | 3594 | - (sizeof(uint64_t) + sizeof(AvailBlockCtl) | |
| 173 | + padding + 1 + sizeof(int32_t)); | ||
| 174 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3594 times.
|
3594 | 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 | 3594 | *reinterpret_cast<MallocArena **>(arena_) = this; | |
| 179 | |||
| 180 | // The initial large free block | ||
| 181 | 3594 | AvailBlockCtl *free_block = new (arena_ + sizeof(uint64_t) | |
| 182 | + sizeof(AvailBlockCtl) + padding + 1) | ||
| 183 | 3594 | AvailBlockCtl(); | |
| 184 | 3594 | free_block->size = usable_size; | |
| 185 | 3594 | free_block->link_next = free_block->link_prev = head_avail_->ConvertToLink( | |
| 186 | arena_); | ||
| 187 | 3594 | new (AvailBlockTag::GetTagLocation(free_block)) AvailBlockTag(usable_size); | |
| 188 | |||
| 189 | 3594 | head_avail_->size = 0; | |
| 190 | 3594 | head_avail_->link_next = head_avail_->link_prev = free_block->ConvertToLink( | |
| 191 | arena_); | ||
| 192 | |||
| 193 | // Prevent succeeding blocks from merging | ||
| 194 | 3594 | *(reinterpret_cast<char *>(free_block) - 1) = kTagReserved; | |
| 195 | // Final tag: reserved block marker | ||
| 196 | 3594 | *reinterpret_cast<int32_t *>(arena_ + arena_size_ - sizeof(int32_t)) = -1; | |
| 197 | 3594 | } | |
| 198 | |||
| 199 | |||
| 200 | /** | ||
| 201 | * Initializes the arena with repeated copies of the given pattern. Used for | ||
| 202 | * testing. | ||
| 203 | */ | ||
| 204 | 17 | MallocArena *MallocArena::CreateInitialized(unsigned arena_size, | |
| 205 | unsigned char pattern) { | ||
| 206 | 17 | MallocArena *result = new MallocArena(arena_size); | |
| 207 | // At this point, there is one big free block linked to by head_avail_ | ||
| 208 | 17 | AvailBlockCtl *free_block = result->head_avail_->GetNextPtr(result->arena_); | |
| 209 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
|
17 | assert(free_block != result->head_avail_); |
| 210 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
|
17 | assert(free_block->size > 0); |
| 211 | // Strip control information at both ends of the block | ||
| 212 | 17 | const int usable_size = free_block->size | |
| 213 | 17 | - (sizeof(AvailBlockCtl) + sizeof(AvailBlockTag)); | |
| 214 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
|
17 | assert(usable_size > 0); |
| 215 | 17 | memset(free_block + 1, pattern, usable_size); | |
| 216 | 17 | return result; | |
| 217 | } | ||
| 218 | |||
| 219 | |||
| 220 | 3589 | 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 | 75771520 | void *MallocArena::ReserveBlock(AvailBlockCtl *block, int32_t block_size) { | |
| 229 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 75771520 times.
|
75771520 | assert(block->size >= block_size); |
| 230 | |||
| 231 | 75771520 | int32_t remaining_size = block->size - block_size; | |
| 232 | // Avoid creation of very small blocks | ||
| 233 |
2/2✓ Branch 0 taken 5187770 times.
✓ Branch 1 taken 70583750 times.
|
75771520 | if (remaining_size < kMinBlockSize) { |
| 234 | 5187770 | block_size += remaining_size; | |
| 235 | 5187770 | remaining_size = 0; | |
| 236 | } | ||
| 237 | |||
| 238 | // Update the list of available blocks | ||
| 239 |
2/2✓ Branch 0 taken 5187770 times.
✓ Branch 1 taken 70583750 times.
|
75771520 | if (remaining_size == 0) { |
| 240 | // Remove free block p from the list of available blocks | ||
| 241 | 5187770 | UnlinkAvailBlock(block); | |
| 242 | } else { | ||
| 243 | 70583750 | block->ShrinkTo(remaining_size); | |
| 244 | } | ||
| 245 | |||
| 246 | // Place the new allocation, which also sets the block type tag at the end | ||
| 247 | 75771520 | char *new_block = reinterpret_cast<char *>(block) + remaining_size; | |
| 248 | 75771520 | new (new_block) ReservedBlockCtl(block_size); | |
| 249 | 75771520 | 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 | 74657675 | void MallocArena::UnlinkAvailBlock(AvailBlockCtl *block) { | |
| 259 | 74657675 | AvailBlockCtl *next = block->GetNextPtr(arena_); | |
| 260 | 74657675 | AvailBlockCtl *prev = block->GetPrevPtr(arena_); | |
| 261 | 74657675 | prev->link_next = block->link_next; | |
| 262 | 74657675 | next->link_prev = block->link_prev; | |
| 263 | 74657675 | } | |
| 264 |