• Please review our updated Terms and Rules here

Is there an EMM document or specification somewhere?

Thanks guys; here is what I came up with:

Code:
int EMSInit(void)
{
  char far *p1;

  //check for ems installed
  p1=(char*)MK_FP(FP_SEG(getvect(0x67)), 0x000a);
  if (memcmp(p1, "EMMXXXX0", 8)!=0)
    return 0;

  //get status
  _AH=0x40;
  asm int 0x67;
  if (_AH!=0)
    return 0;

  //get ems frame
  _AH=0x41;
  asm int 0x67
  if (_AH!=0)
    return 0;
  EMSFrame=(char far*)MK_FP(_BX, 0);
  return 1;
}

Does XMS need a similar check? All I am doing for it is:

Code:
int XMSInit(void)
{
  //check for xms installed
  _AX=0x4300;
  asm int 0x2f;
  if (_AL!=0x80)
    return 0;

  //get control function
  _AX=0x4310;
  asm int 0x2f
  XMSControl=MK_FP(_ES, _BX);
  return 1;
}
 
That's pretty much the standard way of dealing with the XMS service. Forget about using the int 15h functions, as they bypass any DOS-related usage of extended memory. Your next call should be function 8, to see how much extended memory is available (it's possible to have the XMS service active with no available memory).
 
Thanks Chuck - function 8 is exactly what I do next. The above EMM example used to begin with //get status and I thought that would be enough to detect EMM installed or not, but it would hang on my PS/2 model 50Z. It works now that I am checking for the EMMXXXX0 string.

I worked on enhancing my FDIMAGE tool over the weekend to support both XMS/EMS for its track buffer and I think it is good now. I'll post an update to my thread on that with the latest version as soon as I test it some more. It works on my Tandy 1000 with a lotech EMS in it and no XMS too.
 
If anyone wants to use the XMS/EMS functions I came up with in C, I am attaching a zip of the source.

You don't have to use the EMSRead/EMSWrite functions and can just use EMSMap to map pages in, but I wrote the EMSRead/EMSWrite so I could deal with EMS memory similar to XMS memory.

If you see a bug/error/oddity in these, let me know.

From the header files:

xms.h
Code:
int XMSInit(void);
int XMSAvailable(unsigned int *AKB);
int XMSAlloc(unsigned int AKB, unsigned int *AHandle);
int XMSFree(unsigned int AHandle);
int XMSTransfer(unsigned int ASrcHandle, unsigned long ASrcOffset, unsigned long ASize, unsigned int ADestHandle, unsigned long ADestOffset);
int XMSRead(unsigned int AHandle, unsigned long AOffset, unsigned long ASize, char far *AMemory);
int XMSWrite(unsigned int AHandle, unsigned long AOffset, unsigned long ASize, char far *AMemory);

ems.h
Code:
extern unsigned char far *EMSFrame;

int EMSInit(void);
int EMSVersion(unsigned int *AVersion);
int EMSAvailable(unsigned int *APages);
int EMSAlloc(unsigned int AKB, unsigned int *AHandle);
int EMSFree(unsigned int AHandle);
int EMSMap(unsigned int AHandle, unsigned char APhysicalPage, unsigned int ALogicalPage);
int EMSRead(unsigned int AHandle, unsigned long AOffset, unsigned short ASize, char far *AMemory);
int EMSWrite(unsigned int AHandle, unsigned long AOffset, unsigned short ASize, char far *AMemory);
 

Attachments

  • csrc_xms_ems_100.zip
    2.3 KB · Views: 3
I do have a suggestion, if you're open to them.
For a lot of things, I just want memory and don't really care where it is. So how about a "staged" set of routines that are handle-based, but automatically determine the best memory choice? For example, if you have 20MB worth of data to stash away, you might want to use EMS first, but if no EMS, XMS, if neither or both are full, hard disk.

So basically, you have a set of open() close() get() put() seek() functions exposed to the caller, without the caller being aware of where the data is being stored.

Just a thought...
 
I am open to ideas Chuck and that is a good one. I've done that at a higher level in my fdimage program, but it makes more sense to do it at a memory level like what you are describing! Thanks!!

ANYONE - hold off on using the 1.00 versions of the above - there is an issue with one of the registers being clobbered before it could be copied into the available memory variable.
 
What is the difference between:

Code:
mov [offset XMSControl+2], es
mov [offset XMSControl], bx

and

Code:
mov word ptr [XMSControl+2], es
mov word ptr [XMSControl], bx
 
If anyone wants to use the XMS/EMS functions I came up with in C, I am attaching a zip of the source.

You don't have to use the EMSRead/EMSWrite functions and can just use EMSMap to map pages in, but I wrote the EMSRead/EMSWrite so I could deal with EMS memory similar to XMS memory.

If you see a bug/error/oddity in these, let me know.

From the header files:

xms.h
Code:
int XMSInit(void);
int XMSAvailable(unsigned int *AKB);
int XMSAlloc(unsigned int AKB, unsigned int *AHandle);
int XMSFree(unsigned int AHandle);
int XMSTransfer(unsigned int ASrcHandle, unsigned long ASrcOffset, unsigned long ASize, unsigned int ADestHandle, unsigned long ADestOffset);
int XMSRead(unsigned int AHandle, unsigned long AOffset, unsigned long ASize, char far *AMemory);
int XMSWrite(unsigned int AHandle, unsigned long AOffset, unsigned long ASize, char far *AMemory);

ems.h
Code:
extern unsigned char far *EMSFrame;

int EMSInit(void);
int EMSVersion(unsigned int *AVersion);
int EMSAvailable(unsigned int *APages);
int EMSAlloc(unsigned int AKB, unsigned int *AHandle);
int EMSFree(unsigned int AHandle);
int EMSMap(unsigned int AHandle, unsigned char APhysicalPage, unsigned int ALogicalPage);
int EMSRead(unsigned int AHandle, unsigned long AOffset, unsigned short ASize, char far *AMemory);
int EMSWrite(unsigned int AHandle, unsigned long AOffset, unsigned short ASize, char far *AMemory);

If you are using EMS like XMS, you can also check for version 4.0 and use function 24 (move/exchange memory region) for access. So it will work with configs where no EMS 3.2 page frame exists.
 
Anyone using EMM386 will have 4.0 capability. Most software only requires 3.2.

To clarify, I am suggesting to add function 24 in addition to what you already have for maximum compatibility.
 
I am with you. What command line options for EMM386 will cause it to not have a page frame?
 
Thanks Plasma - that gives me something to test with.

If anyone wants to try out the 1.01 versions of these, I'm going to attach their zips along with a test program. I've moved more code into the asm block just to make sure a compiler doesn't to exercise any liberty over choosing instructions.

I'm going to try out Chuck's idea of making some generic functions that use these functions to create a heap/xms/ems independent thing.
 

Attachments

  • xms_101.zip
    9.4 KB · Views: 2
  • ems_101.zip
    9.9 KB · Views: 3
I don't have the function 24 implemented yet, but I did finally get Chuck's idea finished up last night and it works great. I created a single set of functions that then call the XMS/EMS/Heap functions so I called it XEH for the first letter in each memory type.

The available number of pages is overstated because I think EMS is emulating XMS or vice versa per the mem command.

Here is the header/c to show how it works:

Code:
//version 1.00

#ifndef XEH_H
#define XEH_H

#define XEH_MAX_HANDLES 8

#define XMSERROFFSET 100
#define EMSERROFFSET 200

enum {XEH_HEAP=1, XEH_XMS=2, XEH_EMS=4};

enum {XEHERR_NONE=0, XEHERR_NOHANDLES, XEHERR_NOPAGES, XEHERR_BADHANDLE,
      XEHERR_BADOFFSET};

struct xeh_type
{
  char xmsenabled, emsenabled, heapenabled;
};

struct xehtable_type
{
  unsigned int xmshandle, emshandle, xmspages, emspages, heappages;
  unsigned char huge *heaphandle;
  char used;
};

extern xeh_type xeh;
extern xehtable_type xehtable[XEH_MAX_HANDLES];

void XEHInit(unsigned int ATypes);
void XEHAvailable(unsigned int *APages);
int XEHAlloc(unsigned int APages, unsigned int *AHandle);
int XEHFree(unsigned int AHandle);
int XEHTransfer(unsigned int AHandle, unsigned long AOffset, unsigned int ASize, char *AMemory, char ARead);
#define XEHRead(AHandle, AOffset, ASize, AMemory) XEHTransfer(AHandle, AOffset, ASize, AMemory, 1)
#define XEHWrite(AHandle, AOffset, ASize, AMemory) XEHTransfer(AHandle, AOffset, ASize, AMemory, 0)

#endif

Code:
/*

version 1.00

07/14/2022: initial version

*/

#include <dos.h>
#include <alloc.h>
#include <mem.h>

#include "xeh.h"
#include "xms.h"
#include "ems.h"

xeh_type xeh;
xehtable_type xehtable[XEH_MAX_HANDLES];

void XEHInit(unsigned int ATypes)
{
  //clear
  memset(&xeh, 0, sizeof(xeh_type));
  memset(&xehtable, 0, sizeof(xehtable_type)*XEH_MAX_HANDLES);

  //xms
  if ((ATypes & XEH_XMS) && (XMSInit()==XMSERR_NONE))
    xeh.xmsenabled=1;

  //ems
  if ((ATypes & XEH_EMS) && (EMSInit()==EMSERR_NONE))
    xeh.emsenabled=1;

  //heap
  if (ATypes & XEH_HEAP)
    xeh.heapenabled=1;
}

void XEHAvailable(unsigned int *APages)
{
  unsigned int ui1, ui2;
  unsigned long ul1;

  //clear
  ui1=0;
  *APages=0;

  //xms
  if (xeh.xmsenabled && XMSAvailable(&ui2)==XMSERR_NONE)
    {
      ui2/=16;
      ui1+=ui2;
    }

  //ems
  if (xeh.emsenabled && EMSAvailable(&ui2)==EMSERR_NONE)
    ui1+=ui2;

  //heap
  if (xeh.heapenabled)
    {
      //heap available
      ul1=coreleft();
      if (ul1>=1024)
        ul1-=1024;
      else ul1=0;
      ui2=(unsigned int)(ul1/16384);
      ui1+=ui2;
    }

  //set pages
  *APages=ui1;
}

int XEHAlloc(unsigned int APages, unsigned int *AHandle)
{
  unsigned int ui1, ui2, ui3, ui4;
  unsigned long ul1;

  //do we have a handle free
  for (ui1=0; ui1<XEH_MAX_HANDLES; ui1++)
    if (!xehtable[ui1].used)
      break;
  if (ui1==XEH_MAX_HANDLES)
    return XEHERR_NOHANDLES;

  //clear table and mark handle used
  memset(&xehtable[ui1], 0, sizeof(xehtable_type));
  xehtable[ui1].used=1;

  //we need ui1 pages to be allocated
  ui3=APages;

  //xms
  if (xeh.xmsenabled && ui3)
    {
      //xms available
      ui4=XMSAvailable(&ui2);
      if (ui4!=XMSERR_NONE)
        return XMSERROFFSET+ui4;
      ui2/=16;

      //allocate
      if (ui2>ui3)
        ui2=ui3;
      if (ui2)
        {
          ui4=XMSAlloc(ui2*16, &xehtable[ui1].xmshandle);
          if (ui4!=XMSERR_NONE)
            {
              XEHFree(ui1);
              return XMSERROFFSET+ui4;
            }

          xehtable[ui1].xmspages=ui2;
          ui3-=ui2;
        }
    }

  //ems
  if (xeh.emsenabled && ui3)
    {
      //ems available
      ui4=EMSAvailable(&ui2);
      if (ui4!=EMSERR_NONE)
        return EMSERROFFSET+ui4;

      //allocate
      if (ui2>ui3)
        ui2=ui3;

      if (ui2)
        {
          ui4=EMSAlloc(ui2, &xehtable[ui1].emshandle);
          if (ui4!=EMSERR_NONE)
            {
              XEHFree(ui1);
              return EMSERROFFSET+ui4;
            }

          xehtable[ui1].emspages=ui2;
          ui3-=ui2;
        }
    }

  //heap
  if (xeh.heapenabled && ui3)
    {
      //heap available
      ul1=coreleft();
      if (ul1>=1024)
        ul1-=1024;
      else ul1=0;
      ui2=(unsigned int)(ul1/16384);

      //allocate
      if (ui2>ui3)
        ui2=ui3;
      if (ui2)
        {
          if ((xehtable[ui1].heaphandle=(char *)farmalloc(ui2*16384L))==NULL)
            {
              heapfail:
              XEHFree(ui1);
              return XEHERR_NOPAGES;
            }

          xehtable[ui1].heappages=ui2;
          ui3-=ui2;
        }
    }

  //any pages not allocated
  if (ui3)
    goto heapfail;

  //set handle
  *AHandle=ui1;

  //debug
  //printf("xp%u, ep%u, hp%u\n", xehtable[ui1].xmspages, xehtable[ui1].emspages, xehtable[ui1].heappages);

  //return success
  return XEHERR_NONE;
}

//Frees memory and handle
int XEHFree(unsigned int AHandle)
{
  //is this handle valid
  if (AHandle>=XEH_MAX_HANDLES || !xehtable[AHandle].used)
    return XEHERR_BADHANDLE;

  //xms
  if (xehtable[AHandle].xmspages)
    XMSFree(xehtable[AHandle].xmshandle);

  //ems
  if (xehtable[AHandle].emspages)
    EMSFree(xehtable[AHandle].emshandle);

  //heap
  if (xehtable[AHandle].heappages)
    farfree(xehtable[AHandle].heaphandle);

  //clear table
  memset(&xehtable[AHandle], 0, sizeof(xehtable_type));

  //return success
  return XEHERR_NONE;
}

int XEHTransfer(unsigned int AHandle, unsigned long AOffset, unsigned int ASize, char *AMemory, char ARead)
{
  unsigned int ui1, memoffset, page, pageoffset, copysize;
  unsigned long ul1;

  //is this handle valid
  if (AHandle>=XEH_MAX_HANDLES || !xehtable[AHandle].used)
    return XEHERR_BADHANDLE;

  memoffset=0;
  page=(unsigned int)(AOffset/16384);
  pageoffset=(unsigned int)(AOffset%16384);
  while (ASize)
    {
      //how much to copy
      copysize=16384-pageoffset;
      if (copysize>ASize)
        copysize=ASize;

      //type of memory
      if (page>=xehtable[AHandle].xmspages+xehtable[AHandle].emspages+xehtable[AHandle].heappages)
        return XEHERR_BADOFFSET;
      else
      if (page>=xehtable[AHandle].xmspages+xehtable[AHandle].emspages)
        {
          //heap
          ul1=(page-xehtable[AHandle].xmspages-xehtable[AHandle].emspages)*16384L+pageoffset;
          if (ARead)
            memcpy(AMemory+memoffset, xehtable[AHandle].heaphandle+ul1, copysize);
          else memcpy(xehtable[AHandle].heaphandle+ul1, AMemory+memoffset, copysize);
        }
      else
      if (page>=xehtable[AHandle].xmspages)
        {
          //map page
          ui1=EMSMap(xehtable[AHandle].emshandle, 0, page-xehtable[AHandle].xmspages);
          if (ui1)
            return EMSERROFFSET+ui1;

          //read or write
          if (ARead)
            memcpy(AMemory+memoffset, EMSFrame+pageoffset, copysize);
          else memcpy(EMSFrame+pageoffset, AMemory+memoffset, copysize);
        }
      else
        {
          //xms
          ul1=page*16384L+pageoffset;
          if (ARead)
            ui1=XMSRead(xehtable[AHandle].xmshandle, ul1, copysize, AMemory+memoffset);
          else ui1=XMSWrite(xehtable[AHandle].xmshandle, ul1, copysize, AMemory+memoffset);
          if (ui1)
            return XMSERROFFSET+ui1;
        }

      memoffset+=copysize;
      ASize-=copysize;
      pageoffset=0;
      page++;
    }

  //return success
  return XEHERR_NONE;
}
 
Last edited:
Do you anticipate including hard disk overflow in case all other methods fail?
Ideally, what you want is a storage medium for an arbitrarily huge amount of data, gracefully transitioning between conventional, EMS, XMS and disk transparently.
 
Do you anticipate including hard disk overflow in case all other methods fail?

Not in this case, my primary use plan for this is my FDIMAGE disk imaging program where I'd like to buffer as many tracks as possible to memory to reduce disk swaps with a single drive, or that pesky seek to track 0 between passes that I find so annoying.

It can compare disk/file to disk/file in any combination, so one of my tests is two 2.88 MB disk images. Really a very complicated fc /b file file at this point. Although trying that fc /b without smartdrv takes forever on real hardware because it must have very limited buffering. Now I can take this to my Tandy 1000 that only has EMS memory and it will still buffer as much as it can. The point of the track buffer is to limit disk swapping which it also supports on the same drive for single drive systems.

1657907349460.png

I "almost really despise" the concept of paging memory to a disk! Yes, something can still run instead of fail, but at what cost! I've walked up to too many systems where everything has come to a crawl because they had way more memory committed than they had physical RAM. I suppose depending on the situation though, transparently using a disk file would be another option.

This should get me what I'm looking for in terms of any program that needs more than heap/conventional memory, although I do want to still implement the function 24 that Plasma was talking about.
 
You probably only paged data and not code that needs to be run! With Windows it gets into an ugly affair of 100% disk activity.
 
With a disk imager, it wouldn't make much sense to store a second copy of data already on a disk on the same disk.

Most of the times I have seen Windows go bonkers over virtual memory is when a program is making a lot of small allocations. Windows frees up the memory for an allocation and then keeps repeating the process. If the program made one sizable allocation request, the big gulp of disk space would be done in 10 seconds and the system would respond fairly smoothly after that.
 
Back
Top