我最近写了一个小诅咒游戏,因为它需要工作的是一些计时器机制和一个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至少节省一些能量。
答案 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也应该考虑。策略:
如果支持yield,请使用此(我们在多任务处理环境中运行)
如果支持空闲,请使用此选项。可选地,如果我们在ring-0中,每次在调用空闲之前执行hlt
,因为当没有其他程序准备好运行时,空闲被记录为立即返回。
否则,在ring-0中只使用简单的hlt
说明。
忙着等待作为最后的手段。
这是一个小例子程序(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, ®s);
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反映了这些变化。