extlib, memory: remove liballoc and implementation

This commit is contained in:
Edgaru089 2021-10-11 20:50:38 +08:00
parent c3d2cebaaa
commit de281bde32
5 changed files with 0 additions and 996 deletions

View File

@ -1,26 +0,0 @@
This is a step-by-step instruction guide detailing how to
implement the library on your own system.
1. Copy the "liballoc.c" and "liballoc.h" files into
your working directory. (Whatever project you want
to use it in).
2. Create the hooks that the library needs:
Make a new file called "liballoc_hooks.c" (or
whatever) and implement the functions detailed
in the README and explained in "liballoc.h"
Look at "linux.c" for an example. It implements
the hooks for a Linux system.
3. Be sure to include the "liballoc.h" header
into your C/C++ files that are using the
malloc/free/memory operations. (So that
malloc/free/etc are declared.. obviously.)
4. Compile as per your normal method and test.

View File

@ -1,10 +0,0 @@
This code is released into the public domain. Use this code at your own
risk. Feel free to use it for whatever purpose you want. I take no responsibilty or
whatever if anything goes wrong. Use it at your own risk.
If you have any fixes or patches, please email me.
Durand Miller <clutter@djm.co.za>

View File

@ -1,832 +0,0 @@
#include "liballoc_1_1.h"
/** Durand's Amazing Super Duper Memory functions. */
#define VERSION "1.1"
#define ALIGNMENT 16ul//4ul ///< This is the byte alignment that memory must be allocated on. IMPORTANT for GTK and other stuff.
#define ALIGN_TYPE char ///unsigned char[16] /// unsigned short
#define ALIGN_INFO sizeof(ALIGN_TYPE)*16 ///< Alignment information is stored right before the pointer. This is the number of bytes of information stored there.
#define USE_CASE1
#define USE_CASE2
#define USE_CASE3
#define USE_CASE4
#define USE_CASE5
/** This macro will conveniently align our pointer upwards */
#define ALIGN( ptr ) \
if ( ALIGNMENT > 1 ) \
{ \
uintptr_t diff; \
ptr = (void*)((uintptr_t)ptr + ALIGN_INFO); \
diff = (uintptr_t)ptr & (ALIGNMENT-1); \
if ( diff != 0 ) \
{ \
diff = ALIGNMENT - diff; \
ptr = (void*)((uintptr_t)ptr + diff); \
} \
*((ALIGN_TYPE*)((uintptr_t)ptr - ALIGN_INFO)) = \
diff + ALIGN_INFO; \
}
#define UNALIGN( ptr ) \
if ( ALIGNMENT > 1 ) \
{ \
uintptr_t diff = *((ALIGN_TYPE*)((uintptr_t)ptr - ALIGN_INFO)); \
if ( diff < (ALIGNMENT + ALIGN_INFO) ) \
{ \
ptr = (void*)((uintptr_t)ptr - diff); \
} \
}
#define LIBALLOC_MAGIC 0xc001c0de
#define LIBALLOC_DEAD 0xdeaddead
#if defined DEBUG || defined INFO
#include <stdio.h>
#include <stdlib.h>
#define FLUSH() fflush( stdout )
#endif
/** A structure found at the top of all system allocated
* memory blocks. It details the usage of the memory block.
*/
struct liballoc_major
{
struct liballoc_major *prev; ///< Linked list information.
struct liballoc_major *next; ///< Linked list information.
unsigned int pages; ///< The number of pages in the block.
unsigned int size; ///< The number of pages in the block.
unsigned int usage; ///< The number of bytes used in the block.
struct liballoc_minor *first; ///< A pointer to the first allocated memory in the block.
};
/** This is a structure found at the beginning of all
* sections in a major block which were allocated by a
* malloc, calloc, realloc call.
*/
struct liballoc_minor
{
struct liballoc_minor *prev; ///< Linked list information.
struct liballoc_minor *next; ///< Linked list information.
struct liballoc_major *block; ///< The owning block. A pointer to the major structure.
unsigned int magic; ///< A magic number to idenfity correctness.
unsigned int size; ///< The size of the memory allocated. Could be 1 byte or more.
unsigned int req_size; ///< The size of memory requested.
};
static struct liballoc_major *l_memRoot = NULL; ///< The root memory block acquired from the system.
static struct liballoc_major *l_bestBet = NULL; ///< The major with the most free memory.
static unsigned int l_pageSize = 4096; ///< The size of an individual page. Set up in liballoc_init.
static unsigned int l_pageCount = 16; ///< The number of pages to request per chunk. Set up in liballoc_init.
static unsigned long long l_allocated = 0; ///< Running total of allocated memory.
static unsigned long long l_inuse = 0; ///< Running total of used memory.
static long long l_warningCount = 0; ///< Number of warnings encountered
static long long l_errorCount = 0; ///< Number of actual errors
static long long l_possibleOverruns = 0; ///< Number of possible overruns
// *********** HELPER FUNCTIONS *******************************
static void *liballoc_memset(void* s, int c, size_t n)
{
unsigned int i;
for ( i = 0; i < n ; i++)
((char*)s)[i] = c;
return s;
}
static void* liballoc_memcpy(void* s1, const void* s2, size_t n)
{
char *cdest;
char *csrc;
unsigned int *ldest = (unsigned int*)s1;
unsigned int *lsrc = (unsigned int*)s2;
while ( n >= sizeof(unsigned int) )
{
*ldest++ = *lsrc++;
n -= sizeof(unsigned int);
}
cdest = (char*)ldest;
csrc = (char*)lsrc;
while ( n > 0 )
{
*cdest++ = *csrc++;
n -= 1;
}
return s1;
}
#if defined DEBUG || defined INFO
static void liballoc_dump()
{
#ifdef DEBUG
struct liballoc_major *maj = l_memRoot;
struct liballoc_minor *min = NULL;
#endif
printf( "liballoc: ------ Memory data ---------------\n");
printf( "liballoc: System memory allocated: %i bytes\n", l_allocated );
printf( "liballoc: Memory in used (malloc'ed): %i bytes\n", l_inuse );
printf( "liballoc: Warning count: %i\n", l_warningCount );
printf( "liballoc: Error count: %i\n", l_errorCount );
printf( "liballoc: Possible overruns: %i\n", l_possibleOverruns );
#ifdef DEBUG
while ( maj != NULL )
{
printf( "liballoc: %x: total = %i, used = %i\n",
maj,
maj->size,
maj->usage );
min = maj->first;
while ( min != NULL )
{
printf( "liballoc: %x: %i bytes\n",
min,
min->size );
min = min->next;
}
maj = maj->next;
}
#endif
FLUSH();
}
#endif
// ***************************************************************
static struct liballoc_major *allocate_new_page( unsigned int size )
{
unsigned int st;
struct liballoc_major *maj;
// This is how much space is required.
st = size + sizeof(struct liballoc_major);
st += sizeof(struct liballoc_minor);
// Perfect amount of space?
if ( (st % l_pageSize) == 0 )
st = st / (l_pageSize);
else
st = st / (l_pageSize) + 1;
// No, add the buffer.
// Make sure it's >= the minimum size.
if ( st < l_pageCount ) st = l_pageCount;
maj = (struct liballoc_major*)liballoc_alloc( st );
if ( maj == NULL )
{
l_warningCount += 1;
#if defined DEBUG || defined INFO
printf( "liballoc: WARNING: liballoc_alloc( %i ) return NULL\n", st );
FLUSH();
#endif
return NULL; // uh oh, we ran out of memory.
}
maj->prev = NULL;
maj->next = NULL;
maj->pages = st;
maj->size = st * l_pageSize;
maj->usage = sizeof(struct liballoc_major);
maj->first = NULL;
l_allocated += maj->size;
#ifdef DEBUG
printf( "liballoc: Resource allocated %x of %i pages (%i bytes) for %i size.\n", maj, st, maj->size, size );
printf( "liballoc: Total memory usage = %i KB\n", (int)((l_allocated / (1024))) );
FLUSH();
#endif
return maj;
}
void *PREFIX(malloc)(size_t req_size)
{
int startedBet = 0;
unsigned long long bestSize = 0;
void *p = NULL;
uintptr_t diff;
struct liballoc_major *maj;
struct liballoc_minor *min;
struct liballoc_minor *new_min;
unsigned long size = req_size;
// For alignment, we adjust size so there's enough space to align.
if ( ALIGNMENT > 1 )
{
size += ALIGNMENT + ALIGN_INFO;
}
// So, ideally, we really want an alignment of 0 or 1 in order
// to save space.
liballoc_lock();
if ( size == 0 )
{
l_warningCount += 1;
#if defined DEBUG || defined INFO
printf( "liballoc: WARNING: alloc( 0 ) called from %x\n",
__builtin_return_address(0) );
FLUSH();
#endif
liballoc_unlock();
return PREFIX(malloc)(1);
}
if ( l_memRoot == NULL )
{
#if defined DEBUG || defined INFO
#ifdef DEBUG
printf( "liballoc: initialization of liballoc " VERSION "\n" );
#endif
atexit( liballoc_dump );
FLUSH();
#endif
// This is the first time we are being used.
l_memRoot = allocate_new_page( size );
if ( l_memRoot == NULL )
{
liballoc_unlock();
#ifdef DEBUG
printf( "liballoc: initial l_memRoot initialization failed\n", p);
FLUSH();
#endif
return NULL;
}
#ifdef DEBUG
printf( "liballoc: set up first memory major %x\n", l_memRoot );
FLUSH();
#endif
}
#ifdef DEBUG
printf( "liballoc: %x PREFIX(malloc)( %i ): ",
__builtin_return_address(0),
size );
FLUSH();
#endif
// Now we need to bounce through every major and find enough space....
maj = l_memRoot;
startedBet = 0;
// Start at the best bet....
if ( l_bestBet != NULL )
{
bestSize = l_bestBet->size - l_bestBet->usage;
if ( bestSize > (size + sizeof(struct liballoc_minor)))
{
maj = l_bestBet;
startedBet = 1;
}
}
while ( maj != NULL )
{
diff = maj->size - maj->usage;
// free memory in the block
if ( bestSize < diff )
{
// Hmm.. this one has more memory then our bestBet. Remember!
l_bestBet = maj;
bestSize = diff;
}
#ifdef USE_CASE1
// CASE 1: There is not enough space in this major block.
if ( diff < (size + sizeof( struct liballoc_minor )) )
{
#ifdef DEBUG
printf( "CASE 1: Insufficient space in block %x\n", maj);
FLUSH();
#endif
// Another major block next to this one?
if ( maj->next != NULL )
{
maj = maj->next; // Hop to that one.
continue;
}
if ( startedBet == 1 ) // If we started at the best bet,
{ // let's start all over again.
maj = l_memRoot;
startedBet = 0;
continue;
}
// Create a new major block next to this one and...
maj->next = allocate_new_page( size ); // next one will be okay.
if ( maj->next == NULL ) break; // no more memory.
maj->next->prev = maj;
maj = maj->next;
// .. fall through to CASE 2 ..
}
#endif
#ifdef USE_CASE2
// CASE 2: It's a brand new block.
if ( maj->first == NULL )
{
maj->first = (struct liballoc_minor*)((uintptr_t)maj + sizeof(struct liballoc_major) );
maj->first->magic = LIBALLOC_MAGIC;
maj->first->prev = NULL;
maj->first->next = NULL;
maj->first->block = maj;
maj->first->size = size;
maj->first->req_size = req_size;
maj->usage += size + sizeof( struct liballoc_minor );
l_inuse += size;
p = (void*)((uintptr_t)(maj->first) + sizeof( struct liballoc_minor ));
ALIGN( p );
#ifdef DEBUG
printf( "CASE 2: returning %x\n", p);
FLUSH();
#endif
liballoc_unlock(); // release the lock
return p;
}
#endif
#ifdef USE_CASE3
// CASE 3: Block in use and enough space at the start of the block.
diff = (uintptr_t)(maj->first);
diff -= (uintptr_t)maj;
diff -= sizeof(struct liballoc_major);
if ( diff >= (size + sizeof(struct liballoc_minor)) )
{
// Yes, space in front. Squeeze in.
maj->first->prev = (struct liballoc_minor*)((uintptr_t)maj + sizeof(struct liballoc_major) );
maj->first->prev->next = maj->first;
maj->first = maj->first->prev;
maj->first->magic = LIBALLOC_MAGIC;
maj->first->prev = NULL;
maj->first->block = maj;
maj->first->size = size;
maj->first->req_size = req_size;
maj->usage += size + sizeof( struct liballoc_minor );
l_inuse += size;
p = (void*)((uintptr_t)(maj->first) + sizeof( struct liballoc_minor ));
ALIGN( p );
#ifdef DEBUG
printf( "CASE 3: returning %x\n", p);
FLUSH();
#endif
liballoc_unlock(); // release the lock
return p;
}
#endif
#ifdef USE_CASE4
// CASE 4: There is enough space in this block. But is it contiguous?
min = maj->first;
// Looping within the block now...
while ( min != NULL )
{
// CASE 4.1: End of minors in a block. Space from last and end?
if ( min->next == NULL )
{
// the rest of this block is free... is it big enough?
diff = (uintptr_t)(maj) + maj->size;
diff -= (uintptr_t)min;
diff -= sizeof( struct liballoc_minor );
diff -= min->size;
// minus already existing usage..
if ( diff >= (size + sizeof( struct liballoc_minor )) )
{
// yay....
min->next = (struct liballoc_minor*)((uintptr_t)min + sizeof( struct liballoc_minor ) + min->size);
min->next->prev = min;
min = min->next;
min->next = NULL;
min->magic = LIBALLOC_MAGIC;
min->block = maj;
min->size = size;
min->req_size = req_size;
maj->usage += size + sizeof( struct liballoc_minor );
l_inuse += size;
p = (void*)((uintptr_t)min + sizeof( struct liballoc_minor ));
ALIGN( p );
#ifdef DEBUG
printf( "CASE 4.1: returning %x\n", p);
FLUSH();
#endif
liballoc_unlock(); // release the lock
return p;
}
}
// CASE 4.2: Is there space between two minors?
if ( min->next != NULL )
{
// is the difference between here and next big enough?
diff = (uintptr_t)(min->next);
diff -= (uintptr_t)min;
diff -= sizeof( struct liballoc_minor );
diff -= min->size;
// minus our existing usage.
if ( diff >= (size + sizeof( struct liballoc_minor )) )
{
// yay......
new_min = (struct liballoc_minor*)((uintptr_t)min + sizeof( struct liballoc_minor ) + min->size);
new_min->magic = LIBALLOC_MAGIC;
new_min->next = min->next;
new_min->prev = min;
new_min->size = size;
new_min->req_size = req_size;
new_min->block = maj;
min->next->prev = new_min;
min->next = new_min;
maj->usage += size + sizeof( struct liballoc_minor );
l_inuse += size;
p = (void*)((uintptr_t)new_min + sizeof( struct liballoc_minor ));
ALIGN( p );
#ifdef DEBUG
printf( "CASE 4.2: returning %x\n", p);
FLUSH();
#endif
liballoc_unlock(); // release the lock
return p;
}
} // min->next != NULL
min = min->next;
} // while min != NULL ...
#endif
#ifdef USE_CASE5
// CASE 5: Block full! Ensure next block and loop.
if ( maj->next == NULL )
{
#ifdef DEBUG
printf( "CASE 5: block full\n");
FLUSH();
#endif
if ( startedBet == 1 )
{
maj = l_memRoot;
startedBet = 0;
continue;
}
// we've run out. we need more...
maj->next = allocate_new_page( size ); // next one guaranteed to be okay
if ( maj->next == NULL ) break; // uh oh, no more memory.....
maj->next->prev = maj;
}
#endif
maj = maj->next;
} // while (maj != NULL)
liballoc_unlock(); // release the lock
#ifdef DEBUG
printf( "All cases exhausted. No memory available.\n");
FLUSH();
#endif
#if defined DEBUG || defined INFO
printf( "liballoc: WARNING: PREFIX(malloc)( %i ) returning NULL.\n", size);
liballoc_dump();
FLUSH();
#endif
return NULL;
}
void PREFIX(free)(void *ptr)
{
struct liballoc_minor *min;
struct liballoc_major *maj;
if ( ptr == NULL )
{
l_warningCount += 1;
#if defined DEBUG || defined INFO
printf( "liballoc: WARNING: PREFIX(free)( NULL ) called from %x\n",
__builtin_return_address(0) );
FLUSH();
#endif
return;
}
UNALIGN( ptr );
liballoc_lock(); // lockit
min = (struct liballoc_minor*)((uintptr_t)ptr - sizeof( struct liballoc_minor ));
if ( min->magic != LIBALLOC_MAGIC )
{
l_errorCount += 1;
// Check for overrun errors. For all bytes of LIBALLOC_MAGIC
if (
((min->magic & 0xFFFFFF) == (LIBALLOC_MAGIC & 0xFFFFFF)) ||
((min->magic & 0xFFFF) == (LIBALLOC_MAGIC & 0xFFFF)) ||
((min->magic & 0xFF) == (LIBALLOC_MAGIC & 0xFF))
)
{
l_possibleOverruns += 1;
#if defined DEBUG || defined INFO
printf( "liballoc: ERROR: Possible 1-3 byte overrun for magic %x != %x\n",
min->magic,
LIBALLOC_MAGIC );
FLUSH();
#endif
}
if ( min->magic == LIBALLOC_DEAD )
{
#if defined DEBUG || defined INFO
printf( "liballoc: ERROR: multiple PREFIX(free)() attempt on %x from %x.\n",
ptr,
__builtin_return_address(0) );
FLUSH();
#endif
}
else
{
#if defined DEBUG || defined INFO
printf( "liballoc: ERROR: Bad PREFIX(free)( %x ) called from %x\n",
ptr,
__builtin_return_address(0) );
FLUSH();
#endif
}
// being lied to...
liballoc_unlock(); // release the lock
return;
}
#ifdef DEBUG
printf( "liballoc: %x PREFIX(free)( %x ): ",
__builtin_return_address( 0 ),
ptr );
FLUSH();
#endif
maj = min->block;
l_inuse -= min->size;
maj->usage -= (min->size + sizeof( struct liballoc_minor ));
min->magic = LIBALLOC_DEAD; // No mojo.
if ( min->next != NULL ) min->next->prev = min->prev;
if ( min->prev != NULL ) min->prev->next = min->next;
if ( min->prev == NULL ) maj->first = min->next;
// Might empty the block. This was the first
// minor.
// We need to clean up after the majors now....
if ( maj->first == NULL ) // Block completely unused.
{
if ( l_memRoot == maj ) l_memRoot = maj->next;
if ( l_bestBet == maj ) l_bestBet = NULL;
if ( maj->prev != NULL ) maj->prev->next = maj->next;
if ( maj->next != NULL ) maj->next->prev = maj->prev;
l_allocated -= maj->size;
liballoc_free( maj, maj->pages );
}
else
{
if ( l_bestBet != NULL )
{
int bestSize = l_bestBet->size - l_bestBet->usage;
int majSize = maj->size - maj->usage;
if ( majSize > bestSize ) l_bestBet = maj;
}
}
#ifdef DEBUG
printf( "OK\n");
FLUSH();
#endif
liballoc_unlock(); // release the lock
}
void* PREFIX(calloc)(size_t nobj, size_t size)
{
int real_size;
void *p;
real_size = nobj * size;
p = PREFIX(malloc)( real_size );
liballoc_memset( p, 0, real_size );
return p;
}
void* PREFIX(realloc)(void *p, size_t size)
{
void *ptr;
struct liballoc_minor *min;
unsigned int real_size;
// Honour the case of size == 0 => free old and return NULL
if ( size == 0 )
{
PREFIX(free)( p );
return NULL;
}
// In the case of a NULL pointer, return a simple malloc.
if ( p == NULL ) return PREFIX(malloc)( size );
// Unalign the pointer if required.
ptr = p;
UNALIGN(ptr);
liballoc_lock(); // lockit
min = (struct liballoc_minor*)((uintptr_t)ptr - sizeof( struct liballoc_minor ));
// Ensure it is a valid structure.
if ( min->magic != LIBALLOC_MAGIC )
{
l_errorCount += 1;
// Check for overrun errors. For all bytes of LIBALLOC_MAGIC
if (
((min->magic & 0xFFFFFF) == (LIBALLOC_MAGIC & 0xFFFFFF)) ||
((min->magic & 0xFFFF) == (LIBALLOC_MAGIC & 0xFFFF)) ||
((min->magic & 0xFF) == (LIBALLOC_MAGIC & 0xFF))
)
{
l_possibleOverruns += 1;
#if defined DEBUG || defined INFO
printf( "liballoc: ERROR: Possible 1-3 byte overrun for magic %x != %x\n",
min->magic,
LIBALLOC_MAGIC );
FLUSH();
#endif
}
if ( min->magic == LIBALLOC_DEAD )
{
#if defined DEBUG || defined INFO
printf( "liballoc: ERROR: multiple PREFIX(free)() attempt on %x from %x.\n",
ptr,
__builtin_return_address(0) );
FLUSH();
#endif
}
else
{
#if defined DEBUG || defined INFO
printf( "liballoc: ERROR: Bad PREFIX(free)( %x ) called from %x\n",
ptr,
__builtin_return_address(0) );
FLUSH();
#endif
}
// being lied to...
liballoc_unlock(); // release the lock
return NULL;
}
// Definitely a memory block.
real_size = min->req_size;
if ( real_size >= size )
{
min->req_size = size;
liballoc_unlock();
return p;
}
liballoc_unlock();
// If we got here then we're reallocating to a block bigger than us.
ptr = PREFIX(malloc)( size ); // We need to allocate new memory
liballoc_memcpy( ptr, p, real_size );
PREFIX(free)( p );
return ptr;
}

View File

@ -1,87 +0,0 @@
#ifndef _LIBALLOC_H
#define _LIBALLOC_H
/** \defgroup ALLOCHOOKS liballoc hooks
*
* These are the OS specific functions which need to
* be implemented on any platform that the library
* is expected to work on.
*/
/** @{ */
#include <stdlib.h>
#include <stdint.h>
// If we are told to not define our own size_t, then we skip the define.
//#define _HAVE_UINTPTR_T
//typedef unsigned long uintptr_t;
typedef uint64_t uintptr_t;
//This lets you prefix malloc and friends
#ifndef PREFIX
#define PREFIX(func) liballoc_k ## func
#endif
#ifdef __cplusplus
extern "C" {
#endif
/** This function is supposed to lock the memory data structures. It
* could be as simple as disabling interrupts or acquiring a spinlock.
* It's up to you to decide.
*
* \return 0 if the lock was acquired successfully. Anything else is
* failure.
*/
extern int liballoc_lock();
/** This function unlocks what was previously locked by the liballoc_lock
* function. If it disabled interrupts, it enables interrupts. If it
* had acquiried a spinlock, it releases the spinlock. etc.
*
* \return 0 if the lock was successfully released.
*/
extern int liballoc_unlock();
/** This is the hook into the local system which allocates pages. It
* accepts an integer parameter which is the number of pages
* required. The page size was set up in the liballoc_init function.
*
* \return NULL if the pages were not allocated.
* \return A pointer to the allocated memory.
*/
extern void* liballoc_alloc(size_t);
/** This frees previously allocated memory. The void* parameter passed
* to the function is the exact same value returned from a previous
* liballoc_alloc call.
*
* The integer value is the number of pages to free.
*
* \return 0 if the memory was successfully freed.
*/
extern int liballoc_free(void*,size_t);
extern void *PREFIX(malloc)(size_t); ///< The standard function.
extern void *PREFIX(realloc)(void *, size_t); ///< The standard function.
extern void *PREFIX(calloc)(size_t, size_t); ///< The standard function.
extern void PREFIX(free)(void *); ///< The standard function.
#ifdef __cplusplus
}
#endif
/** @} */
#endif

View File

@ -1,41 +0,0 @@
#include "memory.h"
#include "paging_internal.h"
#include "../runtime/stdio.h"
#include "../interrupt/interrupt.h"
#include <stdint.h>
#include "../extlib/liballoc/liballoc_1_1.h"
int liballoc_lock() {
if (interrupt_Enabled)
asm volatile("cli");
return 0;
}
int liballoc_unlock() {
if (interrupt_Enabled)
asm volatile("sti");
return 0;
}
static uint64_t heapBreak = KERNEL_HEAP_VIRTUAL;
void *liballoc_alloc(size_t pages) {
void *ret = (void *)heapBreak;
heapBreak += SYSTEM_PAGE_SIZE * pages;
paging_map_PageAllocated((uint64_t)ret, pages, MAP_PROT_READ | MAP_PROT_WRITE);
io_Printf("liballoc_alloc: allocated %u pages at HEAP+%llx (%llx)\n", pages, ret - KERNEL_HEAP_VIRTUAL, ret);
return ret;
}
int liballoc_free(void *ptr, size_t pages) {
paging_map_FreeAllocated((uint64_t)ptr, (uint64_t)ptr + SYSTEM_PAGE_SIZE * pages);
io_Printf("liballoc_free: freed %u pages at HEAP+%llx (%llx)\n", pages, ptr - KERNEL_HEAP_VIRTUAL, ptr);
return 0;
}