【问题标题】:Implementing timeouts in MS-DOS/FreeDOS applications在 MS-DOS/FreeDOS 应用程序中实现超时
【发布时间】:2012-07-16 07:41:19
【问题描述】:

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

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

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

现在我的问题是:

是否有任何可靠的方式来实现这种超时 ISR。我读到了 0x1C 中断向量,但它提供的分辨率只有 55 毫秒。我需要更多的分辨率。例如如果可能,1ms 甚至更低

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

【问题讨论】:

    标签: assembly timeout dos


    【解决方案1】:

    这是一个演示,展示了如何将默认计时器速率从 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;
    }
    

    它会在默认情况下打印 1s、2s 和 3s,默认速率是默认速率的两倍和三倍,每个 3 秒。

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

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

    【讨论】:

    • 好吧,我在阅读答案后一直在寻找此类问题,但他们确实退出并不令我感到惊讶。 :) 但是有没有更可靠的方法来实现这些东西,让硬件满意。我的意思是像 Windows 和 Linux 这样的操作系统是如何实现这些东西的?当然,内核大师们的辛勤工作必须归功于他们,但我认为在像 DOS 这样的单任务环境中,实现这些相同的原则会更简单。不是吗?
    • Windows 和 Linux(最常见的一种)都不是实时操作系统。它们旨在稳定地在库存硬件上运行。它们在 VM 中运行时提供时间同步,这与获得完美的计时器中断不同。除此之外,他们并不真正关心执行环境的缺陷,也无能为力。
    • 实际上不需要完美的定时器中断 - 可以容忍几毫秒 - 比如说,不是 2ms,而是在 5ms 后调用中断。我正在寻找一种没有搞砸的稳定方法。
    • 你没有说你想要达到的目标。什么是可以忍受的取决于你在做什么,对吧?
    • 我正在尝试为硬盘驱动器 (HDD) 应用程序实现超时。此应用程序将通过 ata 接口访问硬盘。需要超时来关注磁盘是否遵循协议 - 例如在读取期间,HDD 可能会变得无响应,然后主机需要在超时后发出重置。其他硬件也需要超时,例如连接到并行端口的慢速设备等。因此,分辨率需要以微秒为单位。
    【解决方案2】:

    您可以挂钩定时器中断 (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
    

    【讨论】:

    • 好吧,感谢您对问题的了解。 :) 但正如 Alexey 指出的那样,这种方法似乎会出现一些硬件问题。我正在用这种方法寻找更多这样的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多