使用djgpp在DOS中等待 - 繁忙等待的替代方案?

时间:2015-07-25 10:54:53

标签: c dos djgpp

我最近写了一个小诅咒游戏,因为它需要工作的是一些计时器机制和一个curses实现,尝试为DOS构建它的想法很自然。 Curses由pdcurses提供给DOS。

POSIX和Win32之间的时序已经不同了,所以我定义了这个接口:

#ifndef CSNAKE_TICKER_H
#define CSNAKE_TICKER_H

void ticker_init(void);
void ticker_done(void);

void ticker_start(int msec);
void ticker_stop(void);
void ticker_wait(void);

#endif

游戏只需要一个毫秒间隔ticker_init()ticker_done()ticker_start(),需要一个毫秒间隔,并在其主循环中ticker_wait()等待下一个刻度

在DOS上使用与POSIX平台相同的实现,使用setitimer(),没有用。一个原因是与djgpp一起出现的C lib没有实现waitsig()。所以我为DOS创建了一个新的接口实现:

#undef __STRICT_ANSI__
#include <time.h>

uclock_t tick;
uclock_t nextTick;
uclock_t tickTime;

void
ticker_init(void)
{
}

void
ticker_done(void)
{
}

void
ticker_start(int msec)
{
    tickTime = msec * UCLOCKS_PER_SEC / 1000;
    tick = uclock();
    nextTick = tick + tickTime;
}

void
ticker_stop()
{
}

void
ticker_wait(void)
{
    while ((tick = uclock()) < nextTick);
    nextTick = tick + tickTime;
}

这就像dosbox中的魅力一样(我现在没有真正的DOS系统)。但我担心的是:忙着等待真的是我能在这个平台上做的最好吗?我希望有一个解决方案,让CPU至少节省一些能量。

供参考,here's the whole source

1 个答案:

答案 0 :(得分:2)

好的,我想我终于可以回答我自己的问题了(感谢Wyzard提供了有用的评论!)

显而易见的解决方案,因为似乎没有任何库调用这样做,就是在内联汇编中放置hlt。不幸的是,这使我的计划崩溃了。查找原因,这是因为使用的默认dpmi服务器运行ring 3中的程序... hlt保留给ring 0。因此,要使用它,您必须修改加载程序存根以在dpmi中加载运行程序的ring 0服务器。见后面。

浏览文档时,我遇到了__dpmi_yield()。如果我们在多任务环境(Win 3.x或9x ...)中运行,操作系统已经提供了dpmi服务器,当然,在这种情况下,我们想要放弃我们的服务器等待时间片而不是尝试特权hlt

所以,把它们放在一起,DOS的源代码现在看起来像这样:

#undef __STRICT_ANSI__
#include <time.h>
#include <dpmi.h>
#include <errno.h>

static uclock_t nextTick;
static uclock_t tickTime;
static int haveYield;

void
ticker_init(void)
{
    errno = 0;
    __dpmi_yield();
    haveYield = errno ? 0 : 1;
}

void
ticker_done(void)
{
}

void
ticker_start(int msec)
{
    tickTime = msec * UCLOCKS_PER_SEC / 1000;
    nextTick = uclock() + tickTime;
}

void
ticker_stop()
{
}

void
ticker_wait(void)
{
    if (haveYield)
    {
        while (uclock() < nextTick) __dpmi_yield();
    }
    else
    {
        while (uclock() < nextTick) __asm__ volatile ("hlt");
    }
    nextTick += tickTime;
}

为了使其能够在 plain DOS上运行,编译后的可执行文件中的加载程序存根必须像这样修改:

<path to>/stubedit bin/csnake.exe dpmi=CWSDPR0.EXE

CWSDPR0.EXE是运行dpmi中所有代码的ring 0服务器。

仍在测试的是,在win 3.x / 9x下运行时,屈服是否会影响时机。也许时间片太长,必须检查。 更新:使用上面的代码在Windows 95中运行良好。

hlt指令的使用以一种奇怪的方式破坏了与dosbox 0.74的兼容性。当尝试通过PDcurses进行阻塞getch()时,程序似乎永远挂起。但是,在virtualbox中的真实MS-DOS 6.22上并没有发生这种情况。 更新:这是dosbox 0.74中的一个错误,已在当前SVN树中修复。

鉴于这些发现,我认为这是等待的好方法。在DOS程序中。

更新:通过检查所有可用方法并选择最佳方法,可以做得更好。我发现了DOS idle call也应该考虑。策略:

  1. 如果支持yield,请使用此(我们在多任务处理环境中运行)

  2. 如果支持空闲,请使用此选项。可选地,如果我们在ring-0中,每次在调用空闲之前执行hlt,因为当没有其他程序准备好运行时,空闲被记录为立即返回。

  3. 否则,在ring-0中只使用简单的hlt说明。

  4. 忙着等待作为最后的手段。

  5. 这是一个小例子程序(DJGPP),可以测试所有可能性:

    #include <stdio.h>
    #include <dpmi.h>
    #include <errno.h>
    
    static unsigned int ring;
    
    static int
    haveDosidle(void)
    {
        __dpmi_regs regs;
        regs.x.ax = 0x1680;
        __dpmi_int(0x28, &regs);
        return regs.h.al ? 0 : 1;
    }
    
    int main()
    {
        puts("checking idle methods:");
    
        fputs("yield (int 0x2f 0x1680): ", stdout);
        errno = 0;
        __dpmi_yield();
    
        if (errno)
        {
            puts("not supported.");
        }
        else
        {
            puts("supported.");
        }
    
        fputs("idle (int 0x28 0x1680): ", stdout);
    
        if (!haveDosidle())
        {
            puts("not supported.");
        }
        else
        {
            puts("supported.");
        }
    
        fputs("ring-0 HLT instruction: ", stdout);
        __asm__ ("mov %%cs, %0\n\t"
                 "and $3, %0" : "=r" (ring));
    
        if (ring)
        {
            printf("not supported. (running in ring-%u)\n", ring);
        }
        else
        {
            puts("supported. (running in ring-0)");
        }
    }
    

    code in my github repo反映了这些变化。