16位DOS中的XMS分配

3
请原谅我使用复活术的尝试,但我需要为16位DOS编写一些代码!我必须验证在16位平台上构建的软件是否正确执行,并发现我们的XP工作站实际上可以运行16位DOS应用程序,这使得使用现有的批处理测试系统成为可能。
无论如何,该软件由一个库和一个数据库组成。小型数据库(最多约150kB)可以定义为静态全局数组或从文件读取到使用halloc()分配的缓冲区中,因此我相当自信库和测试工具已正确构建。
然而,我们还要测试一些更大的数据库,最多约1.8Mb。这太大了,无法正常分配,因此我编写了一个小的支持库来分配XMS内存。通过这个,我可以在一个小的玩具程序中成功地分配、使用(即写入和读取数据)和释放高达16Mb的数据。然而,在“真实”应用程序中使用XMS设施时,我会收到以下错误:
The NTVDM CPU has encountered an illegal instruction.
CS:0000 IP:00ba OP:0f 04 10 0e 51

在搜索此错误时,很少有相关的结果,这种类型的错误似乎通常归咎于各种恶意软件。
代码基础是严格的C90,目前用于DOS构建的编译器是OpenWatcom 1.9,使用“大型”内存模型。构建过程中没有警告或错误。
接下来是XMS支持库,该错误似乎发生在调用xmsmalloc()之后:
/* This file implements rudimentary XMS memory handling.
 * Documentation on the XMS API was found on http://www.qzx.com/pc-gpe/xms30.txt
 */

#include <stddef.h> /* Definition of NULL */
#include <limits.h> /* Definition of UINT_MAX */
#include <stdio.h>  /* fprintf and (FILE *) */

/* Allow external configuration of maximum concurrent XMS allocations */
#ifndef MAX_XMS_ALLOCATIONS
#define MAX_XMS_ALLOCATIONS 4
#endif

/* Address of the XMS driver */
static long XMSControl;

/* Mapping of XMS handle <-> normal pointer */
typedef struct {
    unsigned int XMSHandle;
    void huge * XMSPointer; 
} XMSHandleMap;

static XMSHandleMap allocMap[MAX_XMS_ALLOCATIONS];

/* Set up the XMS driver, returns 0 on success and non-zero on failure */
static int initxms(void)
{
    char XMSStatus = 0;

    if ( XMSControl == 0 )
    {
        __asm {
        ; Is an XMS driver installed?
            mov ax,4300h
            int 2Fh
            mov [XMSStatus], al
        }

        if ( XMSStatus == 0x80 )
        {
            __asm {
            ; Get the address of the driver control function
                mov ax,4310h
                int 2Fh
                mov word ptr [XMSControl]  ,bx
                mov word ptr [XMSControl+2],es
            }
        }
    }

    return ( XMSControl == 0 );
}

/* Allocate a slab of memory from XMS */
void huge * xmsmalloc(long unsigned int size)
{
    unsigned int kB;
    unsigned int XMSStatus = 0;
    unsigned int XMSHandle = 0;
    void huge * XMSPointer = NULL;
    int n;

    /* If we can not initialize XMS, the allocation fails */
    if ( initxms() )
        return NULL;

    /* It is not possible to allocate more kilobytes than a 16-bit register can hold :-) */
    if ( size / 1024 > UINT_MAX )
        return NULL;

    /* Get the next free entry in the handle <-> pointer mapping */
    for ( n = 0; n < MAX_XMS_ALLOCATIONS; n++ )
    {
        if ( allocMap[n].XMSPointer == NULL )
            break;
    }

    if ( n == MAX_XMS_ALLOCATIONS )
        return NULL;

    kB = size / 1024 + (size % 1024 > 0);

    __asm {
    ; Allocate [kB] kilobytes of XMS memory
        mov ah, 09h
        mov dx, [kB]
        call [XMSControl]
        mov [XMSStatus], ax
        mov [XMSHandle], dx
    }

    /* Check if XMS allocation failed */
    if ( XMSStatus == 0)
        return NULL;

    __asm {
    ; Convert XMS handle to normal pointer
        mov ah, 0Ch
        mov dx, [XMSHandle]
        call [XMSControl]
        mov [XMSStatus], ax

        mov word ptr [XMSPointer],  bx 
        mov word ptr [XMSPointer+2],dx
    }

    if ( XMSStatus == 0 )
    {
        /* Lock failed, deallocate the handle */
        __asm {
        ; Free XMS handle
            mov ah, 0Ah
            mov dx, [XMSHandle]
            call [XMSControl]

        ; Return value is not really interesting 
        ;   mov [XMSStatus], ax
        }
        return NULL;
    }

    /* Create an entry in the handle <-> pointer mapping */
    allocMap[n].XMSHandle = XMSHandle;
    allocMap[n].XMSPointer = XMSPointer;

    return XMSPointer;
}

/* Free a pointer allocated with xmsalloc */
void xmsfree(void huge * XMSPointer)
{
    int n;

    if ( XMSPointer == NULL )
        return;

    if ( initxms() ) 
        return;

    for ( n = 0; n < MAX_XMS_ALLOCATIONS; n++ )
    {
        if ( allocMap[n].XMSPointer == XMSPointer )
        {
            int XMSHandle = allocMap[n].XMSHandle;

            __asm {
            ; Unlock handle so we can free the memory block
                mov ah, 0Dh
                mov dx, [XMSHandle]
                call [XMSControl]

            ; Free XMS memory
                mov ah, 0Ah
                mov dx, [XMSHandle]
                call [XMSControl]

            ; Return value ignored
            }

            /* Clear handle <-> pointer map entry so it can be reused */
            allocMap[n].XMSHandle = 0;
            allocMap[n].XMSPointer = NULL;

            return;
        }
    }
}

/* Write a memory report for debugging purposes */
void xmsreport(FILE * stream)
{
    int XMSVersionNumber = 0;
    int XMSLargestBlock = 0;
    int XMSTotal = 0;

    if ( initxms() ) 
    {
        puts("Could not initialize XMS Driver!");
        return;
    }

    __asm {
    ; Get the driver version number
        mov ah,00h
        call [XMSControl] ; Get XMS Version Number
        mov [XMSVersionNumber], ax

    ; Get the amount of free XMS memory
        mov ah, 08h
        call [XMSControl]
        mov [XMSLargestBlock], ax
        mov [XMSTotal], dx
    }

    fprintf(stream, "XMS Version number: %d\n", XMSVersionNumber);
    fprintf(stream, "Largest available block: %d kB (%d kB total)\n", XMSLargestBlock, XMSTotal);
}

一些具体的问题:

  1. 在哪里可以找到更多关于错误消息的信息?(我猜OP指的是操作码,但其他字段呢?)
  2. 我找到的XMS API参考文献是否仍适用于运行在Windows XP上,还是有一些更新的版本应该参考?
  3. 使用内联汇编会影响系统状态吗?我该如何解决这个问题?
  4. 你有更好的解决办法吗?:-) DOS扩展似乎需要32位模式,这个练习的重点是使用16位模式。

尝试反汇编操作码(在16位模式下)。看起来像是x86机器码,所以你的猜测似乎是正确的。 - Ringding
3个回答

2

我看到了两个问题。

第一个问题:确保你的编译器使用far call而不是near call,否则你会跳转到错误的段并执行未知代码,可能会生成无效的操作码...这似乎就是发生的情况。如果你的编译器默认使用near calls,请在你的代码中尝试使用"call far [XMSControl]"。

第二个问题:NTVDM.EXE在虚拟86模式下运行代码,而不是真正的实模式。虽然Windows XP支持16位应用程序,但它的支持是有限的。你可能会因此在数据库方面遇到其他问题。例如,你将无法访问USB,因此无法打印到USB打印机上,只能使用旧的并口式打印机。祝你好运在旧货市场上找到这样的打印机...


好的建议,但是当使用大内存模型时,所有函数调用默认为“far”。 - Christoffer

2
哎呀,我得回答自己的问题。
虽然使用XMS例程可以分配相对较大的内存,但不可能使用16位指令来寻址。返回值是32位线性地址,如果我理解正确,从16位环境中无法使用它。
可以通过将其部分映射到1Mb可寻址限制以下的某些本地内存缓冲区,并移动窗口来使用该内存块,就像EMS一样,但这根本不值得麻烦。

这就是巨大内存模型的作用。它以不同的方式计算有效地址,然后规范化指针以再次获取段:地址。当然,这会带来性能上的代价。 - Marco van de Voort
1
@Christoffer...你说得对。XMS函数0x0B可以在XMS位置和/或常规内存之间移动内存。但是你已经问了5年了,所以你可能不再关心! - Neil C. Obremski

0
'

0f 04 10 0e 51' 是无效的指令,您正在尝试执行它。

我已经检查过了,我没有看到具有此代码0F 04的x86指令。

'

http://ref.x86asm.net/geek.html#x0F04

所以你需要找出是哪段代码生成了这样的无效指令并进行修复。

至于内存,我一直认为EMS比XMS更容易使用,但我可能是错的。


哦,你知道EMS规范是否可以在网上找到吗? - Christoffer
很遗憾,不是的,那是20年前的事了...我记得大约15年前我用过它,只是几个中断调用来分配交换式EMS页面... - BarsMonster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接