master
c 257 lines 7.66 KB
Raw
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 }