Files
ex-vi/mapmalloc.c
Gunnar Ritter ebbbfc8bd2 * Fixed an error in the realloc() function in mapmalloc.c that sometimes
caused memory to be overwritten because memcpy(new, old, size) was called
  with the size of 'new' if 'new' was larger than 'old'.
2005-02-17 20:38:02 +00:00

571 lines
12 KiB
C

/*
* Memory allocation routines using mmap().
*
* Derived from mm-1.1.3 by Gunnar Ritter, Freiburg i. Br., Germany,
* September 2001.
*/
/* Sccsid @(#)mapmalloc.c 1.15 (gritter) 2/17/05 */
/* ====================================================================
* Copyright (c) 1999-2000 Ralf S. Engelschall. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by
* Ralf S. Engelschall <rse@engelschall.com>."
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by
* Ralf S. Engelschall <rse@engelschall.com>."
*
* THIS SOFTWARE IS PROVIDED BY RALF S. ENGELSCHALL ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RALF S. ENGELSCHALL OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#ifdef __GLIBC__
/*
* Broken GNU libc will include stdlib.h with conflicting
* malloc() types otherwise.
*/
#ifndef __NO_STRING_INLINES
#define __NO_STRING_INLINES
#endif /* !__NO_STRING_INLINES */
#endif /* __GLIBC__ */
#include <string.h>
#include "config.h"
#ifdef LANGMSG
#include <nl_types.h>
extern nl_catd catd;
#else
#define catgets(a, b, c, d) (d)
#endif
#ifndef min_of
#define min_of(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef max_of
#define max_of(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MAP_ANON
#ifdef MAP_ANONYMOUS
#define MAP_ANON MAP_ANONYMOUS
#endif
#endif
#ifndef MAP_FAILED
#define MAP_FAILED ((char *)-1)
#endif
#define MM_ALLOC_MINSIZE 8192
/*
* Define a union with types which are likely to have the longest
* *relevant* CPU-specific memory word alignment restrictions...
*/
union mem_word {
char (*mw_fp)(int);
char *mw_cp;
long mw_l;
double mw_d;
intptr_t mw_it;
};
typedef union mem_word mem_word;
#define SIZEOF_mem_word (sizeof(mem_word))
/*
* Define the structure used for memory chunks
*/
union mem_chunk_mc_u {
struct mem_chunk *mc_next; /* really used when it's free */
mem_word mc_base;/* virtually used when it's allocated */
};
struct mem_chunk {
size_t mc_size; /* physical size */
size_t mc_usize; /* user known size */
struct mem_pool *mc_pool;
union mem_chunk_mc_u mc_u;
};
typedef struct mem_chunk mem_chunk;
#define SIZEOF_mem_chunk (sizeof(mem_chunk)-sizeof(union mem_chunk_mc_u))
/*
* Define the structure describing a memory pool
*/
struct mem_pool {
struct mem_pool *mp_prev;
struct mem_pool *mp_next;
size_t mp_size;
size_t mp_offset;
int mp_users;
mem_chunk mp_freechunks;
mem_word mp_base;
};
typedef struct mem_pool mem_pool;
#define SIZEOF_mem_pool (sizeof(mem_pool)-SIZEOF_mem_word)
/*
* Define the structure describing a shared memory core area
* (the actual contents depends on the shared memory and
* semaphore/mutex type and is stripped down to a minimum
* required)
*/
struct mem_core {
size_t mo_size;
size_t mo_usize;
mem_word mo_base;
};
typedef struct mem_core mem_core;
#define SIZEOF_mem_core (sizeof(mem_core)-SIZEOF_mem_word)
typedef mem_pool MM;
static MM *mm_global = NULL;
#ifndef MAP_ANON
static int zerofd = -1;
#endif
extern int error(char *, ...);
/*
* Determine memory page size of OS
*/
static size_t
mm_core_pagesize(void)
{
static int pagesize = 0;
if (pagesize == 0)
#ifdef _SC_PAGESIZE
pagesize = sysconf(_SC_PAGESIZE);
#else
pagesize = 8192;
#endif
return pagesize;
}
/*
* Align a size to the next page or word boundary
*/
static size_t
mm_core_align2page(size_t size)
{
int psize = mm_core_pagesize();
return ((size) % (psize) > 0 ?
((size) / (psize) + 1) * (psize) : (size));
}
static size_t
mm_core_align2word(size_t size)
{
return ((1 + ((size - 1) / SIZEOF_mem_word)) * SIZEOF_mem_word);
}
/*
* Create a shared memory area
*/
static char
*mm_core_create(size_t usersize)
{
mem_core *mo;
char *area = ((char *)-1);
size_t size;
if (usersize <= 0) {
errno = EINVAL;
return NULL;
}
size = mm_core_align2page(usersize + SIZEOF_mem_core);
#ifdef MAP_ANON
if ((area = (char *)mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE, -1, 0)) == (char *)MAP_FAILED)
error(catgets(catd, 1, 243,
"failed to memory map anonymous area"));
#else /* !MAP_ANON */
if (zerofd == -1 && ((zerofd = open("/dev/zero", O_RDWR)) == -1
#ifdef F_SETFD
#ifndef FD_CLOEXEC
#define FD_CLOEXEC 1
#endif /* !FD_CLOEXEC */
|| fcntl(zerofd, F_SETFD, FD_CLOEXEC) == -1
#endif /* F_SETFD */
))
error(catgets(catd, 1, 244,
"failed to open /dev/zero"));
if ((area = (char *)mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE, zerofd, (off_t)0)) == (char *)MAP_FAILED)
error(catgets(catd, 1, 245,
"failed to memory map /dev/zero"));
#endif /* !MAP_ANON */
/*
* Configure the memory core parameters
*/
mo = (mem_core *)area;
mo->mo_size = size;
mo->mo_usize = usersize;
/*
* Return successfully established core
*/
return ((char *)&(mo->mo_base.mw_cp));
}
/*
* Create a memory pool
*/
static MM *
mm_create(MM *mmPrev, size_t usize)
{
MM *mm = NULL;
char *core;
size_t size;
/*
* defaults
*/
if (usize < MM_ALLOC_MINSIZE)
usize = MM_ALLOC_MINSIZE;
/*
* determine size
*/
size = usize + SIZEOF_mem_pool;
/*
* get a shared memory area
*/
if ((core = mm_core_create(size)) == NULL)
return NULL;
/*
* fill in the memory pool structure
*/
mm = (MM *)core;
mm->mp_prev = mmPrev;
mm->mp_next = NULL;
mm->mp_size = size;
mm->mp_offset = SIZEOF_mem_pool;
mm->mp_users = 0;
/*
* first element of list of free chunks counts existing chunks
*/
mm->mp_freechunks.mc_size = 0; /* has to be 0 forever */
mm->mp_freechunks.mc_usize = 0; /* counts chunks */
mm->mp_freechunks.mc_u.mc_next = NULL;
return mm;
}
/*
* Insert a chunk to the list of free chunks. Algorithm used is:
* Insert in sorted manner to the list and merge with previous
* and/or next chunk when possible to form larger chunks out of
* smaller ones.
*/
static void
mm_insert_chunk(MM *mm, mem_chunk *mcInsert)
{
mem_chunk *mc;
mem_chunk *mcPrev;
mem_chunk *mcNext;
mc = &(mm->mp_freechunks);
while (mc->mc_u.mc_next != NULL &&
(char *)(mc->mc_u.mc_next) < (char *)mcInsert)
mc = mc->mc_u.mc_next;
mcPrev = mc;
mcNext = mc->mc_u.mc_next;
if (mcPrev == mcInsert || mcNext == mcInsert)
error(catgets(catd, 1, 246,
"chunk of memory already in free list"));
if ((char *)mcPrev + (mcPrev->mc_size) == (char *)mcInsert &&
(mcNext != NULL &&
(char *)mcInsert + (mcInsert->mc_size) ==
(char *)mcNext)) {
/*
* merge with previous and next chunk
*/
mcPrev->mc_size += mcInsert->mc_size + mcNext->mc_size;
mcPrev->mc_u.mc_next = mcNext->mc_u.mc_next;
mm->mp_freechunks.mc_usize -= 1;
} else if ((char *)mcPrev + (mcPrev->mc_size) ==
(char *)mcInsert) {
/*
* merge with previous chunk
*/
mcPrev->mc_size += mcInsert->mc_size;
} else if (mcNext != NULL &&
(char *)mcInsert + (mcInsert->mc_size) ==
(char *)mcNext) {
/*
* merge with next chunk
*/
mcInsert->mc_size += mcNext->mc_size;
mcInsert->mc_u.mc_next = mcNext->mc_u.mc_next;
mcPrev->mc_u.mc_next = mcInsert;
} else {
/*
* no merging possible, so insert as new chunk
*/
mcInsert->mc_u.mc_next = mcNext;
mcPrev->mc_u.mc_next = mcInsert;
mm->mp_freechunks.mc_usize += 1;
}
}
/*
* Retrieve a chunk from the list of free chunks. Algorithm used
* is: Search for minimal-sized chunk which is larger or equal
* than the request size. But when the retrieved chunk is still a
* lot larger than the requested size, split out the requested
* size to not waste memory.
*/
static mem_chunk *
mm_retrieve_chunk(MM *mm, size_t size)
{
mem_chunk *mc;
mem_chunk **pmcMin;
mem_chunk *mcRes;
size_t sMin;
size_t s;
if (size == 0)
return NULL;
if (mm->mp_freechunks.mc_usize == 0)
return NULL;
/*
* find best-fitting chunk
*/
pmcMin = NULL;
sMin = mm->mp_size; /* initialize with maximum possible */
mc = &(mm->mp_freechunks);
while (mc->mc_u.mc_next != NULL) {
s = mc->mc_u.mc_next->mc_size;
if (s >= size && s < sMin) {
pmcMin = &(mc->mc_u.mc_next);
sMin = s;
if (s == size)
break;
}
mc = mc->mc_u.mc_next;
}
/*
* create result chunk
*/
if (pmcMin == NULL)
mcRes = NULL;
else {
mcRes = *pmcMin;
if (mcRes->mc_size >= (size + min_of(2 * size, 128))) {
/*
* split out in part
*/
s = mcRes->mc_size - size;
mcRes->mc_size = size;
/*
* add back remaining chunk part as new chunk
*/
mc = (mem_chunk *)((char *)mcRes + size);
mc->mc_size = s;
mc->mc_u.mc_next = mcRes->mc_u.mc_next;
*pmcMin = mc;
} else {
/*
* split out as a whole
*/
*pmcMin = mcRes->mc_u.mc_next;
mm->mp_freechunks.mc_usize--;
}
}
return mcRes;
}
/*
* Allocate a chunk of memory
*/
char *
malloc(size_t usize)
{
MM *mm;
mem_chunk *mc;
size_t size;
size = mm_core_align2word(SIZEOF_mem_chunk + usize);
if (mm_global == NULL && (mm_global = mm_create(NULL, size)) == NULL)
return NULL;
mm = mm_global;
nextpool:
if ((mc = mm_retrieve_chunk(mm, size)) != NULL) {
mc->mc_usize = usize;
mc->mc_pool = mm;
mm->mp_users++;
return (char *)&(mc->mc_u.mc_base.mw_cp);
}
if ((mm->mp_size - mm->mp_offset) < size) {
if (mm->mp_next != NULL ||
(mm->mp_next = mm_create(mm, size)) != NULL) {
mm = mm->mp_next;
goto nextpool;
}
error(catgets(catd, 1, 247, "out of memory"));
errno = ENOMEM;
return NULL;
}
mc = (mem_chunk *)((char *)mm + mm->mp_offset);
mc->mc_size = size;
mc->mc_usize = usize;
mc->mc_pool = mm;
mm->mp_offset += size;
mm->mp_users++;
return (char *)&(mc->mc_u.mc_base.mw_cp);
}
/*
* Free a chunk of memory
*/
void
free(char *ptr)
{
MM *mm;
mem_chunk *mc;
if (mm_global == NULL || ptr == NULL)
return;
mc = (mem_chunk *)((char *)ptr - SIZEOF_mem_chunk);
mm = mc->mc_pool;
mm_insert_chunk(mm, mc);
if (--mm->mp_users == 0) {
mem_core *mo = (mem_core *)((char *)mm - SIZEOF_mem_core);
if (mm == mm_global)
mm_global = mm->mp_next;
if (mm->mp_prev != NULL)
mm->mp_prev->mp_next = mm->mp_next;
if (mm->mp_next != NULL)
mm->mp_next->mp_prev = mm->mp_prev;
munmap((char *)mo, mo->mo_size);
}
}
/*
* Reallocate a chunk of memory
*/
char *
realloc(char *ptr, size_t usize)
{
size_t size;
mem_chunk *mc;
char *vp;
if (ptr == NULL)
return malloc(usize); /* POSIX.1 semantics */
mc = (mem_chunk *)((char *)ptr - SIZEOF_mem_chunk);
if (usize <= mc->mc_usize) {
mc->mc_usize = usize;
return ptr;
}
size = mm_core_align2word(SIZEOF_mem_chunk + usize);
if (size <= mc->mc_size) {
mc->mc_usize = usize;
return ptr;
}
if ((vp = malloc(usize)) == NULL)
return NULL;
memcpy(vp, ptr, mc->mc_usize);
free(ptr);
return vp;
}
/*
* Allocate and initialize a chunk of memory
*/
char *
calloc(size_t number, size_t usize)
{
char *vp;
if ((vp = malloc(number * usize)) == NULL)
return NULL;
memset(vp, 0, number * usize);
return vp;
}
/*ARGSUSED*/
void
cfree(char *p, size_t num, size_t size)
{
free(p);
}
#ifdef notdef
/*ARGSUSED*/
char *
memalign(size_t alignment, size_t size)
{
return NULL;
}
/*ARGSUSED*/
char *
valloc(size_t size)
{
return NULL;
}
char *
mallinfo(void)
{
return NULL;
}
int
mallopt(void)
{
return -1;
}
#endif /* notdef */
/*ARGSUSED*/
char *
poolsbrk(intptr_t val)
{
return NULL;
}