我正在为C中的FreeDOS编写一个DOS保护模式应用程序,编译器是OpenWatcom1.8。
我需要编写一个超时例程,该例程启动一个超时变量,然后当超时变量变为零时,应用程序将知道指定的毫秒数(Ms)已经过去。
我认为这可以通过写ISR来完成。ISR将在每次调用ISR时减少超时变量。应用程序继续检查超时变量是否为零。
现在我的问题是:
是否有任何可靠的方法来实现这种超时ISR。我读过大约0x1C的中断向量,但这只提供了55 of的分辨率。我需要更多的决心。例如,1ms,甚至更低,如果可能的话。
这是如何做到的呢?有什么想法或建议吗?
发布于 2012-07-16 09:11:15
下面是一个演示,演示如何将默认定时器频率从18.2Hz更改为其他值。使用Borland C++ (作为实模式EXE应用程序)和Open 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透明地处理。
发布于 2012-07-16 08:25:13
您可以挂起计时器中断(08h)并配置PIT以获得高达1.2Mhz的速率。
下面是一些旧的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 0https://stackoverflow.com/questions/11499991
复制相似问题