[comp.os.msdos.programmer] SUMMARY: MSDOS memory FREE functionc

dvadura@watdragon.waterloo.edu (Dennis Vadura) (10/28/90)

Summary of MSDOS FREE and memory coalescing.  A while back I posted
a request if anyone knows how to get DOS FREE (int 21, ah=0x49) to coalesce
adjacent blocks.  I received several responses and based on those come to these
conclusions.  DOS FREE on its own does NOT coalesce adjacent free memory blocks.
(in my opinion this is stupid).  DOS ALLOC (int 21, ah=0x48) of a block
whose size is 0xffff seems to do the trick.  FREEING that resulting block has
the effect of coalescing all previously allocated but now free adjacent blocks.
Thanks to Ralf Brown for suggesting this trick.

I have now incorporated this new version into dmake.  This should make
compilation problems with MSC 6.0 go away as I no longer need to rely on
packed structures.  Anyway, below is the output from a test program.

The first test uses the old version of _dos_free that I had written which did
the coalesce manually and the second test uses the trick suggested by
Ralf Brown.  The code for the test follows the output.  All tests run on
a 1M AT with DOS 3.2.

-dennis
-PS>  I annotated the test output with extra info
-----------------------------------------------------------------------------
initial memory block state
M 0x3145 0x3146 0x1178:4472
M 0x42be 0x0000 0x5d35:23861
Z 0x9ff4 0x1aae 0x000b:11

*** Call dos alloc to get a 4 paragraph block, from the end of memory ***

state before free of allocated block 0x9fef
M 0x3145 0x3146 0x1178:4472
M 0x42be 0x0000 0x5d30:23856
M 0x9fef 0x3146 0x0004:4
Z 0x9ff4 0x1aae 0x000b:11

state after DOS free
M 0x3145 0x3146 0x1178:4472
M 0x42be 0x0000 0x5d30:23856
M 0x9fef 0x0000 0x0004:4		<--- NOTE: DOS didn't coalesce
Z 0x9ff4 0x1aae 0x000b:11

after coalesce
M 0x3145 0x3146 0x1178:4472
M 0x42be 0x0000 0x5d35:23861		<--- coalesce by walking arena chain
Z 0x9ff4 0x1aae 0x000b:11

============= Now using a different FREE routine, do it again.
initial memory block state
M 0x3145 0x3146 0x1178:4472
M 0x42be 0x0000 0x5d35:23861
Z 0x9ff4 0x1aae 0x000b:11

*** Call dos alloc to get a 4 paragraph block, from the end of memory ***

before free of allocated block 0x9fef
M 0x3145 0x3146 0x1178:4472
M 0x42be 0x0000 0x5d30:23856
M 0x9fef 0x3146 0x0004:4
Z 0x9ff4 0x1aae 0x000b:11

after free
M 0x3145 0x3146 0x1178:4472
M 0x42be 0x0000 0x5d30:23856
M 0x9fef 0x0000 0x0004:4		<--- again DOS just leaves it there
Z 0x9ff4 0x1aae 0x000b:11

after coalesce
M 0x3145 0x3146 0x1178:4472
M 0x42be 0x0000 0x5d35:23861		<--- coalesce by alloc of 0xffff and
Z 0x9ff4 0x1aae 0x000b:11		     free of resulting block


-------------------------X Test Program Source X-------------------------------
#include <stdio.h>
#include <stdlib.h>

#ifndef _EXEC_h_
#define _EXEC_h_

#ifndef ANSI
#if defined(__STDC__) || defined(__TURBOC__)
#define ANSI(x) x
#else
#define ANSI(x) ()
#endif
#endif

#ifndef MK_FP
#define MK_FP(seg,ofs) \
	((void far *) (((unsigned long)(seg) << 16) | (unsigned)(ofs)))
#endif

#endif
#if defined(_MSC_VER) && _MSC_VER >= 600
	/* Ignore the MSC 6.0 library's "const"-riddled prototype
	   for spawnvpe.
	*/
# define spawnvpe _ignore_msc_spawnvpe
# include <process.h>
# undef spawnvpe
  int spawnvpe(int, char *, char **, char **);
#else
# include <process.h>
#endif

#include <dos.h>
#include <errno.h>
#include <string.h>
#include <alloc.h>

static void _dump_blocks();

/* variables and functions local to this file */
static void      _dos_free  (char far *);
static void      _dos_free_big  (char far *);
static char far *_dos_alloc (unsigned int);


main()
{
   char far *block;

   printf( "initial memory block state\n" );
   _dump_blocks();

   block = _dos_alloc( 4 );
   _dos_free( block );

   /* Now run the test again but use a different _dos_free function
    * this time. */
   printf( "============= Now using a different FREE block\n" );
   printf( "initial memory block state\n" );
   _dump_blocks();

   block = _dos_alloc( 4 );
   _dos_free_big( block );
}

static char far *
_dos_alloc( size )/*
====================
   This routine allocates size paragraphs from DOS.  It changes the memory
   allocation strategy to allocate from the tail and then changes it back.
   to using first fit. */
unsigned int size;
{
   union REGS r;
   union REGS t;

   r.x.ax = 0x5801;
   r.x.bx = 0x0002;
   intdos( &r, &r );

   r.h.ah = 0x48;
   r.x.bx = size;

   intdos( &r, &r );
   if( r.x.cflag )  {
      printf( "alloc failure\n" );
      exit(1);
   }
   
   t.x.ax = 0x5801;
   t.x.bx = 0x0000;
   intdos( &t, &t );

   return( (char far *) MK_FP(r.x.ax, 0) );
}


#if defined(_MSC_VER)
#pragma pack(1)
#endif
typedef struct {
   char 	 mode;
   unsigned int  owner;
   unsigned int  size;
} MB, *MBPTR;
#if defined(_MSC_VER)
#pragma pack()
#endif

static void
_dos_free( pblock )/*
=====================
   ALERT!!!! Major GROSSNESS HERE!!!!  This routine calls DOS free to free
   a block of memory and then walks the DOS allocation chain from the current
   psp forward and coallesces any free blocks it finds into one large free
   block.  This prevents the No more memory error that was being caused by
   long makes causing fragmentation of DOS memory.  I can probably avoid this
   by using some weird combination of DOS calls, but I would have to figger
   out what calls under DOS cause the coalesce to happen.  Haven't found this
   so far so, for now, I will just go with this.  It work fine on my box :-)*/
char far *pblock;
{
   union REGS r;
   struct SREGS s;
   MB far *p;
   MB far *t;
   int flag = 0;

   printf( "before free of allocated block 0x%04x\n", FP_SEG(pblock)-1 );
   _dump_blocks();

   s.es = FP_SEG(pblock);
   r.h.ah = 0x49;
   intdosx( &r, &r, &s );
   if( r.x.cflag ) {
      printf( "cannot free block\n" );
      exit(1);
   }

   printf( "after free\n" );
   _dump_blocks();

   p = (MB far *) MK_FP(_psp-1, 0);

   while(1) {
      if( p->owner == 0 )
         if( !flag ) {
	    t = p;
	    flag++;
	 }
	 else if( FP_SEG(t)+t->size+1 == FP_SEG(p) )
	    t->size += p->size+1;

      if( p->mode == 'Z' ) break;
      p = (MB far *) MK_FP(FP_SEG(p)+p->size+1, 0);
   }

   printf( "after coalesce\n" );
   _dump_blocks();
}


static void
_dos_free_big( pblock )/*
=====================
   ALERT!!!! Major GROSSNESS HERE!!!!  This routine calls DOS free to free
   a block of memory and then walks the DOS allocation chain from the current
   psp forward and coallesces any free blocks it finds into one large free
   block.  This prevents the No more memory error that was being caused by
   long makes causing fragmentation of DOS memory.  I can probably avoid this
   by using some weird combination of DOS calls, but I would have to figger
   out what calls under DOS cause the coalesce to happen.  Haven't found this
   so far so, for now, I will just go with this.  It work fine on my box :-)*/
char far *pblock;
{
   union REGS r;
   struct SREGS s;

   printf( "before free of allocated block 0x%04x\n", FP_SEG(pblock)-1 );
   _dump_blocks();

   s.es = FP_SEG(pblock);
   r.h.ah = 0x49;
   intdosx( &r, &r, &s );
   if( r.x.cflag ) {
      printf( "cannot free block\n" );
      exit(1);
   }

   printf( "after free\n" );
   _dump_blocks();

   r.h.ah = 0x48;
   r.x.bx = 0xffff;
   intdos( &r, &r );
   s.es   = r.x.ax;
   r.h.ah = 0x49;
   intdosx( &r, &r, &s );

   printf( "after coalesce\n" );
   _dump_blocks();
}


static void
_dump_blocks()/*
================
   Walk DOS memory blocks and dump their headers. */
{
   MB far *p;

   p = (MB far *) MK_FP( _psp-1, 0 );

   while(1) {
      printf( "%c 0x%04x 0x%04x 0x%04x:%d\n", p->mode, FP_SEG(p), p->owner,
              p->size, p->size );

      if( p->mode == 'Z' ) break;
      p = (MB far *) MK_FP( FP_SEG(p)+p->size+1, 0 );
   }

   printf( "\n" );
}
-- 
--------------------------------------------------------------------------------
"This is almost worth the HIGH blood pressure!" he  |Dennis Vadura
thought as yet another mosquito exploded.-R.Patching|dvadura@dragon.uwaterloo.ca
================================================================================

ralf@b.gp.cs.cmu.edu (Ralf Brown) (10/28/90)

In article <1990Oct28.001029.19179@watdragon.waterloo.edu> dvadura@watdragon.waterloo.edu (Dennis Vadura) writes:
}Summary of MSDOS FREE and memory coalescing.  A while back I posted
}a request if anyone knows how to get DOS FREE (int 21, ah=0x49) to coalesce
}adjacent blocks.  I received several responses and based on those come to these
}conclusions.  DOS FREE on its own does NOT coalesce adjacent free memory blocks.
}(in my opinion this is stupid).  DOS ALLOC (int 21, ah=0x48) of a block
}whose size is 0xffff seems to do the trick.  FREEING that resulting block has
}the effect of coalescing all previously allocated but now free adjacent blocks.

I've since looked at the actual DOS memory management code, and can state that
generic MSDOS 3.30 works as follows:
	no coalescing on FREE
	all possible blocks coalesced on *any* ALLOC
	free blocks immediately following a block being RESIZEd are coalesced

Also, the coalescing on ALLOC is done by calling a coalesce routine on each
free block, to coalesce any immediately following free blocks (same code that
is called by the RESIZE coalescing).  Since the DOS memory chain is not
doubly-linked, omitting the coalescing on FREE is an overall performance
win, since it would be necessary to walk the memory chain to find the
preceding memory block (which might already be free and need to be coalesced).
ALLOC walks the entire memory chain anyway....

Note: if the alloc of FFFFh paragraphs is ever successful, your memory chain
must be corrupted, since that is 1M-16 bytes....  DOS doesn't allocate any
memory if there is no sufficiently large block available.  Thus there is no
need for the call to FREE after the alloc of FFFFh paragraphs.
-- 
{backbone}!cs.cmu.edu!ralf  ARPA: RALF@CS.CMU.EDU   FIDO: Ralf Brown 1:129/3.1
BITnet: RALF%CS.CMU.EDU@CMUCCVMA   AT&Tnet: (412)268-3053 (school)   FAX: ask
DISCLAIMER?  Did  | Everything is funny as long as it is happening to
I claim something?| someone else.  --Will Rogers