diff --git a/CMakeLists.txt b/CMakeLists.txt index 7289be8..d480bb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ include(Fxconv) option(GINT_USER_VRAM "Put all VRAMs into the user stack (fx-CG 50 only)") option(GINT_STATIC_GRAY "Use static memory instead of malloc for gray buffers (fx-9860G only)") +option(GINT_KMALLOC_DEBUG "Enable debug functions for kmalloc") # Generate with commit hash, version name and options git_version_number(SHORT_HASH "GINT_GIT_HASH" TAG_RELATIVE "GINT_GIT_VERSION") @@ -31,6 +32,7 @@ set(SOURCES_COMMON src/keysc/keycodes.c src/keysc/keydev.c src/keysc/keysc.c + src/kmalloc/arena_gint.c src/kmalloc/arena_osheap.c src/kmalloc/kmalloc.c src/kprint/kprint.c diff --git a/giteapc.make b/giteapc.make index 689b5d0..674e9e2 100644 --- a/giteapc.make +++ b/giteapc.make @@ -3,8 +3,8 @@ -include giteapc-config.make configure: - @ fxsdk build-fx -c - @ fxsdk build-cg -c + @ fxsdk build-fx -c $(GINT_CMAKE_OPTIONS) + @ fxsdk build-cg -c $(GINT_CMAKE_OPTIONS) build: @ fxsdk build-fx diff --git a/include/gint/config.h.in b/include/gint/config.h.in index 34322da..800749e 100644 --- a/include/gint/config.h.in +++ b/include/gint/config.h.in @@ -24,4 +24,10 @@ statically or in the system heap (fx-9860G) */ #cmakedefine GINT_STATIC_GRAY +/* GINT_KMALLOC_DEBUG: Selects whether kmalloc debug functions are enabled + (these are mainly data structure integrity checks and information that make + sense for a developer). This is independent from statistics, which can be + enabled or disabled at runtime. */ +#cmakedefine GINT_KMALLOC_DEBUG + #endif /* GINT_CONFIG */ diff --git a/include/gint/kmalloc.h b/include/gint/kmalloc.h index 1fa4f98..c58723a 100644 --- a/include/gint/kmalloc.h +++ b/include/gint/kmalloc.h @@ -5,6 +5,7 @@ #ifndef GINT_KMALLOC #define GINT_KMALLOC +#include #include //--- @@ -72,12 +73,14 @@ typedef struct { } kmalloc_arena_t; -//--- -// Internal API -//--- +/* kmalloc_init_arena(): Initialize an arena with gint's allocator -/* kmalloc_init(): Initialize the dynamic allocator */ -void kmalloc_init(void); + This function initializes an arena on the region located between (a->start) + and (a->end) and initializes the data structures for gint's allocator. It + only sets the malloc(), realloc(), free() and (data) attributes of the + structure, everything else should be initialized manually. The arena must + have at least 256 bytes. */ +void kmalloc_init_arena(kmalloc_arena_t *a, bool enable_statistics); /* kmalloc_add_arena(): Add a new arena to the heap source Adds a fully-initialized arena to the heap source. The priority of the new @@ -85,4 +88,80 @@ void kmalloc_init(void); success, false if the maximum number of arenas has been reached. */ bool kmalloc_add_arena(kmalloc_arena_t *arena); +//--- +// Internal functions +//--- + +/* kmalloc_init(): Initialize the dynamic allocator */ +void kmalloc_init(void); + +/* kmalloc_get_arena(): Find an arena by name */ +kmalloc_arena_t *kmalloc_get_arena(char const *name); + +//--- +// Introspection functions for arenas managed by gint's allocator +//--- + +/* kmalloc_gint_stats_t: Optional statistics for gint's allocator + + These statistics are tracked by gint's allocator if requested when the arena + is created. They can't be added or removed after creation. + + The free memory and used memory are counted in data bytes. Because the + allocator uses some data, the initial value of (free_memory) is less than + the size of the region. Additionally, because each used or free block has a + 4-byte header, (free_memory + used_memory) is not a constant value, so don't + expect to get exactly what these values indicate (also fragmentation). */ +typedef struct { + /* Free space, used space, peak used space over time */ + uint32_t free_memory; + uint32_t used_memory; + uint32_t peak_used_memory; + /* Number of mallocs that failed because of not enough free space */ + int exhaustion_failures; + /* Number of mallocs that failed because of fragmentation */ + int fragmentation_failures; + /* Number of reallocs that successfully re-used the same location */ + int expanding_reallocs; + /* Number of reallocs that moved the data around */ + int relocating_reallocs; +} kmalloc_gint_stats_t; + +/* kmalloc_get_gint_stats(): Get gint statistics for an arena + + Returns a pointer to the arena's statistics; returns NULL if the arena is + not managed by gint's allocator or statistics were not enabled. */ +kmalloc_gint_stats_t *kmalloc_get_gint_stats(kmalloc_arena_t *arena); + +#ifdef GINT_KMALLOC_DEBUG + +/* Check that the sequence covers exactly the arena's region. */ +bool kmallocdbg_sequence_covers(kmalloc_arena_t *a); + +/* Check that the marker for the last block is correctly set and unique. */ +bool kmallocdbg_sequence_terminator(kmalloc_arena_t *a); + +/* Check that the values of (used) and (previous_used) are coherent. */ +bool kmallocdbg_sequence_coherent_used(kmalloc_arena_t *a); + +/* Check that the size in the footer is correct for all free blocks. */ +bool kmallocdbg_sequence_footer_size(kmalloc_arena_t *a); + +/* Check that all free blocks are surrounded by used blocks. */ +bool kmallocdbg_sequence_merged_free(kmalloc_arena_t *a); + +/* Check that the doubly-linked list is well-formed. */ +bool kmallocdbg_list_structure(kmalloc_arena_t *a); + +/* Check that used blocks and list cover exactly the arena's region. */ +bool kmallocdbg_index_covers(kmalloc_arena_t *a); + +/* Check that all blocs referenced in free lists are of the correct class. */ +bool kmallocdbg_index_class_separation(kmalloc_arena_t *a); + +/* Number of blocks in the sequence. */ +int kmallocdbg_sequence_length(kmalloc_arena_t *a); + +#endif /* GINT_KMALLOC_DEBUG */ + #endif /* GINT_KMALLOC */ diff --git a/src/kernel/kernel.c b/src/kernel/kernel.c index 72dce80..33b86de 100644 --- a/src/kernel/kernel.c +++ b/src/kernel/kernel.c @@ -128,7 +128,7 @@ static void kinit_cpu(void) void kinit(void) { #ifdef FX9860G - /* VBR is loaded at the end of the user RAM. */ + /* On fx-9860G, VBR is loaded at the end of the user RAM. */ uint32_t uram_end = (uint32_t)mmu_uram() + mmu_uram_size(); /* On SH4, stack is at the end of the region, leave 8k */ if(isSH4()) uram_end -= 0x2000; @@ -139,11 +139,13 @@ void kinit(void) /* There are 0x100 unused bytes at the start of the VBR area */ gint_ctx.VBR = uram_end - 0x100; - #endif + #endif /* FX9860G */ #ifdef FXCG50 - /* VBR is loaded at the start of the user RAM */ + /* On fx-CG 50, VBR is loaded at the start of the user RAM. */ gint_ctx.VBR = (uint32_t)mmu_uram(); + /* All of the user RAM can be used, except for some 16k of stack */ + uint32_t uram_end = (uint32_t)mmu_uram() + mmu_uram_size() - 0x4000; #endif /* Event handler entry points */ @@ -163,6 +165,17 @@ void kinit(void) /* Initialize memory allocators */ kmalloc_init(); + + /* Create an allocation arena with unused static RAM */ + static kmalloc_arena_t static_ram = { 0 }; + extern uint32_t euram; + static_ram.name = "_uram"; + static_ram.is_default = isSH4(); + static_ram.start = mmu_uram() + ((uint32_t)&euram - 0x08100000); + static_ram.end = (void *)uram_end; + + kmalloc_init_arena(&static_ram, true); + kmalloc_add_arena(&static_ram); } /* Due to dire space restrictions on SH3, event codes that are translated to diff --git a/src/kmalloc/arena_gint.c b/src/kmalloc/arena_gint.c new file mode 100644 index 0000000..e8aa228 --- /dev/null +++ b/src/kmalloc/arena_gint.c @@ -0,0 +1,561 @@ +//--- +// gint:kmalloc:arena_gint - An arena that uses gint's custom allocator +//--- + +#include +#include +#include +#include + +/* block_t: A memory block managed by the heap. + + The heap is a sequence of blocks made of a block_t header (4 bytes) and raw + data (any size between 8 bytes and the size of the heap). The sequence + extends from the start of the arena region (past the index structure) up to + the end of the arena region. + + Free blocks use the unused raw data to store a footer of either 8 or 12 + bytes, which links to the start of the block, the previous free block, and + the next free block. This forms a doubly-linked list of free blocks (or, to + be more precise, several intertwined doubly-linked lists, each handling a + different class of block sizes). + + The possible ways to traverse the structure are: + 1. Traverse the sequence from left to right -> next_block() + 2. Go back to the previous block, if it's free -> previous_block_if_free() + 3. Traverse each linked list from left to right -> next_link() + 4. Traverse each linked list from right to left -> previous_link() + + Way #2 is implemented using the boundary tag optimization. Basically each + block has a bit (the boundary tag) that tells whether the previous block is + free. If it's free, then that block's footer can be accessed, and because + the footer contains the size the header can be accessed too. This is used to + detect whether to merge into the previous block after a free(). + + The allocation algorithm will mostly use way #3 to find free blocks. When + freeing, ways #1 and #2 are used to coalese adjacent blocks. Ways #3 and #4 + are used to maintain the linked lists. + + The footer uses 8 bytes if the block has 8 bytes of raw data, and 12 bytes + otherwise. The LSB of the last byte is used to distinguish the cases: + * For a block of 8 bytes, the footer has one block_t pointer to the previous + link, then one block_t pointer to the next link with LSB=1 + * For a larger block, the footer has a 4-byte block size, then a pointer to + the previous link, and a pointer to the next link with LSB=0. */ +typedef struct { + uint :5; + /* Marks the last block of the sequence */ + uint last: 1; + /* Whether the block is used; in general this can be kept implicit, but + it has to be specified for the last block */ + uint used: 1; + /* Boundary tag, vital to implement way #2 to merge adjacent blocks */ + uint previous_used: 1; + /* Block size in bytes. */ + uint size: 24; +} block_t; + +typedef kmalloc_gint_stats_t stats_t; + +/* index_t: Data structure at the root of the heap, indexing linked lists */ +typedef struct { + /* Entry points of the free lists for each block size */ + block_t *classes[16]; + /* Pointer to statistics, if used */ + stats_t *stats; +} index_t; + +//--- +// Block-level operations +//--- + +/* Returns a pointer to the next block in the sequence (might be used) */ +static block_t *next_block(block_t *b) +{ + if(b->last) return NULL; + return (void *)b + sizeof(block_t) + b->size; +} + +/* Returns a pointer to the previous block's header, if it's a free block */ +static block_t *previous_block_if_free(block_t *b) +{ + if(b->previous_used) return NULL; + /* The footer of the previous block indicates its size */ + uint32_t *footer = (void *)b; + uint32_t previous_size = (footer[-1] & 1) ? 8 : footer[-3]; + return (void *)b - previous_size - sizeof(block_t); +} + +/* Splits a used or free-floating block into a first block with (size) bytes + and a free second block with the rest. Returns the address of the second + block. If the initial block is too small to split, returns NULL. */ +static block_t *split(block_t *b, int size) +{ + size_t extra_size = b->size - size; + if(extra_size < sizeof(block_t) + 8) return NULL; + + block_t *second = (void *)b + sizeof(block_t) + size; + second->last = b->last; + second->used = false; + second->previous_used = b->used; + second->size = extra_size - sizeof(block_t); + + block_t *third = next_block(second); + if(third) third->previous_used = second->used; + + b->last = 0; + b->size = size; + + return second; +} + +/* Merge a used or free-floating block with its free neighbor. There are two + parameters for clarity, but really (right == next_block(left)). */ +static void merge(block_t *left, block_t *right) +{ + size_t extra_size = sizeof(block_t) + right->size; + left->last = right->last; + left->size += extra_size; + + block_t *next = next_block(left); + if(next) next->previous_used = left->used; +} + +//--- +// List-level operations +//--- + +/* Returns the next free block in the list, assumes (b) is free */ +static block_t *next_link(block_t *b) +{ + uint32_t *footer = (void *)b + sizeof(block_t) + b->size; + return (block_t *)(footer[-1] & ~3); +} + +/* Returns the previous free block in the list, assumes (b) is free */ +static block_t *previous_link(block_t *b) +{ + uint32_t *footer = (void *)b + sizeof(block_t) + b->size; + return (block_t *)footer[-2]; +} + +/* Writes the given free block links to the footer of free block (b) */ +static void set_footer(block_t *b, block_t *previous_link, block_t *next_link) +{ + uint32_t *footer = (void *)b + sizeof(block_t) + b->size; + /* 8-byte block: store the next link with LSB=1 */ + if(b->size == 8) + { + footer[-2] = (uint32_t)previous_link; + footer[-1] = (uint32_t)next_link | 1; + } + /* Larger block: store the size first then the link */ + else + { + footer[-3] = b->size; + footer[-2] = (uint32_t)previous_link; + footer[-1] = (uint32_t)next_link; + } +} + +/* Find a best fit for the requested size in the list */ +static block_t *best_fit(block_t *list, size_t size) +{ + block_t *best_match = NULL; + size_t best_size = 0xffffffff; + + while(list && best_size != size) + { + if(list->size >= size && list->size < best_size) + { + best_match = list; + best_size = list->size; + } + list = next_link(list); + } + + return best_match; +} + +//--- +// Index-level operations +//--- + +/* Returns the size class of the given size */ +GINLINE static inline int size_class(size_t size) +{ + if(size < 64) return (size - 8) >> 2; + if(size < 256) return 14; + return 15; +} + +/* Removes a block from a list, updating the index if needed. The free block is + in a temporary state of being in no list, called "free-floating" */ +static void remove_link(block_t *b, index_t *index) +{ + int c = size_class(b->size); + + block_t *prev = previous_link(b); + block_t *next = next_link(b); + + /* Redirect links around (b) in its list */ + if(prev) set_footer(prev, previous_link(prev), next); + if(next) set_footer(next, prev, next_link(next)); + + if(index->classes[c] == b) index->classes[c] = next; + + if(index->stats) index->stats->free_memory -= b->size; +} + +/* Prepends a block to the list for its size class, and update the index */ +static void prepend_link(block_t *b, index_t *index) +{ + int c = size_class(b->size); + + block_t *first = index->classes[c]; + set_footer(b, NULL, first); + if(first) set_footer(first, b, next_link(first)); + + index->classes[c] = b; + + if(index->stats) index->stats->free_memory += b->size; +} + +//--- +// Arena allocator +//--- + +/* Round a size to the closest allocatable size */ +static size_t round(size_t size) +{ + return (size < 8) ? 8 : ((size + 3) & ~3); +} + +static void *gint_malloc(size_t size, void *data) +{ + index_t *index = data; + stats_t *s = index->stats; + size = round(size); + int c = size_class(size); + + /* Try to find a class that has a free block available */ + block_t *alloc; + for(; c <= 15; c++) + { + block_t *list = index->classes[c]; + /* The first 14 classes are exact-size, so there is no need to + search. For the last two, we use a best fit. */ + alloc = (c < 14) ? list : best_fit(list, size); + if(alloc) break; + } + if(!alloc) + { + if(s && s->free_memory >= size) s->fragmentation_failures++; + if(s && s->free_memory < size) s->exhaustion_failures++; + return NULL; + } + + /* Remove the block to allocate from its list */ + remove_link(alloc, index); + + /* If it's larger than needed, split it and reinsert the leftover */ + block_t *rest = split(alloc, size); + if(rest) prepend_link(rest, index); + + /* Mark the block as allocated and return it */ + block_t *next = next_block(alloc); + alloc->used = true; + if(next) next->previous_used = true; + + if(s) s->used_memory += alloc->size; + if(s) s->peak_used_memory = max(s->peak_used_memory, s->used_memory); + + return (void *)alloc + sizeof(block_t); +} + +static void gint_free(void *ptr, void *data) +{ + index_t *index = data; + block_t *b = ptr - sizeof(block_t); + + block_t *prev = previous_block_if_free(b); + block_t *next = next_block(b); + + /* Mark the block as free */ + b->used = false; + if(index->stats) index->stats->used_memory -= b->size; + if(next) next->previous_used = false; + + /* Merge with the next block if free */ + if(next && !next->used) + { + remove_link(next, index); + merge(b, next); + } + /* Merge with the previous block if free */ + if(prev) + { + remove_link(prev, index); + merge(prev, b); + b = prev; + } + + /* Insert the result in the index */ + prepend_link(b, index); +} + +static void *gint_realloc(void *ptr, size_t size, void *data) +{ + index_t *index = data; + stats_t *s = index->stats; + block_t *b = ptr - sizeof(block_t); + size = round(size); + int size_before = b->size; + + /* When requesting a smaller size, split the original block */ + if(size <= b->size) + { + block_t *rest = split(b, size); + if(!rest) return ptr; + + /* Try to merge the rest with a following free block */ + block_t *next = next_block(rest); + if(next && !next->used) + { + remove_link(next, index); + merge(rest, next); + } + prepend_link(rest, index); + + if(s) s->used_memory -= (size_before - size); + return ptr; + } + + /* When requesting a larger size and the next block is free and large + enough, expand the original allocation */ + block_t *next = next_block(b); + int next_needed = size - b->size - sizeof(block_t); + + if(next && !next->used && next->size >= next_needed) + { + remove_link(next, index); + block_t *rest = split(next, next_needed); + if(rest) prepend_link(rest, index); + merge(b, next); + + if(s) s->used_memory += (b->size - size_before); + if(s) s->expanding_reallocs++; + return ptr; + } + + /* Otherwise, perform a brand new allocation */ + void *new_ptr = gint_malloc(size, data); + if(!new_ptr) + { + if(s && size >= s->free_memory) s->exhaustion_failures++; + if(s && size < s->free_memory) s->fragmentation_failures++; + return NULL; + } + + /* Move the data and free the original block */ + memcpy(new_ptr, ptr, b->size); + gint_free(ptr, data); + + if(s) s->relocating_reallocs++; + return new_ptr; +} + +/* kmalloc_init_arena(): Initialize an arena with gint's allocator */ +void kmalloc_init_arena(kmalloc_arena_t *a, bool enable_statistics) +{ + if(a->end - a->start < 256) return; + block_t *entry_block; + + a->malloc = gint_malloc; + a->free = gint_free; + a->realloc = gint_realloc; + + /* The index is located at the very start of the arena */ + index_t *index = a->start; + a->data = index; + + /* If requested, keep space for statistics */ + if(enable_statistics) + { + index->stats = (void *)a->start + sizeof(index_t); + entry_block = (void *)index->stats + sizeof(stats_t); + + memset(index->stats, 0, sizeof(stats_t)); + } + else + { + index->stats = NULL; + entry_block = (void *)a->start + sizeof(index_t); + } + + /* Initialize the first block */ + entry_block->last = 1; + entry_block->used = 0; + entry_block->previous_used = 1; + entry_block->size = a->end - (void *)entry_block - sizeof(block_t); + set_footer(entry_block, NULL, NULL); + + /* Initialize the index */ + for(int i = 0; i < 16; i++) index->classes[i] = NULL; + index->classes[size_class(entry_block->size)] = entry_block; + + /* Initialize statistics */ + if(index->stats) index->stats->free_memory = entry_block->size; +} + +//--- +// Introspection and debugging +//--- + +stats_t *kmalloc_get_gint_stats(kmalloc_arena_t *arena) +{ + if(arena->malloc != gint_malloc) return NULL; + index_t *index = arena->data; + return index->stats; +} + +#ifdef GINT_KMALLOC_DEBUG + +/* Tests for the structural integrity of the block sequence */ + +static block_t *first_block(kmalloc_arena_t *a) +{ + index_t *index = a->data; + void *sequence = (void *)a->data + sizeof(index_t); + if(index->stats) sequence += sizeof(stats_t); + return sequence; +} + +int kmallocdbg_sequence_length(kmalloc_arena_t *a) +{ + block_t *b = first_block(a); + int length = 0; + while(b) b = next_block(b), length++; + return length; +} + +bool kmallocdbg_sequence_covers(kmalloc_arena_t *a) +{ + index_t *index = a->data; + + void *sequence = (void *)a->data + sizeof(index_t); + if(index->stats) sequence += sizeof(stats_t); + + block_t *b = sequence; + int total_size = 0; + + while((void *)b >= a->start && (void *)b < a->end) + { + total_size += sizeof(block_t) + b->size; + b = next_block(b); + } + + return (total_size == a->end - sequence); +} + +bool kmallocdbg_sequence_terminator(kmalloc_arena_t *a) +{ + block_t *b = first_block(a); + while(!b->last) b = next_block(b); + return ((void *)b + sizeof(block_t) + b->size == a->end); +} + +bool kmallocdbg_sequence_coherent_used(kmalloc_arena_t *a) +{ + block_t *b = first_block(a), *next; + if(!b->previous_used) return false; + + while(b) + { + next = next_block(b); + if(next && b->used != next->previous_used) return false; + b = next; + } + return true; +} + +bool kmallocdbg_sequence_footer_size(kmalloc_arena_t *a) +{ + for(block_t *b = first_block(a); b; b = next_block(b)) + { + if(b->used) continue; + uint32_t *footer = (void *)b + sizeof(block_t) + b->size; + + if((footer[-1] & 1) != (b->size == 8)) return false; + if(b->size != 8 && (b->size != footer[-3])) return false; + } + return true; +} + +bool kmallocdbg_sequence_merged_free(kmalloc_arena_t *a) +{ + for(block_t *b = first_block(a); b; b = next_block(b)) + { + if(b->used) continue; + if(previous_block_if_free(b)) return false; + if(next_block(b) && !next_block(b)->used) return false; + } + return true; +} + +/* Tests for the integrity of the doubly-linked lists */ + +bool kmallocdbg_list_structure(kmalloc_arena_t *a) +{ + index_t *index = a->data; + + for(int c = 0; c < 16; c++) + { + block_t *b = index->classes[c], *next; + if(!b) continue; + if(b->used) return false; + if(previous_link(b)) return false; + + while((next = next_link(b))) + { + if(previous_link(next) != b) return false; + b = next; + } + } + return true; +} + +/* Tests for the coverage and separation of the segregated lists */ + +bool kmallocdbg_index_covers(kmalloc_arena_t *a) +{ + index_t *index = a->data; + int32_t total_size = 0; + + for(block_t *b = first_block(a); b; b = next_block(b)) + { + if(b->used) total_size += sizeof(block_t) + b->size; + } + + for(int c = 0; c < 16; c++) + for(block_t *b = index->classes[c]; b; b = next_link(b)) + { + total_size += sizeof(block_t) + b->size; + } + + return (total_size == a->end - (void *)first_block(a)); +} + +bool kmallocdbg_index_class_separation(kmalloc_arena_t *a) +{ + index_t *index = a->data; + + for(int c = 0; c < 16; c++) + for(block_t *b = index->classes[c]; b; b = next_link(b)) + { + if(size_class(b->size) != c) return false; + } + return true; +} + +#endif /* GINT_KMALLOC_DEBUG */ diff --git a/src/kmalloc/kmalloc.c b/src/kmalloc/kmalloc.c index 74ee075..8e6d408 100644 --- a/src/kmalloc/kmalloc.c +++ b/src/kmalloc/kmalloc.c @@ -5,6 +5,7 @@ #include #include #include +#include /* Maximum number of arenas */ #define KMALLOC_ARENA_MAX 8 @@ -17,13 +18,23 @@ void kmalloc_init(void) { /* Provide the OS heap */ extern kmalloc_arena_t kmalloc_arena_osheap; - arenas[0 /* KMALLOC_ARENA_MAX - 1 */] = &kmalloc_arena_osheap; + arenas[KMALLOC_ARENA_MAX - 1] = &kmalloc_arena_osheap; } //--- // Allocation functions //--- +kmalloc_arena_t *kmalloc_get_arena(char const *name) +{ + for(int i = 0; i < KMALLOC_ARENA_MAX; i++) + { + if(arenas[i] && !strcmp(arenas[i]->name, name)) + return arenas[i]; + } + return NULL; +} + /* Find the arena that contains a given block */ static kmalloc_arena_t *arena_owning(void *ptr) { @@ -89,7 +100,20 @@ void *krealloc(void *ptr, size_t size) kmalloc_arena_t *a = arena_owning(ptr); if(!a) return NULL; - return a->realloc(ptr, size, a->data); + void *rc = a->realloc(ptr, size, a->data); + + /* Maintain statistics */ + if(rc) + { + a->stats.total_volume += size; + a->stats.total_blocks++; + } + else + { + a->stats.total_failures++; + } + + return rc; } /* kfree(): Free memory allocated with kalloc() */