| January 2003 Laboratory Notes: Computer Engineering II | ||
|---|---|---|
| Prev | Chapter 18 Introduction to PModeLib | Next |
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.