Ожидание в 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 простаивает, что следует учитывать. Стратегия:

  1. Если yield поддерживается, используйте это (мы работаем в многозадачной среде)

  2. Если простоя поддерживается, используйте это. При желании, если мы находимся в кольцо-0, сделать hlt каждый раз перед вызовом бездействия, поскольку задокументировано, что бездействие возвращается немедленно, когда никакая другая программа не готова к запуску.

  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)");
    }
}

Код в моем репозитории github отражает изменения.

Другие вопросы по тегам