/* * (C) Copyright 1992, ..., 2007 the "DOSEMU-Development-Team". * * for details see file COPYING.DOSEMU in the DOSEMU distribution */ /* * smalloc - small memory allocator for dosemu. * * Authors: Stas Sergeev (main author), Bart Oldeman */ #include #include #include #include "utilities.h" #include "dosemu_debug.h" #include "smalloc.h" #define POOL_USED(p) (p->mn.used || p->mn.next) static void smerror_dummy(char *fmt, ...) FORMAT(printf, 1, 2); static void (*smerror)(char *fmt, ...) FORMAT(printf, 1, 2) = smerror_dummy; static void smerror_dummy(char *fmt, ...) { } static void mntruncate(struct memnode *pmn, size_t size) { int delta = pmn->size - size; if (delta == 0) return; /* delta can be < 0 */ if (pmn->next && !pmn->next->used) { struct memnode *nmn = pmn->next; assert(size > 0 && nmn->size + delta >= 0); nmn->size += delta; nmn->mem_area -= delta; pmn->size -= delta; if (nmn->size == 0) { pmn->next = nmn->next; if (pmn->next) pmn->next->prev = pmn; free(nmn); assert(!pmn->next || pmn->next->used); } } else { struct memnode *new_mn; assert(size < pmn->size); new_mn = malloc(sizeof(struct memnode)); new_mn->next = pmn->next; new_mn->prev = pmn; if (new_mn->next) new_mn->next->prev = new_mn; new_mn->size = delta; new_mn->used = 0; new_mn->mem_area = pmn->mem_area + size; pmn->next = new_mn; pmn->size = size; } } static struct memnode *find_mn(struct mempool *mp, unsigned char *ptr) { struct memnode *mn; if (!POOL_USED(mp)) { smerror("SMALLOC: unused pool passed\n"); return NULL; } for (mn = &mp->mn; mn; mn = mn->next) { if (mn->mem_area > ptr) return NULL; if (mn->mem_area == ptr) { return mn; } } return NULL; } static struct memnode *smfind_free_area(struct mempool *mp, size_t size) { struct memnode *mn; for (mn = &mp->mn; mn; mn = mn->next) { if (!mn->used && mn->size >= size) return mn; } return NULL; } /* NOTE: it is *guaranteed* that a subsequent call to smalloc() with * the same arguments, will return the same pointer this function will. */ void *smalloc_query(struct mempool *mp, size_t size) { struct memnode *mn = smfind_free_area(mp, size); return mn ? mn->mem_area : NULL; } void *smalloc(struct mempool *mp, size_t size) { struct memnode *mn; if (!size) { smerror("SMALLOC: zero-sized allocation attempted\n"); return NULL; } if (!(mn = smfind_free_area(mp, size))) { smerror("SMALLOC: Out Of Memory on alloc, requested=%zu\n", size); return NULL; } #if SM_COMMIT_SUPPORT if (mp->commit && !mp->commit(mn->mem_area, size)) return NULL; #endif mn->used = 1; mntruncate(mn, size); assert(mn->size == size); memset(mn->mem_area, 0, size); return mn->mem_area; } void smfree(struct mempool *mp, void *ptr) { struct memnode *mn, *pmn; if (!ptr) return; if (!(mn = find_mn(mp, ptr))) { smerror("SMALLOC: bad pointer passed to smfree()\n"); return; } if (!mn->used) { smerror("SMALLOC: attempt to free the not allocated region (double-free)\n"); return; } assert(mn->size > 0); #if SM_COMMIT_SUPPORT if (mp->uncommit) mp->uncommit(mn->mem_area, mn->size); #endif mn->used = 0; if (mn->next && !mn->next->used) { /* merge with next */ assert(mn->next->mem_area >= mn->mem_area); mntruncate(mn, mn->size + mn->next->size); } pmn = mn->prev; if (pmn && !pmn->used) { /* merge with prev */ assert(pmn->mem_area <= mn->mem_area); mntruncate(pmn, pmn->size + mn->size); mn = pmn; } } void *smrealloc(struct mempool *mp, void *ptr, size_t size) { struct memnode *mn, *pmn; if (!ptr) return smalloc(mp, size); if (!(mn = find_mn(mp, ptr))) { smerror("SMALLOC: bad pointer passed to smrealloc()\n"); return NULL; } if (!mn->used) { smerror("SMALLOC: attempt to realloc the not allocated region\n"); return NULL; } if (size == 0) { smfree(mp, ptr); return NULL; } if (size == mn->size) return ptr; if (size < mn->size) { /* shrink */ #if SM_COMMIT_SUPPORT if (mp->uncommit) mp->uncommit(mn->mem_area + size, mn->size - size); #endif mntruncate(mn, size); } else { /* grow */ struct memnode *nmn = mn->next; if (nmn && !nmn->used && mn->size + nmn->size >= size) { /* expand */ #if SM_COMMIT_SUPPORT if (mp->commit && !mp->commit(nmn->mem_area, size - mn->size)) return NULL; #endif memset(nmn->mem_area, 0, size - mn->size); mntruncate(mn, size); } else { pmn = mn->prev; if (pmn && !pmn->used && pmn->size + mn->size + (nmn->used ? 0 : nmn->size) >= size) { /* move */ #if SM_COMMIT_SUPPORT if (mp->commit) { size_t psize = min(size, pmn->size); if (!mp->commit(pmn->mem_area, psize)) return NULL; if (size > pmn->size + mn->size && !mp->commit(nmn->mem_area, size - pmn->size - mn->size)) { if (mp->uncommit) mp->uncommit(pmn->mem_area, psize); return NULL; } } #endif pmn->used = 1; memmove(pmn->mem_area, mn->mem_area, mn->size); memset(pmn->mem_area + mn->size, 0, size - mn->size); mn->used = 0; #if SM_COMMIT_SUPPORT if (size < pmn->size + mn->size) { size_t overl = size > pmn->size ? size - pmn->size : 0; if (mp->uncommit) mp->uncommit(mn->mem_area + overl, mn->size - overl); } #endif if (!nmn->used) mntruncate(mn, mn->size + nmn->size); mntruncate(pmn, size); return pmn->mem_area; } else { /* relocate */ void *new_ptr = smalloc(mp, size); if (!new_ptr) { smerror("SMALLOC: Out Of Memory on realloc, requested=%zu\n", size); return NULL; } memcpy(new_ptr, mn->mem_area, mn->size); smfree(mp, mn->mem_area); return new_ptr; } } } assert(mn->size == size); return mn->mem_area; } int sminit(struct mempool *mp, void *start, size_t size) { mp->mn.size = size; mp->mn.used = 0; mp->mn.next = NULL; mp->mn.prev = NULL; mp->mn.mem_area = start; #if SM_COMMIT_SUPPORT mp->commit = NULL; mp->uncommit = NULL; #endif return 0; } #if SM_COMMIT_SUPPORT int sminit_com(struct mempool *mp, void *start, size_t size, int (*commit)(void *area, size_t size), int (*uncommit)(void *area, size_t size)) { sminit(mp, start, size); mp->commit = commit; mp->uncommit = uncommit; return 0; } #endif int smdestroy(struct mempool *mp) { struct memnode *mn; int leaked, avail = smget_free_space(mp); while (POOL_USED(mp)) { mn = &mp->mn; if (!mn->used) mn = mn->next; assert(mn && mn->used); smfree(mp, mn->mem_area); } assert(!mp->mn.next && mp->mn.size >= avail); leaked = mp->mn.size - avail; return leaked; } size_t smget_free_space(struct mempool *mp) { struct memnode *mn; size_t count = 0; for (mn = &mp->mn; mn; mn = mn->next) { if (!mn->used) count += mn->size; } return count; } size_t smget_largest_free_area(struct mempool *mp) { struct memnode *mn; size_t size = 0; for (mn = &mp->mn; mn; mn = mn->next) { if (!mn->used && mn->size > size) size = mn->size; } return size; } int smget_area_size(struct mempool *mp, void *ptr) { struct memnode *mn; if (!(mn = find_mn(mp, ptr))) { smerror("SMALLOC: bad pointer passed to smget_area_size()\n"); return -1; } return mn->size; } void smregister_error_notifier(void (*func)(char *fmt, ...) FORMAT(printf, 1, 2)) { smerror = func; }