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