| 1 | #include "onewayalloc.h" |
| 2 | |
| 3 | typedef struct owa_page { |
| 4 | size_t stats_pages; |
| 5 | size_t stats_pages_size; |
| 6 | size_t stats_mallocs_made; |
| 7 | size_t stats_mallocs_size; |
| 8 | size_t size; // the total size of the page |
| 9 | size_t offset; // the first free byte of the page |
| 10 | bool mmap; |
| 11 | struct owa_page *next; // the next page on the list |
| 12 | struct owa_page *last; // the last page on the list - we currently allocate on this |
| 13 | } OWA_PAGE; |
| 14 | |
| 15 | static size_t onewayalloc_total_memory = 0; |
| 16 | |
| 17 | size_t onewayalloc_allocated_memory(void) { |
| 18 | return __atomic_load_n(&onewayalloc_total_memory, __ATOMIC_RELAXED); |
| 19 | } |
| 20 | |
| 21 | // Create an OWA |
| 22 | // Once it is created, the caller may call the onewayalloc_mallocz() |
| 23 | // any number of times, for any amount of memory. |
| 24 | |
| 25 | static OWA_PAGE *onewayalloc_create_internal(OWA_PAGE *head, size_t size_hint) { |
| 26 | size_t OWA_NATURAL_PAGE_SIZE = os_get_system_page_size(); |
| 27 | |
| 28 | // our default page size |
| 29 | size_t size = 32768; |
| 30 | |
| 31 | // make sure the new page will fit both the requested size |
| 32 | // and the OWA_PAGE structure at its beginning |
| 33 | size_hint += natural_alignment(sizeof(OWA_PAGE)); |
| 34 | |
| 35 | // prefer the user size if it is bigger than our size |
| 36 | if(size_hint > size) |
| 37 | size = size_hint; |
| 38 | |
| 39 | if(head) { |
| 40 | // double the current allocation |
| 41 | size_t optimal_size = head->stats_pages_size; |
| 42 | |
| 43 | // cap it at 1 MiB |
| 44 | if(optimal_size > 1ULL * 1024 * 1024) |
| 45 | optimal_size = 1ULL * 1024 * 1024; |
| 46 | |
| 47 | // use the optimal if it is more than the required size |
| 48 | if(optimal_size > size) |
| 49 | size = optimal_size; |
| 50 | } |
| 51 | |
| 52 | // Make sure our allocations are always a multiple of the hardware page size |
| 53 | if(size % OWA_NATURAL_PAGE_SIZE) |
| 54 | size = size + OWA_NATURAL_PAGE_SIZE - (size % OWA_NATURAL_PAGE_SIZE); |
| 55 | |
| 56 | // Use netdata_mmap instead of mallocz |
| 57 | OWA_PAGE *page = (OWA_PAGE *)nd_mmap_advanced(NULL, size, MAP_ANONYMOUS | MAP_PRIVATE, 0, false, false, NULL); |
| 58 | if(unlikely(!page)) { |
| 59 | page = mallocz(size); |
| 60 | page->mmap = false; |
| 61 | } |
| 62 | else |
| 63 | page->mmap = true; |
| 64 | |
| 65 | __atomic_add_fetch(&onewayalloc_total_memory, size, __ATOMIC_RELAXED); |
| 66 | |
| 67 | page->size = size; |
| 68 | page->offset = natural_alignment(sizeof(OWA_PAGE)); |
| 69 | page->next = page->last = NULL; |
| 70 | |
| 71 | if(!head) { |
| 72 | // this is the first time we are called |
| 73 | head = page; |
| 74 | head->stats_pages = 0; |
| 75 | head->stats_pages_size = 0; |
| 76 | head->stats_mallocs_made = 0; |
| 77 | head->stats_mallocs_size = 0; |
| 78 | } |
| 79 | else { |
| 80 | // link this page into our existing linked list |
| 81 | head->last->next = page; |
| 82 | } |
| 83 | |
| 84 | head->last = page; |
| 85 | head->stats_pages++; |
| 86 | head->stats_pages_size += size; |
| 87 | |
| 88 | return page; |
| 89 | } |
| 90 | |
| 91 | ONEWAYALLOC *onewayalloc_create(size_t size_hint) { |
| 92 | return (ONEWAYALLOC *)onewayalloc_create_internal(NULL, size_hint); |
| 93 | } |
| 94 | |
| 95 | void *onewayalloc_mallocz(ONEWAYALLOC *owa, size_t size) { |
| 96 | #ifdef FSANITIZE_ADDRESS |
| 97 | return mallocz(size); |
| 98 | #endif |
| 99 | |
| 100 | OWA_PAGE *head = (OWA_PAGE *)owa; |
| 101 | OWA_PAGE *page = head->last; |
| 102 | |
| 103 | // update stats |
| 104 | head->stats_mallocs_made++; |
| 105 | head->stats_mallocs_size += size; |
| 106 | |
| 107 | // make sure the size is aligned |
| 108 | size = natural_alignment(size); |
| 109 | |
| 110 | if(unlikely(page->size - page->offset < size)) { |
| 111 | // we don't have enough space to fit the data |
| 112 | // let's get another page |
| 113 | page = onewayalloc_create_internal(head, (size > page->size)?size:page->size); |
| 114 | } |
| 115 | |
| 116 | char *mem = (char *)page; |
| 117 | mem = &mem[page->offset]; |
| 118 | page->offset += size; |
| 119 | |
| 120 | return (void *)mem; |
| 121 | } |
| 122 | |
| 123 | void *onewayalloc_callocz(ONEWAYALLOC *owa, size_t nmemb, size_t size) { |
| 124 | size_t total = nmemb * size; |
| 125 | void *mem = onewayalloc_mallocz(owa, total); |
| 126 | memset(mem, 0, total); |
| 127 | return mem; |
| 128 | } |
| 129 | |
| 130 | char *onewayalloc_strdupz(ONEWAYALLOC *owa, const char *s) { |
| 131 | size_t size = strlen(s) + 1; |
| 132 | char *d = onewayalloc_mallocz((OWA_PAGE *)owa, size); |
| 133 | memcpy(d, s, size); |
| 134 | return d; |
| 135 | } |
| 136 | |
| 137 | void *onewayalloc_memdupz(ONEWAYALLOC *owa, const void *src, size_t size) { |
| 138 | void *mem = onewayalloc_mallocz((OWA_PAGE *)owa, size); |
| 139 | // memcpy() is way faster than strcpy() since it does not check for '\0' |
| 140 | memcpy(mem, src, size); |
| 141 | return mem; |
| 142 | } |
| 143 | |
| 144 | void onewayalloc_freez(ONEWAYALLOC *owa __maybe_unused, const void *ptr __maybe_unused) { |
| 145 | #ifdef FSANITIZE_ADDRESS |
| 146 | freez((void *)ptr); |
| 147 | return; |
| 148 | #endif |
| 149 | |
| 150 | #ifdef NETDATA_INTERNAL_CHECKS |
| 151 | // allow the caller to call us for a mallocz() allocation |
| 152 | // so try to find it in our memory and if it is not there |
| 153 | // log an error |
| 154 | |
| 155 | if (unlikely(!ptr)) |
| 156 | return; |
| 157 | |
| 158 | OWA_PAGE *head = (OWA_PAGE *)owa; |
| 159 | OWA_PAGE *page; |
| 160 | uintptr_t seeking = (uintptr_t)ptr; |
| 161 | |
| 162 | for(page = head; page ;page = page->next) { |
| 163 | uintptr_t start = (uintptr_t)page; |
| 164 | uintptr_t end = start + page->size; |
| 165 | |
| 166 | if(seeking >= start && seeking <= end) { |
| 167 | // found it - it is ours |
| 168 | // just return to let the caller think we actually did something |
| 169 | return; |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | // not found - it is not ours |
| 174 | // let's free it with the system allocator |
| 175 | netdata_log_error("ONEWAYALLOC: request to free address 0x%p that is not allocated by this OWA", ptr); |
| 176 | #endif |
| 177 | } |
| 178 | |
| 179 | void *onewayalloc_doublesize(ONEWAYALLOC *owa, const void *src, size_t oldsize) { |
| 180 | size_t newsize = oldsize * 2; |
| 181 | void *dst = onewayalloc_mallocz(owa, newsize); |
| 182 | memcpy(dst, src, oldsize); |
| 183 | onewayalloc_freez(owa, src); |
| 184 | return dst; |
| 185 | } |
| 186 | |
| 187 | void onewayalloc_reset(ONEWAYALLOC *owa) { |
| 188 | if (!owa) return; |
| 189 | |
| 190 | #ifdef FSANITIZE_ADDRESS |
| 191 | // Under the sanitizer path, onewayalloc_mallocz goes straight to the |
| 192 | // system allocator and nothing is tracked in the owa page list — there |
| 193 | // is nothing to reset. Individual allocations are released by callers |
| 194 | // via onewayalloc_freez() (which calls freez() under the sanitizer). |
| 195 | return; |
| 196 | #endif |
| 197 | |
| 198 | OWA_PAGE *head = (OWA_PAGE *)owa; |
| 199 | |
| 200 | // Free every page except the head; we keep the head so the caller can |
| 201 | // reuse the arena without another mmap. |
| 202 | size_t freed_size = 0; |
| 203 | OWA_PAGE *page = head->next; |
| 204 | while (page) { |
| 205 | OWA_PAGE *p = page; |
| 206 | page = page->next; |
| 207 | freed_size += p->size; |
| 208 | if (p->mmap) |
| 209 | nd_munmap(p, p->size); |
| 210 | else |
| 211 | freez(p); |
| 212 | } |
| 213 | |
| 214 | if (freed_size) |
| 215 | __atomic_sub_fetch(&onewayalloc_total_memory, freed_size, __ATOMIC_RELAXED); |
| 216 | |
| 217 | // Roll the head page's bump cursor back to the position right after the |
| 218 | // OWA_PAGE header, and rewire the single-page list so head == last. |
| 219 | head->next = NULL; |
| 220 | head->last = head; |
| 221 | head->offset = natural_alignment(sizeof(OWA_PAGE)); |
| 222 | |
| 223 | // stats_pages / stats_pages_size describe the arena's *current* footprint |
| 224 | // (what is mapped right now), so they reflect the single-page post-reset |
| 225 | // state. stats_mallocs_made / stats_mallocs_size are lifetime counters |
| 226 | // (total allocations ever served by this arena) and are intentionally |
| 227 | // preserved across resets, to stay useful for diagnostics. |
| 228 | head->stats_pages = 1; |
| 229 | head->stats_pages_size = head->size; |
| 230 | } |
| 231 | |
| 232 | void onewayalloc_destroy(ONEWAYALLOC *owa) { |
| 233 | if(!owa) return; |
| 234 | |
| 235 | OWA_PAGE *head = (OWA_PAGE *)owa; |
| 236 | |
| 237 | //netdata_log_info("OWA: %zu allocations of %zu total bytes, in %zu pages of %zu total bytes", |
| 238 | // head->stats_mallocs_made, head->stats_mallocs_size, |
| 239 | // head->stats_pages, head->stats_pages_size); |
| 240 | |
| 241 | size_t total_size = 0; |
| 242 | OWA_PAGE *page = head; |
| 243 | while(page) { |
| 244 | total_size += page->size; |
| 245 | |
| 246 | OWA_PAGE *p = page; |
| 247 | page = page->next; |
| 248 | |
| 249 | // Use netdata_munmap instead of freez |
| 250 | if(p->mmap) |
| 251 | nd_munmap(p, p->size); |
| 252 | else |
| 253 | freez(p); |
| 254 | } |
| 255 | |
| 256 | __atomic_sub_fetch(&onewayalloc_total_memory, total_size, __ATOMIC_RELAXED); |
| 257 | } |