在 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 毫秒的主要内容,如果未能解决你的问题,请参考以下文章

在线程池中休眠的线程

在执行过程中休眠一个线程

在 JavaScript 中休眠 - 动作之间的延迟

在 Swift 中休眠或延迟 Timer 线程

在批处理文件中休眠

为啥要在 bash 中休眠和等待?