/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * smalloc - small memory allocator for dosemu. * * Author: Stas Sergeev */ #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 (*smerr)(char *fmt, ...) FORMAT(printf, 1, 2) = smerror_dummy; static void smerror_dummy(char *fmt, ...) { } #define smerror(mp, ...) mp->smerror(__VA_ARGS__) static void sm_uncommit(struct mempool *mp, void *addr, size_t size) { mp->avail += size; assert(mp->avail <= mp->size); if (!mp->uncommit) return; mp->uncommit(addr, size); } static int __sm_commit(struct mempool *mp, void *addr, size_t size, void *e_addr, size_t e_size) { if (!mp->commit) return 1; if (!mp->commit(addr, size)) { smerror(mp, "SMALLOC: failed to commit %p %zi\n", addr, size); if (e_size) sm_uncommit(mp, e_addr, e_size); return 0; } return 1; } static int sm_commit(struct mempool *mp, void *addr, size_t size, void *e_addr, size_t e_size) { int ok = __sm_commit(mp, addr, size, e_addr, e_size); if (ok) { assert(mp->avail >= size); mp->avail -= size; } return ok; } static int sm_commit_simple(struct mempool *mp, void *addr, size_t size) { return sm_commit(mp, addr, size, NULL, 0); } 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; 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->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 **prev) { struct memnode *pmn, *mn; if (!POOL_USED(mp)) { smerror(mp, "SMALLOC: unused pool passed\n"); return NULL; } for (pmn = NULL, mn = &mp->mn; mn; pmn = mn, mn = mn->next) { if (mn->mem_area > ptr) return NULL; if (mn->mem_area == ptr) { if (prev) *prev = pmn; 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; } static struct memnode *sm_alloc_mn(struct mempool *mp, size_t size) { struct memnode *mn; if (!size) { smerror(mp, "SMALLOC: zero-sized allocation attempted\n"); return NULL; } if (!(mn = smfind_free_area(mp, size))) { smerror(mp, "SMALLOC: Out Of Memory on alloc, requested=%zu\n", size); return NULL; } if (!sm_commit_simple(mp, mn->mem_area, size)) return NULL; mn->used = 1; mntruncate(mn, size); assert(mn->size == size); memset(mn->mem_area, 0, size); return mn; } void *smalloc(struct mempool *mp, size_t size) { struct memnode *mn = sm_alloc_mn(mp, size); if (!mn) return NULL; return mn->mem_area; } void smfree(struct mempool *mp, void *ptr) { struct memnode *mn, *pmn; if (!ptr) return; if (!(mn = find_mn(mp, ptr, &pmn))) { smerror(mp, "SMALLOC: bad pointer passed to smfree()\n"); return; } if (!mn->used) { smerror(mp, "SMALLOC: attempt to free the not allocated region (double-free)\n"); return; } assert(mn->size > 0); sm_uncommit(mp, mn->mem_area, mn->size); 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); } if (pmn && !pmn->used) { /* merge with prev */ assert(pmn->mem_area <= mn->mem_area); mntruncate(pmn, pmn->size + mn->size); mn = pmn; } } /* part of smrealloc() that covers the cases where the * extra memnode needs to be allocated for realloc */ static struct memnode *sm_realloc_alloc_mn(struct mempool *mp, struct memnode *pmn, struct memnode *mn, struct memnode *nmn, size_t size) { struct memnode *new_mn; if (pmn && !pmn->used && pmn->size + mn->size + (nmn->used ? 0 : nmn->size) >= size) { /* move to prev memnode */ size_t psize = min(size, pmn->size); if (!sm_commit_simple(mp, pmn->mem_area, psize)) return NULL; if (size > pmn->size + mn->size) { if (!sm_commit(mp, nmn->mem_area, size - pmn->size - mn->size, pmn->mem_area, psize)) return NULL; } 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 (size < pmn->size + mn->size) { size_t overl = size > pmn->size ? size - pmn->size : 0; sm_uncommit(mp, mn->mem_area + overl, mn->size - overl); } if (!nmn->used) // merge with next mntruncate(mn, mn->size + nmn->size); mntruncate(pmn, size); new_mn = pmn; } else { /* relocate */ new_mn = sm_alloc_mn(mp, size); if (!new_mn) { smerror(mp, "SMALLOC: Out Of Memory on realloc, requested=%zu\n", size); return NULL; } memcpy(new_mn->mem_area, mn->mem_area, mn->size); smfree(mp, mn->mem_area); } return new_mn; } 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, &pmn))) { smerror(mp, "SMALLOC: bad pointer passed to smrealloc()\n"); return NULL; } if (!mn->used) { smerror(mp, "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 */ sm_uncommit(mp, mn->mem_area + size, mn->size - size); mntruncate(mn, size); } else { /* grow */ struct memnode *nmn = mn->next; if (nmn && !nmn->used && mn->size + nmn->size >= size) { /* expand by shrinking next memnode */ if (!sm_commit_simple(mp, nmn->mem_area, size - mn->size)) return NULL; memset(nmn->mem_area, 0, size - mn->size); mntruncate(mn, size); } else { /* need to allocate new memnode */ mn = sm_realloc_alloc_mn(mp, pmn, mn, nmn, size); if (!mn) return NULL; } } assert(mn->size == size); return mn->mem_area; } int sminit(struct mempool *mp, void *start, size_t size) { mp->size = size; mp->mn.size = size; mp->mn.used = 0; mp->mn.next = NULL; mp->mn.mem_area = start; mp->avail = size; mp->commit = NULL; mp->uncommit = NULL; mp->smerror = smerr; return 0; } 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; } 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) { return mp->avail; } 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, NULL))) { smerror(mp, "SMALLOC: bad pointer passed to smget_area_size()\n"); return -1; } return mn->size; } void smregister_error_notifier(struct mempool *mp, void (*func)(char *fmt, ...) FORMAT(printf, 1, 2)) { mp->smerror = func; } void smregister_default_error_notifier( void (*func)(char *fmt, ...) FORMAT(printf, 1, 2)) { smerr = func; }