b2b99a41db9a2433309902c868b94163f19abfe1
[srvx.git] / src / alloc-slab.c
1 /* alloc-slab.c - Slab debugging allocator
2  * Copyright 2005 srvx Development Team
3  *
4  * This file is part of srvx.
5  *
6  * srvx is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  */
16
17 #include "common.h"
18 #include "log.h"
19
20 #if defined(HAVE_SYS_MMAN_H)
21 # include <sys/mman.h>
22 #endif
23
24 #if !defined(HAVE_MMAP)
25 # error The slab allocator requires that your system have the mmap() system call.
26 #endif
27
28 #define SLAB_DEBUG 1
29 #define SLAB_RESERVE 1024
30
31 #if SLAB_DEBUG
32
33 #define ALLOC_MAGIC 0x1acf
34 #define FREE_MAGIC  0xfc1d
35
36 struct alloc_header {
37     unsigned int file_id : 8;
38     unsigned int size : 24;
39     unsigned int line : 16;
40     unsigned int magic : 16;
41 };
42
43 static const char *file_ids[256];
44 static struct file_id_entry {
45     const char *name;
46     unsigned int id : 8;
47 } file_id_map[256];
48 unsigned int file_ids_used;
49
50 static int
51 file_id_cmp(const void *a_, const void *b_)
52 {
53     return strcmp(*(const char**)a_, *(const char**)b_);
54 }
55
56 static unsigned int
57 get_file_id(const char *fname)
58 {
59     struct file_id_entry *entry;
60
61     entry = bsearch(&fname, file_id_map, file_ids_used, sizeof(file_id_map[0]), file_id_cmp);
62     if (entry)
63         return entry->id;
64     entry = file_id_map + file_ids_used;
65     file_ids[file_ids_used] = fname;
66     entry->name = fname;
67     entry->id = file_ids_used;
68     qsort(file_id_map, ++file_ids_used, sizeof(file_id_map[0]), file_id_cmp);
69     return file_ids_used - 1;
70 }
71
72 typedef struct alloc_header alloc_header_t;
73
74 #else
75
76 typedef size_t alloc_header_t;
77
78 #endif
79
80 struct slab {
81     struct slabset *parent;
82     struct slab *prev;
83     struct slab *next;
84     void *base;
85     void **free;
86     unsigned int used;
87 };
88
89 struct slabset {
90     struct slab *child;
91     size_t nslabs;
92     size_t nallocs;
93     size_t size;
94     size_t items_per_slab;
95 };
96
97 #define SLAB_MIN     (2 * sizeof(void*))
98 #define SLAB_GRAIN   sizeof(void*)
99 #define SLAB_ALIGN   SLAB_GRAIN
100 #define SMALL_CUTOFF 580
101 /* Element size < SMALL_CUTOFF -> use small slabs.
102  * Larger elements are allocated directly using mmap().  The largest
103  * regularly allocated struct in srvx 1.x is smaller than
104  * SMALL_CUTOFF, so there is not much point in coding support for
105  * larger slabs.
106  */
107
108 static struct slabset little_slabs[SMALL_CUTOFF / SLAB_GRAIN];
109 static struct slab *free_slab_head;
110 static struct slab *free_slab_tail;
111 unsigned long free_slab_count;
112 unsigned long big_alloc_count;
113 unsigned long big_alloc_size;
114 unsigned long slab_count;
115 unsigned long slab_alloc_count;
116 unsigned long slab_alloc_size;
117
118 #if defined(MAP_ANON)
119 #elif defined(MAP_ANONYMOUS)
120 # define MAP_ANON MAP_ANONYMOUS
121 #else
122 # define MAP_ANON 0
123 #endif
124
125 static size_t
126 slab_pagesize(void)
127 {
128     static size_t pagesize;
129     if (pagesize
130 #if defined(HAVE_GETPAGESIZE)
131         || (pagesize  = getpagesize())
132 #endif
133 #if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
134         || (pagesize = sysconf(_SC_PAGESIZE))
135 #endif
136 #if defined(HAVE_SYSCONF) && defined(_SC_PAGE_SIZE)
137         || (pagesize = sysconf(_SC_PAGE_SIZE))
138 #endif
139         ) return pagesize;
140     assert(0 && "unable to find system page size");
141     return pagesize = 4096;
142 }
143
144 static size_t
145 slab_round_up(size_t size)
146 {
147     return (size + slab_pagesize() - 1) & ~(slab_pagesize() - 1);
148 }
149
150 static void *
151 slab_map(size_t length)
152 {
153     static int mmap_fd = -1;
154     void *res;
155
156 #if ! MAP_ANON
157     if (mmap_fd < 0) {
158         mmap_fd = open("/dev/zero", 0);
159         if (mmap_fd < 0)
160             log_module(MAIN_LOG, LOG_FATAL, "Unable to open /dev/zero for mmap: %s", strerror(errno()));
161     }
162 #endif
163     res = mmap(0, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, mmap_fd, 0);
164     if (res == MAP_FAILED)
165         log_module(MAIN_LOG, LOG_FATAL, "Unable to mmap %lu bytes (%s).", (unsigned long)length, strerror(errno));
166     return res;
167 }
168
169 static void *slab_alloc(struct slabset *sset);
170 static void slab_unalloc(void *ptr, size_t size);
171
172 static struct slabset *
173 slabset_create(size_t size)
174 {
175     unsigned int idx;
176
177     size = (size < SLAB_MIN) ? SLAB_MIN : (size + SLAB_GRAIN - 1) & ~(SLAB_GRAIN - 1);
178     idx = size / SLAB_GRAIN;
179     assert(idx < ArrayLength(little_slabs));
180     if (!little_slabs[idx].size) {
181         little_slabs[idx].size = size;
182         little_slabs[idx].items_per_slab = (slab_pagesize() - sizeof(struct slab)) / ((size + SLAB_ALIGN - 1) & ~(SLAB_ALIGN - 1));
183     }
184     return &little_slabs[idx];
185 }
186
187 static void *
188 slab_alloc(struct slabset *sset)
189 {
190     struct slab *slab;
191     void **item;
192
193     if (!sset->child || !sset->child->free) {
194         unsigned int ii, step;
195
196         /* Allocate new slab. */
197         if (free_slab_head) {
198             slab = free_slab_head;
199             if (!(free_slab_head = slab->next))
200                 free_slab_tail = NULL;
201         } else {
202             item = slab_map(slab_pagesize());
203             slab = (struct slab*)((char*)item + slab_pagesize() - sizeof(*slab));
204             slab->base = item;
205             slab_count++;
206         }
207
208         /* Populate free list. */
209         step = (sset->size + SLAB_ALIGN - 1) & ~(SLAB_ALIGN - 1);
210         for (ii = 1, item = slab->free = slab->base;
211              ii < sset->items_per_slab;
212              ++ii, item = (*item = (char*)item + step));
213         *item = NULL;
214
215         /* Link to parent slabset. */
216         slab->parent = sset;
217         slab->prev = sset->child;
218         if (slab->prev) {
219             slab->next = slab->prev->next;
220             slab->prev->next = slab;
221             if (slab->next)
222                 slab->next->prev = slab;
223         } else
224             slab->next = NULL;
225         assert(!slab->next || slab == slab->next->prev);
226         assert(!slab->prev || slab == slab->prev->next);
227         sset->child = slab;
228         sset->nslabs++;
229         /* log_module(MAIN_LOG, LOG_DEBUG, "Allocated new %u-slab %p.", sset->size, slab); */
230     }
231
232     slab = sset->child;
233     item = slab->free;
234     assert(((unsigned long)item & (slab_pagesize() - 1))
235            <= (slab_pagesize() - sizeof(*slab) - sset->size));
236     slab->free = *item;
237     if (++slab->used == sset->items_per_slab) {
238         /* log_module(MAIN_LOG, LOG_DEBUG, "%u-slab %p is empty.", sset->size, item); */
239         if (sset->child != slab) {
240             /* Unlink slab and reinsert before sset->child. */
241             if (slab->prev)
242                 slab->prev->next = slab->next;
243             if (slab->next)
244                 slab->next->prev = slab->prev;
245             if ((slab->prev = sset->child->prev))
246                 slab->prev->next = slab;
247             if ((slab->next = sset->child))
248                 slab->next->prev = slab;
249             assert(!slab->next || slab == slab->next->prev);
250             assert(!slab->prev || slab == slab->prev->next);
251         } else if (slab->next) {
252             /* Advance sset->child to next pointer. */
253             sset->child = slab->next;
254         }
255     }
256     sset->nallocs++;
257     memset(item, 0, sset->size);
258     return item;
259 }
260
261 static void
262 slab_unalloc(void *ptr, size_t size)
263 {
264     void **item;
265     struct slab *slab, *new_next;
266
267     item = ptr;
268     assert(size < SMALL_CUTOFF);
269     slab = (struct slab*)((((unsigned long)ptr | (slab_pagesize() - 1)) + 1) - sizeof(*slab));
270     *item = slab->free;
271     memset(item + 1, 0xde, size - sizeof(*item));
272     slab->free = item;
273     slab->parent->nallocs--;
274
275     if (slab->used-- == slab->parent->items_per_slab
276         && slab->parent->child != slab) {
277         /* Unlink from current position, relink as parent's first child. */
278         new_next = slab->parent->child;
279         assert(new_next != NULL);
280         if (slab->prev)
281             slab->prev->next = slab->next;
282         if (slab->next)
283             slab->next->prev = slab->prev;
284         if ((slab->prev = new_next->prev))
285             slab->prev->next = slab;
286         slab->next = new_next;
287         new_next->prev = slab;
288         slab->parent->child = slab;
289         assert(!slab->next || slab == slab->next->prev);
290         assert(!slab->prev || slab == slab->prev->next);
291         /* log_module(MAIN_LOG, LOG_DEBUG, "%u-slab %p became partial.", slab->parent->size, slab); */
292     } else if (!slab->used) {
293         /* log_module(MAIN_LOG, LOG_DEBUG, "%u-slab %p became full.", slab->parent->size, slab); */
294         /* Unlink slab from its parent. */
295         slab->parent->nslabs--;
296         if (slab->prev)
297             slab->prev->next = slab->next;
298         if (slab->next)
299             slab->next->prev = slab->prev;
300         new_next = slab->next ? slab->next : slab->prev;
301         if (slab == slab->parent->child)
302             slab->parent->child = new_next;
303         if (new_next) {
304             assert(!new_next->next || new_next == new_next->next->prev);
305             assert(!new_next->prev || new_next == new_next->prev->next);
306         }
307
308 #if SLAB_RESERVE
309         if (!free_slab_count) {
310             /* Make sure we have enough free slab pages. */
311             while (free_slab_count < SLAB_RESERVE) {
312                 struct slab *tslab;
313                 item = slab_map(slab_pagesize());
314                 tslab = (struct slab*)((char*)item + slab_pagesize() - sizeof(*slab));
315                 tslab->base = item;
316                 tslab->prev = free_slab_tail;
317                 free_slab_tail = tslab;
318                 if (!free_slab_head)
319                     free_slab_head = tslab;
320                 free_slab_count++;
321                 slab_count++;
322             }
323         }
324
325         /* Unmap old slab, so accesses to stale pointers will fault. */
326         munmap(slab->base, slab_pagesize());
327         slab_count--;
328 #else
329         /* Link to list of free slabs. */
330         slab->parent = NULL;
331         slab->prev = free_slab_tail;
332         slab->next = NULL;
333         free_slab_tail = slab;
334         if (!free_slab_head)
335             free_slab_head = slab;
336         free_slab_count++;
337 #endif
338     }
339 }
340
341 void *
342 slab_malloc(const char *file, unsigned int line, size_t size)
343 {
344     alloc_header_t *res;
345     size_t real;
346
347     assert(size < 1 << 24);
348     real = (size + sizeof(*res) + SLAB_GRAIN - 1) & ~(SLAB_GRAIN - 1);
349     if (real < SMALL_CUTOFF) {
350         res = slab_alloc(slabset_create(real));
351         slab_alloc_count++;
352         slab_alloc_size += size;
353     } else {
354         res = slab_map(slab_round_up(real));
355         big_alloc_count++;
356         big_alloc_size += size;
357     }
358 #if SLAB_DEBUG
359     res->file_id = get_file_id(file);
360     res->size = size;
361     res->line = line;
362     res->magic = ALLOC_MAGIC;
363 #else
364     *res = size;
365     (void)file; (void)line;
366 #endif
367     return res + 1;
368 }
369
370 void *
371 slab_realloc(const char *file, unsigned int line, void *ptr, size_t size)
372 {
373     alloc_header_t *orig;
374     void *newblock;
375     size_t osize;
376
377     if (!ptr)
378         return slab_malloc(file, line, size);
379
380     verify(ptr);
381     orig = (alloc_header_t*)ptr - 1;
382 #if SLAB_DEBUG
383     osize = orig->size;
384 #else
385     osize = *orig;
386 #endif
387     if (osize >= size)
388         return ptr;
389     newblock = slab_malloc(file, line, size);
390     memcpy(newblock, ptr, osize);
391     slab_free(file, line, ptr);
392     return newblock;
393 }
394
395 char *
396 slab_strdup(const char *file, unsigned int line, const char *src)
397 {
398     char *target;
399     size_t len;
400
401     len = strlen(src) + 1;
402     target = slab_malloc(file, line, len);
403     memcpy(target, src, len);
404     return target;
405 }
406
407 void
408 slab_free(const char *file, unsigned int line, void *ptr)
409 {
410     alloc_header_t *hdr;
411     size_t real;
412
413     if (!ptr)
414         return;
415     verify(ptr);
416     hdr = (alloc_header_t*)ptr - 1;
417 #if SLAB_DEBUG
418     hdr->file_id = get_file_id(file);
419     hdr->line = line;
420     hdr->magic = FREE_MAGIC;
421     real = hdr->size + sizeof(*hdr);
422 #else
423     real = *hdr + sizeof(*hdr);
424     (void)file; (void)line;
425 #endif
426     real = (real + SLAB_GRAIN - 1) & ~(SLAB_GRAIN - 1);
427     if (real < SMALL_CUTOFF) {
428         slab_unalloc(hdr, real);
429         slab_alloc_count--;
430         slab_alloc_size -= real - sizeof(*hdr);
431     } else {
432         munmap(hdr, slab_round_up(real));
433         big_alloc_count--;
434         big_alloc_size -= real - sizeof(*hdr);
435     }
436 }
437
438 void
439 verify(const void *ptr)
440 {
441     alloc_header_t *hdr;
442     size_t real;
443
444     if (!ptr)
445         return;
446
447     hdr = (alloc_header_t*)ptr - 1;
448 #if SLAB_DEBUG
449     real = hdr->size + sizeof(*hdr);
450     assert(hdr->file_id < file_ids_used);
451     assert(hdr->magic == ALLOC_MAGIC);
452 #else
453     real = *hdr + sizeof(*hdr);
454 #endif
455     real = (real + SLAB_GRAIN - 1) & ~(SLAB_GRAIN - 1);
456
457     if (real >= SMALL_CUTOFF)
458         assert(((unsigned long)ptr & (slab_pagesize() - 1)) == sizeof(*hdr));
459     else {
460         struct slab *slab;
461         size_t expected;
462
463         expected = (real + SLAB_GRAIN - 1) & ~(SLAB_GRAIN - 1);
464         slab = (struct slab*)((((unsigned long)ptr | (slab_pagesize() - 1)) + 1) - sizeof(*slab));
465         assert(slab->parent->size == expected);
466     }
467 }