Techniques for Using Virtual Memory

Virtual memory can be used as a replacement of DOS memory in data structures. For example, you can build a linked list that resides in virtual memory; such a linked list could contain far more nodes than an ordinary linked list.

The declaration for the node type of such a linked list might look as follows:

#include <malloc.h>

#include <stdio.h>

#include <stdlib.h>

#include <vmemory.h>

#include <string.h>

typedef struct node NODE;

struct node

{

int key;

char data[100];

_vmhnd_t next;

};

// globals

_vmhnd_t vhead = _VM_NULL; // first element in list

_vmhnd_t vlast = _VM_NULL; // last element in list

Each NODE structure contains a _vmhnd_t field rather than a pointer to connect it to the succeeding node.

You can use these NODE structures the same way you use the nodes of an ordinary linked list, except that you must load each node into DOS memory before you access its contents. For example, the following procedure adds a new node to a linked list:

int add( NODE new_node )

{

_vmhnd_t vtemp;

NODE __far *temp;

NODE __far *last;

if ( (vtemp = _vmalloc( sizeof( NODE ) )) == _VM_NULL )

return 0; // could not allocate virtual memory

if ( (temp = (NODE __far *)_vload( vtemp, _VM_DIRTY )) == NULL )

{

_vfree( vtemp ); // free the block we just allocated

return 0; // but could not load

}

temp->key = new_node.key; // copy in new data

strncpy( temp->data, new_node.data, 100 );

if ( vhead == _VM_NULL ) // no nodes in list yet

{

vhead = vtemp;

vlast = vhead;

}

else // add to end of list

{

last = (NODE __far *)_vload( vlast, _VM_DIRTY );

last->next = vtemp;

vlast = vtemp;

}

return 1; // node successfully added

}

The add function always uses two variables when manipulating a node: a handle and a pointer. When creating the node to be added, the function uses a handle to allocate and load the block of memory and then uses a pointer to write the new data into the node. Similarly, when attaching the node to the end of the list, the function uses a handle to load the last node and then uses a pointer to modify its next field. Note that temp and last are explicitly declared as far pointers, because no matter what model the program is compiled under, _vload returns far pointers. Also note that the _VM_DIRTY flag is specified in both calls to _vload, since in both cases the function is modifying the block of memory.

The find function has similar features:

NODE *find( int search_key )

{

_vmhnd_t vcurr;

NODE __far *curr;

NODE *temp;

if ( vhead == _VM_NULL )

printf( “list empty \n” );

vcurr = vhead;

while ( vcurr != _VM_NULL )

{

if ( (curr = (NODE __far *)_vload( vcurr, _VM_CLEAN )) == NULL )

return NULL; // could not load block

if ( curr->key == search_key )

{

if ( (temp = (NODE *)malloc( sizeof( NODE ) )) == NULL )

return NULL; // could not allocate memory

temp->key = curr->key; // copy data from node

strncpy( temp->data, curr->data, 100 );

return temp;

}

else

vcurr = curr->next;

}

return NULL;

}

As with the add function, the search function uses both a handle and a pointer to access nodes. The function traverses the linked list, comparing each node's key with the key being searched for. To examine a node, the function uses a handle to load it into DOS memory, and then uses a pointer to access the key field. Note that this time the _VM_CLEAN flag is specified in the call to _vload, since the function is only reading the block of memory, not writing to it.

Other standard operations on a linked list, such as deleting or modifying a node, can be performed by making a minor modification to the usual implementations: you must load a block before you can access its contents. If you need to access a block many times, you should probably lock it. Or if you need to have more than one block in memory at once (if, for instance, you're comparing their contents), you should lock one or more of them.

Other data structures traditionally implemented with pointers, such as binary trees, can also take advantage of virtual memory if you use handles as well as pointers. Another possible technique is to maintain an array of handles, each of which refers to a large buffer of virtual memory. Your program can switch among these buffers, keeping just one or two of them in DOS memory at any given time.

Summary: Only a portion of virtual memory is accessible at any one time.

When writing a program to use virtual memory, or converting an existing program to use it, you should remember virtual memory's limitations. While virtual memory allows a program to store a large amount of data, only a small portion of it is immediately accessible at any one time. If your program reads and writes a large amount of data at all times, it will require the virtual memory manager to perform a lot of swapping, so its performance will not be as efficient as one which accesses only a small amount of data at a time.

Another way to make your program use extended memory, expanded memory, and disk is to break it up into overlays. For information on creating overlays, see Chapter 15, “Creating Overlaid DOS Programs,” of the Environment and Tools manual.