在 16 位裸机 nasm 组件中休眠 x 毫秒
Posted
技术标签:
【中文标题】在 16 位裸机 nasm 组件中休眠 x 毫秒【英文标题】:Sleep for x milliseconds in 16 bit bare metal nasm assembly 【发布时间】:2021-12-01 10:44:19 【问题描述】:我正在使用 nasm 在裸机 16 位实模式程序集上编写程序。我想休眠(暂停执行)x 毫秒,但我还没有找到方法。
编辑:这是我的代码。我想在每个字符输入到屏幕之间添加大约 0.3 秒的延迟。
[bits 16] ; use 16 bits
[org 0x7c00] ; sets the start address
init:
mov si, msg ; loads the address of "msg" into SI register
mov ah, 0x0e ; sets AH to 0xe (function teletype)
print_char:
lodsb ; loads the current byte from SI into AL and increments the address in SI
cmp al, 0 ; compares AL to zero
je done ; if AL == 0, jump to "done"
int 0x10 ; print to screen using function 0xe of interrupt 0x10
jmp print_char ; repeat with next byte
done:
hlt ; stop execution
msg: db "The quick brown fox jumps over the lazy dog.", 0 ; we need to explicitely put the zero byte here
times 510-($-$$) db 0 ; fill the output file with zeroes until 510 bytes are full
dw 0xaa55 ; magic number that tells the Bios this is bootable
【问题讨论】:
有How to set 1 second time delay at assembly language 8086,但大多数答案都不是毫秒级的。或者您的意思是甚至不使用 BIOS 服务?您是在谈论具有(模拟)传统计时器芯片的 IBM-PC 兼容硬件吗? (或者复古系统上的真正芯片?)如果你想在现代 x86 上使用延迟循环,并启用中断,你应该像这个问答一样在 RDTSC 上旋转:How to calculate time for an asm delay loop on x86 linux? 系统是否有可编程间隔定时器? 既然您有可用的 BIOS(您已经在使用int 0x10
)并且您的延迟时间远远超过一毫秒,int 0x15
/ ah = 0x86
应该完全符合要求,请参阅 @987654323 @在彼得的链接上。它也比现代系统或模拟器上的忙碌等待更有礼貌和省电; BIOS 可以停止 CPU,仿真器可以放弃时间片。
【参考方案1】:
有一天,我也需要一个延迟例程,能够延迟从 0.5 秒到 几毫秒。在this CodeReview question 中阅读所有相关信息,尤其是我需要采用这种方法的原因。
我的解决方案是找出延迟例程在标准 18.2Hz 计时器的 2 个滴答之间的间隔内可以执行多少次迭代。这些滴答声相隔 55 毫秒。因为有时测量可能不稳定,我只接受两次连续测量变化小于 1%% 的结果。最后,我将良好的测量值除以 55,以获得每毫秒的迭代次数,即 SpeedFactor。此后,每当我想暂停程序时,我都会将所需延迟(以毫秒表示)乘以该 SpeedFactor,然后在延迟例程中执行该次数的迭代。
完整代码:
[bits 16]
[org 0x7C00]
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
cld
; Measure the number of iterations (within the ShortWait routine) per msec
; Only accept if consecutive measurements vary by less than 1%%
; If measurements remain erratic than do accept the last one
mov bp, 10 ; Max try
call GetSpeedFactor ; -> DX:AX
.a: xchg si, ax ; 'mov si, ax'
mov di, dx
call GetSpeedFactor ; -> DX:AX
push ax dx ; (1)
.b: sub ax, si
sbb dx, di
jnb .c
add ax, si
adc dx, di
xchg si, ax
xchg di, dx
jmp .b
.c: mov cx, 1000
xchg ax, cx
mul dx
xchg ax, cx
mov dx, 1000
mul dx
add dx, cx
sub si, ax
sbb di, dx
pop dx ax ;(1)
cmc
dec bp
jnbe .a
mov [SpeedFactor], ax
mov [SpeedFactor+2], dx
mov si, msg
lodsb
More: mov bx, 0x0007 ; BH DisplayPage 0, BL GraphicsColor 7
mov ah, 0x0E ; BIOS.Teletype
int 10h
mov bx, 300 ; 0.3 sec
call Pause
lodsb
cmp al, 0
jne More
cli
hlt
jmps $-2
msg db "The quick brown fox jumps over the lazy dog.", 0
SpeedFactor dd 0
; ----------------------------------------------
; IN () OUT (dx:ax)
; Wait for the start of a new TimerTick period (54.9254 msec)
; Then measure a 4 tick period (219.7016 msec)
GetSpeedFactor: push bx cx
mov bx, 1
call .ShortWait ; -> DX:AX BX=0
mov bl, 4 ; BH=0
call .ShortWait ; -> DX:AX BX=0
mov cx, 10
xchg ax, cx
mul dx
xchg ax, cx
mov dx, 10
mul dx
add dx, cx
mov cx, 2197
xchg ax, bx ; BX=0
xchg dx, ax
div cx
xchg ax, bx
div cx
mov dx, bx
pop cx bx
ret
; - - - - - - - - - - - - - - - - - - - - - - -
.ShortWait: mov ax, -1
cwd
; --- --- --- --- --- --- --- ---
; IN (dx:ax,bx) OUT (dx:ax,bx)
; Do DX:AX iterations or loop until Timer did BX Ticks
ShortWait: push ds cx si di
xchg si, ax ; 'mov si, ax'
mov di, dx
xor ax, ax
cwd
mov ds, ax
.a: mov cx, [046Ch] ; BIOS Timer
.b: sub si, 1
sbb di, 0
jb .c
add ax, 1
adc dx, 0
cmp cx, [046Ch]
je .b
dec bx
jnz .a
.c: pop di si cx ds
ret
; ----------------------------------------------
; IN (bx) OUT ()
Pause: push ax bx dx
mov ax, [SpeedFactor+2]
mul bx
xchg bx, ax
mul word [SpeedFactor]
add dx, bx
mov bx, -1
call ShortWait ; -> DX:AX BX
pop dx bx ax
ret
; ----------------------------------------------
times 510-($-$$) db 0
dw 0xAA55
代码用 FASM 汇编。对于 NASM,您将需要更改代码,例如
push ax bx dx
...
pop dx bx ax
进入
push ax
push bx
push dx
...
pop dx
pop bx
pop ax
【讨论】:
您的代码假定 CPU 频率恒定。在某些现代系统上,即使在 16 位模式下也无法保证这一点。我认为 BIOS 在启动 MBR 时将 CPU 置于最高 P 状态设置,因此 turbo 取决于 CPU。由于热限制,低功率笔记本电脑可能不得不在一段时间后从最大涡轮增压中恢复。 (也许不仅仅是在现代 CPU 上运行标量整数代码,但对于几代硬件来说可能是可行的,比如 10 到 13 年前的 Sandybridge 或 Nehalem。或者在纯被动冷却的笔记本电脑上,比如 Surface 2-in- 1 台平板电脑/笔记本电脑。) 您的校准可能会在 CPU 处于最大 turbo 时发生,特别是因为您要丢弃早期的不稳定结果。如果稍后 CPU 频率下降,ShortWait 调用将等待太久。 @PeterCordes 该代码是为在常规程序中使用而编写的。我从未见过任何违规行为。我从未尝试在引导阶段使用它,我不知道这些技术细节,但我希望 BIOS.TimerTick 即使在引导加载程序中也是正确的。 我希望 BIOS 计时器滴答间隔也始终正确。但是,您可以在一个滴答间隔内执行的循环迭代次数可能不是,具体取决于系统。 大多数系统基本上一直能够在单个内核上维持最大涡轮增压,即使在运行此延迟循环和/或使用它的某些程序中的任何其他 16 位代码时(可能除外SSE2 SIMD FP 数学),所以你没有看到硬件上的可变性我并不感到惊讶。 此外,在大多数台式机上,它只会产生很小的影响,例如 Haswell i5 4670 上的 3.4 GHz 与 3.8 GHz。(查看en.wikipedia.org/wiki/… 中的非“T”型号)。那就是如果涡轮增压从最大下降到非涡轮增压,而不是降低到较低的涡轮增压水平。如果您不寻找它,那么睡 3.8/3.4 = 1.11 倍的时间不会很明显。低功耗笔记本电脑的可持续时钟与峰值涡轮时钟的比率更大。以上是关于在 16 位裸机 nasm 组件中休眠 x 毫秒的主要内容,如果未能解决你的问题,请参考以下文章