blob: 1f634c63a77193c6e1498017c7ffc40cffee34a9 [file] [log] [blame]
James Kuszmaul82f6c042021-01-17 11:30:16 -08001/**
2 * @file mem.c Memory management with reference counting
3 *
4 * Copyright (C) 2010 Creytiv.com
5 */
6#include <ctype.h>
7#include <stdlib.h>
8#include <string.h>
9#ifdef HAVE_PTHREAD
10#include <pthread.h>
11#endif
12#include <re_types.h>
13#include <re_list.h>
14#include <re_fmt.h>
15#include <re_mbuf.h>
16#include <re_mem.h>
17
18
19#define DEBUG_MODULE "mem"
20#define DEBUG_LEVEL 5
21#include <re_dbg.h>
22
23
24#ifndef RELEASE
25#define MEM_DEBUG 1 /**< Enable memory debugging */
26#endif
27
28
29/** Defines a reference-counting memory object */
30struct mem {
31 uint32_t nrefs; /**< Number of references */
32 mem_destroy_h *dh; /**< Destroy handler */
33#if MEM_DEBUG
34 struct le le; /**< Linked list element */
35 uint32_t magic; /**< Magic number */
36 size_t size; /**< Size of memory object */
37#endif
38};
39
40#if MEM_DEBUG
41/* Memory debugging */
42static struct list meml = LIST_INIT;
43static const uint32_t mem_magic = 0xe7fb9ac4;
44static ssize_t threshold = -1; /**< Memory threshold, disabled by default */
45
46static struct memstat memstat = {
47 0,0,0,0,~0,0
48};
49
50#ifdef HAVE_PTHREAD
51
52static pthread_mutex_t mem_mutex = PTHREAD_MUTEX_INITIALIZER;
53
54static inline void mem_lock(void)
55{
56 pthread_mutex_lock(&mem_mutex);
57}
58
59
60static inline void mem_unlock(void)
61{
62 pthread_mutex_unlock(&mem_mutex);
63}
64
65#else
66
67#define mem_lock() /**< Stub */
68#define mem_unlock() /**< Stub */
69
70#endif
71
72/** Update statistics for mem_zalloc() */
73#define STAT_ALLOC(m, size) \
74 mem_lock(); \
75 memstat.bytes_cur += (size); \
76 memstat.bytes_peak = max(memstat.bytes_cur, memstat.bytes_peak); \
77 ++memstat.blocks_cur; \
78 memstat.blocks_peak = max(memstat.blocks_cur, memstat.blocks_peak); \
79 memstat.size_min = min(memstat.size_min, size); \
80 memstat.size_max = max(memstat.size_max, size); \
81 mem_unlock(); \
82 (m)->size = (size); \
83 (m)->magic = mem_magic;
84
85/** Update statistics for mem_realloc() */
86#define STAT_REALLOC(m, size) \
87 mem_lock(); \
88 memstat.bytes_cur += ((size) - (m)->size); \
89 memstat.bytes_peak = max(memstat.bytes_cur, memstat.bytes_peak); \
90 memstat.size_min = min(memstat.size_min, size); \
91 memstat.size_max = max(memstat.size_max, size); \
92 mem_unlock(); \
93 (m)->size = (size)
94
95/** Update statistics for mem_deref() */
96#define STAT_DEREF(m) \
97 mem_lock(); \
98 memstat.bytes_cur -= (m)->size; \
99 --memstat.blocks_cur; \
100 mem_unlock(); \
101 memset((m), 0xb5, sizeof(struct mem) + (m)->size)
102
103/** Check magic number in memory object */
104#define MAGIC_CHECK(m) \
105 if (mem_magic != (m)->magic) { \
106 DEBUG_WARNING("%s: magic check failed 0x%08x (%p)\n", \
107 __REFUNC__, (m)->magic, (m)+1); \
108 BREAKPOINT; \
109 }
110#else
111#define STAT_ALLOC(m, size)
112#define STAT_REALLOC(m, size)
113#define STAT_DEREF(m)
114#define MAGIC_CHECK(m)
115#endif
116
117
118/**
119 * Allocate a new reference-counted memory object
120 *
121 * @param size Size of memory object
122 * @param dh Optional destructor, called when destroyed
123 *
124 * @return Pointer to allocated object
125 */
126void *mem_alloc(size_t size, mem_destroy_h *dh)
127{
128 struct mem *m;
129
130#if MEM_DEBUG
131 mem_lock();
132 if (-1 != threshold && (memstat.blocks_cur >= (size_t)threshold)) {
133 mem_unlock();
134 return NULL;
135 }
136 mem_unlock();
137#endif
138
139 m = malloc(sizeof(*m) + size);
140 if (!m)
141 return NULL;
142
143#if MEM_DEBUG
144 memset(&m->le, 0, sizeof(struct le));
145 mem_lock();
146 list_append(&meml, &m->le, m);
147 mem_unlock();
148#endif
149
150 m->nrefs = 1;
151 m->dh = dh;
152
153 STAT_ALLOC(m, size);
154
155 return (void *)(m + 1);
156}
157
158
159/**
160 * Allocate a new reference-counted memory object. Memory is zeroed.
161 *
162 * @param size Size of memory object
163 * @param dh Optional destructor, called when destroyed
164 *
165 * @return Pointer to allocated object
166 */
167void *mem_zalloc(size_t size, mem_destroy_h *dh)
168{
169 void *p;
170
171 p = mem_alloc(size, dh);
172 if (!p)
173 return NULL;
174
175 memset(p, 0, size);
176
177 return p;
178}
179
180
181/**
182 * Re-allocate a reference-counted memory object
183 *
184 * @param data Memory object
185 * @param size New size of memory object
186 *
187 * @return New pointer to allocated object
188 *
189 * @note Realloc NULL pointer is not supported
190 */
191void *mem_realloc(void *data, size_t size)
192{
193 struct mem *m, *m2;
194
195 if (!data)
196 return NULL;
197
198 m = ((struct mem *)data) - 1;
199
200 MAGIC_CHECK(m);
201
202#if MEM_DEBUG
203 mem_lock();
204
205 /* Simulate OOM */
206 if (-1 != threshold && size > m->size) {
207 if (memstat.blocks_cur >= (size_t)threshold) {
208 mem_unlock();
209 return NULL;
210 }
211 }
212
213 list_unlink(&m->le);
214 mem_unlock();
215#endif
216
217 m2 = realloc(m, sizeof(*m2) + size);
218
219#if MEM_DEBUG
220 mem_lock();
221 list_append(&meml, m2 ? &m2->le : &m->le, m2 ? m2 : m);
222 mem_unlock();
223#endif
224
225 if (!m2) {
226 return NULL;
227 }
228
229 STAT_REALLOC(m2, size);
230
231 return (void *)(m2 + 1);
232}
233
234
235#ifndef SIZE_MAX
236#define SIZE_MAX (~((size_t)0))
237#endif
238
239
240/**
241 * Re-allocate a reference-counted array
242 *
243 * @param ptr Pointer to existing array, NULL to allocate a new array
244 * @param nmemb Number of members in array
245 * @param membsize Number of bytes in each member
246 * @param dh Optional destructor, only used when ptr is NULL
247 *
248 * @return New pointer to allocated array
249 */
250void *mem_reallocarray(void *ptr, size_t nmemb, size_t membsize,
251 mem_destroy_h *dh)
252{
253 size_t tsize;
254
255 if (membsize && nmemb > SIZE_MAX / membsize) {
256 return NULL;
257 }
258
259 tsize = nmemb * membsize;
260
261 if (ptr) {
262 return mem_realloc(ptr, tsize);
263 }
264 else {
265 return mem_alloc(tsize, dh);
266 }
267}
268
269
270/**
271 * Reference a reference-counted memory object
272 *
273 * @param data Memory object
274 *
275 * @return Memory object (same as data)
276 */
277void *mem_ref(void *data)
278{
279 struct mem *m;
280
281 if (!data)
282 return NULL;
283
284 m = ((struct mem *)data) - 1;
285
286 MAGIC_CHECK(m);
287
288 ++m->nrefs;
289
290 return data;
291}
292
293
294/**
295 * Dereference a reference-counted memory object. When the reference count
296 * is zero, the destroy handler will be called (if present) and the memory
297 * will be freed
298 *
299 * @param data Memory object
300 *
301 * @return Always NULL
302 */
303void *mem_deref(void *data)
304{
305 struct mem *m;
306
307 if (!data)
308 return NULL;
309
310 m = ((struct mem *)data) - 1;
311
312 MAGIC_CHECK(m);
313
314 if (--m->nrefs > 0)
315 return NULL;
316
317 if (m->dh)
318 m->dh(data);
319
320 /* NOTE: check if the destructor called mem_ref() */
321 if (m->nrefs > 0)
322 return NULL;
323
324#if MEM_DEBUG
325 mem_lock();
326 list_unlink(&m->le);
327 mem_unlock();
328#endif
329
330 STAT_DEREF(m);
331
332 free(m);
333
334 return NULL;
335}
336
337
338/**
339 * Get number of references to a reference-counted memory object
340 *
341 * @param data Memory object
342 *
343 * @return Number of references
344 */
345uint32_t mem_nrefs(const void *data)
346{
347 struct mem *m;
348
349 if (!data)
350 return 0;
351
352 m = ((struct mem *)data) - 1;
353
354 MAGIC_CHECK(m);
355
356 return m->nrefs;
357}
358
359
360#if MEM_DEBUG
361static bool debug_handler(struct le *le, void *arg)
362{
363 struct mem *m = le->data;
364 const uint8_t *p = (const uint8_t *)(m + 1);
365 size_t i;
366
367 (void)arg;
368
369 (void)re_fprintf(stderr, " %p: nrefs=%-2u", p, m->nrefs);
370
371 (void)re_fprintf(stderr, " size=%-7u", m->size);
372
373 (void)re_fprintf(stderr, " [");
374
375 for (i=0; i<16; i++) {
376 if (i >= m->size)
377 (void)re_fprintf(stderr, " ");
378 else
379 (void)re_fprintf(stderr, "%02x ", p[i]);
380 }
381
382 (void)re_fprintf(stderr, "] [");
383
384 for (i=0; i<16; i++) {
385 if (i >= m->size)
386 (void)re_fprintf(stderr, " ");
387 else
388 (void)re_fprintf(stderr, "%c",
389 isprint(p[i]) ? p[i] : '.');
390 }
391
392 (void)re_fprintf(stderr, "]");
393
394 MAGIC_CHECK(m);
395
396 (void)re_fprintf(stderr, "\n");
397
398 return false;
399}
400#endif
401
402
403/**
404 * Debug all allocated memory objects
405 */
406void mem_debug(void)
407{
408#if MEM_DEBUG
409 uint32_t n;
410
411 mem_lock();
412 n = list_count(&meml);
413 mem_unlock();
414
415 if (!n)
416 return;
417
418 DEBUG_WARNING("Memory leaks (%u):\n", n);
419
420 mem_lock();
421 (void)list_apply(&meml, true, debug_handler, NULL);
422 mem_unlock();
423#endif
424}
425
426
427/**
428 * Set the memory allocation threshold. This is only used for debugging
429 * and out-of-memory simulation
430 *
431 * @param n Threshold value
432 */
433void mem_threshold_set(ssize_t n)
434{
435#if MEM_DEBUG
436 mem_lock();
437 threshold = n;
438 mem_unlock();
439#else
440 (void)n;
441#endif
442}
443
444
445/**
446 * Print memory status
447 *
448 * @param pf Print handler for debug output
449 * @param unused Unused parameter
450 *
451 * @return 0 if success, otherwise errorcode
452 */
453int mem_status(struct re_printf *pf, void *unused)
454{
455#if MEM_DEBUG
456 struct memstat stat;
457 uint32_t c;
458 int err = 0;
459
460 (void)unused;
461
462 mem_lock();
463 memcpy(&stat, &memstat, sizeof(stat));
464 c = list_count(&meml);
465 mem_unlock();
466
467 err |= re_hprintf(pf, "Memory status: (%u bytes overhead pr block)\n",
468 sizeof(struct mem));
469 err |= re_hprintf(pf, " Cur: %u blocks, %u bytes (total %u bytes)\n",
470 stat.blocks_cur, stat.bytes_cur,
471 stat.bytes_cur
472 +(stat.blocks_cur*sizeof(struct mem)));
473 err |= re_hprintf(pf, " Peak: %u blocks, %u bytes (total %u bytes)\n",
474 stat.blocks_peak, stat.bytes_peak,
475 stat.bytes_peak
476 +(stat.blocks_peak*sizeof(struct mem)));
477 err |= re_hprintf(pf, " Block size: min=%u, max=%u\n",
478 stat.size_min, stat.size_max);
479 err |= re_hprintf(pf, " Total %u blocks allocated\n", c);
480
481 return err;
482#else
483 (void)pf;
484 (void)unused;
485 return 0;
486#endif
487}
488
489
490/**
491 * Get memory statistics
492 *
493 * @param mstat Returned memory statistics
494 *
495 * @return 0 if success, otherwise errorcode
496 */
497int mem_get_stat(struct memstat *mstat)
498{
499 if (!mstat)
500 return EINVAL;
501#if MEM_DEBUG
502 mem_lock();
503 memcpy(mstat, &memstat, sizeof(*mstat));
504 mem_unlock();
505 return 0;
506#else
507 return ENOSYS;
508#endif
509}