version 1.1.1.1, 2008/06/03 10:38:46 |
version 1.1.1.1.2.1, 2008/08/13 17:12:32 |
|
|
*/ |
*/ |
|
|
/* |
/* |
* This is a memory allocator optimized for the low foot print kernel. |
* This is a memory allocator optimized for the low foot print |
* It works on top of the underlying page allocator, and manages more |
* kernel. It works on top of the underlying page allocator, and |
* smaller memory than page size. It will divide one page into two or |
* manages more smaller memory than page size. It will divide one |
* more blocks, and each page is linked as a kernel page. |
* page into two or more blocks, and each page is linked as a |
|
* kernel page. |
* |
* |
* There are following 3 linked lists to manage used/free blocks. |
* There are following 3 linked lists to manage used/free blocks. |
* 1) All pages allocated for the kernel memory are linked. |
* 1) All pages allocated for the kernel memory are linked. |
|
|
* Currently, it can not handle the memory size exceeding one page. |
* Currently, it can not handle the memory size exceeding one page. |
* Instead, a driver can use page_alloc() to allocate larger memory. |
* Instead, a driver can use page_alloc() to allocate larger memory. |
* |
* |
* The kmem functions are used by not only the kernel core but also |
* The kmem functions are used by not only the kernel core but |
* by the buggy drivers. If such kernel code illegally writes data in |
* also by the buggy drivers. If such kernel code illegally |
* exceeding the allocated area, the system will crash easily. In order |
* writes data in exceeding the allocated area, the system will |
* to detect the memory over run, each free block has a magic ID. |
* crash easily. In order to detect the memory over run, each |
|
* free block has a magic ID. |
*/ |
*/ |
|
|
#include <kernel.h> |
#include <kernel.h> |
#include <page.h> |
#include <page.h> |
#include <sched.h> |
#include <sched.h> |
#include <vm.h> |
#include <vm.h> |
|
#include <kmem.h> |
|
|
/* |
/* |
* Block header |
* Block header |
|
|
/* |
/* |
* Page header |
* Page header |
* |
* |
* The page header is placed at the top of each page. This header is |
* The page header is placed at the top of each page. This |
* used in order to free the page when there are no used block left in |
* header is used in order to free the page when there are no |
* the page. If nr_alloc value becomes zero, that page can be removed |
* used block left in the page. If nr_alloc value becomes zero, |
* from kernel page. |
* that page can be removed from kernel page. |
*/ |
*/ |
struct page_hdr { |
struct page_hdr { |
u_short magic; /* magic number */ |
u_short magic; /* magic number */ |
|
|
|
|
#define ALIGN_SIZE 16 |
#define ALIGN_SIZE 16 |
#define ALIGN_MASK (ALIGN_SIZE - 1) |
#define ALIGN_MASK (ALIGN_SIZE - 1) |
#define ALLOC_ALIGN(n) (((n) + ALIGN_MASK) & ~ALIGN_MASK) |
#define ALLOC_ALIGN(n) (((vaddr_t)(n) + ALIGN_MASK) & (vaddr_t)~ALIGN_MASK) |
|
|
#define BLOCK_MAGIC 0xdead |
#define BLOCK_MAGIC 0xdead |
#define PAGE_MAGIC 0xbeef |
#define PAGE_MAGIC 0xbeef |
|
|
#define BLKHDR_SIZE (sizeof(struct block_hdr)) |
#define BLKHDR_SIZE (sizeof(struct block_hdr)) |
#define PGHDR_SIZE (sizeof(struct page_hdr)) |
#define PGHDR_SIZE (sizeof(struct page_hdr)) |
#define MAX_ALLOC_SIZE (PAGE_SIZE - PGHDR_SIZE) |
#define MAX_ALLOC_SIZE (size_t)(PAGE_SIZE - PGHDR_SIZE) |
|
|
#define MIN_BLOCK_SIZE (BLKHDR_SIZE + 16) |
#define MIN_BLOCK_SIZE (BLKHDR_SIZE + 16) |
#define MAX_BLOCK_SIZE (u_short)(PAGE_SIZE - (PGHDR_SIZE - BLKHDR_SIZE)) |
#define MAX_BLOCK_SIZE (u_short)(PAGE_SIZE - (PGHDR_SIZE - BLKHDR_SIZE)) |
|
|
/* macro to point the page header from specific address */ |
/* macro to point the page header from specific address */ |
#define PAGE_TOP(n) (struct page_hdr *) \ |
#define PAGE_TOP(n) (struct page_hdr *) \ |
((u_long)(n) & ~(PAGE_SIZE - 1)) |
((vaddr_t)(n) & (vaddr_t)~(PAGE_SIZE - 1)) |
|
|
/* index of free block list */ |
/* index of free block list */ |
#define BLKIDX(b) ((int)((b)->size) >> 4) |
#define BLKIDX(b) ((u_int)((b)->size) >> 4) |
|
|
/* number of free block list */ |
/* number of free block list */ |
#define NR_BLOCK_LIST (PAGE_SIZE / ALIGN_SIZE) |
#define NR_BLOCK_LIST (PAGE_SIZE / ALIGN_SIZE) |
|
|
* free_blocks[255] = list for 4096 byte block |
* free_blocks[255] = list for 4096 byte block |
* |
* |
* Generally, only one list is used to search the free block with |
* Generally, only one list is used to search the free block with |
* a first fit algorithm. Basically, this allocator also uses a first |
* a first fit algorithm. Basically, this allocator also uses a |
* fit method. However it uses multiple lists corresponding to each |
* first fit method. However it uses multiple lists corresponding |
* block size. |
* to each block size. A search is started from the list of the |
* A search is started from the list of the requested size. So, it is |
* requested size. So, it is not necessary to search smaller |
* not necessary to search smaller block's list wastefully. |
* block's list wastefully. |
* |
* |
* Most of kernel memory allocator is using 2^n as block size. But, |
* Most of kernel memory allocator is using 2^n as block size. |
* these implementation will throw away much memory that the block |
* But, these implementation will throw away much memory that |
* size is not fit. This is not suitable for the embedded system with |
* the block size is not fit. This is not suitable for the |
* low foot print. |
* embedded system with low foot print. |
*/ |
*/ |
static struct list free_blocks[NR_BLOCK_LIST]; |
static struct list free_blocks[NR_BLOCK_LIST]; |
|
|
static int kmem_bytes; /* number of bytes currently allocated */ |
|
|
|
#ifdef DEBUG |
|
/* |
/* |
* profiling data |
|
*/ |
|
static int nr_pages; /* number of pages currently used */ |
|
static int nr_blocks[NR_BLOCK_LIST]; /* number of blocks currently used */ |
|
|
|
#endif /* DEBUG */ |
|
|
|
/* |
|
* Find the free block for the specified size. |
* Find the free block for the specified size. |
* Returns pointer to free block, or NULL on failure. |
* Returns pointer to free block, or NULL on failure. |
* |
* |
|
|
int i; |
int i; |
list_t n; |
list_t n; |
|
|
for (i = (int)size >> 4; i < NR_BLOCK_LIST; i++) { |
for (i = (int)((u_int)size >> 4); i < NR_BLOCK_LIST; i++) { |
if (!list_empty(&free_blocks[i])) |
if (!list_empty(&free_blocks[i])) |
break; |
break; |
} |
} |
|
|
void * |
void * |
kmem_alloc(size_t size) |
kmem_alloc(size_t size) |
{ |
{ |
struct block_hdr *blk, *new_blk; |
struct block_hdr *blk, *newblk; |
struct page_hdr *pg; |
struct page_hdr *pg; |
void *p; |
void *p; |
|
|
|
|
blk->magic = BLOCK_MAGIC; |
blk->magic = BLOCK_MAGIC; |
blk->size = MAX_BLOCK_SIZE; |
blk->size = MAX_BLOCK_SIZE; |
blk->pg_next = NULL; |
blk->pg_next = NULL; |
#ifdef DEBUG |
|
nr_pages++; |
|
#endif |
|
} |
} |
/* Sanity check */ |
/* Sanity check */ |
if (pg->magic != PAGE_MAGIC || blk->magic != BLOCK_MAGIC) |
if (pg->magic != PAGE_MAGIC || blk->magic != BLOCK_MAGIC) |
panic("kmem overrun: addr=%x", blk); |
panic("kmem_alloc: overrun"); |
/* |
/* |
* If the found block is large enough, split it. |
* If the found block is large enough, split it. |
*/ |
*/ |
if (blk->size - size >= MIN_BLOCK_SIZE) { |
if (blk->size - size >= MIN_BLOCK_SIZE) { |
/* Make new block */ |
/* Make new block */ |
new_blk = (struct block_hdr *)((u_long)blk + size); |
newblk = (struct block_hdr *)((char *)blk + size); |
new_blk->magic = BLOCK_MAGIC; |
newblk->magic = BLOCK_MAGIC; |
new_blk->size = (u_short)(blk->size - size); |
newblk->size = (u_short)(blk->size - size); |
list_insert(&free_blocks[BLKIDX(new_blk)], &new_blk->link); |
list_insert(&free_blocks[BLKIDX(newblk)], &newblk->link); |
|
|
/* Update page list */ |
/* Update page list */ |
new_blk->pg_next = blk->pg_next; |
newblk->pg_next = blk->pg_next; |
blk->pg_next = new_blk; |
blk->pg_next = newblk; |
|
|
blk->size = (u_short)size; |
blk->size = (u_short)size; |
} |
} |
/* Increment allocation count of this page */ |
/* Increment allocation count of this page */ |
pg->nallocs++; |
pg->nallocs++; |
kmem_bytes += blk->size; |
p = (char *)blk + BLKHDR_SIZE; |
#ifdef DEBUG |
|
nr_blocks[BLKIDX(blk)]++; |
|
#endif |
|
p = (void *)((u_long)blk + BLKHDR_SIZE); |
|
sched_unlock(); |
sched_unlock(); |
return p; |
return p; |
} |
} |
|
|
sched_lock(); |
sched_lock(); |
|
|
/* Get the block header */ |
/* Get the block header */ |
blk = (struct block_hdr *)((u_long)ptr - BLKHDR_SIZE); |
blk = (struct block_hdr *)((char *)ptr - BLKHDR_SIZE); |
if (blk->magic != BLOCK_MAGIC) |
if (blk->magic != BLOCK_MAGIC) |
panic("kmem_free"); |
panic("kmem_free: invalid address"); |
|
|
kmem_bytes -= blk->size; |
|
|
|
#ifdef DEBUG |
|
nr_blocks[BLKIDX(blk)]--; |
|
#endif |
|
/* |
/* |
* Return the block to free list. |
* Return the block to free list. Since kernel code will |
* Since kernel code will request fixed size of memory block, |
* request fixed size of memory block, we don't merge the |
* we don't merge the blocks to use it as cache. |
* blocks to use it as cache. |
*/ |
*/ |
list_insert(&free_blocks[BLKIDX(blk)], &blk->link); |
list_insert(&free_blocks[BLKIDX(blk)], &blk->link); |
|
|
|
|
*/ |
*/ |
for (blk = &(pg->first_blk); blk != NULL; blk = blk->pg_next) { |
for (blk = &(pg->first_blk); blk != NULL; blk = blk->pg_next) { |
list_remove(&blk->link); /* Remove from free list */ |
list_remove(&blk->link); /* Remove from free list */ |
#ifdef DEBUG |
|
nr_blocks[BLKIDX(blk)]--; |
|
#endif |
|
} |
} |
pg->magic = 0; |
pg->magic = 0; |
page_free(virt_to_phys(pg), PAGE_SIZE); |
page_free(virt_to_phys(pg), PAGE_SIZE); |
#ifdef DEBUG |
|
nr_pages--; |
|
#endif |
|
} |
} |
sched_unlock(); |
sched_unlock(); |
} |
} |
|
|
return NULL; |
return NULL; |
return phys_to_virt(phys); |
return phys_to_virt(phys); |
} |
} |
|
|
void |
|
kmem_info(size_t *size) |
|
{ |
|
|
|
*size = (size_t)kmem_bytes; |
|
} |
|
|
|
#if defined(DEBUG) && defined(CONFIG_KDUMP) |
|
void |
|
kmem_dump(void) |
|
{ |
|
list_t head, n; |
|
int i, cnt; |
|
struct block_hdr *blk; |
|
|
|
printk("\nKernel memory dump:\n"); |
|
|
|
printk(" allocated blocks:\n"); |
|
printk(" block size count\n"); |
|
printk(" ---------- --------\n"); |
|
|
|
for (i = 0; i < NR_BLOCK_LIST; i++) { |
|
if (nr_blocks[i]) |
|
printk(" %4d %8d\n", i << 4, nr_blocks[i]); |
|
} |
|
printk("\n free blocks:\n"); |
|
printk(" block size count\n"); |
|
printk(" ---------- --------\n"); |
|
|
|
for (i = 0; i < NR_BLOCK_LIST; i++) { |
|
cnt = 0; |
|
head = &free_blocks[i]; |
|
for (n = list_first(head); n != head; n = list_next(n)) { |
|
cnt++; |
|
|
|
blk = list_entry(n, struct block_hdr, link); |
|
} |
|
if (cnt > 0) |
|
printk(" %4d %8d\n", i << 4, cnt); |
|
} |
|
printk(" Total: page=%d (%dKbyte) alloc=%dbyte unused=%dbyte\n", |
|
nr_pages, nr_pages * 4, kmem_bytes, |
|
nr_pages * PAGE_SIZE - kmem_bytes); |
|
} |
|
#endif |
|
|
|
void |
void |
kmem_init(void) |
kmem_init(void) |