汇编 x8616 位:更准确的时间测量

问题描述

我正在使用 DOSBox 以 TASM 16 位编程,这是今天的问题: 使用 DOS INT 21h/2Ch 我可以获得系统当前的百分之一秒。 这很好,一切......直到它不是。

看,我正在寻找以毫秒为单位的至少半准确的时间测量,我相信这是可能的。

为什么,你问?看看INT 15h/86h。 使用此中断,我可以将程序延迟微秒。 如果存在这样的精度,我相信获得毫秒将是在公园散步。

我的一些想法:使用每 1/1024 秒发生一次的 INT 70h,但我不知道如何监听中断,我也不想要一个不能被除以的计时系统10.

这个问题现在已经让我变得更好了,我没能在网上找到现有的解决方案。

提前干杯。

解决方法

在 16 位 PC 兼容的 x86 系统中,PIT(可编程间隔定时器)使用 1.19318MHz 的时钟输入来递减 16 位计数器。每当计数器在 216 = 65536 次增量后回绕时,就会产生一个中断。 BIOS 提供的 ISR(中断服务例程)处理它然后增加一个软件计数器,频率为 1.19318MHz / 65536 ~= 18.2 Hz。

在DOS等实模式操作系统下,16位PIT计数器可以直接从相关端口分两个8位chunk读取,这个数据可以结合软件维护的tick counter实现毫秒级解决。基本上,最后使用的是 48 位滴答计数器,其中由 BIOS 维护的 32 位软件计数器构成最高有效位,而 16 位 PIT 计数器构成最低有效位。

由于数据并非一口气全部读出,因此存在竞争条件的风险,必须适当处理。此外,一些 BIOS 用于将 PIT 编程为方波发生器,而不是简单的速率计数器。虽然这不会干扰递增软件节拍的任务,但会干扰 PIT 计数器寄存器与软件节拍的直接组合。这需要对 PIT 进行一次性初始化,以确保它在速率计数模式下运行。

以下是 16 位汇编代码,封装为 Turbo Pascal 单元,我多年来一直使用它来实现毫秒精度的稳健计时。这里从滴答计数到毫秒的转换有点像黑盒子。我丢失了它的设计文档,现在无法快速重建它。我记得这个定点计算有一个足够小的抖动,可以可靠地测量毫秒。 Turbo-Pascal 的调用约定要求在 DX:AX 寄存器对中返回 32 位整数结果。

UNIT Time;   { Copyright (c) 1989-1993 Norbert Juffa }

INTERFACE

FUNCTION Clock: LONGINT;             { same as VMS; time in milliseconds }


IMPLEMENTATION

FUNCTION Clock: LONGINT; ASSEMBLER;
ASM
             PUSH    DS              { save caller's data segment }
             MOV     DS,Seg0040     {  access ticker counter }
             MOV     BX,6Ch         { offset of ticker counter in segm.}
             MOV     DX,43h         { timer chip control port }
             MOV     AL,4           { freeze timer 0 }
             PUSHF                   { save caller's int flag setting }
             CLI                     { make reading counter an atomic operation}
             MOV     DI,DS:[BX]     { read BIOS ticker counter }
             MOV     CX,DS:[BX+2]
             STI                     { enable update of ticker counter }
             OUT     DX,AL          { latch timer 0 }
             CLI                     { make reading counter an atomic operation}
             MOV     SI,DS:[BX]     { read BIOS ticker counter }
             MOV     BX,DS:[BX+2]
             IN      AL,40h         { read latched timer 0 lo-byte }
             MOV     AH,AL          { save lo-byte }
             IN      AL,40h         { read latched timer 0 hi-byte }
             POPF                    { restore caller's int flag }
             XCHG    AL,AH          { correct order of hi and lo }
             CMP     DI,SI          { ticker counter updated ? }
             JE      @no_update      { no }
             OR      AX,AX          { update before timer freeze ? }
             JNS     @no_update      { no }
             MOV     DI,SI          { use second }
             MOV     CX,BX          {  ticker counter }
@no_update:  NOT     AX              { counter counts down }
             MOV     BX,36EDh       { load multiplier }
             MUL     BX              { W1 * M }
             MOV     SI,DX          { save W1 * M (hi) }
             MOV     AX,BX          { get M }
             MUL     DI              { W2 * M }
             XCHG    BX,AX          { AX = M,BX = W2 * M (lo) }
             MOV     DI,DX          { DI = W2 * M (hi) }
             ADD     BX,SI          { accumulate }
             ADC     DI,0           {  result }
             XOR     SI,SI          { load zero }
             MUL     CX              { W3 * M }
             ADD     AX,DI          { accumulate }
             ADC     DX,SI          {  result in DX:AX:BX }
             MOV     DH,DL          { move result }
             MOV     DL,AH          {  from DL:AX:BX }
             MOV     AH,AL          {   to }
             MOV     AL,BH          {    DX:AX:BH }
             MOV     DI,DX          { save result }
             MOV     CX,AX          {  in DI:CX }
             MOV     AX,25110       { calculate correction }
             MUL     DX              {  factor }
             SUB     CX,DX          { subtract correction }
             SBB     DI,SI          {  factor }
             XCHG    AX,CX          { result back }
             MOV     DX,DI          {  to DX:AX }
             POP     DS              { restore caller's data segment }
END;


BEGIN
   Port [$43] := $34;                { need rate generator,not square wave }
   Port [$40] := 0;                  { generator as programmed by some BIOSes }
   Port [$40] := 0;                  { for timer 0 }
END. { Time }
,

非常感谢 Peter Cordes 在评论中的回答,我现在会将答案发布给其他计划使用 30 年前的老式编译器的人。

粗略地说,您可以在 16 位 TASM 中获得的最佳时钟仍然不够准确。 幸运的是,在 TASM 中,您可以使用 .386 指令(如前面提到的 here)“解锁”32 位模式。

然后,您可以使用RDTSC 命令(Read Time-Stamp Counter),但是有一个问题......它在TASM 中不存在。 它不存在的事实对我们没有任何意义,因为所有命令都在 TASM 中(通常称为助记符)只是 OpCode 的替代品,它定义了 CPU 可以运行的每条指令。

当 Intel Pentium CPU 发布时,包含了 RDTSC 的操作码,所以如果你有一个 CPU 和更高版本......你很好。

现在,如果 TASM 中不存在 RDTSC 指令,我们如何运行它? (但在我们的 CPU 中确实如此)

在 TASM 中,有一个名为 db 的指令,我们可以使用它直接运行 OpCode。

here所示,运行 RDTSC 需要做的是:db 0Fh,31h

就是这样!您现在可以轻松地运行此指令,您的程序仍将保持一团糟,但 定时 是一团糟!