Annotation of prex-old/sys/mem/kmem.c, Revision 1.1
1.1 ! nbrk 1: /*-
! 2: * Copyright (c) 2005-2006, Kohsuke Ohtani
! 3: * All rights reserved.
! 4: *
! 5: * Redistribution and use in source and binary forms, with or without
! 6: * modification, are permitted provided that the following conditions
! 7: * are met:
! 8: * 1. Redistributions of source code must retain the above copyright
! 9: * notice, this list of conditions and the following disclaimer.
! 10: * 2. Redistributions in binary form must reproduce the above copyright
! 11: * notice, this list of conditions and the following disclaimer in the
! 12: * documentation and/or other materials provided with the distribution.
! 13: * 3. Neither the name of the author nor the names of any co-contributors
! 14: * may be used to endorse or promote products derived from this software
! 15: * without specific prior written permission.
! 16: *
! 17: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
! 18: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
! 19: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
! 20: * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
! 21: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
! 22: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
! 23: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
! 24: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
! 25: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
! 26: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
! 27: * SUCH DAMAGE.
! 28: */
! 29:
! 30: /*
! 31: * kmem.c - kernel memory allocator
! 32: */
! 33:
! 34: /*
! 35: * This is a memory allocator optimized for the low foot print kernel.
! 36: * It works on top of the underlying page allocator, and manages more
! 37: * smaller memory than page size. It will divide one page into two or
! 38: * more blocks, and each page is linked as a kernel page.
! 39: *
! 40: * There are following 3 linked lists to manage used/free blocks.
! 41: * 1) All pages allocated for the kernel memory are linked.
! 42: * 2) All blocks divided in the same page are linked.
! 43: * 3) All free blocks of the same size are linked.
! 44: *
! 45: * Currently, it can not handle the memory size exceeding one page.
! 46: * Instead, a driver can use page_alloc() to allocate larger memory.
! 47: *
! 48: * The kmem functions are used by not only the kernel core but also
! 49: * by the buggy drivers. If such kernel code illegally writes data in
! 50: * exceeding the allocated area, the system will crash easily. In order
! 51: * to detect the memory over run, each free block has a magic ID.
! 52: */
! 53:
! 54: #include <kernel.h>
! 55: #include <page.h>
! 56: #include <sched.h>
! 57: #include <vm.h>
! 58:
! 59: /*
! 60: * Block header
! 61: *
! 62: * All free blocks that have same size are linked each other.
! 63: * In addition, all free blocks within same page are also linked.
! 64: */
! 65: struct block_hdr {
! 66: u_short magic; /* magic number */
! 67: u_short size; /* size of this block */
! 68: struct list link; /* link to the free list */
! 69: struct block_hdr *pg_next; /* next block in same page */
! 70: };
! 71:
! 72: /*
! 73: * Page header
! 74: *
! 75: * The page header is placed at the top of each page. This header is
! 76: * used in order to free the page when there are no used block left in
! 77: * the page. If nr_alloc value becomes zero, that page can be removed
! 78: * from kernel page.
! 79: */
! 80: struct page_hdr {
! 81: u_short magic; /* magic number */
! 82: u_short nallocs; /* number of allocated blocks */
! 83: struct block_hdr first_blk; /* first block in this page */
! 84: };
! 85:
! 86: #define ALIGN_SIZE 16
! 87: #define ALIGN_MASK (ALIGN_SIZE - 1)
! 88: #define ALLOC_ALIGN(n) (((n) + ALIGN_MASK) & ~ALIGN_MASK)
! 89:
! 90: #define BLOCK_MAGIC 0xdead
! 91: #define PAGE_MAGIC 0xbeef
! 92:
! 93: #define BLKHDR_SIZE (sizeof(struct block_hdr))
! 94: #define PGHDR_SIZE (sizeof(struct page_hdr))
! 95: #define MAX_ALLOC_SIZE (PAGE_SIZE - PGHDR_SIZE)
! 96:
! 97: #define MIN_BLOCK_SIZE (BLKHDR_SIZE + 16)
! 98: #define MAX_BLOCK_SIZE (u_short)(PAGE_SIZE - (PGHDR_SIZE - BLKHDR_SIZE))
! 99:
! 100: /* macro to point the page header from specific address */
! 101: #define PAGE_TOP(n) (struct page_hdr *) \
! 102: ((u_long)(n) & ~(PAGE_SIZE - 1))
! 103:
! 104: /* index of free block list */
! 105: #define BLKIDX(b) ((int)((b)->size) >> 4)
! 106:
! 107: /* number of free block list */
! 108: #define NR_BLOCK_LIST (PAGE_SIZE / ALIGN_SIZE)
! 109:
! 110: /**
! 111: * Array of the head block of free block list.
! 112: *
! 113: * The index of array is decided by the size of each block.
! 114: * All block has the size of the multiple of 16.
! 115: *
! 116: * ie. free_blocks[0] = list for 16 byte block
! 117: * free_blocks[1] = list for 32 byte block
! 118: * free_blocks[2] = list for 48 byte block
! 119: * .
! 120: * .
! 121: * free_blocks[255] = list for 4096 byte block
! 122: *
! 123: * Generally, only one list is used to search the free block with
! 124: * a first fit algorithm. Basically, this allocator also uses a first
! 125: * fit method. However it uses multiple lists corresponding to each
! 126: * block size.
! 127: * A search is started from the list of the requested size. So, it is
! 128: * not necessary to search smaller block's list wastefully.
! 129: *
! 130: * Most of kernel memory allocator is using 2^n as block size. But,
! 131: * these implementation will throw away much memory that the block
! 132: * size is not fit. This is not suitable for the embedded system with
! 133: * low foot print.
! 134: */
! 135: static struct list free_blocks[NR_BLOCK_LIST];
! 136:
! 137: static int kmem_bytes; /* number of bytes currently allocated */
! 138:
! 139: #ifdef DEBUG
! 140: /*
! 141: * profiling data
! 142: */
! 143: static int nr_pages; /* number of pages currently used */
! 144: static int nr_blocks[NR_BLOCK_LIST]; /* number of blocks currently used */
! 145:
! 146: #endif /* DEBUG */
! 147:
! 148: /*
! 149: * Find the free block for the specified size.
! 150: * Returns pointer to free block, or NULL on failure.
! 151: *
! 152: * First, it searches from the list of same size. If it does not
! 153: * exists, then it will search the list of larger one.
! 154: * It will use the block of smallest size that satisfies the
! 155: * specified size.
! 156: */
! 157: static struct block_hdr *
! 158: block_find(size_t size)
! 159: {
! 160: int i;
! 161: list_t n;
! 162:
! 163: for (i = (int)size >> 4; i < NR_BLOCK_LIST; i++) {
! 164: if (!list_empty(&free_blocks[i]))
! 165: break;
! 166: }
! 167: if (i >= NR_BLOCK_LIST)
! 168: return NULL;
! 169:
! 170: n = list_first(&free_blocks[i]);
! 171: return list_entry(n, struct block_hdr, link);
! 172: }
! 173:
! 174: /*
! 175: * Allocate memory block for kernel
! 176: *
! 177: * This function does not fill the allocated block by 0 for performance.
! 178: * kmem_alloc() returns NULL on failure.
! 179: */
! 180: void *
! 181: kmem_alloc(size_t size)
! 182: {
! 183: struct block_hdr *blk, *new_blk;
! 184: struct page_hdr *pg;
! 185: void *p;
! 186:
! 187: ASSERT(irq_level == 0);
! 188:
! 189: sched_lock(); /* Lock scheduler */
! 190: /*
! 191: * First, the free block of enough size is searched
! 192: * from the page already used. If it does not exist,
! 193: * new page is allocated for free block.
! 194: */
! 195: size = ALLOC_ALIGN(size + BLKHDR_SIZE);
! 196:
! 197: ASSERT(size && size <= MAX_ALLOC_SIZE);
! 198:
! 199: blk = block_find(size);
! 200: if (blk) {
! 201: /* Block found */
! 202: list_remove(&blk->link); /* Remove from free list */
! 203: pg = PAGE_TOP(blk); /* Get the page address */
! 204: } else {
! 205: /* No block found. Allocate new page */
! 206: if ((pg = page_alloc(PAGE_SIZE)) == NULL) {
! 207: sched_unlock();
! 208: return NULL;
! 209: }
! 210: pg = phys_to_virt(pg);
! 211: pg->nallocs = 0;
! 212: pg->magic = PAGE_MAGIC;
! 213:
! 214: /* Setup first block */
! 215: blk = &(pg->first_blk);
! 216: blk->magic = BLOCK_MAGIC;
! 217: blk->size = MAX_BLOCK_SIZE;
! 218: blk->pg_next = NULL;
! 219: #ifdef DEBUG
! 220: nr_pages++;
! 221: #endif
! 222: }
! 223: /* Sanity check */
! 224: if (pg->magic != PAGE_MAGIC || blk->magic != BLOCK_MAGIC)
! 225: panic("kmem overrun: addr=%x", blk);
! 226: /*
! 227: * If the found block is large enough, split it.
! 228: */
! 229: if (blk->size - size >= MIN_BLOCK_SIZE) {
! 230: /* Make new block */
! 231: new_blk = (struct block_hdr *)((u_long)blk + size);
! 232: new_blk->magic = BLOCK_MAGIC;
! 233: new_blk->size = (u_short)(blk->size - size);
! 234: list_insert(&free_blocks[BLKIDX(new_blk)], &new_blk->link);
! 235:
! 236: /* Update page list */
! 237: new_blk->pg_next = blk->pg_next;
! 238: blk->pg_next = new_blk;
! 239:
! 240: blk->size = (u_short)size;
! 241: }
! 242: /* Increment allocation count of this page */
! 243: pg->nallocs++;
! 244: kmem_bytes += blk->size;
! 245: #ifdef DEBUG
! 246: nr_blocks[BLKIDX(blk)]++;
! 247: #endif
! 248: p = (void *)((u_long)blk + BLKHDR_SIZE);
! 249: sched_unlock();
! 250: return p;
! 251: }
! 252:
! 253: /*
! 254: * Free allocated memory block.
! 255: *
! 256: * Some kernel does not release the free page for the kernel memory
! 257: * because it is needed to allocate immediately later. For example,
! 258: * it is efficient here if the free page is just linked to the list
! 259: * of the biggest size. However, consider the case where a driver
! 260: * requires many small memories temporarily. After these pages are
! 261: * freed, they can not be reused for an application.
! 262: */
! 263: void
! 264: kmem_free(void *ptr)
! 265: {
! 266: struct block_hdr *blk;
! 267: struct page_hdr *pg;
! 268:
! 269: ASSERT(irq_level == 0);
! 270: ASSERT(ptr);
! 271:
! 272: /* Lock scheduler */
! 273: sched_lock();
! 274:
! 275: /* Get the block header */
! 276: blk = (struct block_hdr *)((u_long)ptr - BLKHDR_SIZE);
! 277: if (blk->magic != BLOCK_MAGIC)
! 278: panic("kmem_free");
! 279:
! 280: kmem_bytes -= blk->size;
! 281:
! 282: #ifdef DEBUG
! 283: nr_blocks[BLKIDX(blk)]--;
! 284: #endif
! 285: /*
! 286: * Return the block to free list.
! 287: * Since kernel code will request fixed size of memory block,
! 288: * we don't merge the blocks to use it as cache.
! 289: */
! 290: list_insert(&free_blocks[BLKIDX(blk)], &blk->link);
! 291:
! 292: /* Decrement allocation count of this page */
! 293: pg = PAGE_TOP(blk);
! 294: if (--pg->nallocs <= 0) {
! 295: /*
! 296: * No allocated block in this page.
! 297: * Remove all blocks and deallocate this page.
! 298: */
! 299: for (blk = &(pg->first_blk); blk != NULL; blk = blk->pg_next) {
! 300: list_remove(&blk->link); /* Remove from free list */
! 301: #ifdef DEBUG
! 302: nr_blocks[BLKIDX(blk)]--;
! 303: #endif
! 304: }
! 305: pg->magic = 0;
! 306: page_free(virt_to_phys(pg), PAGE_SIZE);
! 307: #ifdef DEBUG
! 308: nr_pages--;
! 309: #endif
! 310: }
! 311: sched_unlock();
! 312: }
! 313:
! 314: /*
! 315: * Map specified virtual address to the kernel address
! 316: * Returns kernel address on success, or NULL if no mapped memory.
! 317: */
! 318: void *
! 319: kmem_map(void *addr, size_t size)
! 320: {
! 321: void *phys;
! 322:
! 323: phys = vm_translate(addr, size);
! 324: if (phys == NULL)
! 325: return NULL;
! 326: return phys_to_virt(phys);
! 327: }
! 328:
! 329: void
! 330: kmem_info(size_t *size)
! 331: {
! 332:
! 333: *size = (size_t)kmem_bytes;
! 334: }
! 335:
! 336: #if defined(DEBUG) && defined(CONFIG_KDUMP)
! 337: void
! 338: kmem_dump(void)
! 339: {
! 340: list_t head, n;
! 341: int i, cnt;
! 342: struct block_hdr *blk;
! 343:
! 344: printk("\nKernel memory dump:\n");
! 345:
! 346: printk(" allocated blocks:\n");
! 347: printk(" block size count\n");
! 348: printk(" ---------- --------\n");
! 349:
! 350: for (i = 0; i < NR_BLOCK_LIST; i++) {
! 351: if (nr_blocks[i])
! 352: printk(" %4d %8d\n", i << 4, nr_blocks[i]);
! 353: }
! 354: printk("\n free blocks:\n");
! 355: printk(" block size count\n");
! 356: printk(" ---------- --------\n");
! 357:
! 358: for (i = 0; i < NR_BLOCK_LIST; i++) {
! 359: cnt = 0;
! 360: head = &free_blocks[i];
! 361: for (n = list_first(head); n != head; n = list_next(n)) {
! 362: cnt++;
! 363:
! 364: blk = list_entry(n, struct block_hdr, link);
! 365: }
! 366: if (cnt > 0)
! 367: printk(" %4d %8d\n", i << 4, cnt);
! 368: }
! 369: printk(" Total: page=%d (%dKbyte) alloc=%dbyte unused=%dbyte\n",
! 370: nr_pages, nr_pages * 4, kmem_bytes,
! 371: nr_pages * PAGE_SIZE - kmem_bytes);
! 372: }
! 373: #endif
! 374:
! 375: void
! 376: kmem_init(void)
! 377: {
! 378: int i;
! 379:
! 380: for (i = 0; i < NR_BLOCK_LIST; i++)
! 381: list_init(&free_blocks[i]);
! 382: }
CVSweb