【问题标题】:Assembly x86 (16bit): More accurate time measurement汇编 x86 (16bit):更准确的时间测量
【发布时间】:2021-07-31 18:43:21
【问题描述】:

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

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

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

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

到目前为止,这个问题已经占据了我的大部分时间,但我无法在网上找到现有的解决方案。

提前干杯。

【问题讨论】:

  • 哇,这让我回到了 30 年前... :-)
  • 您可以将提供 IRQ #0(默认为中断 8)的 PI​​T 重新编程为更高的频率。 IIRC 默认情况下,它以大约 18.2 Hz(每小时近 65_536 次)的频率触发,除数为 65_536,最大值。您的中断 8 处理程序应仅在某些时候调用先前的处理程序以保留预期的 ROM-BIOS 的计时器滴答频率,即存储在双字 40h:6Ch 中的计数器。
  • 可能最简单的方法是使用out 80h, al。这应该会延迟 1us。我找不到有关精确延迟的权威来源。 Linux uses it 但我不认为它假设有特定的延迟。您可以将out 80h, al 与不太精确的中断源结合起来,以获得更好的精度。此外,根据您针对的特定 CPU 型号和您配置的特定硬件,可能有很多同步源。
  • 您是在尝试查找当前时间(绝对时间)、计时其他运行的时间间隔(测量使用的 CPU 时间),还是有意延迟在一段时间内没有做任何有用的工作?你的标题谈到了测量,我认为你只是提到延迟调用作为某种精确计时应该是可能的证据。
  • 如果您要在现代 x86 上以实模式运行它,rdtsc 用作时间源,如果您校准偏移和比例因子。 (EDX:EAX 中的 64 位计数器,计数自上次重置以来的固定频率“参考周期”(在足够新的 CPU 上)。)How to get the CPU cycle count in x86_64 from C++? 有更多关于 felixcloutier.com/x86/RDTSC.html 的详细信息

标签: assembly time dos x86-16 tasm


【解决方案1】:

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

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

然后,您可以使用RDTSC 命令(读取时间戳计数器),但是有一个问题.. 它在 TASM 中不存在。 它不存在的事实对我们毫无用处,因为所有命令都在 TASM(通常称为助记符)中只是替换操作码,它定义了 CPU 可以运行的每条指令。

当 Intel Pentium CPU 发布时,包含用于 RDTSC 的 OpCode,所以如果你有它的 CPU 及更高版本...你很好。

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

在 TASM 中,有一条指令叫做db,通过它我们可以直接运行 OpCode。

正如 here 所见,我们需要做的是运行 RDTSC:db 0Fh, 31h

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

【讨论】:

  • 请注意,在实模式下使用 32 位操作数大小不同于 32 位模式。后者的意思是“保护模式”。但是在 16 位模式下组装 mov eax, ecx 只需要机器代码中的操作数大小的前缀字节。 .386 after .model 指令释放了 TASM 这样做的意愿。当然,如果你只是想编写完全 32 位的代码,你可以这样做,但它不可能是 DOS .com 程序。
  • @Peter Cordes:从技术上讲,您可以在 DOS .COM 可执行文件中包含 32 位代码段部分,因为您可以包含处理 which switches into DPMI and sets up a 32-bit code descriptor。当然,这需要一个 DPMI 主机。如果您使用 VCPI,或者在 Real 86 模式下启动并自行设置保护模式,情况也是如此。所有可能都来自 .COM 可执行文件。
【解决方案2】:

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

在DOS等实模式操作系统下,16位PIT计数器可以直接从相关端口分两个8位块读取,这个数据可以结合软件维护的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 }

【讨论】:

    猜你喜欢
    • 2011-11-04
    • 1970-01-01
    • 1970-01-01
    • 2011-05-16
    • 1970-01-01
    • 2011-03-16
    • 1970-01-01
    • 1970-01-01
    • 2017-01-25
    相关资源
    最近更新 更多