Ожидание в DOS с использованием djgpp - альтернативы занятому ожиданию?
Недавно я написал небольшую игру curses, и поскольку все, что ей нужно для работы, - это какой-то механизм таймера и реализация curses, идея попробовать построить его для DOS приходит естественным образом. Проклятия предоставлены pdcurses
для дос.
Времена уже отличаются между 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()
не работал. Одной из причин было то, что C lib, поставляемая с djgpp, не реализует 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). Но меня беспокоит: действительно ли занят ожидание - лучшее, что я могу сделать на этой платформе? Я хотел бы иметь решение, позволяющее процессору хотя бы сэкономить энергию.
Для справки вот весь источник.
1 ответ
Хорошо, я думаю, что смогу наконец ответить на свой вопрос (спасибо 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;
}
Для того чтобы это работало в простой 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
странным образом.. программа, кажется, навсегда зависает при попытке сделать блокировку getch()
через PDcurses. Это не происходит, однако, на реальном MS-DOS 6.22 в virtualbox
, Обновление: это ошибка в dosbox 0.74
это зафиксировано в текущем SVN
дерево.
Учитывая эти выводы, я полагаю, что это лучший способ "приятно" подождать в программе для DOS.
Обновление: можно сделать еще лучше, проверив все доступные методы и выбрав лучший. Я обнаружил, что вызов DOS простаивает, что следует учитывать. Стратегия:
Если yield поддерживается, используйте это (мы работаем в многозадачной среде)
Если простоя поддерживается, используйте это. При желании, если мы находимся в кольцо-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)");
}
}
Код в моем репозитории github отражает изменения.