windows 如何在 16 位 MASM 程序集 x86 中创建睡眠功能?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1858640/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I create a sleep function in 16bit MASM Assembly x86?
提问by Yuval Karmi
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.
我正在尝试在 16 位 MASM 程序集 x86 中创建一个睡眠/延迟程序,例如,每 500 毫秒在屏幕上打印一个字符。从我所做的研究来看,似乎有三种方法可以实现这一点——我想使用一种使用 CPU 时钟滴答的方法。
Please note I am running Windows XP through VMWare Fusion on Mac OS X Snow Leopard - I am not sure if that affects anything.
请注意,我在 Mac OS X Snow Leopard 上通过 VMWare Fusion 运行 Windows XP - 我不确定这是否会影响任何事情。
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).
我发现的代码应该每秒在屏幕上打印“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
EDIT: Following GJ's advice, here's a working procedure. Just call it
编辑:按照 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
采纳答案by GJ.
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.
实际上你可以使用 ROM BIOS 中断 1Ah 函数 00h, 'Read Current Clock Count'。或者您可以在地址 $40:$6C 处读取 dword,但您必须确保原子读取。它由 MS-DOS 以大约 18.2 Hz 的频率递增。
For more information read: The DOS Clock
有关更多信息,请阅读:DOS 时钟
回答by kquinn
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.
这不能在纯 MASM 中完成。设置固定延迟的所有旧技巧都假设您完全控制了机器并且是 CPU 上运行的唯一线程,因此如果您等待 5 亿个周期,则恰好500,000,000/f
几秒钟已经过去(对于一个 CPU频率f
); 对于 1GHz 处理器,这将是 500 毫秒。
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 threadwill 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.
因为您在现代操作系统上运行,您正在与许多其他线程共享 CPU(其中,内核 - 无论您做什么,您都不能优先于内核!),所以只等待 5 亿个周期您的线程将意味着在现实世界中经过了超过 5 亿个周期。仅靠用户空间代码无法解决此问题;您将需要内核的合作。
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!).
解决此问题的正确方法是查找哪个 Win32 API 函数会将您的线程挂起指定的毫秒数,然后调用该函数。您应该能够直接从程序集执行此操作,可能会为您的链接器提供额外的参数。或者,可能有一个 NT 内核系统调用来执行这个功能(我对 NT 系统调用的经验很少,老实说不知道 NT 系统调用表是什么样的,但我可能会使用睡眠函数期待看到)。如果系统调用可用,那么从程序集发出直接系统调用可能是执行所需操作的最快方法;它也是最不便携的(但是,您正在编写程序集!)。
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.
编辑:查看NT 内核系统调用表,似乎没有任何与睡眠或获取日期和时间相关的调用(就像您的原始代码使用的那样),但是有几个系统调用可以设置和查询计时器。在等待计时器达到所需延迟时旋转是一种有效但不优雅的解决方案。
回答by Dave
use INT 15h, function 86h:
使用 INT 15h,函数 86h:
Call With: AH = 86h CX:DX = interval in uS
呼叫方式:AH = 86h CX:DX = 以美国为单位的间隔
回答by GJ.
I didn't test this code but concept must work... Save/restore es register is optional! Check code carefully!
我没有测试这段代码,但概念必须有效......保存/恢复 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
回答by Gunther Piez
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.
延迟是常数的二次方。但是如果你使用这个延迟循环,世界上的某个地方就会有一只年轻的无辜小猫死亡。
回答by Dave
..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%.
..上述所有代码示例的问题在于它们使用非阻塞操作。如果您在相对较长的等待期内检查 CPU 使用率,您会看到它运行在 50% 左右。我们想要的是使用一些阻止执行的 DOS 或 BIOS 功能,以便 CPU 使用率接近 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.
..副手,BIOS INT 16h, AH=1 功能浮现在脑海中。您可以设计一个调用该函数的例程,然后在时间到期时将击键插入键盘缓冲区。这个想法有很多问题;),但它可能值得深思。您很可能会编写某种中断处理程序。
..In the 32-bit windows API, there is a "Sleep" function. I suppose you could thunk to that.
..在32位windows API中,有一个“睡眠”功能。我想你可以想到这一点。