/* Author: Mark Moraes */ /*LINTLIBRARY*/ #include "defs.h" #include "globals.h" RCSID("$Id: memalign.c,v 1.1.1.1 1998/02/10 21:01:46 mea Exp $") /* * !! memalign may leave small (< _malloc_minchunk) blocks as garbage. * Not worth fixing now -- I've only seen two applications call valloc() * or memalign(), and they do it only once in their life. */ /* * This is needed to be compatible with Sun mallocs - Dunno how many * programs need it - the X server sure does... Returns a block 'size' * bytes long, such that the address is a multiple of 'alignment'. * (alignment MUST be a power of 2). This routine is possibly more * convoluted than free() - certainly uglier. Since it is rarely called * - possibly once in a program, it should be ok. Since this is called * from valloc() which is usually needed in conjunction with * mmap()/munmap(), note the comment in the Sun manual page about * freeing segments of size 128K and greater. Ugh. */ univptr_t memalign(alignment, size) size_t alignment, size; { univptr_t cp; univptr_t addr; REGISTER Word *p0, *p1; REGISTER size_t before, after; size_t blksize; #ifdef DEBUG int tmp_debugging = _malloc_debugging; #endif /* DEBUG */ if (alignment < sizeof(int) || !is_power_of_2(alignment) ||size == 0) { errno = EINVAL; return(NULL); } if (alignment < sizeof(Word)) return(malloc(size)); /* We guarantee this alignment anyway */ /* * Life starts to get complicated - need to get a block large * enough to hold a block 'size' long, starting on an 'alignment' * boundary */ if ((cp = malloc((size_t) (size + alignment - 1))) == NULL) return(NULL); addr = SIMPLEALIGN(cp, alignment); /* * This is all we really need - can go back now, except that we * might be wasting 'alignment - 1' bytes, which can be large since * this junk is usually called to align with things like pagesize. * So we try to push any free space before 'addr' and after 'addr + * size' back on the free list by making the memaligned chunk * ('addr' to 'addr + size') a block, and then doing stuff with the * space left over - either making them free blocks or coalescing * them whichever way is simplest. This usually involves making * them look like allocated blocks and calling free() which has all * the code to deal with this, and should do it reasonably fast. */ p0 = (Word *) cp; p0 -= HEADERWORDS; /* * p0 now points to the word tag starting the block which we got * from malloc. This remains invariant from now on - p1 is our * temporary pointer */ p1 = (Word *) addr; p1 -= HEADERWORDS; blksize = (size + sizeof(Word) - 1) / sizeof(Word); before = p1 - p0; after = SIZE(p0) - ALLOC_OVERHEAD - blksize - before; /* * p1 now points to the word before addr - this is going to be the * start of the memaligned block */ if (after < _malloc_minchunk) { /* * We merge the extra space after the memaligned block into * it since that space isn't enough for a separate block. * Note that if the block after the one that malloc * returned is free, we might be able to merge the space * into that block even if it is too small - unfortunately, * free() won't accept a block of this size, and I don't * want to do that code here, so we'll just let it go to * waste in the memaligned block. !! fix later, maybe */ blksize += after; after = 0; } /* * We mark the newly carved memaligned block p1 as alloced. addr is * (p1 + 1) which is the address we'll return */ SIZEFIELD(p1) = ALLOCMASK(blksize + ALLOC_OVERHEAD); SIZEFIELD(p1 + blksize + ALLOC_OVERHEAD - 1) = SIZEFIELD(p1); SET_REALSIZE(p1, size); if (after > 0) { /* We can now free the block after the memaligned block. */ p1 += blksize + ALLOC_OVERHEAD; /* SIZE(p1) */ /* * p1 now points to the space after the memaligned block. we * fix the size, mark it alloced, and call free - the block * after this may be free, which isn't simple to coalesce - let * free() do it. */ SIZEFIELD(p1) = ALLOCMASK(after); SIZEFIELD(p1 + after - 1) = SIZEFIELD(p1); SET_REALSIZE(p1, (after - ALLOC_OVERHEAD) * sizeof(Word)); #ifdef DEBUG /* Full heap checking will break till we finish memalign */ _malloc_debugging = 0; #endif /* DEBUG */ free((univptr_t) (p1 + HEADERWORDS)); } if (addr != cp) { /* * If what's 'before' is large enough to be freed, add p0 to * free list after changing its size to just consist of the * space before the memaligned block, also setting the * alloced flag. Then call free() -- may merge with preceding * block. (block after it is the memaligned block) */ /* * Else the space before the block is too small to form a * free block, and the preceding block isn't free, so we * aren't touching it. Theoretically, we could put it in * the preceding alloc'ed block, but there are painful * complications if this is the start of the arena. We * pass, but MUST mark it as allocated. This sort of garbage * can split up the arena -- fix later with special case * maybe?!! */ p1 = p0; SIZEFIELD(p1) = ALLOCMASK(before); SIZEFIELD(p1 + before - 1) = SIZEFIELD(p1); SET_REALSIZE(p1, (before - ALLOC_OVERHEAD) * sizeof(Word)); if (before >= _malloc_minchunk) { free(cp); } } #ifdef DEBUG _malloc_debugging = tmp_debugging; #endif /* DEBUG */ return(addr); } /* Just following the Sun manual page here */ univptr_t valloc(size) size_t size; { static size_t pagesz = 0; if (pagesz == 0) pagesz = (size_t) getpagesize(); return(memalign(pagesz, size)); }