我正在为C中的FreeDOS编写DOS保护模式应用程序。编译器是开放的Watcom 1.8。
我需要编写一个超时例程,它启动一个超时变量,然后当超时变量变为零时,应用程序将知道指定的毫秒数(ms)已经过去。
我认为可以通过编写ISR来完成。每次调用ISR时,ISR都会减少超时变量。应用程序不断检查超时变量是否为零。
现在我的问题是:
是否有任何可靠的方式来实现此类超时ISR。我读到了关于0x1C中断向量,但是它提供的分辨率只有55ms。我需要更多的分辨率。对于例如如果可能, 1ms甚至更低。
如何做到这一点?有什么想法或建议吗?
答案 0 :(得分:1)
您可以挂钩定时器中断(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
答案 1 :(得分: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透明地处理。