Page 1 of 1

Sleeping certain amount of time in real mode

Posted: Sun Sep 30, 2018 2:56 pm
by yasar11732
Hi,

I am developing a simple snake game in real mode (for fun and educational purposes). I need to run main game loop periodically. I am using following code to set timer frequency to 1000hz.

Code: Select all

    # setup timer freq (once per milliseconds)
    mov $1193,%bx
    mov $0x36,%al
    out %al,$0x43
    mov %bl,%al
    out %al,$0x40
    mov %bh,%al
    out %al,$0x40
I am using this function to sleep certain amount of time.

Code: Select all

/* put milliseconds to sleep in %ax, %ax trashed */
sleep:
    push %bx
    mov timer_ticks,%bx
    add %bx,%ax
sleep0:
    mov timer_ticks,%bx
    cmp %bx,%ax
    jle sleep1
    hlt
    jmp sleep0
sleep1:
    pop %bx
    ret
I am testing this code using Virtualbox. sleep function seems to take more time than I intend. Am I doing everything properly here?

Complete code is accessible here: https://github.com/yasar11732/snakeos/b ... 54/snake.s

Edit:
My interrupt handler:

Code: Select all

irq_return:
    mov $0x20,%al
    out %al,$0x20
    popa
    iret

timer_handler:
    pusha
    incw timer_ticks
    jmp irq_return

Re: Sleeping certain amount of time in real mode

Posted: Sun Sep 30, 2018 5:03 pm
by Octocontrabass
Virtual machines have trouble keeping time accurately. Does it also run at the wrong speed on bare metal?

If it runs at the correct speed on bare metal, you may need to reconfigure your virtual machine or use a different one.

Re: Sleeping certain amount of time in real mode

Posted: Sun Sep 30, 2018 5:43 pm
by Brendan
Hi,

I can't find a bug in the code that would explain the symptoms; but...
yasar11732 wrote:Am I doing everything properly here?
A signed 16-bit integer has a range from -32768 to +32768 (a total of 65536 values). If it's incremented 1000 times per second it'll overflow every 65536/1000 = 65.536 seconds.

Now let's see what happens when the "timer_ticks" contains +32767 and you call this code with "milliseconds to sleep = 10":

Code: Select all

/* put milliseconds to sleep in %ax, %ax trashed */
sleep:
    push %bx
    mov timer_ticks,%bx          ;bx = +32767
    add %bx,%ax                  ;ax = +32767 + 10 = -32759 due to overflow
sleep0:
    mov timer_ticks,%bx          ;bx = +32767 still
    cmp %bx,%ax
    jle sleep1                   ;if(-32759 <= +32767) exit the loop
    hlt
    jmp sleep0
sleep1:
    pop %bx
    ret
What this means is that sometimes it will return immediately with no delay at all.

Note: It's more common to use unsigned integers (e.g. "jbe sleep1"), but you'd have the same bug in that case (e.g. "65535 + 10 = 9 due to overflow").

To fix both of these, try something like:

Code: Select all

/* put milliseconds to sleep in %ax, %ax trashed */
sleep:
    push %cx
    push %bx
    mov timer_ticks,%cx          ;cx = starting tick
sleep0:
    mov timer_ticks,%bx          ;bx = current tick
    sub %cx,%bx                  ;bx = current tick - starting tick = time passed

    cmp %ax,%bx
    jae sleep1                   ;If time passed >= delay exit the loop
    hlt
    jmp sleep0
sleep1:
    pop %bx
    pop %cx
    ret
This code should be immune to overflows.

Also note that if you ask for a 1 ms delay immediately before the timer IRQ occurs you could delay for almost no time at all. Essentially the code waits for between (AX-1) and AX milliseconds (and doesn't wait for at least AX milliseconds). To fix that you can change the the branch to "ja sleep1".


Cheers,

Brendan