assembly 组件x86(16比特):更精确的时间测量

yrdbyhpb  于 2022-11-24  发布在  其他
关注(0)|答案(3)|浏览(153)

我正在使用DOSBox在TASM 16位中编程,今天的问题是:使用DOS INT 21 h/2Ch我可以得到系统当前的百分之一秒。这很好...直到它不是。
我在找一个至少半精确的时间单位我确定这是可能的
你会问为什么?看看INT 15h/86h。使用这个中断,我可以将程序延迟微秒。如果存在这样的精度,我确信获得毫秒级的延迟将是轻而易举的事。
我有一些想法:使用每1/1024秒发生一次的INT 70h,但我不知道如何监听中断,也不希望使用不能被10除的定时系统。
这个问题现在已经占据了我的上风,我在网上找不到一个已经存在的解决方案。
提前干杯。

gwbalxhn

gwbalxhn1#

在16位PC兼容x86系统中,PIT(可编程间隔定时器)使用1.19318MHz的时钟输入来递减16位计数器。每当计数器在216 = 65536次递增后回绕时,就会产生中断。BIOS提供的ISR(中断服务程序)处理它,然后以1.19318MHz / 65536 ~= 18.2 Hz的频率递增软件计数器。
在DOS和其它实模式操作系统下,16位PIT计数器可以从相关端口以两个8位块的形式直接读取,并且该数据可以与软件维护的滴答计数器组合以实现毫秒分辨率。基本上,使用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 }
de90aj5v

de90aj5v2#

非常感谢Peter Cordes在评论中的回答,我现在将把答案发布给任何计划使用30年前老式编译器的人。
粗略地说,在16位TASM中你能得到的最好的时钟仍然不足以保证准确性。幸运的是,在TASM中你可以通过使用.386指令(如这里所提到的)“解锁”32位模式。
然后,您可以使用RDTSC命令(读取时间戳计数器),但有一个问题..它不存在于TASM中。它不存在的事实对我们没有任何意义,因为TASM中的所有命令(通常称为助记符)只是OpCode的替代品,OpCode定义了CPU可以运行的每条指令。
当英特尔奔腾CPU发布时,包含了RDTSC的OpCode,所以如果您有一个来自它和更高版本的CPU......您很好。
现在,如果RDTSC指令在TASM中不存在(但在CPU中存在),我们如何运行它?
在TASM中,有一条名为db的指令,我们可以使用它直接运行OpCode。
如此处所示,要运行RDTSC,我们需要执行以下操作:db 0Fh, 31h .
就是这样!你现在可以很容易地运行这条指令,你的程序仍然会很混乱,但是是一个 * 定时 * 的混乱!

mrfwxfqh

mrfwxfqh3#

注意:这不是一个限定的答案,而是对@njuffa的答案的补充说明。希望它能帮助其他人理解代码。google把我带到这里,我忍不住在使用它之前先阅读他的代码。
计算毫秒的公式为(BIOS_counter*65536+PIT_counter) / 1193.18
asm代码在移位过程中丢弃最后8位,类似于(counter*multiplier)>>8counter*multiplier/256
65536/1193.18 = multiplier/256得到程序集中的multiplier=0x36ED。代码使用乘法和除法(移位)来完成非整数计算。
另一点值得注意:(BIOS_counter*65536 + PIT_counter) / 1193.18等于
(BIOS_counter*65536 + PIT_counter*65536/65536) / 1193.18等于
(BIOS_counter*65536 + HIWORD(PIT_counter*65536)) / 1193.18队列
(BIOS_counter*multiplier + HIWORD(PIT_counter*multiplier)) >> 8
我不明白的是最后的修正使用25110。
另一件事值得注意:RDTSC仅适用于Pentium+,如果您关心的话。(参考:(第10页)

相关问题