Category : Assembly Language Source Code
Archive   : ASMTUT3.ZIP
Filename : CHAP17.DOC

 
Output of file : CHAP17.DOC contained in archive : ASMTUT3.ZIP



177

CHAPTER 17 - INTERRUPTS


Your word processor will work on a Compaq, an IBM, an AST or any
other type of PC compatible computer. Every time it wants to read
a file from disk or write to a printer, it calls a DOS or BIOS
function that takes care of it.{1} Yet every computer has its
own versions of these subroutines, and they not only are
different, they are in different places in memory. How does the
word processor know where to find them? It uses interrupts.{2}

An interrupt is a glorified subprogram call. The first 1024 bytes
of your computer's memory (that's from 0000:0000 to 0000:03FF)
contain the addresses of all the DOS and BIOS interrupts. Which
address contains the address of which subprogram was decided by
the triumverate of Intel/IBM/Microsoft. We have two different
sets of addresses here, so let's keep them straight. Starting at
memory address 0000 there are 4 byte addresses called interrupt
vectors. There is a different vector at 0000d, at 0004d, at
0008d, at 0012d at 0016d etc.; there are 256 of them in all. Each
of these 256 places can hold the address of a subprogram
somewhere in memory (although not all of them are used).

When you call an interrupt, the 8086 goes to the appropriate
place in low memory (from 0000 to 3FFh) and finds the address of
the subprogram that you want, loads it into CS and IP, and goes
to that subprogram. When that subprogram is done, it goes back to
the next instruction in your program after the interrupt.

How did those addresses get into the first 1024 bytes? The
computer put them there when you started it up. It is one of the
first things that the computer did when you turned it on. These
subprograms do EVERYTHING, and you can't run the computer without
them.

If you want to scroll the screen, you put the appropriate
information in the 8086 registers and use:

int 10h ; decimal 16 {3}

The 8086 goes to the interrupt 16 address (4*16 = 64), gets the
address of the program that contains the video subprograms, and
____________________

1. BIOS stands for basic input/output services.

2. If you haven't gotten it yet, it's time to get either "DOS
Programmer's Reference" or "The Peter Norton Programmer's Guide
to the IBM PC." I'm serious. They contain the information you
need to make use of this chapter.

3. It is normal to use hex numbers for the interrupts, so if
you read about an interrupt make sure you know if the numbers are
hex or decimal.

______________________

The PC Assembler Tutor - Copyright (C) 1989 Chuck Nelson




The PC Assembler Tutor 178
______________________

goes to it. If you want to write to the printer, you put the
appropriate information in the 8086 registers and call:

int 21h ; decimal 33

The 8086 goes to address 132 (4*33 = 132), gets the address of
the DOS program, puts it in CS and IP, and starts it. If you want
to get input from the keyboard, you put the appropriate
information in the 8086 registers and call:

int 21h ; decimal 33

That's right, it is the same program as the one that does the
printer. The 8086 goes to 132 (4*33), gets the program address,
and goes to it.

The lowest interrupt is int 0 (address = 4*0 = 0000). The highest
interrupt is int 255 (address = 4*255 = 1020).

This is an intelligent way to handle the situation. As long as
everyone agrees which interrupt contains the address of which
subprogram, our programs will work on any PC compatible. This is
one of the things that is meant by PC compatible.

On my computer, here is a section of these addresses starting
with int 1Eh (30d). High memory is at the top, low memory is at
the bottom.

INT # DATA LOCATION

0724h cs 146
36 04A8h ip 144
cs 0724h 142
35 ip 01BDh 140
0724h cs 138
34 01B0h ip 136
cs 019Fh 134
33 ip 05EBh 132
019Fh cs 130
32 05E7h ip 128
cs 0000h 126
31 ip 0000h 124
0070h cs 122
30 0EB8h ip 120

If we call int 30d, the 8086 goes to 120 (4*30), puts 0EB8h into
the IP, and puts 0070h (from the next higher location) into CS.
The next instruction it does will be 0070:0EB8. If we call int
35d, the 8086 goes to 140 (4*35), puts 01BDh in IP, puts 0724h
(from the next higher location) in CS. The next instruction the
8086 does will be 0724:01BD. If we call int 33d, the 8086 goes to
132 (4*33), and puts 05EBh in IP, 019Fh (from the next higher
location) in CS. The next instruction executed will be
019F:05EBh. The 0000:0000 for int 31d indicates that there is no
int 31d (address 0000:0000 contains data, [the vectors for int
0], not a program). Make sure that you understand how this is
working before you go on.




Chapter 17 - Interrupts 179
_______________________


On the PC, information for the interrupts is always passed
through registers, not on the stack. Each interrupt type has a
specific register for each piece of information it needs. We will
do a couple to see how they work.


DISPLAYING A CHARACTER

We can print a character at a time on the monitor, so we'll input
a string, and then print out each character of the string
individually. We will stop the printout when we see the 0 at the
end of the string.

; - - - - - - - - - - START DATA BELOW THIS LINE
buffer db 80 dup (?)
; - - - - - - - - - - START DATA BELOW THIS LINE
; - - - - - - - - - - START CODE BELOW THIS LINE
call show_regs
outer_loop:
mov ax, offset buffer
call get_string

mov si, offset buffer
inner_loop:
mov al, [si]
cmp al, 0
je next_string
mov ah, 14 ; ah contains function number
mov bh, 0 ; where in memory
int 10h ; 33d
inc si
jmp inner_loop

next_string:
call get_continue
jmp outer_loop

; - - - - - - - - - - START CODE BELOW THIS LINE

The program is simple. It gets a string, then checks each
character for 0 (end of string), before shipping it off to the
screen. I'll explain AH in a second. There are several places
this character can be displayed{4}, so use show_regs to force the
video screen to a certain place in memory, then put BH = 0 to
tell the interrupt that the screen memory is in that place.
Finally, with all the information in place, we do the interrupt.
You will not print a carriage return, only the data, so we use
get_continue to give us a carriage return. It's a little sloppy,
but much easier.

We have lots and lots of subprograms for the disks, printer,
screen, etc. There are only 255 interrupts, so it was decided at
the beginning to make most of the interrupts groups of programs
____________________

4. Cf. one of those two books.




The PC Assembler Tutor 180
______________________

instead of a single program. Int 10h (16d) contains about two
dozed different video subprograms. Each program is distinguished
by a specific number in AH. For int 10h (16d):

ah = 2h Get cursor position
ah = 6h Scroll window up
ah = Eh (14d) Write character to screen

For int 21h (33d):

ah = 1h Keyboard input
ah = 5h Printer output
ah = 17h Rename a file
ah = 2Ch Get the time

As you can see, int 21h is a potpourri of subprograms. We'll do
the same program as above, but with printer output. Everything is
the same except that the inner loop should be changed to look
like this:

; - - - - -
mov si, offset buffer
inner_loop:
mov dl, [si]
cmp dl, 0
je next_string
mov ah, 5 ; ah contains function number
int 21h ; 33d
inc si
jmp inner_loop

; - - - - -

There is practically no change. We use DL instead of AL, have
"int 21h" instead of "int 10h", and change the function number in
AH to 5. Also, BH is not needed.

Leave your printer off at the beginning to see what happens. Turn
your printer on and enter a string of 10 or 20 letters. Probably
nothing happened. Enter another 20 or 30 letters. Nothing again.
Try 50 letters this time. This time it should work. Lots of
printers won't print anything unless (1) they get a carriage
return (which you haven't sent) or (2) they have a backlog of
more than 80 characters. If you ever use the printer interrupt,
you'll have to remember that.

These interrupts which are in your program are called software
interrupts. They are your interface with the peripheral devices
on your machine, doing everything from disk i/o to handling
memory allocation. They do all your housekeeping for you.{5} If
you are going to work at this level then you should buy one of
those two books. They contain all the software interrupts (and
there are about a hundred of them), tell how they work and which
registers to set for each interrupt. If you don't have access to
____________________

5. But they don't do Windows.




Chapter 17 - Interrupts 181
_______________________

these interrupts it's like having your arms cut off - you're
unable to do any i/o at all.


HARDWARE INTERRUPTS

The interrupts that we write in our programs aren't the only
interrupts there are. The hardware uses interrupts to take
temporary control of the computer. On a Macintosh, if you insert
a disk, the program stops and the operating system reads in the
disk directory. A modem that is in use will request time for
doing i/o. Also, if the 8086 detects a zero divide, it will
trigger a special interrupt. You have met int 4 already. It is
the INTO instruction, which will trigger an interrupt if the
overflow flag is set.

Your interrupts are in specific places in your code, but these
machine interrupts can happen at any time. There are two lines
(wires) into the 8086. One is for serious problems that need to
be taken care of NOW, and it has non-maskable interrupts. The
other line is for interrupts that need to be taken care of in a
timely fashion, and they are maskable interrupts.

A non-maskable interrupt (NMI) is when the hardware detects that
it is in deep doo doo. It sends a signal on the NMI line that
says "Hey! I need an interrupt." The 8086 finishes the
instruction it is processing and then IMMEDIATELY gives over
control. The NMI uses the same 1024 bytes in low memory for
interrupt vectors, but has its own interrupt numbers. Normally
this is for very serious errors, so the interrupt program may
decide to abort your program and return to the operating system;
if it makes sense to, it will return to your program where your
program left off.

A maskable interrupt is when a piece of hardware has some work to
do. It sends a signal to the 8086 (on the INTR line), and the
8086 takes care of it when it is ready.

When the 8086 is ready depends on you. In the flags register is
the IEF, the interrupt enable flag. It should always be set to 1
unless you are doing something critical. Basically, the only
things that are critical are interrupts themselves and context
switches. Context switches are done by the operating system in
multitasking environments, so they don't concern you, and you are
not writing interrupts, so they don't concern you. Therefore,
always keep the IEF set. Just for your information, you set the
IEF with:

sti ; set interrupt flag (interrupts enabled)

and clear it with:

cli ; clear interrupt flag (interrupts disabled)

Why do interrupt programs clear the interrupt flag? Because you
could have interrupts interrupting other interrupts and wind up
with scads of half finished interrupts lying around. This way,




The PC Assembler Tutor 182
______________________

one interrupt finishes before another can take over.


Suppose you are in the middle of the following two instructions
when an interrupt hits:

cmp al, 7
jne some_label

If the hardware interrupt takes over after the CMP instruction
but before the JNE instruction, the correctness of the result
will depend on the zero flag staying the same. Can you trust it?
The answer is yes. The first three things an interrupt request
does (in the microcode) are:

push flags ; push the flags
push old CS ; push the code segment
push old IP ; push the instruction pointer

On return, it pops the flags back in place, so they are exactly
the same as just before the interrupt. You will notice that there
are 3 things on the stack instead of the usual 2 in a far call.
Therefore, there is a special RET instruction called IRET (return
from interrupt) which pops not only IP and CS, but the flags as
well. You can only use this instruction in an interrupt because
it assumes that the flags register is right after CS on the
stack.


DEBUGGING

Finally, if you have a debugger installed, the debugger uses two
interrupts, int 1 and int 3. You code int 3 into the program by
placing:

int

in the program with no number.{6} The interrupt will call the
debugger. The reason for this int with no number is that
afterwards, you can replace it with NOP, since they are both 1
byte instructions. Int 3 is a 2 byte instruction (they are coded
differently, even though they wind up at the same place).

Int 1 is the trap instruction. In the flags register is the trap
flag (TF). If TF is set, the 8086 will interrupt after the next
instruction. That way, the debugger can single step through
sections of code. You put Int 3 to set a breakpoint and then set
TF to single step from there.

The only way to set TF from your own program is:

____________________

6. Though the Microsoft assembler considers this an error. You
must code it as:

int 3




Chapter 17 - Interrupts 183
_______________________

pushf
pop ax
or ax, 0100h
push ax
popf

but you should let the debugger do it.

Just so you can get a feel for how the debugger system works,
there is a pseudo-debugger called PSEUDDBG.COM in \XTRAFILE. It
is not a debugger. It does nothing but tell you whether you have
generated a breakpoint interrupt (int 3) or a single_step
interrupt (int 1). It is memory resident. That means that once
you have loaded it, it will stay in memory until you shut the
machine off or reset the machine. You load it by executing:

>pseuddbg

It will tell you that it has been loaded and then sit there
waiting for interrupts. When you generate a breakpoint interrupt,
PSEUDDBG will allow you to set the trap flag or just continue.
When you generate a single step interrupt, PSEUDDBG will allow
you to clear TF or to continue single stepping. All you need to
try it out is a simple program:

; - - - - - ENTER CODE BELOW THIS LINE
outer_loop:
call get_continue

mov cx, 4
int 3
inner_loop:
mov ax, 5
add ax, 3
add ax, 7
add ax, 10
loop inner_loop

loop outer_loop
; - - - - - FINISH CODE ABOVE THIS LINE

Each time you start the outer loop, it will generate a breakpoint
interrupt. At this point you can set TF to single step or
continue. If you single step, watch IP. It will be changing,
cycling through the loop till it gets to get_continue. If you
start trapping through get_continue, things will get strange
because get_continue stores the flags. This means that after you
quit trapping, get_continue will POP some flags that have TF set
and it will start trapping again. If this happens, just continue
hitting 0 until you get out of the single step mode.

If you like this method of single stepping through code, there is
a memory resident version of ASMHELP called HELPMEM.COM which
contains show_regs and allows single stepping. It can be used in
conjunction with ASMHELP.OBJ. For details consult APP1.DOC in the
appendix.





The PC Assembler Tutor 184
______________________

SUMMARY

Software interrupts send the 8086 to the first 1024 bytes of
memory where it finds the address of the DOS or BIOS subprogram
that you want to go to. The correspondence between the interrupt
number and the location of the address is:

address location = 4 * interrupt number

The address is stored in the next 4 bytes; first IP, then CS.



The first 5 interrupts are:

int 0 divide by zero
int 1 trap flag induced single stepping for the debugger
int 2 non_maskable_interrupt (NMI)
int 3 1 byte interrupt for the debugger
int 4 interrupt on overflow


There are two types of hardware interrupts. NMI (non maskable
interrupt) interrupts are for serious problems that need to be
taken care of IMMEDIATELY. They have priority and take over right
after the current instruction is finished.

Maskable interrupts are hardware interrupts that should be taken
care of in a timely fashion. If IEF (the interrupt enable flag)
is set the maskable interrupts will go through. If IEF is
cleared, the maskable interrupts must wait until it is set again.
Set the IEF with:

sti ; set interrupt flag

and clear it with:

cli ; clear interrupt flag

The IEF should always stay set unless there is a specific reason
for not allowing interrupts to happen.


If you are writing an interrupt routine, then instead of RET you
use IRET (return from interrupt) which not only pops off IP and
CS, but pops the flags register as well.



  3 Responses to “Category : Assembly Language Source Code
Archive   : ASMTUT3.ZIP
Filename : CHAP17.DOC

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/