assembly - 在 MS-DOS/FreeDOS 应用程序中实现超时

标签 assembly timeout dos

我正在用 C 语言为 FreeDOS 编写一个 DOS 保护模式应用程序。编译器是 Open Watcom 1.8。

我需要编写一个超时例程,它启动一个超时变量,然后当超时变量变为零时,应用程序将知道已经过了指定的毫秒数。

我认为这可以通过编写 ISR 来完成。每次调用 ISR 时,ISR 都会递减超时变量。应用程序不断检查超时变量是否为零。

现在我的问题是:

是否有任何可靠的方法来实现这种超时ISR。我读到了有关 0x1C 中断向量的内容,但它提供的分辨率仅为 55ms。我需要更多的决心。例如1ms 甚至更低(如果可能的话)。

如何做到这一点?有什么想法或建议吗?

最佳答案

以下演示展示了如何将默认计时器速率从 18.2 Hz 更改为其他值。使用 Borland Turbo C++(作为实模式 EXE 应用程序)和 Open Watcom C++ 1.9(作为 DPMI/dos4gw 应用程序)进行编译。

// file: tmr.c
#include <stddef.h>
#include <stdio.h>
#include <dos.h>
#include <conio.h>

#ifdef __WATCOMC__
// Define Borland C aliases:
#define getvect _dos_getvect
#define setvect _dos_setvect
#define outportb outp
#define inportb inp
#define disable _disable
#define enable _enable
#endif

typedef unsigned uint;
typedef unsigned char uchar;

void interrupt (*pOldInt1C)(void) = NULL;

volatile uint int1Ccnt = 0;

void interrupt NewInt1C(void)
{
  int1Ccnt++;
  pOldInt1C();
}

void SetPitResolutionInHz(uint ResolutionInHz)
{
  uint count;

  if (ResolutionInHz < 18 || ResolutionInHz >= 65535)
    return;

  count = (ResolutionInHz == 18) ? 0 : (uint)(1193181 / ResolutionInHz);

  disable();

  outportb(0x43, 0x34);
  outportb(0x40, (uchar)(count & 0xFF));
  outportb(0x40, (uchar)(count >> 8));

  enable();
}

int main(void)
{
  pOldInt1C = getvect(0x1C);
  setvect(0x1C, &NewInt1C);

  printf("3 seconds delay, default timer rate...\n");
  while (int1Ccnt < 18*1*3)
  {
    static uint last = 0;
    if (last != int1Ccnt)
    {
      printf("1"); fflush(stdout);
      last = int1Ccnt;
    }
  }
  printf("\n");

  SetPitResolutionInHz(18*2);
  printf("3 seconds delay, double timer rate...\n");
  int1Ccnt = 0;
  while (int1Ccnt < 18*2*3)
  {
    static uint last = 0;
    if (last != int1Ccnt)
    {
      printf("2"); fflush(stdout);
      last = int1Ccnt;
    }
  }
  printf("\n");

  SetPitResolutionInHz(18*3);
  printf("3 seconds delay, triple timer rate...\n");
  int1Ccnt = 0;
  while (int1Ccnt < 18*3*3)
  {
    static uint last = 0;
    if (last != int1Ccnt)
    {
      printf("3"); fflush(stdout);
      last = int1Ccnt;
    }
  }
  printf("\n");

  // Set default rate: 1193181 MHz / 65536 = 18.2 Hz
  SetPitResolutionInHz(18*1);
  printf("3 seconds delay, default timer rate...\n");
  int1Ccnt = 0;
  while (int1Ccnt < 18*1*3)
  {
    static uint last = 0;
    if (last != int1Ccnt)
    {
      printf("1"); fflush(stdout);
      last = int1Ccnt;
    }
  }
  printf("\n");

  setvect(0x1C, pOldInt1C);

  return 0;
}

它将以默认速率打印 1 秒、2 秒和 3 秒,默认速率的两倍和三倍,各 3 秒。

请注意,这段代码会搞乱 BIOS/DOS 计时(两者都使用定时器中断来实现各种延迟)。要解决此问题,您需要 Hook 向量 8 (IRQ0) 而不是向量 0x1C(它是从向量 8 ISR 调用的),并以大约默认速率从新 IRQ0 ISR 调用原始 IRQ0 ISR(您需要为此计算中断数)。当您不从 ISR 中调用它时,您需要在从 ISR 返回之前通过执行 outportb(0x20, 0x20); 手动发出中断处理结束信号(原始 ISR 会这样做,但是如果你不调用它,这是你的责任)。

编辑:请注意,在虚拟化环境中,计时器中断可能会丢失或以不规则的间隔传递,特别是如果您设置了较高的中断率并且您的 PC 正忙于其他任务。此外,即使在物理机上,您也可能会遇到高定时器频率的问题,系统管理中断 (SMI) 会在中断传递中引入抖动/可变延迟。您无法摆脱它们,它们由 BIOS 透明地处理。

关于assembly - 在 MS-DOS/FreeDOS 应用程序中实现超时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11499991/

相关文章:

c - ARM 检测中断何时占用过多 CPU 时间

c - 用 while 循环代替递归

c - C 等价于什么?

linux - 条件移动问题

c++ - C++中的多个超时

assembly - 参加汇编类(class),陷入DOS!

file - 如何使用 DOS 批处理脚本读取具有分隔名称值对的文件?

Emacs - 如何避免或替换错误的字符编码?

printing - 写入时 libusb_bulk_transfer 超时

JAVA Apache HttpClient SSL 超时