gpt4 book ai didi

c - 使用 djgpp 在 DOS 中等待——忙等待的替代方法?

转载 作者:太空宇宙 更新时间:2023-11-03 23:24:03 25 4
gpt4 key购买 nike

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

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

#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() 在需要 ticks 时以毫秒为间隔ticker_wait() 在其主循环中等待下一个报价。

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

#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 .

最佳答案

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

显而易见的解决方案是在内联汇编中放置一个 hlt,因为似乎没有任何库调用这样做。不幸的是,这使我的程序崩溃了。查找原因,是因为默认使用的dpmi server运行程序在ring 3 ... hlt reserved to 环 0。因此,要使用它,您必须修改加载程序 stub 以加载 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;
}

为了让它在普通 DOS 上工作,编译后的可执行文件中的加载器 stub 必须像这样修改:

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

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

还有待测试的是在 win 3.x/9x 下运行时 yielding 是否会扰乱时序。也许时间片太长了,必须检查一下。 更新:使用上面的代码,它在 Windows 95 中运行良好。

hlt 指令的使用以一种奇怪的方式破坏了与 dosbox 0.74 的兼容性。当尝试执行阻塞 getch 时,程序似乎永远挂起() 通过 PDcurses。然而,在 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反射(reflect)了变化。

关于c - 使用 djgpp 在 DOS 中等待——忙等待的替代方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31625723/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com