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
4

6 回答 6

12

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.

于 2009-12-07T08:44:11.297 回答
6

use INT 15h, function 86h:

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

于 2012-01-14T19:29:46.913 回答
3

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.

于 2009-12-07T09:20:00.643 回答
3

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

于 2009-12-07T19:16:01.727 回答
2

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
于 2009-12-09T14:23:05.130 回答
0

..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.

于 2012-01-14T17:35:39.500 回答