18.7 Protected Mode Interrupt Handling

We've previously covered real mode interrupt handling, calling DOS to change the interrupt table to point at our code, chaining to the old interrupt handler for timer interrupts, and other concepts. While the general concepts don't change when we go to protected mode, the implementation does, and there are several functions in PModeLib to make the transition less painful.

The Install_Int() and Remove_Int() PModeLib functions make it easy to install a standard interrupt handler in protected mode (eg, one for timer or keyboard). The interrupt handler is just a normal subroutine (it should end with a ret instruction), and it should return a value in EAX to indicate whether the interrupt should be chained to the old handler or not: a zero value indicates the interrupt should just return (real-mode iret), a nonzero value indicates the interrupt should chain to the old handler (real-mode jmp or call).

One thing that is important to remember is to lock the memory areas an interrupt handler will access; this includes any variables it uses and the interrupt handler code itself. The reason we need to lock these areas is due to paging: any area of the program may be swapped out to disk by the operating system and replaced with another piece of code or data. While it is automatically reloaded when accessed by the program, this can cause unacceptable delay for interrupt handlers, as it may take many milliseconds to load the code or data back from disk. Locking prevents the operating system from paging out that area of memory. So why don't we lock the whole program? It's really unfriendly to do that in a multitasking environment, especially if your program takes up a lot of memory and it's a limited-memory system. Locking is another reason to keep your interrupt handlers short and keep most of the processing in the main loop (which doesn't have to be locked). The PModeLib function LockArea() is used to lock memory areas.

Example 18-5. Hooking the timer interrupt

%include "lib291.inc"

GLOBAL _main

SECTION .bss

timercount resd 1        ; Number of ticks received

SECTION .text

; Timer interrupt handler
TimerDriver
        inc     dword [timercount]

        ; No PIC acknowledge (out 20h, 20h) required because we're chaining.
        mov     eax, 1           ; Chain to the previous handler
        ret                      ; Note it's ret, not iret!
TimerDriver_end

_main
        call    _LibInit         ; You could use invoke here, too
        test    eax, eax         ; Check for error (nonzero return value)
        jnz     near .initerror

        ; Lock up memory the interrupt will access
        invoke  _LockArea, ds, dword timercount, dword 4
        test    eax, eax         ; Check for error (nonzero return value)
        jnz     near .error

        ; Lock the interrupt handler itself.
        ; Note that we use the TimerDriver_end label to calculate the length
        ;  of the code.
        invoke  _LockArea, cs, dword TimerDriver, \
                           dword TimerDriver_end-TimerDriver
        test    eax, eax         ; Check for error (nonzero return value)
        jnz     near .error

        ; Install the timer handler
        invoke  _Install_Int, dword 8, dword TimerDriver
        test    eax, eax         ; Check for error (nonzero return value)
        jnz     near .error

        ; Loop until we get a keypress, using int 16h
.loop:
        mov     ah, 1            ; BIOS check key pressed function
        int     16h
        jz      .loop            ; Loop while no keypress

        xor     eax, eax         ; BIOS get key pressed
        int     16h

        ; Uninstall the timer handler (don't forget this!)
        invoke  _Remove_Int, dword 8

.error:
        call    _LibExit
.initerror:
        ret                      ; Return to DOS

See the examples directory in V:\ece291\pmodelib for more examples.