static char sccsid[] = "@(#)33 1.53.1.1 src/bos/kernel/ldr/xmalloc.c, sysldr, bos41J, 9508A 2/15/95 10:34:51"; /* * COMPONENT_NAME: (SYSLDR) Program Management * * FUNCTIONS: pmalloc(), frmalloc(), xmalloc(), pgfree(), xmfree(), * init_heap(), verify_empty_heap() * * ORIGINS: 27 * * IBM CONFIDENTIAL -- (IBM Confidential Restricted when * combined with the aggregated modules for this product) * SOURCE MATERIALS * (C) COPYRIGHT International Business Machines Corp. 1989, 1993 * All Rights Reserved * * US Government Users Restricted Rights - Use, duplication or * disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ /* this is a complete rewrite from scratch of xmalloc. * The allocator uses "quick cells" for power of two sizes less than a * page, and a traditional first fit allocator for pages. The page * allocator could be improved if necessary. All requests for less than * a page are rounded up to the next power of two. The heap data * structure contains a vector, called fr, with an entry for each * power of 2 size. * * This allocator must be able te retreive the size of a request at free * time since xmfree does not provide the size. It does this without * tracking each request as follows - * * There is a vector "pages" with an entry for each page in the heap. Each * entry may be in any of the states listed below in the P_ defines. If * a page is in single page allocated state it may further be a container * for fragments all of the same size. Thus, free can look up the page * containing its parameter and determine what size it is. * * Since small request are allocated from page sized pools, allocate and * free of small blocks is VERY fast. Most applications should not need * special purpose storage allocators for small, high traffic allocation. * */ #include #include #include #include #include #include #include #include #include #include typedef void * heapaddr_t; #ifdef DEBUG /* make the individual debug hacks independent */ /* #define DEBUG1 /* clear or ruin storage to catch bugs */ #define DEBUG2 /* keep track of allocated space */ /*#define DEBUG3 /* use extra storage to catch people who use too much */ #define DEBUG4 /* count usage for measurement purposes */ #define DEBUG5 /* eliminate old construct and check calling interrupt level */ #endif #ifdef DEBUG3 #define DEBUG2 /* prerequisite */ #endif #ifdef _KERNEL heapaddr_t kernel_heap; heapaddr_t pinned_heap; #else #undef DEBUG5 /* these checks only make sense for kernel compile */ #endif #define SANITY 0x48454150 /* The characters 'HEAP' */ #define HALFPGSIZE (PAGESIZE>>1) /* all about alt ..... * alt is 1 for the alternate heap, 0 for the primary heap. * but the alternate heap appears first in memory, immediately followed by the * primary heap. thus heap[heap->alt] for the alt is heap[1], the primary., * but also, heap[heap->alt] is heap[0] for the primary, again the primary */ struct pages{ uint type:3; uint size:5; /* maximum page size 2**32 */ uint offset:24; /* minimum page size in 32 bit machine 2**8*/ }; #define NO_PAGE 0xffffff /*maximum value that fits in offset*/ #define P_allocpage 0 /* if fragments, size is set, offset is first free fragment in page */ #define P_allocrange 1 /* size and offset not used */ #define P_freepage 2 /* offset is page index of */ #define P_freerange 3 /* next free page or range */ #define P_freesize 4 /* offset is number of pages in this freerange */ #define P_freerangeend 5 /* offset is page index of first page of this range */ #define P_allocsize 6 /* offset is size of this allocated range */ #define P_allocrangeend 7 /* offset is page index of first page of this range */ struct frag{ /* implies min alloc of 16 bytes */ uint next; /* offset in page of next free fragment */ uint size; /* amount of space on free chain from here */ uint nextpg; /* page index of next page of fragments */ uint prevpg; /* page index of previous page of fragments */ }; #ifdef DEBUG2 /* various stuff for keeping track of allocated space */ uint first_time; uint rec_buf[1024*128]; uint recording=1; /* 1 means recording on - 0 is recording off */ #ifdef _KERNEL uint record_all=0; /* 1 means record all heaps, 0 only kernel/pinned*/ #endif struct heap *rec_heap; struct record *rec_hash[256]; #endif uint pmalloc( struct pages *pages, uint num) { uint i,ip,j,k; i = pages[-1].offset; /*anchor of page free list */ if (i==NO_PAGE) return NO_PAGE; for(ip=-1;i!=NO_PAGE;ip=i,i=pages[i].offset) { /* Check for xmalloc page structure damaged. */ ASSERT( (pages[i].type == P_freepage) || (pages[i].type == P_freerange) ); switch(pages[i].type){ case P_freepage: if (num == 1) { pages[ip].offset = pages[i].offset; pages[i].type = P_allocpage; return i; } break; case P_freerange: /* exact fit? */ if (num == pages[i+1].offset){ pages[ip].offset = pages[i].offset; pages[i].type=P_allocrange; pages[i+1].type=P_allocsize; if (num > 2) pages[i+num-1].type = P_allocrangeend; return i; } /* too small ? */ if (num > pages[i+1].offset) break; /* allocate start of range - this guarantees that all fragments * will preceed the (usually large) block of memory never used * at all - and thus that fragments will be reused before * allocating from the unused piece. In effect, the allocation * must be left to right since the search for free space is * left to right */ k=pages[i+1].offset-num; /* amount left - must be nonezero*/ j=i+num; /* j is first free page, k pages left free * i is first allocated page, num pages allocated */ /* reformat remaining free space at end */ if (k==1) pages[j].type = P_freepage; else { pages[j].type = P_freerange; pages[j+1].type = P_freesize; pages[j+1].offset = k; if (k>2) { pages[j+k-1].type = P_freerangeend; pages[j+k-1].offset = j; } } /* rechain following free block to this new free block */ pages[j].offset = pages[i].offset; /* rechain this new free block to previous free block */ pages[ip].offset = j; /* format allocated space */ if (num == 1) pages[i].type = P_allocpage; else { pages[i].type = P_allocrange; pages[i+1].type = P_allocsize; pages[i+1].offset = num; if (num > 2){ pages[i+num-1].type = P_allocrangeend; pages[i+num-1].offset = i; } } return i; }/* end of switch */ } /* end of "for" */ return NO_PAGE; } int frmalloc( char *area, struct heap *heap, struct pages *pages, uint size2) { struct frag *frag; char *fpage; uint ipage; uint size; uint i,j,k; ipage = pmalloc(pages,1); if (ipage == NO_PAGE) return 1; frag = (void *)(area + (ipage<pinflag && pin (frag, PAGESIZE, 0)) { (void) pgfree (area, heap, pages, ipage, 1); return 1; } #endif heap->fr[size2] = ipage; pages[ipage].size = size2; pages[ipage].offset = 0; frag->nextpg = frag->prevpg = NO_PAGE; frag->size = PAGESIZE; size = (1<next=j; frag=(void *)((char *)frag + size); } frag->next = NO_PAGE; return 0; } #ifdef DEBUG1 uint xmalloc_pattern = 0x66666666; /* for debug set memory to pattern */ uint xmfree_pattern = 0x77777777; /* for debug ruin free storage */ #endif #ifdef DEBUG2 char trailer = 0xaa; /* filler for space after allocated area */ #endif void* xmalloc( uint req_size, uint align, heapaddr_t heapp) { #ifdef DEBUG2 int nested_lock = 0; #endif struct heap *heap; char *area; struct pages *pages; struct frag *frag; char *fpage; uint ipage; char *result; uint size; uint size2; /*log2 of size*/ uint i,j,k; #ifdef DEBUG5 #include extern struct proc si_proc; ASSERT(heapp != &pinned_heap); /* obsolete construct */ ASSERT(heapp != &kernel_heap); /* obsolete construct */ ASSERT(curproc == &si_proc || csa->prev == NULL); ASSERT(curproc == &si_proc || csa->intpri == INTBASE); #endif #ifdef _KERNEL TRCHKGT(HKWD_KERN_XMALLOC,req_size,align,heapp,NULL,NULL); #endif heap = (struct heap *) heapp; if (req_size == 0) return NULL; /* * a recursive call can be made to xmalloc() if DEBUG2 has been * defined. Therefore it is necessary to determine if we already * have the heap lock, so that xmalloc() can determine whether to * unlock it when returning. */ #ifdef DEBUG2 if (!simple_lock_try(&(heap[heap->alt].lock))) if (!(nested_lock = lock_mine(&(heap[heap->alt].lock)))) simple_lock(&(heap[heap->alt].lock)); #else simple_lock(&(heap[heap->alt].lock)); #endif ASSERT(heap[heap->alt].sanity == SANITY); ASSERT(heap[heap->alt-1].sanity == SANITY); ASSERT(align <= PGSHIFT); result = NULL; area = (char *)heap + heap->base; pages = (struct pages *)&heap[1+heap->alt]+1; size = req_size; #ifdef DEBUG3 size = size+4; /* get enough extra space for a trailer */ #endif if (size > HALFPGSIZE || align == PGSHIFT) { size2 = PGSHIFT; size = (size+PAGESIZE-1) & ~(PAGESIZE-1); ipage = pmalloc(pages,size>>PGSHIFT); if (ipage == NO_PAGE) goto exit; result = area + (ipage<pinflag && pin (result, size, 0)) { pgfree (area, heap, pages, ipage, size >> PGSHIFT); result = NULL; goto exit; } #endif pages[ipage].size = 0; /*mark as full page*/ #ifdef DEBUG4 heap->pagtot += size>>PGSHIFT; heap->pagused += size>>PGSHIFT; #endif goto exit; } if (size <= 16) size2 = 4; else size2 = 32 - clz32(size-1); size2 = MAX(align,size2); size = (1<frtot[size2] += 1; heap->frused[size2] += 1; #endif if (heap->fr[size2] == NO_PAGE) if (frmalloc(area,heap,pages,size2)) goto exit; ipage = heap->fr[size2]; fpage = area + (ipage<next == NO_PAGE) { /* this page has only one fragment left - so use it and * remove this page from the fragment page chain for this size */ ASSERT(frag->nextpg==NO_PAGE || (pages[frag->nextpg].type==P_allocpage && pages[frag->nextpg].size==size2) ); heap->fr[size2] = frag->nextpg; pages[ipage].offset = NO_PAGE; result = (void *)frag; } else { /* alocate the second fragment - the first is used to keep * the fragment page size free and chain pointers whenever * there is any space in the page */ ASSERT( frag->next <= PAGESIZE-size && ((size-1)&frag->next) == 0); result = fpage + frag->next; frag->next = ((struct frag *)result)->next; frag->size -= size; } exit: heap->amount += size; #ifdef DEBUG1 { uint pat = xmalloc_pattern; uint *w; extern int firstfitsize; if (result && (req_size != firstfitsize)) for(w=result;(char *)wrhash) { /* get callers address - platform dependent */ #ifdef _POWER struct frame { struct frame * next; int junk; int link; } *fp; fp = (struct frame *)get_stkp(); fp = fp->next; caller = (void *)fp->link; #endif #ifdef _IBMRT /* in RT, callers lr is stored before arg vector */ caller = *(((void**)(&req_size))-6); #endif r = (struct record *) xmalloc((sizeof(struct record)),0,rec_heap); if (r == NULL) { recording = 0; printf("xmalloc: out of recording space"); brkpoint(); goto exit_debug2; } /* we remember which heap (primary or alt, * e.g. kernel or pinned - in low bit of addr */ r->addr = result+heap->alt; r->req_size = req_size; r->from = (uint)caller; i = ((uint)result>>16)+(uint)result; i = ((i>>8) + i) & 0x000000ff; r->next = (heap->rhash)[i]; (heap->rhash)[i] = r; } #ifdef _KERNEL /* in the kernel xmalloc, we don't want to record private heaps * like the process private kernel heap in the main rec_hash * since this leads to false indications of lost storage. * It takes a kludge to get the kernel and pinned heap recording. * This is it! */ else if (recording && (heap == kernel_heap || heap == pinned_heap)){ heap->rhash = rec_hash; goto tryagain; } #endif } #endif #ifdef DEBUG2 exit_debug2: if (!nested_lock) simple_unlock(&(heap[heap->alt].lock)); #else simple_unlock(&(heap[heap->alt].lock)); #endif return result; } int pgfree( char *area, struct heap *heap, struct pages *pages, uint ipage, uint num) { uint ppage,npage; ASSERT(ipagenumpages); ASSERT(ipage+num <= heap->numpages); #ifdef DEBUG4 heap->pagused -= num; #endif #ifdef DEBUG1 { /* ruin free pages */ uint pat = xmfree_pattern; uint *w; uint addr; if (pat != 0) { addr = area + (ipage<pinflag) unpin(area + (ipage<vmrelflag) vm_release(area + (ipage< 2 ) { pages[ipage+num-1].type = P_freerangeend; pages[ipage+num-1].offset = ipage; } } return 0; } int xmfree( char *addr, heapaddr_t heapp) { struct heap *heap; char *area; struct pages *pages; struct frag *frag,*firstfrag; char *fpage; uint ipage,npage,ppage; int result; uint size2; /*log2 of size*/ uint size; uint i,j,k; #ifdef DEBUG2 int nested_lock = 0; #endif #ifdef DEBUG5 #include extern struct proc si_proc; ASSERT(heapp != &pinned_heap); /* obsolete construct */ ASSERT(heapp != &kernel_heap); /* obsolete construct */ ASSERT(curproc == &si_proc || csa->prev == NULL); ASSERT(curproc == &si_proc || csa->intpri == INTBASE); #endif #ifdef _KERNEL TRCHKGT(HKWD_KERN_XMFREE,addr,heapp,NULL,NULL,NULL); #endif heap = (struct heap *) heapp; if (addr == NULL) return 0; /* * a recursive call can be made to xmfree() if DEBUG2 has been * defined. Therefore it is necessary to determine if we already * have the heap lock, so that xmfree() can determine whether to * unlock it when returning. */ #ifdef DEBUG2 if (!simple_lock_try(&(heap[heap->alt].lock))) if (!(nested_lock = lock_mine(&(heap[heap->alt].lock)))) simple_lock(&(heap[heap->alt].lock)); #else simple_lock(&(heap[heap->alt].lock)); #endif ASSERT(heap[heap->alt].sanity == SANITY); ASSERT(heap[heap->alt-1].sanity == SANITY); result = 0; area = (char *)heap + heap->base; pages = (struct pages *)&heap[1+heap->alt]+1; ASSERT(addr >= area); if (addr < area) goto returnEINVAL; ipage = (addr-area)>>PGSHIFT; ASSERT(ipage < heap->numpages); if(ipage >= heap->numpages) goto returnEINVAL; if(pages[ipage].type > P_allocrange) goto returnEINVAL; size2 = pages[ipage].size; size = (1<rhash) { i = ((uint)addr>>16)+(uint)addr; i = ((i>>8) + i) & 0x000000ff; rp = (void *)&(heap->rhash)[i]; r = (heap->rhash)[i]; while(r) { if (r->addr == addr+heap->alt) break; rp = r; r = r->next; } if (r == NULL) { printf("xfree of unallocated address"); brkpoint(); } rp->next = r->next; { char pat = trailer; char *w; for( w = (char *) addr+r->req_size; (char*) w < addr+size; w++) if (*w != pat) { printf( "xmalloc clobbered trailer"); brkpoint(); } } if (xmfree((void *)r,rec_heap)) { printf( "xmalloc allocation record keeping broken"); brkpoint(); } } } #endif switch (pages[ipage].type){ case P_allocrange: ASSERT((addr-area) == ipage<frused[size2] -= 1; #endif #ifdef DEBUG1 { /* ruin free fragment */ uint pat = xmfree_pattern; uint *w; if (pat != 0) { for(w=(void*)frag;(char*)w<(char *)frag+size;*w++=pat); } } #endif if (pages[ipage].offset != NO_PAGE) { /* this page of fragments is already on a free chain * and has at least one free fragment. Put this new * one as SECOND since the first one serves to record * size and page chain pointers */ firstfrag = (void*)(fpage + pages[ipage].offset); frag->next = firstfrag->next; firstfrag->next = (uint)frag - (uint)fpage; firstfrag->size += size; if ( firstfrag->size == PAGESIZE && ( heap->fr[size2] != ipage || firstfrag->nextpg != NO_PAGE) ){ /* we have freed all the fragments in this * page. It is not the only page on the chain. */ if(heap->fr[size2] == ipage){ /* easy case - its first */ ASSERT(pages[firstfrag->nextpg].type==P_allocpage && pages[firstfrag->nextpg].size==size2 ); heap->fr[size2] = firstfrag->nextpg; /* not NO_PAGE*/ } else { ppage = firstfrag->prevpg; npage = firstfrag->nextpg; /* fix up predecessor - it must have one * since its not first */ ASSERT(pages[ppage].type==P_allocpage && pages[ppage].size==size2 ); fpage = area + (ppage<nextpg = npage; /* fix up sucessor if it has one */ if (npage != NO_PAGE) { ASSERT(pages[npage].type==P_allocpage && pages[npage].size==size2 ); fpage = area + (npage<prevpg = ppage; } }/* end of removing this page from the chain */ result = pgfree(area,heap,pages,ipage,1); }/*end of remove case*/ }/* end of fragment page already on free chain*/ else { /* freeing first fragment of this page */ pages[ipage].offset = (uint)frag - (uint)fpage; frag->size = size; frag->next = NO_PAGE; /* put this page on the front of the chain of pages of * this size */ npage = frag->nextpg = heap->fr[size2]; heap->fr[size2] = ipage; if ( npage != NO_PAGE ) { ASSERT(pages[npage].type==P_allocpage && pages[npage].size==size2 ); fpage = area + (npage<size == PAGESIZE) { /* this was a full page left on because it * was alone */ result = pgfree(area,heap,pages,npage,1); frag = (void*)addr; /* back to original value */ frag->nextpg = NO_PAGE; } else frag->prevpg = ipage; } }/* end of fragment page to be added to free chain when * first fragment is freed */ exit: heap->amount -= size; returnresult: #ifdef DEBUG2 if (!nested_lock) simple_unlock(&(heap[heap->alt].lock)); #else simple_unlock(&(heap[heap->alt].lock)); #endif return result; returnEINVAL: result = EINVAL; goto returnresult; } heapaddr_t init_heap( char *area, uint size, heapaddr_t heapp) { struct heap *heap=(struct heap*)heapp; struct heap *altheap; struct pages *pages; uint i,j,k; uint numpages,hpages; uint hsize; if (heap) { /* return address of secondary heap for this primary*/ ASSERT(0==heap->alt); #ifdef _KERNEL if(heap == kernel_heap) heap[-1].pinflag = 1; #endif return &heap[-1]; } #ifdef DEBUG2 if (first_time == 0) { first_time = 1; rec_heap = init_heap( (char *)(((uint)rec_buf+PAGESIZE - 1) & ~(PAGESIZE-1)), ((sizeof(rec_buf)) - 2*PAGESIZE) & ~(PAGESIZE-1),0); rec_heap->pinflag = 1; /* pin entries so they are available at dump time */ rec_heap->rhash = NULL; /* avoid recursive recording */ } #endif ASSERT(0==((uint)area&(PAGESIZE-1))); ASSERT(0==(size&(PAGESIZE-1))); numpages = size >> PGSHIFT; hsize = 2*sizeof(struct heap) + (1+numpages)*sizeof(struct pages); hsize = (hsize+PAGESIZE-1) & ~(PAGESIZE-1); hpages = hsize >> PGSHIFT; numpages = numpages - hpages; altheap = (struct heap*)(area + (numpages<sanity = altheap->sanity = SANITY; heap->alt = 0; altheap->alt = 1; pages = (struct pages *)&altheap[2]+1; /* remember +1 adds an element size */ heap -> base = (uint) area - (uint) heap; altheap -> base = (uint) area - (uint) altheap; simple_lock_init(&heap->lock); for(i=0;ifr[i]=altheap->fr[i]=NO_PAGE,i++); #ifdef DEBUG4 for(i=0;ifrtot[i]=heap->frused[i]=0,i++); heap->pagtot = heap->pagused = 0; #endif { pages[-1].offset=0; pages[-1].type=P_allocpage; pages[0].offset = NO_PAGE; pages[0].type = P_freerange; pages[1].offset = numpages; pages[1].type = P_freesize; pages[numpages-1].offset=0; pages[numpages-1].type=P_freerangeend; } heap->numpages = altheap->numpages = numpages; heap->rhash = altheap->rhash = NULL; #if defined(_KERNEL) && defined(DEBUG2) if (record_all) heap->rhash = altheap->rhash = rec_hash; #endif return heap; } /* this interface is used by xmalloc testing code. It is called when * the heap is expected to be empty ( nothing allocated) and verifys * that the heap has returned to its initial state. This is tricky * since xmalloc defers frees of empty fragment pages. The code * actually frees empty fragment pages and then checks. * * returns 0 if all is well, an error number otherwise */ int verify_empty_heap( heapaddr_t heapp) { struct heap *heap=(struct heap*)heapp; struct heap *altheap; struct pages *pages; int i,j,k; uint numpages; struct frag *frag; uint ipage; char *area; if ( heap[0].alt ) return 1; area = (char *)heap + heap->base; pages = (struct pages *)&heap[1+heap->alt]+1; /* clean up fragment storage for both heaps */ /* for each heap */ for(i=-1;i<=0;i++){ /* for each fragment size, verify that there is at most one page * of fragments, and that that page is empty - then free it */ for(j=0;jsize != PAGESIZE) return 3; if (frag->nextpg != NO_PAGE) return 4; pgfree(area,heap,pages,ipage,1); heap[i].fr[j] = NO_PAGE; } } /* now there should be no allocated pages at all - check the page array */ numpages = heap->numpages; if ( pages[-1].offset != 0 ) return 10; if (pages[-1].type!=P_allocpage) return 11; if (pages[0].offset != NO_PAGE) return 12; if (pages[0].type != P_freerange) return 13; if (pages[1].offset != numpages) return 14; if (pages[1].type != P_freesize) return 15; if (pages[numpages-1].offset!=0) return 16; if (pages[numpages-1].type!=P_freerangeend) return 17; return 0 ; /* successful check */ }