mm.c 15.4 KB
Newer Older
okuji's avatar
okuji committed
1 2
/* mm.c - functions for memory manager */
/*
3
 *  GRUB  --  GRand Unified Bootloader
4
 *  Copyright (C) 2002,2005,2007,2008,2009  Free Software Foundation, Inc.
okuji's avatar
okuji committed
5
 *
6
 *  GRUB is free software: you can redistribute it and/or modify
okuji's avatar
okuji committed
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
okuji's avatar
okuji committed
9 10
 *  (at your option) any later version.
 *
11
 *  GRUB is distributed in the hope that it will be useful,
okuji's avatar
okuji committed
12 13 14 15 16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
17
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
okuji's avatar
okuji committed
18 19
 */

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
/*
  The design of this memory manager.

  This is a simple implementation of malloc with a few extensions. These are
  the extensions:

  - memalign is implemented efficiently.

  - multiple regions may be used as free space. They may not be
  contiguous.

  Regions are managed by a singly linked list, and the meta information is
  stored in the beginning of each region. Space after the meta information
  is used to allocate memory.

  The memory space is used as cells instead of bytes for simplicity. This
  is important for some CPUs which may not access multiple bytes at a time
  when the first byte is not aligned at a certain boundary (typically,
  4-byte or 8-byte). The size of each cell is equal to the size of struct
  grub_mm_header, so the header of each allocated/free block fits into one
  cell precisely. One cell is 16 bytes on 32-bit platforms and 32 bytes
  on 64-bit platforms.

  There are two types of blocks: allocated blocks and free blocks.

  In allocated blocks, the header of each block has only its size. Note that
  this size is based on cells but not on bytes. The header is located right
  before the returned pointer, that is, the header resides at the previous
  cell.

  Free blocks constitutes a ring, using a singly linked list. The first free
  block is pointed to by the meta information of a region. The allocator
  attempts to pick up the second block instead of the first one. This is
  a typical optimization against defragmentation, and makes the
  implementation a bit easier.

  For safety, both allocated blocks and free ones are marked by magic
  numbers. Whenever anything unexpected is detected, GRUB aborts the
  operation.
 */

okuji's avatar
okuji committed
61
#include <config.h>
62 63 64 65 66 67
#include <grub/mm.h>
#include <grub/misc.h>
#include <grub/err.h>
#include <grub/types.h>
#include <grub/disk.h>
#include <grub/dl.h>
68
#include <grub/i18n.h>
69
#include <grub/mm_private.h>
okuji's avatar
okuji committed
70

71 72
#ifdef MM_DEBUG
# undef grub_malloc
73
# undef grub_zalloc
74 75 76 77 78
# undef grub_realloc
# undef grub_free
# undef grub_memalign
#endif

okuji's avatar
okuji committed
79 80


81
grub_mm_region_t grub_mm_base;
okuji's avatar
okuji committed
82 83 84 85 86

/* Get a header from the pointer PTR, and set *P and *R to a pointer
   to the header and a pointer to its region, respectively. PTR must
   be allocated.  */
static void
87
get_header_from_pointer (void *ptr, grub_mm_header_t *p, grub_mm_region_t *r)
okuji's avatar
okuji committed
88
{
89
  if ((grub_addr_t) ptr & (GRUB_MM_ALIGN - 1))
90
    grub_fatal ("unaligned pointer %p", ptr);
okuji's avatar
okuji committed
91

92 93 94
  for (*r = grub_mm_base; *r; *r = (*r)->next)
    if ((grub_addr_t) ptr > (grub_addr_t) ((*r) + 1)
	&& (grub_addr_t) ptr <= (grub_addr_t) ((*r) + 1) + (*r)->size)
okuji's avatar
okuji committed
95 96 97
      break;

  if (! *r)
98
    grub_fatal ("out of range pointer %p", ptr);
99

100
  *p = (grub_mm_header_t) ptr - 1;
101 102
  if ((*p)->magic == GRUB_MM_FREE_MAGIC)
    grub_fatal ("double free at %p", *p);
103
  if ((*p)->magic != GRUB_MM_ALLOC_MAGIC)
104 105
    grub_fatal ("alloc magic is broken at %p: %lx", *p,
		(unsigned long) (*p)->magic);
okuji's avatar
okuji committed
106 107 108 109 110
}

/* Initialize a region starting from ADDR and whose size is SIZE,
   to use it as free space.  */
void
111
grub_mm_init_region (void *addr, grub_size_t size)
okuji's avatar
okuji committed
112
{
113 114
  grub_mm_header_t h;
  grub_mm_region_t r, *p, q;
okuji's avatar
okuji committed
115

116 117 118
#if 0
  grub_printf ("Using memory for heap: start=%p, end=%p\n", addr, addr + (unsigned int) size);
#endif
119

120 121 122 123 124 125 126 127 128
  /* Exclude last 4K to avoid overflows. */
  /* If addr + 0x1000 overflows then whole region is in excluded zone.  */
  if ((grub_addr_t) addr > ~((grub_addr_t) 0x1000))
    return;

  /* If addr + 0x1000 + size overflows then decrease size.  */
  if (((grub_addr_t) addr + 0x1000) > ~(grub_addr_t) size)
    size = ((grub_addr_t) -0x1000) - (grub_addr_t) addr;

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
  for (p = &grub_mm_base, q = *p; q; p = &(q->next), q = *p)
    if ((grub_uint8_t *) addr + size + q->pre_size == (grub_uint8_t *) q)
      {
	r = (grub_mm_region_t) ALIGN_UP ((grub_addr_t) addr, GRUB_MM_ALIGN);
	*r = *q;
	r->pre_size += size;
	
	if (r->pre_size >> GRUB_MM_ALIGN_LOG2)
	  {
	    h = (grub_mm_header_t) (r + 1);
	    h->size = (r->pre_size >> GRUB_MM_ALIGN_LOG2);
	    h->magic = GRUB_MM_ALLOC_MAGIC;
	    r->size += h->size << GRUB_MM_ALIGN_LOG2;
	    r->pre_size &= (GRUB_MM_ALIGN - 1);
	    *p = r;
	    grub_free (h + 1);
	  }
	*p = r;
	return;
      }

okuji's avatar
okuji committed
150
  /* Allocate a region from the head.  */
151
  r = (grub_mm_region_t) ALIGN_UP ((grub_addr_t) addr, GRUB_MM_ALIGN);
152

153
  /* If this region is too small, ignore it.  */
154
  if (size < GRUB_MM_ALIGN + (char *) r - (char *) addr + sizeof (*r))
155 156
    return;

157 158
  size -= (char *) r - (char *) addr + sizeof (*r);

159
  h = (grub_mm_header_t) (r + 1);
okuji's avatar
okuji committed
160
  h->next = h;
161 162
  h->magic = GRUB_MM_FREE_MAGIC;
  h->size = (size >> GRUB_MM_ALIGN_LOG2);
okuji's avatar
okuji committed
163 164

  r->first = h;
165
  r->pre_size = (grub_addr_t) r - (grub_addr_t) addr;
166
  r->size = (h->size << GRUB_MM_ALIGN_LOG2);
okuji's avatar
okuji committed
167 168

  /* Find where to insert this region. Put a smaller one before bigger ones,
169
     to prevent fragmentation.  */
170
  for (p = &grub_mm_base, q = *p; q; p = &(q->next), q = *p)
okuji's avatar
okuji committed
171 172
    if (q->size > r->size)
      break;
173

okuji's avatar
okuji committed
174 175 176 177 178
  *p = r;
  r->next = q;
}

/* Allocate the number of units N with the alignment ALIGN from the ring
179 180 181
   buffer starting from *FIRST.  ALIGN must be a power of two. Both N and
   ALIGN are in units of GRUB_MM_ALIGN.  Return a non-NULL if successful,
   otherwise return NULL.  */
okuji's avatar
okuji committed
182
static void *
183
grub_real_malloc (grub_mm_header_t *first, grub_size_t n, grub_size_t align)
okuji's avatar
okuji committed
184
{
185
  grub_mm_header_t p, q;
186

187 188
  /* When everything is allocated side effect is that *first will have alloc
     magic marked, meaning that there is no room in this region.  */
189
  if ((*first)->magic == GRUB_MM_ALLOC_MAGIC)
okuji's avatar
okuji committed
190 191
    return 0;

192
  /* Try to search free slot for allocation in this memory region.  */
okuji's avatar
okuji committed
193 194
  for (q = *first, p = q->next; ; q = p, p = p->next)
    {
195
      grub_off_t extra;
okuji's avatar
okuji committed
196

197
      extra = ((grub_addr_t) (p + 1) >> GRUB_MM_ALIGN_LOG2) & (align - 1);
okuji's avatar
okuji committed
198 199 200 201
      if (extra)
	extra = align - extra;

      if (! p)
202
	grub_fatal ("null in the ring");
okuji's avatar
okuji committed
203

204 205
      if (p->magic != GRUB_MM_FREE_MAGIC)
	grub_fatal ("free magic is broken at %p: 0x%x", p, p->magic);
okuji's avatar
okuji committed
206 207 208

      if (p->size >= n + extra)
	{
209
	  extra += (p->size - extra - n) & (~(align - 1));
okuji's avatar
okuji committed
210 211
	  if (extra == 0 && p->size == n)
	    {
212 213 214 215 216 217 218 219 220 221 222
	      /* There is no special alignment requirement and memory block
	         is complete match.

	         1. Just mark memory block as allocated and remove it from
	            free list.

	         Result:
	         +---------------+ previous block's next
	         | alloc, size=n |          |
	         +---------------+          v
	       */
okuji's avatar
okuji committed
223 224
	      q->next = p->next;
	    }
225
	  else if (align == 1 || p->size == n + extra)
okuji's avatar
okuji committed
226
	    {
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
	      /* There might be alignment requirement, when taking it into
	         account memory block fits in.

	         1. Allocate new area at end of memory block.
	         2. Reduce size of available blocks from original node.
	         3. Mark new area as allocated and "remove" it from free
	            list.

	         Result:
	         +---------------+
	         | free, size-=n | next --+
	         +---------------+        |
	         | alloc, size=n |        |
	         +---------------+        v
	       */
242

okuji's avatar
okuji committed
243 244 245
	      p->size -= n;
	      p += p->size;
	    }
246 247 248 249 250 251 252 253 254
	  else if (extra == 0)
	    {
	      grub_mm_header_t r;
	      
	      r = p + extra + n;
	      r->magic = GRUB_MM_FREE_MAGIC;
	      r->size = p->size - extra - n;
	      r->next = p->next;
	      q->next = r;
255 256 257 258 259 260

	      if (q == p)
		{
		  q = r;
		  r->next = r;
		}
okuji's avatar
okuji committed
261 262 263
	    }
	  else
	    {
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
	      /* There is alignment requirement and there is room in memory
	         block.  Split memory block to three pieces.

	         1. Create new memory block right after section being
	            allocated.  Mark it as free.
	         2. Add new memory block to free chain.
	         3. Mark current memory block having only extra blocks.
	         4. Advance to aligned block and mark that as allocated and
	            "remove" it from free list.

	         Result:
	         +------------------------------+
	         | free, size=extra             | next --+
	         +------------------------------+        |
	         | alloc, size=n                |        |
	         +------------------------------+        |
	         | free, size=orig.size-extra-n | <------+, next --+
	         +------------------------------+                  v
	       */
283
	      grub_mm_header_t r;
okuji's avatar
okuji committed
284 285

	      r = p + extra + n;
286
	      r->magic = GRUB_MM_FREE_MAGIC;
okuji's avatar
okuji committed
287
	      r->size = p->size - extra - n;
288
	      r->next = p;
289

okuji's avatar
okuji committed
290
	      p->size = extra;
291
	      q->next = r;
okuji's avatar
okuji committed
292 293 294
	      p += extra;
	    }

295 296 297
	  p->magic = GRUB_MM_ALLOC_MAGIC;
	  p->size = n;

298 299 300
	  /* Mark find as a start marker for next allocation to fasten it.
	     This will have side effect of fragmenting memory as small
	     pieces before this will be un-used.  */
301
	  /* So do it only for chunks under 64K.  */
302
	  if (n < (0x8000 >> GRUB_MM_ALIGN_LOG2)
303 304
	      || *first == p)
	    *first = q;
305

okuji's avatar
okuji committed
306 307 308
	  return p + 1;
	}

309
      /* Search was completed without result.  */
okuji's avatar
okuji committed
310 311 312 313 314 315 316 317 318
      if (p == *first)
	break;
    }

  return 0;
}

/* Allocate SIZE bytes with the alignment ALIGN and return the pointer.  */
void *
319
grub_memalign (grub_size_t align, grub_size_t size)
okuji's avatar
okuji committed
320
{
321 322
  grub_mm_region_t r;
  grub_size_t n = ((size + GRUB_MM_ALIGN - 1) >> GRUB_MM_ALIGN_LOG2) + 1;
323
  int count = 0;
324

325 326 327
  if (!grub_mm_base)
    goto fail;

328 329 330 331 332 333 334 335 336
  if (size > ~(grub_size_t) align)
    goto fail;

  /* We currently assume at least a 32-bit grub_size_t,
     so limiting allocations to <adress space size> - 1MiB
     in name of sanity is beneficial. */
  if ((size + align) > ~(grub_size_t) 0x100000)
    goto fail;

337
  align = (align >> GRUB_MM_ALIGN_LOG2);
okuji's avatar
okuji committed
338 339 340 341
  if (align == 0)
    align = 1;

 again:
342

343
  for (r = grub_mm_base; r; r = r->next)
okuji's avatar
okuji committed
344 345
    {
      void *p;
346

347
      p = grub_real_malloc (&(r->first), n, align);
okuji's avatar
okuji committed
348 349 350 351
      if (p)
	return p;
    }

352 353
  /* If failed, increase free memory somehow.  */
  switch (count)
okuji's avatar
okuji committed
354
    {
355 356
    case 0:
      /* Invalidate disk caches.  */
357
      grub_disk_cache_invalidate_all ();
358 359
      count++;
      goto again;
360

361
#if 0
362 363
    case 1:
      /* Unload unneeded modules.  */
364
      grub_dl_unload_unneeded ();
365
      count++;
okuji's avatar
okuji committed
366
      goto again;
367
#endif
okuji's avatar
okuji committed
368

369 370 371
    default:
      break;
    }
372

373
 fail:
374
  grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));
okuji's avatar
okuji committed
375 376 377 378 379
  return 0;
}

/* Allocate SIZE bytes and return the pointer.  */
void *
380
grub_malloc (grub_size_t size)
okuji's avatar
okuji committed
381
{
382
  return grub_memalign (0, size);
okuji's avatar
okuji committed
383 384
}

385 386 387 388 389 390 391 392 393 394 395 396 397
/* Allocate SIZE bytes, clear them and return the pointer.  */
void *
grub_zalloc (grub_size_t size)
{
  void *ret;

  ret = grub_memalign (0, size);
  if (ret)
    grub_memset (ret, 0, size);

  return ret;
}

okuji's avatar
okuji committed
398 399
/* Deallocate the pointer PTR.  */
void
400
grub_free (void *ptr)
okuji's avatar
okuji committed
401
{
402 403
  grub_mm_header_t p;
  grub_mm_region_t r;
okuji's avatar
okuji committed
404 405 406 407 408 409

  if (! ptr)
    return;

  get_header_from_pointer (ptr, &p, &r);

410
  if (r->first->magic == GRUB_MM_ALLOC_MAGIC)
okuji's avatar
okuji committed
411
    {
412
      p->magic = GRUB_MM_FREE_MAGIC;
413
      r->first = p->next = p;
okuji's avatar
okuji committed
414 415 416
    }
  else
    {
417
      grub_mm_header_t q, s;
418 419 420 421 422

#if 0
      q = r->first;
      do
	{
423
	  grub_printf ("%s:%d: q=%p, q->size=0x%x, q->magic=0x%x\n",
424
		       GRUB_FILE, __LINE__, q, q->size, q->magic);
425 426 427 428
	  q = q->next;
	}
      while (q != r->first);
#endif
429

430
      for (s = r->first, q = s->next; q <= p || q->next >= p; s = q, q = s->next)
okuji's avatar
okuji committed
431
	{
432 433
	  if (q->magic != GRUB_MM_FREE_MAGIC)
	    grub_fatal ("free magic is broken at %p: 0x%x", q, q->magic);
434

435
	  if (q <= q->next && (q > p || q->next < p))
okuji's avatar
okuji committed
436 437 438
	    break;
	}

439
      p->magic = GRUB_MM_FREE_MAGIC;
okuji's avatar
okuji committed
440 441
      p->next = q->next;
      q->next = p;
442

443
      if (p->next + p->next->size == p)
okuji's avatar
okuji committed
444
	{
445
	  p->magic = 0;
446

447 448 449
	  p->next->size += p->size;
	  q->next = p->next;
	  p = p->next;
okuji's avatar
okuji committed
450
	}
451

452 453 454
      r->first = q;

      if (q == p + p->size)
okuji's avatar
okuji committed
455
	{
456 457 458 459 460 461
	  q->magic = 0;
	  p->size += q->size;
	  if (q == s)
	    s = p;
	  s->next = p;
	  q = s;
okuji's avatar
okuji committed
462 463 464 465 466 467 468 469 470
	}

      r->first = q;
    }
}

/* Reallocate SIZE bytes and return the pointer. The contents will be
   the same as that of PTR.  */
void *
471
grub_realloc (void *ptr, grub_size_t size)
okuji's avatar
okuji committed
472
{
473 474
  grub_mm_header_t p;
  grub_mm_region_t r;
okuji's avatar
okuji committed
475
  void *q;
476
  grub_size_t n;
477

okuji's avatar
okuji committed
478
  if (! ptr)
479
    return grub_malloc (size);
okuji's avatar
okuji committed
480 481 482

  if (! size)
    {
483
      grub_free (ptr);
okuji's avatar
okuji committed
484 485 486 487
      return 0;
    }

  /* FIXME: Not optimal.  */
488
  n = ((size + GRUB_MM_ALIGN - 1) >> GRUB_MM_ALIGN_LOG2) + 1;
okuji's avatar
okuji committed
489
  get_header_from_pointer (ptr, &p, &r);
490

okuji's avatar
okuji committed
491
  if (p->size >= n)
492
    return ptr;
493

494
  q = grub_malloc (size);
okuji's avatar
okuji committed
495 496
  if (! q)
    return q;
497

498 499
  /* We've already checked that p->size < n.  */
  grub_memcpy (q, ptr, p->size << GRUB_MM_ALIGN_LOG2);
500
  grub_free (ptr);
okuji's avatar
okuji committed
501 502 503
  return q;
}

504
#ifdef MM_DEBUG
505 506 507 508 509 510 511
int grub_mm_debug = 0;

void
grub_mm_dump_free (void)
{
  grub_mm_region_t r;

512
  for (r = grub_mm_base; r; r = r->next)
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
    {
      grub_mm_header_t p;

      /* Follow the free list.  */
      p = r->first;
      do
	{
	  if (p->magic != GRUB_MM_FREE_MAGIC)
	    grub_fatal ("free magic is broken at %p: 0x%x", p, p->magic);

	  grub_printf ("F:%p:%u:%p\n",
		       p, (unsigned int) p->size << GRUB_MM_ALIGN_LOG2, p->next);
	  p = p->next;
	}
      while (p != r->first);
    }

  grub_printf ("\n");
}
532

okuji's avatar
okuji committed
533
void
534
grub_mm_dump (unsigned lineno)
okuji's avatar
okuji committed
535
{
536
  grub_mm_region_t r;
okuji's avatar
okuji committed
537

538
  grub_printf ("called at line %u\n", lineno);
539
  for (r = grub_mm_base; r; r = r->next)
okuji's avatar
okuji committed
540
    {
541
      grub_mm_header_t p;
542

543 544 545
      for (p = (grub_mm_header_t) ALIGN_UP ((grub_addr_t) (r + 1),
					    GRUB_MM_ALIGN);
	   (grub_addr_t) p < (grub_addr_t) (r+1) + r->size;
okuji's avatar
okuji committed
546 547 548 549
	   p++)
	{
	  switch (p->magic)
	    {
550 551
	    case GRUB_MM_FREE_MAGIC:
	      grub_printf ("F:%p:%u:%p\n",
552
			   p, (unsigned int) p->size << GRUB_MM_ALIGN_LOG2, p->next);
okuji's avatar
okuji committed
553
	      break;
554
	    case GRUB_MM_ALLOC_MAGIC:
555
	      grub_printf ("A:%p:%u\n", p, (unsigned int) p->size << GRUB_MM_ALIGN_LOG2);
okuji's avatar
okuji committed
556 557 558 559 560
	      break;
	    }
	}
    }

561
  grub_printf ("\n");
okuji's avatar
okuji committed
562
}
563 564 565 566 567 568 569

void *
grub_debug_malloc (const char *file, int line, grub_size_t size)
{
  void *ptr;

  if (grub_mm_debug)
570
    grub_printf ("%s:%d: malloc (0x%" PRIxGRUB_SIZE ") = ", file, line, size);
571 572 573 574 575 576
  ptr = grub_malloc (size);
  if (grub_mm_debug)
    grub_printf ("%p\n", ptr);
  return ptr;
}

577 578 579 580 581 582
void *
grub_debug_zalloc (const char *file, int line, grub_size_t size)
{
  void *ptr;

  if (grub_mm_debug)
583
    grub_printf ("%s:%d: zalloc (0x%" PRIxGRUB_SIZE ") = ", file, line, size);
584 585 586 587 588 589
  ptr = grub_zalloc (size);
  if (grub_mm_debug)
    grub_printf ("%p\n", ptr);
  return ptr;
}

590 591 592 593 594
void
grub_debug_free (const char *file, int line, void *ptr)
{
  if (grub_mm_debug)
    grub_printf ("%s:%d: free (%p)\n", file, line, ptr);
595
  grub_free (ptr);
596 597 598 599 600 601
}

void *
grub_debug_realloc (const char *file, int line, void *ptr, grub_size_t size)
{
  if (grub_mm_debug)
602
    grub_printf ("%s:%d: realloc (%p, 0x%" PRIxGRUB_SIZE ") = ", file, line, ptr, size);
603 604 605 606 607 608 609 610 611 612 613
  ptr = grub_realloc (ptr, size);
  if (grub_mm_debug)
    grub_printf ("%p\n", ptr);
  return ptr;
}

void *
grub_debug_memalign (const char *file, int line, grub_size_t align,
		    grub_size_t size)
{
  void *ptr;
614

615
  if (grub_mm_debug)
616 617
    grub_printf ("%s:%d: memalign (0x%" PRIxGRUB_SIZE  ", 0x%" PRIxGRUB_SIZE  
		 ") = ", file, line, align, size);
618 619 620 621 622 623
  ptr = grub_memalign (align, size);
  if (grub_mm_debug)
    grub_printf ("%p\n", ptr);
  return ptr;
}

okuji's avatar
okuji committed
624
#endif /* MM_DEBUG */