【问题标题】:Waiting in DOS using djgpp -- alternatives to busy wait?在 DOS 中使用 djgpp 等待——忙等待的替代方案?
【发布时间】:2015-07-25 10:54:52
【问题描述】:

我最近写了一个小诅咒游戏,因为它所需要的只是一些计时器机制和一个诅咒实现,所以尝试为 DOS 构建它的想法很自然。诅咒是由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

【问题讨论】:

  • 我怀疑 DOS 提供了这个。 Wikipedia page on the HLT instruction 表示“在 1994 年英特尔 DX4 处理器发布之前,它没有被 MS-DOS 使用,也不是专门为降低功耗而设计的”,当时 DOS 正被 Windows 取代。 AFAIK,当时的忙碌等待是非常典型的。
  • 您是否考虑过基于中断的键盘例程?当你在 DOS 中按下一个键时,会产生一个中断,然后你会找出哪个键被按下并将其存储在一个键数组中。然后,当您的游戏逻辑需要处理输入时,它只需检查数组以查看是否按下了按钮,这会导致您的玩家移动、开火等。您还可以使用 Allegro 之类的库在 DJGPP 中执行与游戏相关的任务更容易做到。
  • 大多数时候,我只需要这个游戏以恒定的速度运行(它是蛇的变种),因此是滴答声。输入通过 curses 处理以保持可移植性。如果我只为 DOS 端口执行自己的输入中断例程,那么问题(我 认为 已解决,请参阅我自己的答案,但欢迎使用 cmets)是否会持续存在做某事(例如hlt)等待中断?

标签: c dos djgpp


【解决方案1】:

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

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

浏览文档时,我遇到了__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 下运行时,yield 是否会影响时间。也许时间片太长了,必须检查一下。 更新:使用上面的代码,它在 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 中,每次调用 idle 之前都执行hlt,因为当没有其他程序准备好运行时,idle 被记录为立即返回。

  3. 否则,在 ring-0 中只使用普通的hlt 指令。

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

这是一个测试所有可能性的小示例程序 (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 反映了更改。

【讨论】:

    猜你喜欢
    • 2014-04-07
    • 1970-01-01
    • 1970-01-01
    • 2012-11-02
    • 2015-03-13
    • 1970-01-01
    • 2013-10-15
    • 1970-01-01
    • 2012-03-31
    相关资源
    最近更新 更多