如何在 16 位 MASM Assembly x86 中创建睡眠函数?

发布于 2024-08-13 22:26:46 字数 892 浏览 7 评论 0原文

我正在尝试在 16 位 MASM Assembly 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

I am trying to create a sleep/delay procedure in 16bit MASM Assembly x86 that will, say, print a character on the screen every 500ms.
From the research I have done, it seems that there are three methods to achieve this - I would like to use the one that uses CPU clock ticks.

Please note I am running Windows XP through VMWare Fusion on Mac OS X Snow Leopard - I am not sure if that affects anything.

Could someone please point me in the right direction, or provide a working piece of code I can tweak? Thank you!

The code I have found is supposed to print 'A' on the screen every second, but does not work (I'd like to use milliseconds anyways).

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

EDIT: Following GJ's advice, here's a working procedure. Just call it

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

奈何桥上唱咆哮 2024-08-20 22:26:46

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

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

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

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

This cannot be done in pure MASM. All the old tricks for setting a fixed delay operate on the assumption that you have total control of the machine and are the only thread running on a CPU, so that if you wait 500 million cycles, exactly 500,000,000/f seconds will have elapsed (for a CPU at frequency f); that'd be 500ms for a 1GHz processor.

Because you are running on a modern operating system, you are sharing the CPU with many other threads (among them, the kernel -- no matter what you do, you cannot take priority over the kernel!), so waiting 500 million cycles in only your thread will mean that more than 500 million cycles elapse in the real world. This problem cannot be solved by userspace code alone; you are going to need the cooperation of the kernel.

The proper way to solve this is to look up what Win32 API function will suspend your thread for a specified number of milliseconds, then just call that function. You should be able to do this directly from assembly, possibly with additional arguments to your linker. Or, there might be an NT kernel system call to perform this function (I have very little experience with NT system calls, and honestly have no idea what the NT system call table looks like, but a sleep function is the sort of thing I might expect to see). If a system call is available, then issuing a direct system call from assembly is probably the quickest way to do what you want; it's also the least portable (but then, you're writing assembly!).

Edit: Looking at the NT kernel system call table, there don't appear to be any calls related to sleeping or getting the date and time (like your original code uses), but there are several system calls to set up and query timers. Spinning while you wait for a timer to reach the desired delay is one effective, if inelegant, solution.

蓝礼 2024-08-20 22:26:46

使用 INT 15h,功能 86h:

调用方式:
啊=86小时
CX:DX = 间隔(以我们为单位)

use INT 15h, function 86h:

Call With:
AH = 86h
CX:DX = interval in uS

九局 2024-08-20 22:26:46

实际上,您可以使用 ROM BIOS 中断 1Ah 功能 00h,“读取当前时钟计数”。或者您可以读取地址 $40:$6C 处的双字,但必须确保原子读取。它由 MS-DOS 以大约 18.2 Hz 的频率递增。

有关更多信息,请阅读:DOS 时钟

Actually you can use ROM BIOS interrupt 1Ah function 00h, 'Read Current Clock Count'. Or you can read dword at address $40:$6C but you must ensure atomic read. It is incremented by MS-DOS at about 18.2 Hz.

For more information read: The DOS Clock

勿忘初心 2024-08-20 22:26:46

好吧,那么。旧式的、非恒定的、耗电的延迟循环会导致其他线程运行速度变慢,看起来像:

       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

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

Well, then. An old style, non constant, power consuming delay loop which will make other threads running slow down would look like:

       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

The delay is quadratic to the constant. But if you use this delay loop, somewhere in the world a young innocent kitten will die.

禾厶谷欠 2024-08-20 22:26:46

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

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

I didn't test this code but concept must work...
Save/restore es register is optional!
Check code carefully!

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
赠意 2024-08-20 22:26:46

这是一个相当简单的示例,如果需要较长且不高精度的延迟,则该示例应该可以工作。

要使用该延迟,请以 AX 为单位以 125 毫秒为增量指定延迟。

;----------------------------------------------------------------------------;
; Simple delay based on PIT timer ticks
;----------------------------------------------------------------------------;
; Input: AX = delay_time in 1/8th of a second (~125ms) increments
;----------------------------------------------------------------------------;
DELAY_TIMER PROC
    STI                             ; ensure interrupts are on
    PUSH    CX                      ; call-preserve CX and DS (if needed)
    PUSH    DS
    MOV     CX, 40H                 ; set DS to BIOS Data Area
    MOV     DS, CX
    MOV     CX, 583                 ; delay_factor = 1/8 * 18.2 * 256
    MUL     CX                      ; AH (ticks) = delay_time * delay_factor
    XOR     CX, CX                  ; CX = 0
    MOV     CL, AH                  ; CX = # of ticks to wait
    MOV     AH, BYTE PTR DS:[6CH]   ; get starting tick counter
TICK_DELAY:
    HLT                             ; wait for any interrupt
    MOV     AL, BYTE PTR DS:[6CH]   ; get current tick counter
    CMP     AL, AH                  ; still the same?
    JZ      TICK_DELAY              ; loop if the same
    MOV     AH, AL                  ; otherwise, save new tick value to AH
    LOOP    TICK_DELAY              ; loop until # of ticks (CX) has elapsed
    POP     DS
    POP     CX
    RET
DELAY_TIMER ENDP

下面是一个“睡眠”1/2 秒的示例:

MOV     AX, 4                       ; delay for 1/2 seconds (4 * 1/8 seconds)
CALL    IO_DELAY_TIMER

这有点“非阻塞”,因为它会在滴答之间停止 CPU。在单用户 DOS 上运行的真实硬件上,这当然不是问题,但在 VM/Windows 环境中,它可能会使其成为更好的邻居。

Here's a fairly simple example that should work if a long, not highly precise delay is needed.

To use, specify the delay in AX in 125ms increments.

;----------------------------------------------------------------------------;
; Simple delay based on PIT timer ticks
;----------------------------------------------------------------------------;
; Input: AX = delay_time in 1/8th of a second (~125ms) increments
;----------------------------------------------------------------------------;
DELAY_TIMER PROC
    STI                             ; ensure interrupts are on
    PUSH    CX                      ; call-preserve CX and DS (if needed)
    PUSH    DS
    MOV     CX, 40H                 ; set DS to BIOS Data Area
    MOV     DS, CX
    MOV     CX, 583                 ; delay_factor = 1/8 * 18.2 * 256
    MUL     CX                      ; AH (ticks) = delay_time * delay_factor
    XOR     CX, CX                  ; CX = 0
    MOV     CL, AH                  ; CX = # of ticks to wait
    MOV     AH, BYTE PTR DS:[6CH]   ; get starting tick counter
TICK_DELAY:
    HLT                             ; wait for any interrupt
    MOV     AL, BYTE PTR DS:[6CH]   ; get current tick counter
    CMP     AL, AH                  ; still the same?
    JZ      TICK_DELAY              ; loop if the same
    MOV     AH, AL                  ; otherwise, save new tick value to AH
    LOOP    TICK_DELAY              ; loop until # of ticks (CX) has elapsed
    POP     DS
    POP     CX
    RET
DELAY_TIMER ENDP

Here is an example to "sleep" for 1/2 second:

MOV     AX, 4                       ; delay for 1/2 seconds (4 * 1/8 seconds)
CALL    IO_DELAY_TIMER

This is somewhat "non-blocking" as it will halt the CPU between ticks. It would of course be a non-issue on real hardware running on single-user DOS, but in a VM/Windows environment it might make it a better neighbor.

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

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

..在32位Windows API中,有一个“睡眠”功能。我想你可能会想到这一点。

..The problem with all of the above code examples is that they use non-blocking operations. If you examine the CPU usage during a relatively long wait period, you will see it running around 50%. What we want is to use some DOS or BIOS function that blocks execution so that CPU usage is near 0%.

..Offhand, the BIOS INT 16h, AH=1 function comes to mind. You may be able to devise a routine that calls that function, then inserts a keystroke into the keyboard buffer when the time has expired. There are numerous problems with that idea ;), but it may be food for thought. It is likely that you will be writing some sort of interrupt handler.

..In the 32-bit windows API, there is a "Sleep" function. I suppose you could thunk to that.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文