我正在试图在16位MASM程序集x86中创build一个睡眠/延迟程序,比如每500ms在屏幕上打印一个字符。 从我所做的研究,似乎有三种方法来实现这一点 – 我想使用CPU时钟滴答。
请注意我在Mac OS X Snow Leopard上通过VMWare Fusion运行Windows XP – 我不确定这是否会影响任何内容。
有人可以请我指出正确的方向,或者提供一个我可以调整的工作代码? 谢谢!
我find的代码应该每秒在屏幕上打印“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的build议,这是一个工作程序。 只要打电话吧
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
其实你可以使用ROM BIOS中断1Ah函数00h,'Read Current Clock Count'。 或者你可以在地址$ 40:$ 6C阅读dword,但你必须确保原子读取。 它以大约18.2赫兹由MS-DOS递增。
欲了解更多信息,请阅读: DOS时钟
这不能在纯MASM中完成。 所有的设置固定延迟的技巧都是基于你完全控制机器的情况下运行的,并且是唯一运行在CPU上的线程,所以如果你等待5亿个周期,精确到500,000,000/f
秒频率为f
)的CPU; 那对于1GHz的处理器来说就是500ms。
因为你正在运行一个现代化的操作系统,所以你与其他许多线程(包括内核 – 不管你做什么,你不能优先于内核!)共享CPU,所以只能等待5亿个周期你的线程将意味着在现实世界中超过5亿个周期。 这个问题不能单靠用户空间代码来解决; 你将需要内核的合作。
解决此问题的正确方法是查找Win32 API函数将暂停您的线程一个指定的毫秒数,然后调用该函数。 你应该可以直接从汇编做到这一点,可能还有其他的参数给你的链接器。 或者,可能有一个NT内核系统调用来执行这个功能(我对NT系统调用的经验很少,并且不知道NT系统调用表是什么样的,但是睡眠函数是我可能期待看到)。 如果系统调用可用,那么从程序集发出直接系统调用可能是最快的方法来做你想做的事情; 这也是最不便携的(但是,你正在编写程序集!)。
编辑 :看看NT内核系统调用表 ,看起来没有任何与睡眠或获取日期和时间(如您的原始代码使用)有关的调用,但有几个系统调用来设置和查询计时器。 当您等待计时器达到所需的延迟时旋转是一个有效的,如果不雅的解决方案。
使用INT 15h,功能86h:
呼叫:AH = 86h CX:DX =美国的时间间隔
好吧。 一个老式的,不恒定的,耗电的延迟循环会使其他线程运行速度变慢,如下所示:
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
延迟是常数的二次方。 但是如果你使用这个延迟循环,世界上某个地方的年轻无辜的小猫就会死亡。
我没有测试这个代码,但概念必须工作…保存/恢复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
所有上面的代码示例的问题是,他们使用非阻塞操作。 如果在相当长的等待期间检查CPU使用率,则会看到它运行在50%左右。 我们想要的是使用一些阻止执行的DOS或BIOS功能,以使CPU使用率接近0%。
..事实上,BIOS INT 16h,AH = 1功能浮现在脑海。 您可以设计一个调用该函数的例程,然后在时间到期时在键盘缓冲区中插入一个按键。 这个想法有许多问题,但是这可能是一种思考的食物。 你可能会写一些中断处理程序。
在32位Windows API中,有一个“休眠”功能。 我想你可以这样做。