Nat! bio photo

Nat!

Senior Mull

Twitter Github Twitch

The Mullocator - Part III - Testing Boundaries

Fixing point #4 in the most straightforward way, is to use an indirection. We keep a current block and if we exhaust it, we grab a new one from the OS.

This poses the question how to get memory from the OS. We can't use malloc obviously, because we're trying to replace it. In the old unix days one used sbrk and brk. It still can be used today - maybe I should use it :) - but instead we are going to use the slightly more "modern" mmap feature. There is also vm_allocate which can be used to allocate memory under Mach. I prefer to use mmap because it's a little bit more universal and at least a few years ago, also faster. The actual implementation details are for another installment. I weasel my way out of the predicament by using a os_alloc function, that will call the required OS function. os_alloc will be written later.

Because malloc(0) is guaranteed by convention to return a unique pointer and not NULL (if memory is available), let's also check for that and align the size at the same time.

Also we now support errno. It should be set to ENOMEM if memory is exhausted.

static inline void  *malloc( size_t size)
{
   extern long  *memory;
   extern long  *sentinel;
   extern long  *buf;
   void         *p;
   long         *q;
   size_t       len;

   size += (size == 0);
   size  = (size + sizeof( long) - 1) / sizeof( long);

   q = &buf[ size];  
   if( q > sentinel)
   {
       len    = size < BLOCK_SIZE_LONGS ? BLOCK_SIZE_LONGS : size;
       memory = os_alloc( len);
       if( ! memory)
       {
           errno = ENOMEM;  // yes should be setting errno..
           return( NULL);
       }
       sentinel = &memory[ len];
       buf      = memory;
       q        = &buf[ size];  
   }

   p   = buf;
   buf = q;
   return( p);
}

Although this code does solve problem #4, it also introduces a few problems.

  • The BLOCK_SIZE_LONGS constant is an arbitrary choice. If we set it to 0x1000000 / sizeof( longs) we haven't gained anything compared to the code before. If we set it to 1, we'll be calling os_alloc for every malloc call. The truth is somewhere in the unknown middle.
  • The old memory block is immediately forgotten once we cannot satisfy a memory request. This is very wasteful. In a call sequence of
        for(;;)
        {
             malloc( 1);
             malloc( BLOCK_SIZE_LONGS * sizeof( long));
        }
    
    we'd be burning memory at almost twice the rate necessary. This is probably too wasteful.

It's good to be aware of these problems. We'll suspend this stories thread for now and turn the attention to free in the next installment.