916 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			916 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* SB - Copyright 1982 by Ken Harrenstien, SRI International
 | ||
|  *	This software is quasi-public; it may be used freely with
 | ||
|  *	like software, but may NOT be sold or made part of licensed
 | ||
|  *	products without permission of the author.  In all cases
 | ||
|  *	the source code and any modifications thereto must remain
 | ||
|  *	available to any user.
 | ||
|  *
 | ||
|  *	This is part of the SB library package.
 | ||
|  *	Any software using the SB library must likewise be made
 | ||
|  *	quasi-public, with freely available sources.
 | ||
|  */
 | ||
| 
 | ||
| #if 0
 | ||
| 	This file contains the low-level memory allocation
 | ||
| subroutines which are used by the SBLK routines.  The code here
 | ||
| is quite machine-dependent, and the definitions in "sb.h" should be
 | ||
| carefully checked to verify that they are correct for the target
 | ||
| machine.
 | ||
| 
 | ||
| 	The ultimate low-level routine is "sbrk()" which must be
 | ||
| provided by the system''s C library.  SBM expects that successive calls
 | ||
| to sbrk() will return contiguous areas of memory with progressively
 | ||
| higher addresses.  Also, the very first call to sbrk() is assumed to
 | ||
| return a word-aligned address.
 | ||
| #endif /*COMMENT*/
 | ||
| 
 | ||
| #include "sb.h"
 | ||
| 
 | ||
| #define FUDGE (sizeof(struct smblk))	/* Allow this much fudge in
 | ||
| 				allocation, to prevent undue fragmentation */
 | ||
| 
 | ||
| char *(*sbm_debug)();           /* Debug switch - user-furnished routine */
 | ||
| 
 | ||
| struct smblk *sbm_nfl;          /* Pointer to node freelist */
 | ||
| struct smblk *sbm_nxtra;	/* Reserved extra free node */
 | ||
| struct smblk *sbm_list;         /* Pointer to smblk memory alloc list.
 | ||
| 				 * ALL smblks are strung onto this list
 | ||
| 				 * except for the freelist!
 | ||
| 				 */
 | ||
| SBMA sbm_lowaddr;		/* Lowest word-aligned address we know about.*/
 | ||
| 
 | ||
| /* If compiling with debug switch set, use special routine in place of
 | ||
|  * sbrk so we can pretend we have a very limited area of free memory.
 | ||
|  */
 | ||
| #ifdef DBG_SIZE
 | ||
| #define SBM_SBRK sbm_brk
 | ||
| char *sbm_brk();
 | ||
| #else
 | ||
| #define SBM_SBRK sbrk
 | ||
| #endif /*DBG_SIZE*/
 | ||
| 
 | ||
| /* Forward routine declarations */
 | ||
| char *sbrk();
 | ||
| struct smblk *sbm_nmak(), *sbm_nget(), *sbm_mget(), *sbm_split();
 | ||
| struct smblk *sbm_lmak(), *sbm_err();
 | ||
| 
 | ||
| /* SBM_INIT - Initialize storage management.
 | ||
|  *	If args are zero, normal initialization is done.  Otherwise,
 | ||
|  *	args are understood to be pointers to an area of memory allocated
 | ||
|  *	on the stack (eg by an "int mem[2000]" declaration in MAIN) and
 | ||
|  *	initialization will include this area in addition to the
 | ||
|  *	unused space between "_end" and the start of the stack segment.
 | ||
|  *	This is mostly of use for PDP11s which would otherwise waste a lot
 | ||
|  *	of address space.
 | ||
|  *	Maybe should have a SBM_RESET() function?
 | ||
|  */
 | ||
| 
 | ||
| struct smblk *
 | ||
| sbm_init(xaddr,xlen)
 | ||
| SBMA xaddr;		/* Address of allocated stack area if any */
 | ||
| SBMO xlen;		/* Size of this area */
 | ||
| {       register struct smblk *sm, *sml;
 | ||
| 	register char *cp;
 | ||
| 
 | ||
| 	/* Get initial chunk of memory from standard system rtn */
 | ||
| 	if((cp = SBM_SBRK(SMNODES*sizeof(struct smblk))) == 0
 | ||
| 	  || (int) cp == -1)
 | ||
| 		return(sbm_err(0,"Can't sbrk"));
 | ||
| 	sm = (struct smblk *)cp;		/* Better be word-aligned! */
 | ||
| 	sbm_lmak(sm,(SBMO)sizeof(struct smblk),SMNODES);      /* Make list */
 | ||
| 	sbm_nfl = sm;				/* Point freelist at it */
 | ||
| 	sbm_lowaddr = (SBMA)sm;			/* Remember lowest addr seen */
 | ||
| 
 | ||
| 	/* Set up 1st node pointing to all memory from here on up.
 | ||
| 	 * We don't know exactly how much will be available at this point,
 | ||
| 	 * so we just pretend we have the maximum possible.
 | ||
| 	 */
 | ||
| 	sbm_list = sml = sbm_nget();
 | ||
| 	sml->smforw = sml->smback = 0;
 | ||
| 	sml->smflags = SM_USE|SM_NID;		/* Initial flags */
 | ||
| 	sml->smaddr = (SBMA) sml;
 | ||
| 	sml->smlen = MAXSBMO;			/* Pretend we have lots */
 | ||
| 	sml->smuse = (SMNODES * sizeof(struct smblk));
 | ||
| 
 | ||
| 	/* Now split off everything above initial allocation as NXM. */
 | ||
| 	sm = sbm_split(sml, sm->smuse);
 | ||
| 	sml->smflags |= SM_MNODS;	/* Mark 1st node as having SM nodes */
 | ||
| 	sm->smflags  |= SM_NXM;		/* Mark 2nd node as NXM */
 | ||
| 
 | ||
| 	/* Now possibly set up extra nodes, if stack mem is being allocated
 | ||
| 	 * (From our viewpoint it looks as if a chunk in the middle of
 | ||
| 	 * the initial NXM section has been declared usable)
 | ||
| 	 */
 | ||
| 	if(xlen)
 | ||
| 	  {     /* Allow for "extra" static stack memory */
 | ||
| 		/* Will lose if xaddr <= 1st NXM! */
 | ||
| 		sml = sbm_split(sm, (SBMO)(xaddr - sm->smaddr));
 | ||
| 		sbm_split(sml, xlen);		/* Split off following NXM */
 | ||
| 		sml->smflags &= ~(SM_USE|SM_NXM); /* This node is free mem! */
 | ||
| 	  }
 | ||
| 
 | ||
| 	/* Now set up a small additional node which points to the NXM
 | ||
| 	 * that we cannot get from SBRK.  At this stage, this is just
 | ||
| 	 * a place-holder, to reserve the node so we don't have to
 | ||
| 	 * worry about running out of nodes at the same time sbrk stops
 | ||
| 	 * returning memory.
 | ||
| 	 * SM points to the NXM that we expect SBRK to dig into.
 | ||
| 	 */
 | ||
| 	sbm_split(sm, sm->smlen - WDSIZE); /* Chop off teensy bit */
 | ||
| 	sm->smflags &= ~SM_USE;		/* Now mark NXM "free"!! */
 | ||
| 
 | ||
| 	/* Finally, reserve an "extra" SM node for use by sbm_nget
 | ||
| 	 * when it is allocating more freelist node chunks.
 | ||
| 	 */
 | ||
| 	sbm_nxtra = sbm_nget();
 | ||
| 
 | ||
| 	return(sbm_list);
 | ||
| }
 | ||
| 
 | ||
| /* SBM_NGET() - Get a free SM node.
 | ||
|  *	Note the hair to provide a spare SM node when
 | ||
|  *	we are allocating memory for more SM nodes.  This is necessary
 | ||
|  *	because sbm_mget and sbm_nget call each other recursively and
 | ||
|  *	sbm_mget cannot create any new memory without a SM node to point
 | ||
|  *	at the allocated chunk.
 | ||
|  */
 | ||
| struct smblk *
 | ||
| sbm_nget()
 | ||
| {       register struct smblk *sm, *sml;
 | ||
| 
 | ||
| 	if(!(sm = sbm_nfl))		/* Get a node from freelist */
 | ||
| 	  {	/* Freelist is empty, try to allocate more nodes. */
 | ||
| 
 | ||
| 		/* Put our "spare" smblk on freelist temporarily so that
 | ||
| 		 * sbm_mget has a chance of winning.
 | ||
| 		 * Infinite recursion is avoided by a test
 | ||
| 		 * in sbm_mget which checks sbm_nfl and sbm_nxtra.
 | ||
| 		 */
 | ||
| 		if(!(sm = sbm_nxtra))
 | ||
| 			return(sbm_err(0,"Zero sbm_nxtra!"));
 | ||
| 		sm->smforw = 0;
 | ||
| 		sbm_nfl = sm;
 | ||
| 		sbm_nxtra = 0;
 | ||
| 
 | ||
| 		/* Try to allocate another chunk of SM nodes. */
 | ||
| 		sml = sbm_nmak(sizeof(struct smblk),SM_MNODS);
 | ||
| 
 | ||
| 		/* Put the new free nodes (if any) on freelist.
 | ||
| 		 * Done this way because freelist may have had one or two
 | ||
| 		 * nodes added to it by sbm_mget, so can't just stick
 | ||
| 		 * a new pointer in sbm_nfl.
 | ||
| 		 */
 | ||
| 		while(sm = sml)
 | ||
| 		  {	sml = sm->smforw;
 | ||
| 			sbm_nfre(sm);
 | ||
| 		  }
 | ||
| 
 | ||
| 		/* Now reserve an extra node again.
 | ||
| 		 * It is an error if there is nothing on freelist here,
 | ||
| 		 * because even if sbm_mget failed the "extra node" should
 | ||
| 		 * still be on freelist.  The check for a zero sbm_nxtra
 | ||
| 		 * above will catch such an error.
 | ||
| 		 */
 | ||
| 		sbm_nxtra = sbm_nget();
 | ||
| 
 | ||
| 		/* Now see if anything to return */
 | ||
| 		if(!(sm = sbm_nfl))		/* If freelist empty again, */
 | ||
| 			return(0);		/* give up. */
 | ||
| 	  }
 | ||
| 	sbm_nfl = sm->smforw;   /* If win, take it off freelist */
 | ||
| 	return(sm);		/* Return ptr or 0 if none */
 | ||
| }
 | ||
| 
 | ||
| /* SBM_NFRE(sm) - Return a SM node to the SM freelist.
 | ||
|  */
 | ||
| sbm_nfre(smp)
 | ||
| struct smblk *smp;
 | ||
| {       register struct smblk *sm;
 | ||
| 	(sm = smp)->smflags = 0;
 | ||
| 	sm->smforw = sbm_nfl;
 | ||
| 	sbm_nfl = sm;
 | ||
| }
 | ||
| 
 | ||
| /* SBM_NMAK(elsize, flag) - Make (allocate & build) a typeless node freelist.
 | ||
|  */
 | ||
| struct smblk *
 | ||
| sbm_nmak(elsize, flag)
 | ||
| SBMO elsize;
 | ||
| unsigned flag;
 | ||
| {       register struct smblk *sm, *smp;
 | ||
| 	register int cnt;
 | ||
| 
 | ||
| 	if((sm = sbm_mget(SMNODES*elsize,SMNODES*elsize)) == 0)
 | ||
| 		return(0);
 | ||
| 
 | ||
| 	sm->smflags |= flag;            /* Indicate type of nodes */
 | ||
| 	cnt = sm->smlen/elsize;		/* Find # nodes that will fit */
 | ||
| 	sm->smuse = cnt * elsize;	/* Actual size used */
 | ||
| 	smp = (struct smblk *)(sm->smaddr);	/* Ptr to 1st loc of mem */
 | ||
| 	sbm_lmak(smp, (SBMO)elsize, cnt);	/* Build freelist */
 | ||
| 	return(smp);            /* Return 1st free node. Caller is */
 | ||
| 				/* responsible for setting freelist ptr. */
 | ||
| }
 | ||
| 
 | ||
| /* SBM_LMAK - Build freelist of typeless nodes.
 | ||
|  *	Note this does not allocate memory, it just converts an already
 | ||
|  *	allocated memory area.
 | ||
|  */
 | ||
| struct smblk *
 | ||
| sbm_lmak(addr, elsize, num)
 | ||
| SBMA addr;
 | ||
| SBMO elsize;
 | ||
| int num;
 | ||
| {	register struct smblk *sm, *smp;
 | ||
| 	register int cnt;
 | ||
| 
 | ||
| 	smp = (struct smblk *) addr;
 | ||
| 	if((cnt = num) <= 0)
 | ||
| 		return(0);
 | ||
| 	do {	sm = smp;       /* Save ptr */
 | ||
| 		sm->smforw = (smp = (struct smblk *) ((SBMA)smp + elsize));
 | ||
| 		sm->smflags = 0;
 | ||
| 	  } while(--cnt);
 | ||
| 	sm->smforw = 0;         /* Last node points to nothing */
 | ||
| 	return(sm);		/* Return ptr to last node */
 | ||
| }
 | ||
| 
 | ||
| /* SBM_NMOV(sm1, sm2, begp, elsize) - Move a typeless node.
 | ||
|  *	Copy sm1 to sm2, adjust ptrs, leave sm1 free.
 | ||
|  */
 | ||
| sbm_nmov(smp1,smp2,begp,elsize)
 | ||
| struct smblk *smp1, *smp2, **begp;
 | ||
| int elsize;
 | ||
| {       register struct smblk *sm;
 | ||
| 
 | ||
| 	bcopy((SBMA)smp1,(SBMA)(sm = smp2), elsize);     /* Copy the stuff */
 | ||
| 	if(sm->smforw) sm->smforw->smback = sm; /* Fix up links */
 | ||
| 	if(sm->smback) sm->smback->smforw = sm;
 | ||
| 	else *begp = sm;
 | ||
| }
 | ||
| 
 | ||
| /* SBM_MGET(min,max) - Get a SMBLK with specified amount of memory.
 | ||
|  *      Returns 0 if none available.
 | ||
|  *      Memory is guaranteed to start on word boundary, but may not
 | ||
|  *              end on one.  Note that sbm_mfree is responsible for
 | ||
|  *              ensuring that free mem starts word-aligned.
 | ||
|  *	A subtle but major concern of this code is the number of freelist
 | ||
|  * nodes gobbled by a single call.  If the freelist happens to not have
 | ||
|  * enough nodes, then a recursive call to sbm_mget is made (via sbm_nget)
 | ||
|  * in order to allocate a new batch of freelist nodes!  sbm_nget will
 | ||
|  * always provide a single "spare" node during such an allocation, but
 | ||
|  * there is only one and it is essential that sbm_mget gobble only ONE
 | ||
|  * (if any) during such a call, which is indicated by sbm_nxtra==0.
 | ||
|  *	The maximum # of freelist nodes that sbm_mget can gobble is
 | ||
|  * 2, when (1) NXM memory is obtained, and a SM is needed to point at
 | ||
|  * the new free mem, plus (2) the resulting SM is too big, and has to
 | ||
|  * be split up, which requires another SM for the remainder.
 | ||
|  *	The "used-NXM" smblk is set up at init time precisely in order to
 | ||
|  * avoid the necessity of creating it here when sbrk stops winning, since
 | ||
|  * that would require yet another freelist node and make it possible for
 | ||
|  * sbm_mget to gobble 3 during one call -- too many.
 | ||
|  *	Further note: the sbm_nfl checks are necessary in order
 | ||
|  * to ensure that a SM node is available for use by sbm_split.  Otherwise
 | ||
|  * the calls to sbm_split might create a new SM freelist by gobbling the
 | ||
|  * very memory which we are hoping to return!
 | ||
|  */
 | ||
| SBMO sbm_chksiz = SMCHUNKSIZ;	/* Current chunk size to feed sbrk */
 | ||
| 
 | ||
| struct smblk *
 | ||
| sbm_mget(cmin,cmax)
 | ||
| SBMO cmin,cmax;
 | ||
| {       register struct smblk *sm, *sml;
 | ||
| 	register SBMO csiz;
 | ||
| 	register SBMA addr, xaddr;
 | ||
| 
 | ||
| 	if((sm = sbm_list) == 0         /* If never done, */
 | ||
| 	  && (sm = sbm_init((SBMA)0,(SBMO)0)) == 0)	/* initialize mem alloc stuff. */
 | ||
| 		return(0);		/* Can't init??? */
 | ||
| 
 | ||
| 	/* Round up sizes to word boundary */
 | ||
| 	if(rndrem(cmin)) cmin = rndup(cmin);
 | ||
| 	if(rndrem(cmax)) cmax = rndup(cmax);
 | ||
| 
 | ||
| 	/* Search for a free block having enough memory.
 | ||
| 	 * If run into a free-NXM block, always "win", since there may be
 | ||
| 	 * a combination of preceding free-mem and new mem which will satisfy
 | ||
| 	 * the request.  If it turns out this didn't work, we'll just fail
 | ||
| 	 * a little farther on.
 | ||
| 	 */
 | ||
| retry:	csiz = cmin;			/* Set size that will satisfy us */
 | ||
| 	do {
 | ||
| 		if(  ((sm->smflags&SM_USE) == 0)
 | ||
| 		  && ((sm->smlen >= csiz) || (sm->smflags&SM_NXM)) )
 | ||
| 			break;
 | ||
| 	  } while(sm = sm->smforw);
 | ||
| 	if(sm == 0)
 | ||
| 		return(0);	/* Found none that minimum would fit */
 | ||
| 
 | ||
| 	if(sm->smflags&SM_NXM)
 | ||
| 	  {	/* Found free area, but it's marked NXM and the system
 | ||
| 		 * must be persuaded (via sbrk) to let us use that portion
 | ||
| 		 * of our address space.  Grab a good-sized chunk.
 | ||
| 		 */
 | ||
| 		if(sbm_nfl == 0)	/* Verify a spare SM node is avail */
 | ||
| 			goto getnod;	/* Nope, must get one. */
 | ||
| 
 | ||
| 		/* Decide amount of mem to ask system for, via sbrk.
 | ||
| 		 * The fine point here is the check of sbm_nxtra to make sure
 | ||
| 		 * that, when building more freelist nodes, we don't have
 | ||
| 		 * to use more than one SM node in the process.  If we
 | ||
| 		 * asked for too much mem, we'd have to use a SM node
 | ||
| 		 * to hold the excess after splitting.
 | ||
| 		 */
 | ||
| 		csiz = cmax;
 | ||
| 		if(sbm_nxtra		/* If normal then try for big chunk */
 | ||
| 		  && csiz < sbm_chksiz) csiz = sbm_chksiz;	/* Max */
 | ||
| 		if (csiz > sm->smlen)  csiz = sm->smlen;	/* Min */
 | ||
| 
 | ||
| 		/* Get the NXM mem */
 | ||
| 		if((addr = (SBMA)SBM_SBRK(csiz)) != sm->smaddr)
 | ||
| 		  {     /* Unexpected value returned from SBRK! */
 | ||
| 
 | ||
| 			if((int)addr != 0 && (int)addr != -1)
 | ||
| 			  {	return(sbm_err(0,"SBRK %o != %o", addr,
 | ||
| 						sm->smaddr));
 | ||
| #if 0
 | ||
| 			/* If value indicates couldn't get the stuff, then
 | ||
| 			 * we have probably hit our limit and the rest of
 | ||
| 			 * NXM should be declared "used" to prevent further
 | ||
| 			 * hopeless sbrk calls.  We split off the portion
 | ||
| 			 * of NXM that is known for sure to be unavailable,
 | ||
| 			 * and mark it "used".  If a "used NXM" area already
 | ||
| 			 * exists following this one, the two are merged.
 | ||
| 			 * The chunk size is then reduced by half, so
 | ||
| 			 * only log2(SMCHUNKSIZ) attempts will be made, and
 | ||
| 			 * we try again.
 | ||
| 			 */
 | ||
| 				/* If returned some mem which starts outside
 | ||
| 				 * the NXM then something is screwed up. */
 | ||
| 				if(addr < sm->smaddr
 | ||
| 				  || (addr >= sm->smaddr+sm->smlen))
 | ||
| 					return(sbm_err(0,"SBRK %o != %o",
 | ||
| 						addr, sm->smaddr));
 | ||
| 				/* Got some mem, falls within NXM.
 | ||
| 				 * Presumably someone else has called sbrk
 | ||
| 				 * since last time, so we need to fence off
 | ||
| 				 * the intervening area. */
 | ||
| 				sm = sbm_split((sml=sm),(addr - sm->smaddr));
 | ||
| 				sml->smflags |= SM_USE|SM_EXT;
 | ||
| 				return(sbm_mget(cmin,cmax));
 | ||
| #endif /*COMMENT*/
 | ||
| 			  }
 | ||
| 
 | ||
| 			/* Handle case of SBRK claiming no more memory.
 | ||
| 			 * Gobble as much as we can, and then turn this NXM
 | ||
| 			 * block into a free-mem block, and leave the
 | ||
| 			 * remainder in the used-NXM block (which should
 | ||
| 			 * immediately follow this free-NXM block!)
 | ||
| 			 */
 | ||
| 			if(!(sml = sm->smforw)	/* Ensure have used-NXM blk */
 | ||
| 			  || (sml->smflags&(SM_USE|SM_NXM))
 | ||
| 					!= (SM_USE|SM_NXM))
 | ||
| 				return(sbm_err(0,"No uNXM node!"));
 | ||
| 			xaddr = sm->smaddr;	/* Use this for checking */
 | ||
| 			sm->smuse = 0;		/* Use this for sum */
 | ||
| 			for(csiz = sm->smlen; csiz > 0;)
 | ||
| 			  {	addr = SBM_SBRK(csiz);
 | ||
| 				if((int)addr == 0 || (int)addr == -1)
 | ||
| 				  {	csiz >>= 1;
 | ||
| 					continue;
 | ||
| 				  }
 | ||
| 				if(addr != xaddr)
 | ||
| 					return(sbm_err(0,"SBRK %o != %o", addr,
 | ||
| 						xaddr));
 | ||
| 				sm->smuse += csiz;
 | ||
| 				xaddr += csiz;
 | ||
| 			  }
 | ||
| 
 | ||
| 			/* Have gobbled as much from SBRK as we could.
 | ||
| 			 * Turn the free-NXM block into a free-mem block,
 | ||
| 			 * unless we got nothing, in which case just merge
 | ||
| 			 * it into the used-NXM block and continue
 | ||
| 			 * searching from this point.
 | ||
| 			 */
 | ||
| 			if(!(csiz = sm->smuse))	/* Get total added */
 | ||
| 			  {	sm->smflags = sml->smflags;	/* Ugh. */
 | ||
| 				sbm_mmrg(sm);
 | ||
| 				goto retry;		/* Keep looking */
 | ||
| 			  }
 | ||
| 			else
 | ||
| 			  {	sml->smaddr = sm->smaddr + csiz;
 | ||
| 				sml->smlen += sm->smlen - csiz;
 | ||
| 				sm->smlen = csiz;
 | ||
| 				sm->smflags &= ~SM_NXM;	/* No longer NXM */
 | ||
| 			  }
 | ||
| 		  }
 | ||
| 
 | ||
| 		/* Here when we've acquired CSIZ more memory from sbrk.
 | ||
| 		 * If preceding mem area is not in use, merge new mem
 | ||
| 		 * into it.
 | ||
| 		 */
 | ||
| 		if((sml = sm->smback) && 
 | ||
| 		  (sml->smflags&(SM_USE|SM_NXM))==0)    /* Previous free? */
 | ||
| 		  {     sml->smlen += csiz;		/* Yes, simple! */
 | ||
| 			sm->smaddr += csiz;		/* Fix up */
 | ||
| 			if((sm->smlen -= csiz) == 0)	/* If no NXM left,*/
 | ||
| 				sbm_mmrg(sml);	/* Merge NXM node w/prev */
 | ||
| 			sm = sml;		/* Prev is now winning node */
 | ||
| 		  }
 | ||
| 		else
 | ||
| 		  {	/* Prev node isn't a free area.  Split up the NXM
 | ||
| 			 * node to account for acquired mem, unless we
 | ||
| 			 * gobbled all the mem available.
 | ||
| 			 */
 | ||
| 			if(sm->smlen > csiz	/* Split unless all used */
 | ||
| 			  && !sbm_split(sm,csiz)) /* Call shd always win */
 | ||
| 				return(sbm_err(0,"getsplit err: %o",sm));
 | ||
| 			sm->smflags &= ~SM_NXM;	/* Node is now real mem */
 | ||
| 		  }
 | ||
| 
 | ||
| 		/* Now make a final check that we have enough memory.
 | ||
| 		 * This can fail because SBRK may not have been able
 | ||
| 		 * to gobble enough memory, either because (1) not
 | ||
| 		 * as much NXM was available as we thought,
 | ||
| 		 * or (2) we noticed the free-NXM area and immediately
 | ||
| 		 * gambled on trying it without checking any lengths.
 | ||
| 		 * In any case, we try again starting from the current SM
 | ||
| 		 * because there may be more free mem higher up (eg on
 | ||
| 		 * stack).
 | ||
| 		 */
 | ||
| 		if(sm->smlen < cmin)
 | ||
| 			goto retry;
 | ||
| 	  }
 | ||
| 
 | ||
| 	/* Check to see if node has too much mem.  This is especially true
 | ||
| 	 * for memory just acquired via sbrk, which gobbles a huge chunk each
 | ||
| 	 * time.  If there's too much, we split up the area.
 | ||
| 	 */
 | ||
| 	if(sm->smlen > cmax+FUDGE)	/* Got too much?  (Allow some fudge)*/
 | ||
| 		/* Yes, split up so don't gobble too much. */
 | ||
| 		if(sbm_nfl)                     /* If success guaranteed, */
 | ||
| 			sbm_split(sm,cmax);     /* split it, all's well. */
 | ||
| 		else goto getnod;
 | ||
| 
 | ||
| 	sm->smuse = 0;
 | ||
| 	sm->smflags |= SM_USE;  /* Finally seize it by marking "in-use". */
 | ||
| 	return(sm);
 | ||
| 
 | ||
| 	/* Come here when we will need to get another SM node but the
 | ||
| 	 * SM freelist is empty.  We have to forget about using the area
 | ||
| 	 * we just found, because sbm_nget may gobble it for the
 | ||
| 	 * freelist.  So, we first force a refill of the freelist, and then
 | ||
| 	 * invoke ourselves again on what's left.
 | ||
| 	 */
 | ||
| getnod:
 | ||
| 	if(sml = sbm_nget())		/* Try to build freelist */
 | ||
| 	  {	sbm_nfre(sml);		/* Won, give node back, */
 | ||
| 		sm = sbm_list;		/* and retry, starting over! */
 | ||
| 		goto retry;	
 | ||
| 	  }
 | ||
| 	/* Failed.  Not enough memory for both this request
 | ||
| 	 * and one more block of SM nodes.  Since such a SM_MNODS
 | ||
| 	 * block isn't very big, we are so close to the limits that it
 | ||
| 	 * isn't worth trying to do something fancy here to satisfy the
 | ||
| 	 * original request.  So we just fail.
 | ||
| 	 */
 | ||
| 	return(0);
 | ||
| }
 | ||
| 
 | ||
| #ifdef DBG_SIZE
 | ||
| /* Code for debugging stuff by imposing an artificial limitation on size
 | ||
|  * of available memory.
 | ||
|  */
 | ||
| SBMO sbm_dlim = MAXSBMO;	/* Amount of mem to allow (default is max) */
 | ||
| 
 | ||
| char *
 | ||
| sbm_brk(size)
 | ||
| unsigned size;
 | ||
| {	register char *addr;
 | ||
| 
 | ||
| 	if(size > sbm_dlim) return(0);
 | ||
| 	addr = sbrk(size);
 | ||
| 	if((int)addr == 0 || (int)addr == -1)
 | ||
| 		return(0);
 | ||
| 	sbm_dlim -= size;
 | ||
| 	return(addr);
 | ||
| }
 | ||
| #endif /*DBG_SIZE*/
 | ||
| 
 | ||
| /* SBM_MFREE(sm) - Free up an allocated memory area.
 | ||
|  */
 | ||
| sbm_mfree(sm)
 | ||
| register struct smblk *sm;
 | ||
| {       register struct smblk *smx;
 | ||
| 	register SBMO crem;
 | ||
| 
 | ||
| 	sm->smflags &= ~SM_USE;			/* Say mem is free */
 | ||
| 	if((smx = sm->smback)                   /* Check preceding mem */
 | ||
| 	  && (smx->smflags&(SM_USE|SM_NXM))==0) /*   If it's free, */
 | ||
| 		sbm_mmrg(sm = smx);		/*   then merge 'em. */
 | ||
| 	if((smx = sm->smforw)			/* Check following mem */
 | ||
| 	  && (smx->smflags&(SM_USE|SM_NXM))==0) /*   Again, if free,  */
 | ||
| 		sbm_mmrg(sm);                   /*   merge them.   */
 | ||
| 
 | ||
| 	if(sm->smlen == 0)              /* Just in case, chk for null blk */
 | ||
| 	  {     if(smx = sm->smback)            /* If pred exists, */
 | ||
| 			sbm_mmrg(smx);          /* merge quietly. */
 | ||
| 		else {
 | ||
| 			sbm_list = sm->smforw;  /* 1st node on list, so */
 | ||
| 			sbm_nfre(sm);           /* simply flush it. */
 | ||
| 		  }
 | ||
| 		return;
 | ||
| 	  }
 | ||
| 
 | ||
| 	/* This code is slightly over-general for some machines.
 | ||
| 	 * The pointer subtraction is done in order to get a valid integer
 | ||
| 	 * offset value regardless of the internal representation of a pointer.
 | ||
| 	 * We cannot reliably force alignment via casts; some C implementations
 | ||
| 	 * treat that as a no-op.
 | ||
| 	 */
 | ||
| 	if(crem = rndrem(sm->smaddr - sbm_lowaddr))	/* On word bndry? */
 | ||
| 	  {     /* No -- must adjust.  All free mem blks MUST, by fiat,
 | ||
| 		 * start on word boundary.  Here we fix things by
 | ||
| 		 * making the leftover bytes belong to the previous blk,
 | ||
| 		 * no matter what it is used for.  Prev blk is guaranteed to
 | ||
| 		 * (1) Exist (this cannot be 1st blk since 1st is known to
 | ||
| 		 * start on wd boundary) and to be (2) Non-free (else it would
 | ||
| 		 * have been merged).
 | ||
| 		 */
 | ||
| 		if((smx = sm->smback) == 0)     /* Get ptr to prev blk */
 | ||
| 		  {	sbm_err(0,"Align err");	/* Catch screws */
 | ||
| 			return;
 | ||
| 		  }
 | ||
| 		crem = WDSIZE - crem;	/* Find # bytes to flush */
 | ||
| 		if(crem >= sm->smlen)	/* Make sure node has that many */
 | ||
| 		  {	sbm_mmrg(smx);  /* Flush node to avoid zero length */
 | ||
| 			return;
 | ||
| 		  }
 | ||
| 		smx->smlen += crem;	/* Make stray bytes part of prev */
 | ||
| 		sm->smaddr += crem;	/* And flush from current. */
 | ||
| 		sm->smlen -= crem;
 | ||
| 	  }
 | ||
| }
 | ||
| 
 | ||
| /* SBM_EXP - Expand (or shrink) size of an allocated memory chunk.
 | ||
|  *	"nsize" is desired new size; may be larger or smaller than current
 | ||
|  *	size.
 | ||
|  */
 | ||
| struct smblk *
 | ||
| sbm_exp(sm,size)
 | ||
| register struct smblk *sm;
 | ||
| register SBMO size;
 | ||
| {       register struct smblk *smf;
 | ||
| 	register SBMO mexp, pred, succ;
 | ||
| 
 | ||
| 	if(sm->smlen >= size)		/* Do we want truncation? */
 | ||
| 		goto realo2;		/* Yup, go split block */
 | ||
| 
 | ||
| 	/* Block is expanding. */
 | ||
| 	mexp = size - sm->smlen;		/* Get # bytes to expand by */
 | ||
| 	pred = succ = 0;
 | ||
| 	if((smf = sm->smforw)           	/* See if free mem follows */
 | ||
| 	 && (smf->smflags&(SM_USE|SM_NXM)) == 0)
 | ||
| 		if((succ = smf->smlen) >= mexp)
 | ||
| 			goto realo1;		/* Quick stuff if succ OK */
 | ||
| 
 | ||
| 	if((smf = sm->smback)			/* See if free mem precedes */
 | ||
| 	 && (smf->smflags&(SM_USE|SM_NXM)) == 0)
 | ||
| 		pred = smf->smlen;
 | ||
| 
 | ||
| 	/* If not enough free space combined on both sides of this chunk,
 | ||
| 	 * we have to look for a completely new block.
 | ||
| 	 */
 | ||
| 	if(pred+succ < mexp)
 | ||
| 	  {	if((smf = sbm_mget(size,size)) == 0)
 | ||
| 			return(0);              /* Couldn't find one */
 | ||
| 		else pred = 0;			/* Won, indicate new block */
 | ||
| 	  }
 | ||
| 
 | ||
| 	/* OK, must copy either into new block or down into predecessor
 | ||
| 	 * (overlap is OK as long as bcopy moves 1st byte first)
 | ||
| 	 */
 | ||
| 	bcopy(sm->smaddr, smf->smaddr, sm->smlen);
 | ||
| 	smf->smflags = sm->smflags;     /* Copy extra attribs */
 | ||
| 	smf->smuse = sm->smuse;
 | ||
| 	if(!pred)			/* If invoked sbm_mget */
 | ||
| 	  {	sbm_mfree(sm);		/* then must free up old area */
 | ||
| 		return(smf);		/* and can return immediately. */
 | ||
| 	  }
 | ||
| 	sbm_mmrg(smf);			/* Merge current into pred blk */
 | ||
| 	sm = smf;			/* Now pred is current blk. */
 | ||
| 
 | ||
| 	if(succ)
 | ||
| realo1:		sbm_mmrg(sm);		/* Merge succ into current blk */
 | ||
| realo2: if(sm->smlen > size		/* If now have too much, */
 | ||
| 	  && sbm_split(sm, size))       /* split up and possibly */
 | ||
| 		sbm_mfree(sm->smforw);  /* free up unused space. */
 | ||
| 	return(sm);
 | ||
| 
 | ||
| 	/* Note that sbm_split can fail if it can't get a free node,
 | ||
| 	 * which is only possible if we are reducing the size of an area.
 | ||
| 	 * If it fails, we just return anyway without truncating the area.
 | ||
| 	 */
 | ||
| }
 | ||
| 
 | ||
| /* SBM_MMRG(sm) - Merge a memory area with the area following it.
 | ||
|  *	The node (and memory area) following the SM pointed to are
 | ||
|  *	merged in and the successor node freed up.  The flags
 | ||
|  *	and smuse of the current SM (which is not moved or anything)
 | ||
|  *	remain the same.
 | ||
|  */
 | ||
| sbm_mmrg(smp)
 | ||
| struct smblk *smp;
 | ||
| {       register struct smblk *sm, *sm2;
 | ||
| 
 | ||
| 	sm = smp;
 | ||
| 	sm->smlen += (sm2 = sm->smforw)->smlen;	/* Add succ's len */
 | ||
| 	if(sm->smforw = sm2->smforw)            /* and fix linkages */
 | ||
| 		sm->smforw->smback = sm;
 | ||
| 	sbm_nfre(sm2);                          /* now can flush succ node */
 | ||
| }
 | ||
| 
 | ||
| /* SBM_SPLIT - Split up an area (gets a new smblk to point to split-off
 | ||
|  *	portion.)
 | ||
|  * Note returned value is ptr to 2nd smblk, since this is a new one.
 | ||
|  * Ptr to 1st remains valid since original smblk stays where it is.
 | ||
|  * NOTE: Beware of splitting up free mem (SM_USE == 0) since sbm_nget may
 | ||
|  * steal it out from under unless precautions are taken!  See comments
 | ||
|  * at sbm_mget related to this.
 | ||
|  */
 | ||
| struct smblk *
 | ||
| sbm_split(smp,coff)
 | ||
| struct smblk *smp;
 | ||
| SBMO coff;
 | ||
| {       register struct smblk *sm, *smx;
 | ||
| 	register SBMO csiz;
 | ||
| 
 | ||
| 	if((sm = smp)->smlen <= (csiz = coff))
 | ||
| 		return(0);
 | ||
| 	if((smx = sbm_nget()) == 0)
 | ||
| 		return(0);
 | ||
| 	smx->smlen = sm->smlen - csiz;          /* Set 2nd size */
 | ||
| 	smx->smaddr = sm->smaddr + csiz;        /* Set 2nd addr */
 | ||
| 	sm->smlen = csiz;			/* Take from 1st size */
 | ||
| 	smx->smflags = sm->smflags;             /* Copy flags */
 | ||
| 	if(smx->smforw = sm->smforw)            /* Splice 2nd after 1 */
 | ||
| 		smx->smforw->smback = smx;
 | ||
| 	smx->smback = sm;
 | ||
| 	sm->smforw = smx;                       /* Put 2nd into chain */
 | ||
| 	return(smx);                            /* Return ptr to 2nd smblk */
 | ||
| }
 | ||
| 
 | ||
| #if 0	/* Replaced by "bcopy" for system-dep efficiency */
 | ||
| /* SBM_SCPY - Copy string of bytes.  Somewhat machine-dependent;
 | ||
|  *	Tries to be clever about using word moves instead of byte moves.
 | ||
|  */
 | ||
| sbm_scpy(from, to, count)       /* Copy count bytes from -> to */
 | ||
| char *from, *to;
 | ||
| unsigned count;
 | ||
| {       register char *s1, *s2;
 | ||
| 	register unsigned cnt;
 | ||
| 	int tmp;
 | ||
| 
 | ||
| 	if((cnt = count) == 0)
 | ||
| 		return;
 | ||
| 	s1 = from;
 | ||
| 	s2 = to;
 | ||
| 	while(rndrem((int)s1))		/* Get 1st ptr aligned */
 | ||
| 	  {     *s2++ = *s1++;
 | ||
| 		if(--cnt == 0) return;
 | ||
| 	  }
 | ||
| 	if(rndrem((int)s2) == 0)	/* Do wd move if ptr 2 now aligned */
 | ||
| 	  {
 | ||
| #ifdef DUMBPCC /* Code for dumber (Portable C type) compiler */
 | ||
| 		register WORD *ap, *bp;
 | ||
| 		tmp = cnt;
 | ||
| 		ap = (WORD *) s1;
 | ||
| 		bp = (WORD *) s2;
 | ||
| 		if(cnt = rnddiv(cnt))
 | ||
| 			do { *bp++ = *ap++; }
 | ||
| 			while(--cnt);
 | ||
| 		if ((cnt = rndrem(tmp)) ==0)
 | ||
| 			return;
 | ||
| 		s1 = (char *) ap;
 | ||
| 		s2 = (char *) bp;
 | ||
| #else
 | ||
| 	/* Tight loop for efficient copying on 11s */
 | ||
| 		tmp = cnt;
 | ||
| 		if(cnt = rnddiv(cnt))
 | ||
| 			do { *((WORD *)s2)++ = *((WORD *)s1)++; }
 | ||
| 			while(--cnt);
 | ||
| 		if((cnt = rndrem(tmp)) == 0)
 | ||
| 			return;
 | ||
| #endif /*-DUMBPCC*/
 | ||
| 	  }                             
 | ||
| 	do { *s2++ = *s1++; }	/* Finish up with byte loop */
 | ||
| 	while(--cnt);
 | ||
| }
 | ||
| #endif /*COMMENT*/
 | ||
| 
 | ||
| struct smblk *		/* If it returns at all, this is most common type */
 | ||
| sbm_err(val,str,a0,a1,a2,a3)
 | ||
| char *str;
 | ||
| struct smblk *val;
 | ||
| {	int *sptr;
 | ||
| 
 | ||
| 	sptr = (int *) &sptr;	/* Point to self on stack */
 | ||
| 	sptr += 5;		/* Point to return addr */
 | ||
| 	if((int)sbm_debug==1)
 | ||
| 		abort();
 | ||
| 	if(sbm_debug)
 | ||
| 		(*sbm_debug)(0,*sptr,str,a0,a1,a2,a3);
 | ||
| 	return(val);
 | ||
| }
 | ||
| 
 | ||
| /* These routines correspond to the V7 LIBC routines as described
 | ||
|  * in the V7 UPM (3).  They should provide satisfactory emulation
 | ||
|  * if the documentation is correct.  Replacement is necessary since
 | ||
|  * the SBM routines are jealous and cannot tolerate competition for
 | ||
|  * calls of SBRK; i.e. the memory being managed must be contiguous.
 | ||
|  */
 | ||
| 
 | ||
| /* Guaranteed to return word-aligned pointer to area of AT LEAST
 | ||
|  * requested size.  Area size is rounded up to word boundary.
 | ||
|  */
 | ||
| 
 | ||
| char *
 | ||
| malloc(size)
 | ||
| unsigned size;
 | ||
| {       register struct smblk *sm, **sma;
 | ||
| 	register SBMO siz;
 | ||
| 
 | ||
| 	siz = rndup(size + sizeof (struct smblk *));   /* Make room for ptr */
 | ||
| 	if((sm = sbm_mget(siz,siz)) == 0)
 | ||
| 		return(0);
 | ||
| 	*(sma = (struct smblk **)sm->smaddr) = sm; /* Store ptr in addr-1 */
 | ||
| 	return((char *)++sma);
 | ||
| }
 | ||
| 
 | ||
| char *
 | ||
| alloc(size)     /* For V6 programs - note different failure value! */
 | ||
| unsigned size;
 | ||
| {       register char *addr;
 | ||
| 	return((addr = malloc(size)) ? addr : (char *) -1);
 | ||
| }
 | ||
| 
 | ||
| free(ptr)
 | ||
| char *ptr;
 | ||
| {       register struct smblk *sm, **smp;
 | ||
| 
 | ||
| 	smp = &((struct smblk **)ptr)[-1];	/* Point to addr-1 */
 | ||
| 	sm = *smp;				/* Pluck SM ptr therefrom */
 | ||
| 	if(((sm->smflags&0377) != SM_NID) || sm->smaddr != (SBMA)smp)
 | ||
| 		return((int)sbm_err(0,"free: bad arg %o", ptr));
 | ||
| 	sbm_mfree(sm);
 | ||
| 	return(1);
 | ||
| }
 | ||
| 
 | ||
| char *
 | ||
| realloc(ptr,size)
 | ||
| char *ptr;
 | ||
| unsigned size;
 | ||
| {       register struct smblk *sm, **smp;
 | ||
| 
 | ||
| 	smp = &((struct smblk **)ptr)[-1];	/* Point to addr-1 */
 | ||
| 	sm = *smp;				/* Pluck SM ptr therefrom */
 | ||
| 	if(((sm->smflags&0377) != SM_NID) || (sm->smaddr != (SBMA)smp))
 | ||
| 		return((char *)sbm_err(0,"realloc: bad arg %o",ptr));
 | ||
| 	if((sm = sbm_exp(sm, rndup(size+(sizeof(struct smblk *))))) == 0)
 | ||
| 		return(0);
 | ||
| 	*(smp = (struct smblk **)sm->smaddr) = sm;      /* Save smblk ptr */
 | ||
| 	return((char *)++smp);
 | ||
| }
 | ||
| 
 | ||
| char *
 | ||
| calloc(nelem,elsize)
 | ||
| unsigned nelem, elsize;
 | ||
| {       register SBMO cmin;
 | ||
| 	register WORD *ip;                     /* Clear in units of words */
 | ||
| 	register char *addr;
 | ||
| 
 | ||
| 	if((cmin = nelem*elsize) == 0           /* Find # bytes to get */
 | ||
| 	  || (addr = malloc(cmin)) == 0)        /* Get it */
 | ||
| 		return(0);
 | ||
| 	ip = (WORD *) addr;			/* Set up ptr to area */
 | ||
| 	cmin = rnddiv(cmin+WDSIZE-1);		/* Find # words to clear */
 | ||
| 	do { *ip++ = 0; } while (--cmin);       /* Zap the area */
 | ||
| 	return(addr);
 | ||
| }
 | ||
| 
 | ||
| /* SBM_NGC() - Specific routine for GC'ing SMBLK nodes.
 | ||
|  *
 | ||
|  * SBM_XNGC(begp, elsize, type) - Compact nodes of specified type.
 | ||
|  *      Scans allocated mem from low to high to find chunks with nodes of
 | ||
|  *	the specified type.
 | ||
|  *      Flushes current freelist and rebuilds it as scan progresses,
 | ||
|  *      such that 1st thing on list is lowest-addr node.  When a node is
 | ||
|  *      seen that can be moved, new node is acquired from freelist if
 | ||
|  *      it exists, otherwise no move is made.  If a chunk has been scanned
 | ||
|  *      and no active nodes remain, it is flushed and freelist updated.
 | ||
|  *      NOTE: This has not yet been verified to work with nodes of any
 | ||
|  *		type other than SMBLK.
 | ||
|  */
 | ||
| 
 | ||
| sbm_ngc()
 | ||
| {	register struct smblk *sm;
 | ||
| 	if(!(sm = sbm_nxtra))
 | ||
| 		return((int)sbm_err(0,"Zero sbm_nxtra"));
 | ||
| 	sm->smflags |= SM_USE;		/* Ensure this one isn't GC'd */
 | ||
| 	sbm_xngc(&sbm_nfl, sizeof(struct smblk), SM_MNODS);
 | ||
| 	sm->smflags = 0;		/* Flush temporary crock */
 | ||
| }
 | ||
| sbm_xngc(begp, elsize, flag)
 | ||
| struct smblk **begp;
 | ||
| unsigned elsize, flag;
 | ||
| {       register struct smblk *sm, *chk, *smf;
 | ||
| 	struct smblk *ftail, *savtail;
 | ||
| 	int cnt, inuse;
 | ||
| 
 | ||
| 	*begp = ftail = 0;		/* Flush node freelist */
 | ||
| 	for(chk = sbm_list; chk; chk = chk->smforw)
 | ||
| 	  if(chk->smflags&flag)
 | ||
| 	    {   sm = (struct smblk *) chk->smaddr;
 | ||
| 		cnt = (chk->smuse)/elsize;
 | ||
| 		savtail = ftail;
 | ||
| 		inuse = 0;
 | ||
| 		smf = *begp;
 | ||
| 					 /* Set up ptr to 1st freelist node */
 | ||
| 		while(--cnt >= 0)
 | ||
| 		  {     /* Here decide if movable */
 | ||
| 			if(sm->smflags && smf   /* Live and have copy place */
 | ||
| 			  && (
 | ||
| 				(sm->smflags&SM_USE) == 0       /* Free mem? */
 | ||
| 			    ||  (sm->smflags&(SM_MNODS|SM_DNODS))
 | ||
| 			     )
 | ||
| 			  && sm->smback)        /* has backptr (see ncpy) */
 | ||
| 			  {                             /* Move the node */
 | ||
| 				*begp = smf->smforw;	/* Get free node */
 | ||
| 				if(smf == ftail)
 | ||
| 					ftail = 0;
 | ||
| 				if(smf == savtail)
 | ||
| 					savtail = 0;
 | ||
| 				/* Move node.  Already checked for back ptr
 | ||
| 				 * of 0 since no obvious way to tell where
 | ||
| 				 * the ptr to list is kept.  Sigh.
 | ||
| 				 */
 | ||
| 				sbm_nmov(sm,smf,(struct smblk **)0,elsize);
 | ||
| 				/* Get ptr to new freelist node.  Note
 | ||
| 				 * check to ensure that it is not in this
 | ||
| 				 * same chunk (if it is, no point in moving
 | ||
| 				 * any nodes!)
 | ||
| 				 */
 | ||
| 				if((smf = *begp) >= chk)
 | ||
| 					smf = 0;        /* Zero if same chk */
 | ||
| 				sm->smflags = 0;        /* Make node free */
 | ||
| 			  }
 | ||
| 			/* At this point, not movable */
 | ||
| 			if(sm->smflags == 0)            /* Free node? */
 | ||
| 			  {     if(ftail)               /* Add to freelist */
 | ||
| 					ftail->smforw = sm;
 | ||
| 				ftail = sm;
 | ||
| 				if(*begp == 0)
 | ||
| 					*begp = sm;
 | ||
| 				sm->smforw = 0;
 | ||
| 			  }
 | ||
| 			else inuse++;
 | ||
| 			sm = (struct smblk *)((SBMA)sm + elsize);
 | ||
| 		  }
 | ||
| 		if(inuse == 0                           /* All free? */
 | ||
| 		  && (sm = chk->smback))		/* & not 1st? */
 | ||
| 		  {     if(savtail)                     /* Edit freelist */
 | ||
| 				(ftail = savtail)->smforw = 0;
 | ||
| 			else *begp = ftail = 0;
 | ||
| 			sbm_mfree(chk);
 | ||
| 			chk = sm;
 | ||
| 		  }
 | ||
| 	    }
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
|  *      Note that proc must return a zero value, or loop aborts and
 | ||
|  *      returns that selfsame value.
 | ||
|  */
 | ||
| sbm_nfor(flag,nodsiz,proc,arg)
 | ||
| int flag;
 | ||
| int (*proc)();
 | ||
| int nodsiz;
 | ||
| struct sbfile *arg;
 | ||
| {       register struct smblk *sm, *np;
 | ||
| 	register int cnt;
 | ||
| 	int res;
 | ||
| 
 | ||
| 	for(sm = sbm_list; sm; sm = sm->smforw)
 | ||
| 	  if(sm->smflags&flag)
 | ||
| 	    {   np = (struct smblk *) sm->smaddr;
 | ||
| 		cnt = sm->smuse/nodsiz;
 | ||
| 		do {
 | ||
| 			if(np->smflags)
 | ||
| 				if(res = (*proc)(np,arg))
 | ||
| 					return(res);
 | ||
| 			np = (struct smblk *)((SBMA)np + nodsiz);
 | ||
| 		  } while(--cnt);
 | ||
| 	    }
 | ||
| 	return(0);
 | ||
| }
 | 
