Interrupt 0x15 function 0x86 (BIOS WAIT) runs far

2019-07-18 13:20发布

问题:

I've been writing a bootloader in assembly (a game). The bootloader uses the bios WAIT function (int 0x15, ah 0x86) for delays between frames. I'm debugging with BOCHS and everything works just splendid (timing is perfect). I also made a bootable iso with the isogenimage tool and tested my bootloader in virtualbox and everything works as expected there too. So I think it's safe to say that delays work in virtual environments.

Now here's the peculiar part: when I write the bootloader to a usb drive and boot from it, the WAIT function waits FAR longer than on the virtual machines. I'd estimate it's 2-3 times longer. At least. Such long delays between frames are absolutely unacceptably for a game.

What in the world could cause this behaviour? Is it possible that my buggy bios clock runs at a slower speed (>18 microseconds)? But surely, that would violate some IBM standard? It's inexplicable to me.

By the way, if I remove the WAIT interrupt, the bootloader runs as expected (just too fast for my purposes), so it's not my actual code that's running slow. Plus, I'm running on a recent ~2013 laptop, so it can't be because of low performance.

Any insight, appreciated!

EDIT: I had the idea to test it on another physical computer and IT ALSO RAN EXTREMELY SLOW! So maybe it's not a "shitty bios" :) The odds of it running slow on two computers are pretty low (I think), so that might mean something...

CODE:

;MACRO CONSTANTS
%define p_width 10      ; player width
%define p_left_offset   20  ; space between left margin and player's left side
%define p_right_offset  290 ; space between right margin and player's right side; for linear player drawing purposes (xres - p_width - p_left_offset)
%define beak_width  5

%define xres    320     ; VGA resolution width
%define yres    200     ; VGA resolution height
%define buffer_addr 0x1000  ; start of offscrean buffer

[bits 16]
[org 0x7c00]

section .text
boot:
    jmp start
    times 3-($-$$) DB 0x90   ; Support 2 or 3 byte encoded JMPs before BPB.

        ; Dos 4.0 EBPB 1.44MB floppy
        OEMname:           db    "mkfs.fat"  ; mkfs.fat is what OEMname mkdosfs uses
        bytesPerSector:    dw    512
        sectPerCluster:    db    1
        reservedSectors:   dw    1
        numFAT:            db    2
        numRootDirEntries: dw    224
        numSectors:        dw    2880
        mediaType:         db    0xf0
    numFATsectors:     dw    9
        sectorsPerTrack:   dw    18
        numHeads:          dw    2
        numHiddenSectors:  dd    0
        numSectorsHuge:    dd    0
        driveNum:          db    0
        reserved:          db    0
        signature:         db    0x29
        volumeID:          dd    0x2d7e5a1a
        volumeLabel:       db    "NO NAME    "
        fileSysType:       db    "FAT12   "


start:
    mov ax, 0x13        ; VGA 16bit colors 320x200 mode
    int     0x10            ; call video update bios
    mov ax, 0xA000      ; Video memory startaddr
    mov es, ax          ; Real buffer video segment into segment register (for segment:offset format)
    mov ax, buffer_addr     ; temp storage of virtual buffer addr
    mov gs, ax          ; Virtual buffer segment stored in GS

    xor ax, ax
    mov ds, ax          ; zero offset to access vars defined in ;Data secition

   gameloop:
    call    clear_buffer        ; prepare to draw by clearing memory buffer
    call    draw_player     ; draw player to memory buffer
    call    switch_buffers      ; copy memory buffer to vram

    add     word [y_offset], 0x03   ; downward velocity, moves player around

   ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
   ; HERE IS THE WAIT INTERRUPT THAT CAUSES THE PROBLEM ON REAL HARDWARE
   ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
   ; WAIT (sleep) for a little bit
    mov ah, 0x86        ; specify for int 0x15 WAIT interupt
    mov cx, 0x0006      ; high word of wait time 
    mov dx, 0xffff      ; low word of wait time 
    int     0x15            ; waits for cx:dx 1,000,000ths of a second
    jmp     gameloop    
   ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    hlt


; Draws the player square on screen, with y offset of y_offset
draw_player:
    xor     si, si          ; make sure si is 0; this will be pixel offset accumulatore
    xor bx, bx          ; this will be row counter; paintnig will stop if bl == p_width

    mov ax, xres        ; used to get space above player
    mul     word [y_offset]     ; result will be in dx:ax (high, low bytes)
    add si, ax          ; add top offset to pixel accumulator

   accum_p_offset:  
    inc bl          ; keep track of rows drawn.
    add si, p_left_offset   ; now offset is at starting point to paint
    mov cx, si          ; cx looks into the future
    add cx, p_width     ; cx will be the goal pixel

   draw_p_pixel:            ; draws the player pixels
    inc si          ; go to next pixel (compensate for 0-start)
    mov [gs:si], byte 0x0E  ; move white pixel to virtual memory at offset ax
    cmp si, cx          ; check if ax caught up with cx
    jne draw_p_pixel        ; if not, draw next player pixel

    ret


clear_buffer:  
    mov cx, 32000       ; number of times loop is performaned. 320 * 200 / 2 (because moving words)
    xor     si, si          ; clear si to make sure acumulator starts at first pixel
   zero_buf:
    mov     [gs:si], word 0x0101    ; set background to blue
    add si, 2           ; increment accumulator
    loop    zero_buf        ; jumps to zero_buf if cx is not equal to 0, then decrements cx

    ret


; copies contents from memory buffer to vram
switch_buffers:
    push    ds          ; save old data-segment (needed for getting the address of databytes later on)
    mov si, gs          ; use si as temp for gs (virtual memory segment)
    mov ds, si          ; move into datasegment the addr of virtual memory segment for movsw instruction

    xor si, si          ; clear accumulators for string operation
    xor di, di

    mov     cx, 32000       ; how many words to copy 320 * 200 / 2
    cld             ; copy direction
    rep     movsw           ; repeat copy from ds:si to es:di until cx is 0 (copies buffer from memory to vram)
    pop     ds          ; restores data segment to reference databytes properly after switch_buffers

    ret


;DATA
y_offset:
    dw  0x16

; FILLER
times 510 - ($-$$) db 0
dw 0xaa55