【问题标题】:How can I create a sleep function in 16bit MASM Assembly x86?如何在 16 位 MASM 程序集 x86 中创建睡眠功能?
【发布时间】:2010-12-23 22:31:12
【问题描述】:

我正在尝试在 16 位 MASM 程序集 x86 中创建一个睡眠/延迟过程,例如每 500 毫秒在屏幕上打印一个字符。 从我所做的研究来看,似乎有三种方法可以实现这一点 - 我想使用一种使用 CPU 时钟滴答的方法。

请注意,我在 Mac OS X Snow Leopard 上通过 VMWare Fusion 运行 Windows XP - 我不确定这是否会影响任何事情。

有人可以为我指明正确的方向,或者提供一段我可以调整的工作代码吗?谢谢!

我发现的代码应该每秒在屏幕上打印“A”,但不起作用(我还是想使用毫秒)。

TOP:
MOV AH,2C
INT 21
MOV BH,DH  ; DH has current second
GETSEC:      ; Loops until the current second is not equal to the last, in BH
MOV AH,2C
INT 21
CMP BH,DH  ; Here is the comparison to exit the loop and print 'A'
JNE PRINTA
JMP GETSEC
PRINTA:
MOV AH,02
MOV DL,41
INT 21
JMP TOP

编辑:按照 GJ 的建议,这是一个工作程序。就叫它

DELAY PROC
 TIMER:
 MOV     AH, 00H
 INT     1AH
 CMP     DX,WAIT_TIME
 JB      TIMER
 ADD     DX,3         ;1-18, where smaller is faster and 18 is close to 1 second
 MOV     WAIT_TIME,DX
 RET
DELAY ENDP

【问题讨论】:

  • 您是否在用户空间中运行您的代码?在 Windows 上?
  • 是的。如我的帖子所述,我正在通过虚拟机在 Windows 上运行它:)
  • 在午夜小心你可能会遇到问题。直接读取 0x0040:0x0070 处的刻度内存位置可能会更好。另请阅读:merlyn.demon.co.uk/pas-time.htm#RDT
  • 当定时器的前 16 位滚动到 0 时,您的程序也会失败。
  • 任何人都可以发布对此程序的更正吗?我不确定如何修复它。

标签: windows assembly sleep


【解决方案1】:

实际上你可以使用 ROM BIOS 中断 1Ah 函数 00h, 'Read Current Clock Count'。或者您可以在地址 $40:$6C 处读取 dword,但您必须确保原子读取。它由 MS-DOS 以大约 18.2 Hz 的频率递增。

更多信息请阅读:The DOS Clock

【讨论】:

  • The DOS Clock 链接将我带到一个空白页面。
【解决方案2】:

使用 INT 15h,函数 86h:

致电: AH = 86 小时 CX:DX = 以 uS 为单位的间隔

【讨论】:

  • 这是最好的答案。
【解决方案3】:

..上述所有代码示例的问题在于它们使用非阻塞操作。如果您在相对较长的等待期内检查 CPU 使用率,您会看到它运行在 50% 左右。我们想要的是使用一些 DOS 或 BIOS 功能来阻止执行,使 CPU 使用率接近 0%。

..顺便说一句,BIOS INT 16h, AH=1 函数浮现在脑海。您可以设计一个调用该函数的例程,然后在时间到期时将击键插入键盘缓冲区。这个想法有很多问题;),但它可能是值得深思的。您可能会编写某种中断处理程序。

..在 32 位 windows API 中,有一个“睡眠”功能。我想你可以考虑一下。

【讨论】:

    【解决方案4】:

    我没有测试此代码,但概念必须有效... 保存/恢复 es 寄存器是可选的! 仔细检查代码!

    DelayProcedure:
        push  es                      //Save es and load new es
        mov   ax, 0040h
        mov   es, ax
    //Pseudo atomic read of 32 bit DOS time tick variable
    PseudoAtomicRead1:
        mov   ax, es:[006ch]
        mov   dx, es:[006eh]
        cmp   ax, es:[006ch]
        jne   PseudoAtomicRead1
    //Add time delay to dx,ax where smaller is faster and 18 is close to 1 second
        add   ax, 3
        adc   dx, 0
    //1800AFh is last DOS time tick value so check day overflow
        mov   cx, ax
        mov   bx, dx
    //Do 32 bit subtract/compare
        sub   cx, 00AFh
        sbb   dx, 0018h
        jbe   DayOverflow
    //Pseudo atomic read of 32 bit DOS time tick variable
    PseudoAtomicRead2:
        mov   cx, es:[006ch]
        mov   bx, es:[006eh]
        cmp   cx, es:[006ch]
        jne   PseudoAtomicRead2
    NotZero:
    //At last do 32 bit compare
        sub   cx, ax
        sbb   bx, dx
        jae   Exit
    //Check again day overflow because task scheduler can overjumps last time ticks
        inc   bx                //If no Day Overflow then bx = 0FFh
        jz    PseudoAtomicRead2
        jmp   Exit
    DayOverflow:
    //Pseudo atomic read of 32 bit DOS time tick variable
    PseudoAtomicRead3:
        mov   ax, es:[006ch]
        mov   dx, es:[006eh]
        cmp   dx, es:[006ch]
        jne   PseudoAtomicRead3
    //At last do 32 bit compare
        sub   ax, cx
        sbb   dx, bx
        jb    PseudoAtomicRead3
    Exit:
        pop   es                      //Restore es
        ret
    

    【讨论】:

      【解决方案5】:

      嗯,那么。一个老式的、非恒定的、耗电的延迟循环会使其他线程运行缓慢,如下所示:

             delay equ 5000
      
      top:   mov ax, delay
      loopa: mov bx, delay
      loopb: dec bx
             jnc loopb
             dec ax
             jnc loopa
      
             mov ah,2
             mov dl,'A'
             int 21
             jmp top
      

      延迟是常数的二次方。但是如果你使用这个延迟循环,在世界的某个地方,一只年轻的无辜小猫会死去。

      【讨论】:

        【解决方案6】:

        这不能在纯 MASM 中完成。设置固定延迟的所有旧技巧都在假设您完全控制机器并且是 CPU 上运行的唯一线程的假设下运行,因此如果您等待 5 亿个周期,则恰好 500,000,000/f 秒将过去(对于频率为f的CPU);对于 1GHz 处理器,这将是 500 毫秒。

        因为你在现代操作系统上运行,你正在与许多其他线程共享 CPU(其中包括内核——无论你做什么,你都不能优先于内核!),所以等待 5 亿仅你的线程中的循环将意味着在现实世界中经过了超过 5 亿个循环。这个问题不能单靠用户空间代码解决;您将需要内核的合作。

        解决这个问题的正确方法是查看哪个 Win32 API 函数将您的线程挂起指定的毫秒数,然后调用该函数。您应该能够直接从程序集中执行此操作,可能需要为您的链接器添加额外的参数。或者,可能有一个 NT 内核系统调用来执行这个功能(我对 NT 系统调用的经验很少,老实说不知道 NT 系统调用表是什么样的,但我可能会使用睡眠函数期待看到)。如果系统调用可用,那么从程序集发出直接系统调用可能是执行您想要的最快的方法;它也是最不便携的(但是,你正在编写程序集!)。

        编辑:查看the NT kernel system call table,似乎没有任何与睡眠或获取日期和时间相关的调用(就像您的原始代码使用的那样),但有几个系统调用设置和查询计时器。在等待计时器达到所需延迟时旋转是一种有效但不优雅的解决方案。

        【讨论】:

        • 首先,非常感谢您的精心回复。那么让我扩大选择范围。我如何在机器之间每隔固定时间拨打电话(即我不介意它在一台机器上每 500 毫秒运行一次,在另一台机器上每 150 毫秒运行一次)。
        • 我认为您的意思是“周期/f”而不是“f / 周期”。
        • kigurai:已修复。叹。 yuval:老实说,我对 NT API 还不够熟悉,我不知道你是如何做到这一点的。我只能建议我在这种情况下总是做的事情:查看系统调用表,看看你可以用你拥有的工具构建什么!
        • 在 XP 的 16 位子系统中,他无论如何都不会使用 win32 调用。
        猜你喜欢
        • 1970-01-01
        • 2019-01-12
        • 2021-04-07
        • 1970-01-01
        • 2019-07-02
        • 2012-01-12
        • 1970-01-01
        • 2021-08-29
        • 1970-01-01
        相关资源
        最近更新 更多