2009-07-20 Vladimir Serbinenko <phcoder@gmail.com>
[grub2/phcoder.git] / kern / mm.c
blobcbcb9956019c14a9a0084b4c7221ed8a04044053
1 /* mm.c - functions for memory manager */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2002,2005,2007,2008 Free Software Foundation, Inc.
6 * GRUB 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 3 of the License, or
9 * (at your option) any later version.
11 * GRUB 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.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
21 The design of this memory manager.
23 This is a simple implementation of malloc with a few extensions. These are
24 the extensions:
26 - memalign is implemented efficiently.
28 - multiple regions may be used as free space. They may not be
29 contiguous.
31 Regions are managed by a singly linked list, and the meta information is
32 stored in the beginning of each region. Space after the meta information
33 is used to allocate memory.
35 The memory space is used as cells instead of bytes for simplicity. This
36 is important for some CPUs which may not access multiple bytes at a time
37 when the first byte is not aligned at a certain boundary (typically,
38 4-byte or 8-byte). The size of each cell is equal to the size of struct
39 grub_mm_header, so the header of each allocated/free block fits into one
40 cell precisely. One cell is 16 bytes on 32-bit platforms and 32 bytes
41 on 64-bit platforms.
43 There are two types of blocks: allocated blocks and free blocks.
45 In allocated blocks, the header of each block has only its size. Note that
46 this size is based on cells but not on bytes. The header is located right
47 before the returned pointer, that is, the header resides at the previous
48 cell.
50 Free blocks constitutes a ring, using a singly linked list. The first free
51 block is pointed to by the meta information of a region. The allocator
52 attempts to pick up the second block instead of the first one. This is
53 a typical optimization against defragmentation, and makes the
54 implementation a bit easier.
56 For safety, both allocated blocks and free ones are marked by magic
57 numbers. Whenever anything unexpected is detected, GRUB aborts the
58 operation.
61 #include <config.h>
62 #include <grub/mm.h>
63 #include <grub/misc.h>
64 #include <grub/err.h>
65 #include <grub/types.h>
66 #include <grub/disk.h>
67 #include <grub/dl.h>
69 #ifdef MM_DEBUG
70 # undef grub_malloc
71 # undef grub_zalloc
72 # undef grub_realloc
73 # undef grub_free
74 # undef grub_memalign
75 #endif
77 /* Magic words. */
78 #define GRUB_MM_FREE_MAGIC 0x2d3c2808
79 #define GRUB_MM_ALLOC_MAGIC 0x6db08fa4
81 typedef struct grub_mm_header
83 struct grub_mm_header *next;
84 grub_size_t size;
85 grub_size_t magic;
86 #if GRUB_CPU_SIZEOF_VOID_P == 4
87 char padding[4];
88 #elif GRUB_CPU_SIZEOF_VOID_P == 8
89 char padding[8];
90 #else
91 # error "unknown word size"
92 #endif
94 *grub_mm_header_t;
96 #if GRUB_CPU_SIZEOF_VOID_P == 4
97 # define GRUB_MM_ALIGN_LOG2 4
98 #elif GRUB_CPU_SIZEOF_VOID_P == 8
99 # define GRUB_MM_ALIGN_LOG2 5
100 #endif
102 #define GRUB_MM_ALIGN (1 << GRUB_MM_ALIGN_LOG2)
104 typedef struct grub_mm_region
106 struct grub_mm_header *first;
107 struct grub_mm_region *next;
108 grub_addr_t addr;
109 grub_size_t size;
111 *grub_mm_region_t;
115 static grub_mm_region_t base;
117 /* Get a header from the pointer PTR, and set *P and *R to a pointer
118 to the header and a pointer to its region, respectively. PTR must
119 be allocated. */
120 static void
121 get_header_from_pointer (void *ptr, grub_mm_header_t *p, grub_mm_region_t *r)
123 if ((grub_addr_t) ptr & (GRUB_MM_ALIGN - 1))
124 grub_fatal ("unaligned pointer %p", ptr);
126 for (*r = base; *r; *r = (*r)->next)
127 if ((grub_addr_t) ptr > (*r)->addr
128 && (grub_addr_t) ptr <= (*r)->addr + (*r)->size)
129 break;
131 if (! *r)
132 grub_fatal ("out of range pointer %p", ptr);
134 *p = (grub_mm_header_t) ptr - 1;
135 if ((*p)->magic != GRUB_MM_ALLOC_MAGIC)
136 grub_fatal ("alloc magic is broken at %p", *p);
139 /* Initialize a region starting from ADDR and whose size is SIZE,
140 to use it as free space. */
141 void
142 grub_mm_init_region (void *addr, grub_size_t size)
144 grub_mm_header_t h;
145 grub_mm_region_t r, *p, q;
147 #if 0
148 grub_printf ("Using memory for heap: start=%p, end=%p\n", addr, addr + (unsigned int) size);
149 #endif
151 /* If this region is too small, ignore it. */
152 if (size < GRUB_MM_ALIGN * 2)
153 return;
155 /* Allocate a region from the head. */
156 r = (grub_mm_region_t) (((grub_addr_t) addr + GRUB_MM_ALIGN - 1)
157 & (~(GRUB_MM_ALIGN - 1)));
158 size -= (char *) r - (char *) addr + sizeof (*r);
160 h = (grub_mm_header_t) ((char *) r + GRUB_MM_ALIGN);
161 h->next = h;
162 h->magic = GRUB_MM_FREE_MAGIC;
163 h->size = (size >> GRUB_MM_ALIGN_LOG2);
165 r->first = h;
166 r->addr = (grub_addr_t) h;
167 r->size = (h->size << GRUB_MM_ALIGN_LOG2);
169 /* Find where to insert this region. Put a smaller one before bigger ones,
170 to prevent fragmentation. */
171 for (p = &base, q = *p; q; p = &(q->next), q = *p)
172 if (q->size > r->size)
173 break;
175 *p = r;
176 r->next = q;
179 /* Allocate the number of units N with the alignment ALIGN from the ring
180 buffer starting from *FIRST. ALIGN must be a power of two. Both N and
181 ALIGN are in units of GRUB_MM_ALIGN. Return a non-NULL if successful,
182 otherwise return NULL. */
183 static void *
184 grub_real_malloc (grub_mm_header_t *first, grub_size_t n, grub_size_t align)
186 grub_mm_header_t p, q;
188 /* When everything is allocated side effect is that *first will have alloc
189 magic marked, meaning that there is no room in this region. */
190 if ((*first)->magic == GRUB_MM_ALLOC_MAGIC)
191 return 0;
193 /* Try to search free slot for allocation in this memory region. */
194 for (q = *first, p = q->next; ; q = p, p = p->next)
196 grub_off_t extra;
198 extra = ((grub_addr_t) (p + 1) >> GRUB_MM_ALIGN_LOG2) % align;
199 if (extra)
200 extra = align - extra;
202 if (! p)
203 grub_fatal ("null in the ring");
205 if (p->magic != GRUB_MM_FREE_MAGIC)
206 grub_fatal ("free magic is broken at %p: 0x%x", p, p->magic);
208 if (p->size >= n + extra)
210 if (extra == 0 && p->size == n)
212 /* There is no special alignment requirement and memory block
213 is complete match.
215 1. Just mark memory block as allocated and remove it from
216 free list.
218 Result:
219 +---------------+ previous block's next
220 | alloc, size=n | |
221 +---------------+ v
223 q->next = p->next;
224 p->magic = GRUB_MM_ALLOC_MAGIC;
226 else if (extra == 0 || p->size == n + extra)
228 /* There might be alignment requirement, when taking it into
229 account memory block fits in.
231 1. Allocate new area at end of memory block.
232 2. Reduce size of available blocks from original node.
233 3. Mark new area as allocated and "remove" it from free
234 list.
236 Result:
237 +---------------+
238 | free, size-=n | next --+
239 +---------------+ |
240 | alloc, size=n | |
241 +---------------+ v
243 p->size -= n;
244 p += p->size;
245 p->size = n;
246 p->magic = GRUB_MM_ALLOC_MAGIC;
248 else
250 /* There is alignment requirement and there is room in memory
251 block. Split memory block to three pieces.
253 1. Create new memory block right after section being
254 allocated. Mark it as free.
255 2. Add new memory block to free chain.
256 3. Mark current memory block having only extra blocks.
257 4. Advance to aligned block and mark that as allocated and
258 "remove" it from free list.
260 Result:
261 +------------------------------+
262 | free, size=extra | next --+
263 +------------------------------+ |
264 | alloc, size=n | |
265 +------------------------------+ |
266 | free, size=orig.size-extra-n | <------+, next --+
267 +------------------------------+ v
269 grub_mm_header_t r;
271 r = p + extra + n;
272 r->magic = GRUB_MM_FREE_MAGIC;
273 r->size = p->size - extra - n;
274 r->next = p->next;
276 p->size = extra;
277 p->next = r;
278 p += extra;
279 p->size = n;
280 p->magic = GRUB_MM_ALLOC_MAGIC;
283 /* Mark find as a start marker for next allocation to fasten it.
284 This will have side effect of fragmenting memory as small
285 pieces before this will be un-used. */
286 *first = q;
288 return p + 1;
291 /* Search was completed without result. */
292 if (p == *first)
293 break;
296 return 0;
299 /* Allocate SIZE bytes with the alignment ALIGN and return the pointer. */
300 void *
301 grub_memalign (grub_size_t align, grub_size_t size)
303 grub_mm_region_t r;
304 grub_size_t n = ((size + GRUB_MM_ALIGN - 1) >> GRUB_MM_ALIGN_LOG2) + 1;
305 int count = 0;
307 align = (align >> GRUB_MM_ALIGN_LOG2);
308 if (align == 0)
309 align = 1;
311 again:
313 for (r = base; r; r = r->next)
315 void *p;
317 p = grub_real_malloc (&(r->first), n, align);
318 if (p)
319 return p;
322 /* If failed, increase free memory somehow. */
323 switch (count)
325 case 0:
326 /* Invalidate disk caches. */
327 grub_disk_cache_invalidate_all ();
328 count++;
329 goto again;
331 case 1:
332 /* Unload unneeded modules. */
333 grub_dl_unload_unneeded ();
334 count++;
335 goto again;
337 default:
338 break;
341 grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");
342 return 0;
345 /* Allocate SIZE bytes and return the pointer. */
346 void *
347 grub_malloc (grub_size_t size)
349 return grub_memalign (0, size);
352 /* Allocate SIZE bytes, clear them and return the pointer. */
353 void *
354 grub_zalloc (grub_size_t size)
356 void *ret;
358 ret = grub_memalign (0, size);
359 if (ret)
360 grub_memset (ret, 0, size);
362 return ret;
365 /* Deallocate the pointer PTR. */
366 void
367 grub_free (void *ptr)
369 grub_mm_header_t p;
370 grub_mm_region_t r;
372 if (! ptr)
373 return;
375 get_header_from_pointer (ptr, &p, &r);
377 if (r->first->magic == GRUB_MM_ALLOC_MAGIC)
379 p->magic = GRUB_MM_FREE_MAGIC;
380 r->first = p->next = p;
382 else
384 grub_mm_header_t q;
386 #if 0
387 q = r->first;
390 grub_printf ("%s:%d: q=%p, q->size=0x%x, q->magic=0x%x\n",
391 __FILE__, __LINE__, q, q->size, q->magic);
392 q = q->next;
394 while (q != r->first);
395 #endif
397 for (q = r->first; q >= p || q->next <= p; q = q->next)
399 if (q->magic != GRUB_MM_FREE_MAGIC)
400 grub_fatal ("free magic is broken at %p: 0x%x", q, q->magic);
402 if (q >= q->next && (q < p || q->next > p))
403 break;
406 p->magic = GRUB_MM_FREE_MAGIC;
407 p->next = q->next;
408 q->next = p;
410 if (p + p->size == p->next)
412 if (p->next == q)
413 q = p;
415 p->next->magic = 0;
416 p->size += p->next->size;
417 p->next = p->next->next;
420 if (q + q->size == p)
422 p->magic = 0;
423 q->size += p->size;
424 q->next = p->next;
427 r->first = q;
431 /* Reallocate SIZE bytes and return the pointer. The contents will be
432 the same as that of PTR. */
433 void *
434 grub_realloc (void *ptr, grub_size_t size)
436 grub_mm_header_t p;
437 grub_mm_region_t r;
438 void *q;
439 grub_size_t n;
441 if (! ptr)
442 return grub_malloc (size);
444 if (! size)
446 grub_free (ptr);
447 return 0;
450 /* FIXME: Not optimal. */
451 n = ((size + GRUB_MM_ALIGN - 1) >> GRUB_MM_ALIGN_LOG2) + 1;
452 get_header_from_pointer (ptr, &p, &r);
454 if (p->size >= n)
455 return ptr;
457 q = grub_malloc (size);
458 if (! q)
459 return q;
461 grub_memcpy (q, ptr, size);
462 grub_free (ptr);
463 return q;
466 #ifdef MM_DEBUG
467 int grub_mm_debug = 0;
469 void
470 grub_mm_dump_free (void)
472 grub_mm_region_t r;
474 for (r = base; r; r = r->next)
476 grub_mm_header_t p;
478 /* Follow the free list. */
479 p = r->first;
482 if (p->magic != GRUB_MM_FREE_MAGIC)
483 grub_fatal ("free magic is broken at %p: 0x%x", p, p->magic);
485 grub_printf ("F:%p:%u:%p\n",
486 p, (unsigned int) p->size << GRUB_MM_ALIGN_LOG2, p->next);
487 p = p->next;
489 while (p != r->first);
492 grub_printf ("\n");
495 void
496 grub_mm_dump (unsigned lineno)
498 grub_mm_region_t r;
500 grub_printf ("called at line %u\n", lineno);
501 for (r = base; r; r = r->next)
503 grub_mm_header_t p;
505 for (p = (grub_mm_header_t) ((r->addr + GRUB_MM_ALIGN - 1)
506 & (~(GRUB_MM_ALIGN - 1)));
507 (grub_addr_t) p < r->addr + r->size;
508 p++)
510 switch (p->magic)
512 case GRUB_MM_FREE_MAGIC:
513 grub_printf ("F:%p:%u:%p\n",
514 p, (unsigned int) p->size << GRUB_MM_ALIGN_LOG2, p->next);
515 break;
516 case GRUB_MM_ALLOC_MAGIC:
517 grub_printf ("A:%p:%u\n", p, (unsigned int) p->size << GRUB_MM_ALIGN_LOG2);
518 break;
523 grub_printf ("\n");
526 void *
527 grub_debug_malloc (const char *file, int line, grub_size_t size)
529 void *ptr;
531 if (grub_mm_debug)
532 grub_printf ("%s:%d: malloc (0x%zx) = ", file, line, size);
533 ptr = grub_malloc (size);
534 if (grub_mm_debug)
535 grub_printf ("%p\n", ptr);
536 return ptr;
539 void *
540 grub_debug_zalloc (const char *file, int line, grub_size_t size)
542 void *ptr;
544 if (grub_mm_debug)
545 grub_printf ("%s:%d: zalloc (0x%zx) = ", file, line, size);
546 ptr = grub_zalloc (size);
547 if (grub_mm_debug)
548 grub_printf ("%p\n", ptr);
549 return ptr;
552 void
553 grub_debug_free (const char *file, int line, void *ptr)
555 if (grub_mm_debug)
556 grub_printf ("%s:%d: free (%p)\n", file, line, ptr);
557 grub_free (ptr);
560 void *
561 grub_debug_realloc (const char *file, int line, void *ptr, grub_size_t size)
563 if (grub_mm_debug)
564 grub_printf ("%s:%d: realloc (%p, 0x%zx) = ", file, line, ptr, size);
565 ptr = grub_realloc (ptr, size);
566 if (grub_mm_debug)
567 grub_printf ("%p\n", ptr);
568 return ptr;
571 void *
572 grub_debug_memalign (const char *file, int line, grub_size_t align,
573 grub_size_t size)
575 void *ptr;
577 if (grub_mm_debug)
578 grub_printf ("%s:%d: memalign (0x%zx, 0x%zx) = ",
579 file, line, align, size);
580 ptr = grub_memalign (align, size);
581 if (grub_mm_debug)
582 grub_printf ("%p\n", ptr);
583 return ptr;
586 #endif /* MM_DEBUG */