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



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

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

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

现在我的问题是:

是否有任何可靠的方法来实现这种超时ISR。我读过关于0x1C中断向量的文章,但它提供的分辨率只有55ms。我需要更多的决心。例如,1ms或更低(如果可能)。

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

这里有一个演示,展示了如何将默认计时器频率从18.2Hz更改为其他值。使用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的计时(两者都使用定时器中断来进行各种延迟)。要解决此问题,您需要挂接向量8(IRQ0)而不是向量0x1C(它是从向量8 ISR调用的),并以大约默认的速率从新的IRQ0 ISR调用原始的IRQ0ISR(您需要为此计算中断)。当您没有从ISR调用它时,您需要在从ISR返回之前通过执行outportb(0x20, 0x20);手动发出中断处理结束的信号(原始ISR会这样做,但如果您没有调用它,则由您负责)。

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

您可以挂接定时器中断(08h)并配置PIT以获得高达1.2 Mhz的速率。

以下是一些旧的TASM风格的组装,展示了如何做到这一点:

tmTimerHandler PROC
    push ds
    mov ds,cs:tmDataSeg
    add ds:tmTicker,65536
    pop ds
    jmp cs:tmOldTimer
tmTimerHandler ENDP

tmInit PROC
    mov tmDataSeg,ds
    mov tmTicker,65536
    push es
    ; Save the old timer interrupt vector
    mov ax,3508h
    int 21h
    mov dword ptr tmOldTimer+0,ebx
    mov word ptr tmOldTimer+4,es
    pop es
    ; Install our own timer interrupt vector
    push ds
    mov ax,2508h
    push cs
    pop ds
    mov edx,OFFSET tmTimerHandler
    int 21h
    pop ds
    ; Configure the PIT to generate interrupts
    ; at the maximum rate
    mov al,34h
    out 43h,al
    xor al,al  ; zero divisor
    out 40h,al
    out 40h,al
    ret
tmInit ENDP

tmClose PROC
   push ds
   mov ax,2508h
   lds edx,tmOldTimer
   int 21h
   pop ds
   ret
tmClose ENDP

; Returns the current tick count in eax
tmGetTimer PROC
   pushf
   cli
   xor eax,eax
   out 43h,al
   in al,40h
   mov ah,al
   in al,40h
   xchg ah,al
   neg eax
   add eax,tmTicker
   popf
   ret
tmGetTimer ENDP

.data
  tmOldTimer    df 0
  tmDataSeg dw 0
  tmTicker  dd 0

最新更新