Chapter 9 - Interrupts BIOS/DOS/Hardware - Direct Hardware Access

Document last modified: 

Overview

Interrupts are used for multiple purposes on the PC system architecture. One use is for hardware devices such as a mouse click, a key press, etc. to interrupt the usual program execution flow and handle the hardware action. The effect is to interrupt a program's execution temporarily, perform some operation related to the hardware, then return to the interrupted program's normal execution.

A second use provides a standard software interface to widely different PC hardware. For example, all PC's run the same Windows, DOS, C++, your own software, etc. though the hardware is quite different (laptops, desktops, manufacturers, CPU's). From the development of the original PC, IBM defined a set of hardware generic operations that all PC compatible hardware must support. To access these operations, a program issues an interrupt numbered to correspond to the desired operation. For example, for a program to print something on the video screen, it would use interrupt number 10h (e.g. Int 10h), for printing use interrupt 17h (e.g. Int 17h). The interrupt number serves as an index into an array (interrupt vector) that holds the address of functions executed for each interrupt. Int 17h essentially indirectly calls a function using the address stored at index 17h. Since the address stored at index 17h can be changed dynamically, the function executed by Int 17h can also change dynamically. Normally the array is initialized with function addresses when the computer system boots. This numbering scheme has been used since the beginning of the PC architecture so programs that use this scheme can execute on all PC's.

It is important to note the alternative to using a fixed operation number would be to call a function name directly as has been done with PutDec, etc. Directly calling the function by name breaks down when a different version were needed for different hardware or operating systems (NT, Windows 2000, DOS). For example, a C++ program that prints to the console should not need to know that the console is a LCD or CRT. The basic problem is that different hardware and operating systems need different software to talk to the hardware. If a program calls the Mouse$ function directly (e.g. Call Mouse$), the Mouse$ function address and code must be known when the program is linked (we're ignoring dynamically linked functions as not relevant to this discussion). The program now contains the hardware specific Mouse$ code and can only work on specific hardware platforms. Now consider implementing an operating system that must support many combinations of hardware, using the direct function call method different versions of the operating system would need be linked for every possible hardware combination. This creates obvious maintenance difficulties such as when changes must be made to accommodate new hardware. 

Recall the general hardware design of a computer system that includes the PC architecture. The CPU and memory sections appear much the same to software, as long as the CPU supports the program instructions and enough memory exists the program can be executed. The Input/Output section connects the computer to the outside world and must operate with a wide variety of computer interfacing hardware (mice, touch pads, NIC, video, sounds, virtual reality headsets, gloves, etc., etc.) many not conceived when the computer rolled of the production line. Software must be adaptable to changes in the hardware attached to the system. On the PC architecture, these hardware differences are handled primarily by the BIOS (Basic Input/Output System) abstraction.

Discussion

Interrupts serve two main purposes on PC-systems 1) to respond to hardware events such as a key press, and 2) to provide a uniform interface to anonymous software functions such as those of the BIOS (Basic Input Output System) or DOS (Disk Operating System). Software functions may be organized into a hierarchy of layered operations where high-level operations make use of operations in the lower-layers of software/hardware. For example, consider the C++ statement: when executed on a PC under DOS. The layers involved are:
1) User program:   cout << "Hello world";
2) C++ :           Call DOS video function 9 to output string "Hello world"      Page 464 of text  
3) DOS:            Call BIOS video function by Int 10h                           Page 448-450 of text
4) BIOS:           "Hello world" placed in hardware video memory                 
5) Video hardware: "Hello world" display from video memory
This hierarchy also reflects hardware specificity or how generic operations are implemented across different hardware. The layered list becomes:
1) User program             Any machine with C++ compiler
2) C++                      PC's, Mac's, Unix, mainframes, etc.
3) DOS                      All PC compatibles.
4) BIOS                     Same functions but specific for hardware.
5) Hardware                 Dependent upon whether laptop, VGA, SVGA, etc.
Layered software, where each layer calls functions of the layer below, allows a degree of hardware independence at the higher layers. The same C++ user program can execute on all DOS computers because cout calls only DOS functions directly. DOS can execute on all PC's because DOS directly calls only standard BIOS functions. BIOS actually accesses the hardware and its functions must be designed for the specific hardware, the BIOS of one computer is not generally compatible with that of another. Because of the independence of layering, MS-DOS can operate on any PC, but a BIOS is usually specific for a particular manufactures mother board and processor model. The great advantage of this scheme is to make higher-level programs independent of lower-level, hardware-specific details, supporting generic operations by isolating software and hardware function into distinct layers. The high-level operations are isolated from the differences of the hardware.

Interrupt Mechanism

Interrupts force the processor to stop its normal execution path and, temporarily execute another path. Once the interrupt path is completed, execution returns to the normal path that was interrupted. The sequence of events for a typical interruption are:
 
Interrupt Sequence
Computer 
  1. Perform some 'normal' program task.
  2. Interrupts occurs.
  3. Save program state.
  4. Handle interruption.
  5. Restore program state.
  6. Continue with program .
People
  1. Perform work on C335 homework 9 assignment.
  2. Phone rings.
  3. Note what you are working on.
  4. Answer phone, blah, blah, blah, and hangup.
  5. Remember where to continue.
  6. Continue with homework 9.

There are two origins of interrupts 1) generated by the PC hardware in response to some event such as division by zero, a keypress or printer signal, and 2) execution of the software instruction Int by the CPU. From the perspective of the CPU execution both interrupts are treated identically.

The Int instruction acts similar to a Call, the return address is saved on the stack and Cs:Ip point to the next instruction to fetch. Additionally, the flags are saved and the trap and interrupt flag are set to 0 to temporarily prevent further interrupts. The key difference between a Call and an Int is how the location of the next instruction to fetch is determined.

For the Call instruction the function address is part of the instruction itself. For example, Call Power would contain the address of the Power function. When executed the CPU saves the return address on the stack (the Cs:Ip registers), and sets the next instruction to fetch (the Cs:Ip registers) to the address of Power.

Interrupts allow hardware to signal the CPU to suspend the currently executing algorithm and execute one for the interrupting hardware. In the figure at right:

  1. A key press causes Int 9 (Interrupt 9) to be sent to the CPU. 
  2. The CPU saves the IP (return address of 1006) and flags on the the stack.
  3. The CPU sets IP=5763, the value of the interrupt vector at index 9.
  4. The interrupt handler is executed until IRET, which restores the return address (1006) and flags from the stack.
  5. The interrupted algorithm is continued.

For the Int instruction the function address is stored in an array called an interrupt vector that holds the memory address of functions. The Int instruction indexes the interrupt vector, for example Int 5 indexes interrupt vector array entry 5. Each function address occupies 4 bytes, the interrupt vector is an array of function addresses, so each array entry is 4 bytes. When the Int 5 is executed the CPU saves the return address (Cs:Ip), and flags. The address of the next instruction (Cs:Ip) to fetch is stored at index 5 of the array. The CPU indexes into the interrupt vector array for the function address at index 5 (each entry is 4 bytes, index 5 is 4*5 bytes from the start of the array (2010 = 1416 bytes). To execute the function address at index 5, the 4 bytes in the array at index 5 are copied to Cs and Ip. In the following example Cs=432116 and Ip= 002516, the address of the next instruction to execute, the start of the function execution.
 
Detailed Execution of the Int 5 Instruction
 Int 5  
 
 Stack
 Result
  _____
 |Flags|
 | Cs  |
 | IP  |

1. Sp = Sp - 2
   (Sp) = Flags
   IF=0, TF=0
2. Sp=Sp-2
   (Sp) = Cs
3. Sp=Sp-2
   (Sp) = Ip = Return Address
4. Ip=0000:[4*5] = 002516
5. Cs=0000:[4*5+2] = 432116


   Interrupt    Physical
    Vector       Address
   | 1234 |4*3  =0000C16
   | 5678 |4*3+2=0000E16
   | 9ABC |4*4  =0001016
   | DEF0 |4*4+2=0001216
Ip | 0025 |4*5  =0001416
Cs | 4321 |4*5+2=0001616
   | CBA9 |4*6  =0001816
   | 0FED |4*6+2=0001A16  

Interrupt Vector Assignments

Page 433, Table 9.1 of the text lists PC architecture interrupt assignments. For example, Interrupt 5 has been defined for the Print Screen operation. The addresses of functions are stored in the interrupt vector, most of those defined in Table 9.1 are stored into the interrupt vector during system boot. To better visualize how the interrupt vector is used consider only the interrupts 3, 4, and 5. Suppose we had written interrupt functions for each and wanted the following : The interrupt vector would need to contain the address of function PrtBk at index 3, the address of PrtOF at index 4, etc. Assuming that the functions all are stored in code segment 9999 and the offset listed in the following table at right, the partial interrupt vector would appear as in the table at left:
 
Index  Interrupt    Physical
        Vector       Address
  3    | 1234 |4*3  =0000C16
PrtBk__| 9999 |4*3+2=0000E16
  4    | 9ABC |4*4  =0001016
PrtOF__| 9999 |4*4+2=0001216
  5    | 0025 |4*5  =0001416
PrtSc  | 9999 |4*5+2=0001616
  6    |      |4*6  =0001816
_______|      |4*6+2=0001A16
  1234             PrtBk proc    Far  
  1234 CF                Iret
  1235             PrtBk endp

  9ABC             PrtOF proc    Far  
  9ABC CF                Iret   
  9ABD             PrtOF endp

  0025             PrtSc proc    Far  
  0025 CF                Iret 
  0026             PrtSc endp

Initializing the Interrupt Vector

The interrupt vector must be initialized before any interrupts can be handled. This normally is done during system boot but can be performed by user programs also. The following defines a function PrintHelp and initializes interrupt vector index entry 5 with the segment and offset of the PrintHelp function. As discussed above, when an Int 5 instruction is executed, the CPU will indirectly execute the PrintHelp function located at offset 002516(see below listing) and segment 432116.
 
Initialize Entry 5 with Address of PrintHelp Function
  Offset
13 0000 B8 0000         Mov  Ax, 0                          ; Initialize interrupt
14 0003 8E D8           Mov  Ds, Ax                         ; vector with PrintHelp
15 0005 BB 0014         Mov  Bx, 5*4                        ; function in place of
16 0008 FA              Cli                                 ; regular Print Screen
17 0009 C7 07 0025r     Mov  word ptr [Bx],offset PrintHelp ; [0001416]=offset PrintHelp
18 000D C7 47 02 0000s  Mov  word ptr [Bx]+2, Seg PrintHelp ; [0001616]=seg PrintHelp
19 0012 FB              Sti
33 0025             PrintHelp proc    Far                ; Interrupt function
34 0025  1E                   Push    Ds                 ; required to save all
35 0026  56                   Push    Si                 ; registers modified
36 0027  50                   Push    Ax
37 0028  53                   Push    Bx
38 0029  51                   Push    Cx
39 002A  9C                   Pushf
40
41 002B  B8 0000s             Mov     Ax, Seg Help       ; Print Help!
42 002E  8E D8                Mov     Ds, Ax
43 0030  BE 0000r             Mov     Si, Offset Help    ;    Interrupt    Physical
44 0033  B9 0007              Mov     Cx, 7              ;     Vector      Address
45 0036  AC            Do2:   Lodsb                      ;    | 1234 |4*3  =0000C16
46 0037  B4 0E                Mov     Ah, 14             ;    | 5678 |4*3+2=0000E16
47 0039  BB 0000              Mov     Bx, 0              ;    | 9ABC |4*4  =0001016
48 003C  CD 10                Int     10h                ;    | DEF0 |4*4+2=0001216
49 003E  E2 F6        While2: Loop    Do2                ; Ip | 0025 |4*5  =0001416
50                                                       ; Cs | 4321 |4*5+2=0001616
51 0040  9D                   Popf                       ;    | CBA9 |4*6  =0001816
52 0041  59                   Pop     Cx                 ;    | 0FED |4*6+2=0001A16
53 0042  5B                   Pop     Bx
54 0043  58                   Pop     Ax
55 0044  5E                   Pop     Si
56 0045  1F                   Pop     Ds
57 0046  CF                   Iret                      ; Execute Interrupt Return
58 0047             PrintHelp endp

Iret Interrupt Return

Because interrupt functions are executed by Int instructions which save the flags in addition to the return address, the Ret instruction is not correct for returning from an interrupt function, as the flags are not restored. Use Iret instead (see above example).
 
Iret
Ip = (Sp)
Sp = Sp+2
Cs=(Sp)
Sp=Sp+2
Flags = (Sp)
Sp=Sp+2
Ret
Ip = (Sp)
Sp = Sp+2
Cs=(Sp)
Sp=Sp+2

Enable and Disable Interrupts - Sti and Cli

It is sometimes necessary that the CPU execute a critical section of code without being interrupted. For example, when the processor is used in potentially life threatening applications (weapons, transportation, safety systems). Executing an interrupt automatically disables interrupts until an Iret is executed (recall that the If flag is cleared to 0 on interrupt execution and the Iret restores the flags). The following two instructions control whether the CPU responds to external or hardware interrupts by the value of If (interrupt flag), the CPU will always execute software interrupts (e.g. Int 5) that are part of the program code. Notice in lines 16 through 19 of the code above, interrupts are disabled when the interrupt vector is manipulated. If an interrupt Int 5 occurred when the vector was only partially initialized the results would be unpredictable.

DOS Display 'Hello world'

DOS is an operating system and operating systems provide access to system resources and management of those resources. DOS provides resources access by interrupts to provide operating system functions, Int 21h being the primary interrupt used. To access hardware, DOS calls the BIOS layer functions.
3) DOS:            Int 21h, function 9 - "Hello World" passed to BIOS video function 10h
4) BIOS:           Int 10h - "Hello world" placed in hardware video memory             
5) Video hardware: "Hello world" display from video memory

The following displays the string "Hello world" using the main DOS interrupt (Int 21h). The basic DOS function 21h use is to place the function number in Ah and other parameters in designated registers. From our study of parameter passing methods, passing parameters in registers generally preclude multiple instances of function invocation and recursion, hence DOS operations are not reentrant (recursive) and the DOS operating system cannot handle multiple tasks simultaneously as can Windows 95, NT, etc. using the same CPU.

The DOS definition (text page 464 or INT 21h list) of the function to display a string that ends with a '$' is:

     Function 9     Display to standard output string terminated by '$'
     Parameters
               Ah   9
               Ds   Segment of string
               Dx   Offset of string
     Returns
               Nothing
The general use of this and most DOS operations is: to place the number of the function in the Ah register and other registers with parameters. The following uses DOS Int 21h, function 9 (print a $ terminated string) to print a string pointed to by the parameter in Dx.
               Mov  Ah, 9               ;;   Function number
               Mov  Dx, Offset string   ;;   Function parameter
               Int  21h                 ;;   Execute function
Example of "Hello world" using DOS 
Title    Display "Hello world" to Screen using DOS interrupts
; v:\common\user\c335\assembler
; ml helloworld.asm
; helloworld

.model	small
.code
Main     Proc     near

         Mov      Dx, @data        ;; Initialize to program's Data Segment
         Mov      Ds, Dx           
         Mov      Dx, Offset Hello ;; 'Hello world'    
         Mov      Ah, 9            ;; DOS Function to Display a string  
         Int      21h              ;; Execute DOS Function     
         
         Mov      Ah, 4ch
         Int      21h              ;; Return to Caller, operating system

Main     Endp

.data
Hello    db  'Hello world', '$'

         End       Main

BIOS Display 'Hello world'

BIOS interrupt functions generally access the input/output hardware directly and are specific for each hardware characteristics, providing a standard interface layer for accessing hardware functions. Each different version of hardware requires different BIOS function code but still uses the same interrupt number (that's why the BIOS from one computer system is not compatible with another's). The BIOS function interrupt numbers and behavior was defined by IBM for the original PC architecture so any program (such as DOS itself) that uses the BIOS interrupts can ignore the specific hardware, BIOS function code is specific for specific hardware.
4) BIOS:           Int 10h - "Hello world" placed in hardware video memory             
5) Video hardware: "Hello world" display from video memory

The following displays the string "Hello world" using the BIOS video interrupt (Int 10h). The basic use is to issue the interrupt number of the operation with Ah the device function and other parameters in registers. From our study of parameter passing methods, register parameters generally preclude recursion, hence BIOS operations are not reentrant (recursive). Generally BIOS functions use the Ah register to define the function and other registers for parameters. The text definition (page 450) of the function to display to video (or see INT 10H) output is:

     Interrupt 10h   Video

     Parameters
               Ah   function number
                    Other registers used dependent upon the function
          Returns   Dependent upon the function

To display a single character 'A' to video is:

        Mov     Ah, 14                  ;;      Function number
        Mov     Al, 'A'                 ;;      Character to display
        Mov     Bl, 0                   ;;      Foreground color white
        Mov     Bh, 0                   ;;      Display to video page 0
        Int     10h                     ;;      Execute video operation
Example of "Hello world" in BIOS
Title    Display "Hello world" using BIOS Interrupt routines
; v:\common\user\c335\assembler
; ml helloworld.asm
; helloworld

.model	small
.code
Main     Proc	near
         Mov	Dx, @data
         Mov	Ds, Dx
         Cld
         Mov	Si, offset Hello
@@While: Lodsb                   ;; While Hello[Si] != '$' 
         Cmp	Al, '$'
         Jne	@@Do
         Jmp	@@EndWhile
   @@Do: Mov	Ah, 14           ;;     Video operation in Ah to write
                                 ;;           character in Al to screen.
         Mov	Bl, 0            ;;     Foreground color 0 (white)
         Mov	Bh, 0            ;;     Alpha page 0

         Int	10h              ;;     Execute Bios video routine

         Jmp	@@While
@@EndWhile:
         Mov	Ah, 4ch          ;; Return to Caller, operating system
         Int	21h
Main     Endp

.data      
         Hello	db  'Hello world', '$'

         End	Main

Interrupt Handlers for Hardware Interrupt

DOS and BIOS interrupt routines already exist, have addresses stored in the interrupt vector, and can be invoked by the INT instruction. Creating an interrupt routine that can be executed the same as DOS or BIOS routines requires several steps. The following outlines an interrupt handler, the routine invoked when an interrupt occurs, and an example of a hardware caused interrupt, arithmetic overflow.

Overflow is a hardware event causing the CPU to execute a Type 4 interrupt but can also be executed by a software interrupt, the INT 4 instruction. In either case the interrupt vector must contain the segment and offset of the interrupt handler function to be executed, for a Type 4 interrupt, the function address is stored at vector index number 4. The following prints a message on overflow error. The INTO instruction should be placed after arithmetic operations that may overflow to execute an interrupt  when OF=1. Note that the example may not work correctly on protected operating systems such as Microsoft NT which limit general user access to the hardware. Try OverFlow interrupt on your machine.
Overflow Interrupt Handler
Title    Initialize and Display Message on Overflow Interrupt
; v:\common\user\c335\assembler
; ml overflow.asm
; overflow

.model	small
.code
Main     Proc          near

;__1__________________________________Initialize Interrupt Vector_______
	Cli                       ;; Disable interrupts during vector  
	Mov      Ax, 0            ;; modifications.                    
	Mov      Ds, Ax           ;; Address segment 0 as Data Segment 
                                  ;;                                   
	Mov      Bx, 4 * 4        ;; Interrupt type * 4 vector address  
                                  ;; Initialize interrupt vector with   
                                  ;; value of CS:IP on type 4 interrupt 
	Mov      Word Ptr [Bx], Offset Overflow                       
	Mov      Word Ptr [Bx+2], Seg Overflow   
;__2___________________________Enable Interrupts________________________
	Sti                       ;; Ready to handle interrupts         |

;__3___________________________Generate Hardware Interrupt Type 4_______
	Mov	Al, -128	;					|
	Sub	Al, 1		;					|
	Into	
;__3___________________________Generate Software Interrupt Type 4_______
	Int	4
        Mov     Ah, 4ch
        Int     21h           
Main    Endp
;___4__________________________Interrupt Routine________________________
;;   Overflow - Interrupt routine called when type 4 interrupt, OF=1    |
;;     Parameters - None. Hardware interrupt routines have no parameters|
;;     Returns    - Nothing                                             |
;;     Registers  - None. Interrupt routines should not alter registers |
;;                                                                      |
Overflow Proc	Far		;					|
	Push	Ax		;					|
	Push	Dx		;					|
	Push	Ds		;					|
	Mov	Dx, Seg Error	; Initialize to program's Data Segment	|
	Mov	Ds, Dx          ;					| 
	Mov	Dx, Offset Error; 'Error Overflow' 			|   
	Mov	Ah, 9           ; DOS Function to Display a string  	|
	Int	21h             ; Execute DOS Function  		|
	Pop	Ds		;					|
	Pop	Dx		;					|
	Pop	Ax		;					|
	IRet	  		;					|
  	Error	db	'Error Overflow', 13, 10, '$'	;		|
Overflow Endp	  		;					|

         End       Main

Interrupt Execution - The INTO instruction at line 11 causes the following program to execute in the order of 1-11, 16-28, 12, ... 

What is the result of the INT 4 instruction at line 12?

 
  1. Main         Proc          near
  2.     Cli                      
  3.     Mov        Ax, 0            
  4.     Mov        Ds, Ax
  5.     Mov        Bx, 4 * 4        
  6.     Mov       Word Ptr [Bx], Offset Overflow
  7.     Mov       Word Ptr [Bx+2], Seg Overflow
  8.     Sti  
  9.     Mov       Al, -128
  10.     Sub        Al, 1
  11.     Into
  12.     Int         4
  13.     Mov      Ah, 4ch
  14.     Int         21h           
  15. Main       Endp
  1. Overflow Proc Far
  2.      Push     Ax
  3.      Push     Dx
  4.      Push     Ds
  5.      Mov     Dx, Seg Error
  6.      Mov     Ds, Dx 
  7.      Mov     Dx, Offset Error
  8.      Mov     Ah, 9
  9.      Int         21h  
  10.      Pop       Ds
  11.      Pop       Dx
  12.      Pop       Ax
  13.      IRet
  14.    Error db 'Error Overflow', 13, 10, '$'
  15. Overflow Endp
  16.     End     Main

Division by zero is a hardware event causing the CPU to execute a Type 0 interrupt but can also be executed by a software interrupt, the INT 0 instruction. It is important to recognize that division by zero causes an internal CPU interrupt that executes the Division routine. Note that rather than returning to the point of the division error, the Division routine exits program execution. Try division by 0 interrupt on your machine.

Divide Error Interrupt Handler
Title    Initialize and Display Message on Divide Interrupt
; v:\common\user\c335\assembler
; ml zerodiv.asm /link io.lib
; zerodiv

.model	small
.code
	PutDec	 proto Far
Main    Proc     near
;__1__________________________________Initialize Interrupt Vector_______
	Cli                       ;; Disable interrupts during vector   
	Mov      Ax, 0            ;; modifications.                     
	Mov      Ds, Ax           ;; Address segment 0 as Data Segment  
                                  ;;                                    
	Mov      Bx, 0 * 4        ;; Interrupt type * 4 vector address  
                                  ;; Initialize interrupt vector with   
                                  ;; value of CS:IP on type 0 interrupt 
	Mov      Word Ptr [Bx], Offset Divide  
	Mov      Word Ptr [Bx+2], Seg Divide
;__2___________________________Enable Interrupts________________________
	Sti                       
;__3___________________________Generate Hardware Interrupt Type 0_______
	Mov	Ax, 0		
        Div	Ax		
	Call	PutDec		  ; Never reached due to divide error

        Mov     Ah, 4ch
        Int     21h              ;; Return to Caller, Operating System
Main    Endp

;___4__________________________Interrupt Routine________________________
;;   Divide - Interrupt routine called when type 0 interrupt		|
;;     Parameters - None. Hardware interrupt routines have no parameters|
;;     Returns    - Does not return.                                    |

Divide  Proc	Far			
	Mov	Dx, Seg Error	; Initialize to program's Data Segment	
	Mov	Ds, Dx          					 
	Mov	Dx, Offset Error; 'Error Divide' 			   
	Mov	Ah, 9           ; DOS Function to Display a string  	
	Int	21h             ; Execute DOS Function  		

        Mov     Ah, 4ch
        Int     21h             ; Exit program execution

  	Error	db  'Error Divide', 13, 10, '$'	
Divide  Endp	  							

         End       Main

Interrupt Execution - What is the order of instruction execution for the following?

  1. Main    Proc     near
  2.     Cli
  3.     Mov      Ax, 0
  4.     Mov      Ds, Ax
  5.     Mov      Bx, 0 * 4 
  6.     Mov      Word Ptr [Bx], Offset Divide  
  7.     Mov      Word Ptr [Bx+2], Seg Divide
  8.     Mov      Ax, 0
  9.     Div        Ax
  10.     Call        PutDec
  11.     Mov      Ah, 4ch
  12.     Int         21h 
  13. Main       Endp
  1. Divide    Proc Far
  2.     Mov      Dx, Seg Error
  3.     Mov      Ds, Dx  
  4.     Mov      Dx, Offset Error
  5.     Mov      Ah, 9 
  6.     Int         21h
  7.     Mov      Ah, 4ch
  8.     Int         21h
  9.     Error     db  'Error Divide', 13, 10, '$'
  10. Divide     Endp
  11.   End       Main

Print Screen Interrupt Handler

As another example of an interrupt routine, the following is an interrupt handler for the Print Screen interrupt 5. Its effect is to print Help! each time the Shift Prt Sc Keys are pressed. Again, the program works on unprotected versions of Windows such as 95, 98, and ME but not on protected operating systems such as NT.
Print Screen Interrupt Handler
; v:\common\user\c335\assembler
; ml printscreen.asm
; printscreen
; Shift Print Screen

.model	small
.code
Main    proc    near
;______________________________________1
        Mov     Ax,0     		; Initialize interrupt 
        Mov     Ds, Ax    		; vector with PrintHelp
        Mov     Bx,16h*4         	; function in place of
        Cli                          	; regular Print Screen
        Mov     word ptr [Bx],offset PrintHelp       
        Mov     word ptr [Bx]+2, Seg PrintHelp
        Sti
;______________________________________2
        Mov     Cx, 65000		; Execute some sequence
Do1:                     		; of control. Int 5 will
        Mov     Al, 'Z'			; transfer control to
        Mov     Ah, 14			; PrintHelp function.
        Mov     Bx, 0
        Int     10h
While1: Loop    Do1
        Mov     Ah, 4ch
        Int     21h
Main    endp

;______________________________________3
PrintHelp proc    Far			; Interrupt function
        Push    Ds			; required to save all
        Push    Si			; registers modified
        Push    Ax
        Push    Bx
        Push    Cx

        Mov     Ax, @data		; Print Help!
        Mov     Ds, Ax
        Mov     Si, Offset Help
        Mov     Cx, 7
Do2:    Lodsb
        Mov     Ah, 14
        Mov     Bx, 0
        Int     10h
While2: Loop    Do2

        Pop     Cx
        Pop     Bx
        Pop     Ax
        Pop     Si
        Pop     Ds
;______________________________________4
        Iret				; Interrupt Return
PrintHelp endp

.data  
        Help db	'Help!', 10, 13
        End	Main

Printer Polling Using BIOS Interrupts

Polling is the process where the CPU queries a device of its status prior to inputting/outputting data through that device. In the Polled Input/Output Example, the hardware status is input directly from the hardware using the IN instruction and data is output to the hardware using the OUT instruction. Using hardware directly, programs are dependent upon specific hardware configurations and are not generally portable to other systems. The BIOS is designed to provide a standard set of functions to access hardware so that programs can be more portable, the programmer uses BIOS interrupt numbers versus hardware addresses.

The following procedure, PrintChar, uses the BIOS printer interrupt to poll the printer's status prior to outputting a character. The printer status is continually tested by the CPU to determine that the printer is ready to accept more characters to print. The CPU waits until the status indicates that the printer is ready, then the CPU outputs a character to the printer. Should the printer never be ready (out of paper. etc.) the CPU is left busy waiting for the device to become ready, possibly an eternity if the status never indicates device readiness, a pitfall of polling. See page 460, Table 9.6 and 9.7 (or INT 17h) for details.

 
Printer Polling Using BIOS Interrupts
Title    Screen to Printer
; v:\common\user\c335\assembler
; ml printscreen.asm
; printscreen

CR       Equ           13
LF       Equ           10

.model	small
.code

;;   void PrintChar(char Al) - Procedure to output to printer
;;     Parameters - Al Character to output
;;     Returns    - Nothing
;;
PrintChar Proc	Near            
	Push	Dx
	Push	Ax
	PushF
	Mov	Dx, 0           ;; Assume Printer 0                    
@@do:	Mov	Ah, 2           ;; do                            
	Int	17h             ;;   cin >> PrinterStatus            
	Test	Ah,10000000B    ;; while PrinterBusy                
@@while:Jnz	@@do         	
                               
	Mov	Ah, 0           ;; Print Al
	Int	17h

	PopF
	Pop	Ax
	Pop	Dx
	Ret			
PrintChar Endp          

Main	Proc 	near
	Mov	Dx, 0		;; Initialize Printer 0
	Mov	Ah, 1		;; Reset function
	Int	17h

	Mov	Dh, 0		;; Row
@@ForRow:
	Cmp	Dh, 24		;; for (Dh = 0; Dh<=24;Dh++) {
	Jle	@@doForRow	;;   for (Dl = 0; Dl<=79; Dl++) {
	jmp	@@EndForRow	;;      Video.cin >> Al 
  @@doForRow:
	Mov	Dl, 0		;;	printer.cout << Al
  @@ForColumn:			;;   }
	Cmp	Dl, 79		;;   printer.cout << "\n"; 
	jle	@@doForColumn	;; }
	Jmp	@@EndForColumn	
    @@doForColumn:   
	Mov	Bh, 0		;; Video Page 0
	Mov	Ah, 2		;; Set Cursor Position to (Dl, Dh)
	Int	10h
	Mov	Ah, 8		;; Read character at cursor
	Int	10h

	Call	PrintChar	;; Print Al

	Inc	Dl
	Jmp	@@ForColumn
  @@EndForColumn:
	Mov	Al, CR
	Call	PrintChar
	Mov	Al, LF
	Call	PrintChar
	Inc	Dh
	Jmp	@@ForRow
@@EndForRow:
	Mov	Ah, 4Ch
	Int	21h               
Main	Endp           
	End	Main

Direct Hardware Polled Input/Output

Because the CPU is generally far faster than input and output devices (printers, monitors, disks) it can overwhelm the devices ability to send or receive data. Two input/output methods common to synchronize the CPU with external devices are interrupt driven, where the device signals its readiness by generating an interrupt to the CPU, and polling where the CPU executes an IN instruction to query the device status. But interrupts occur asynchronous to the CPU execution of a program which can induce conflicting demands on the CPU time, so programming is somewhat difficult. Because polling occurs within the flow of program execution, the CPU is only performing a single task at a time, so programming is much simplified.

The following uses the IN and OUT instructions to directly access the hardware that composes the printer status, data, and control registers. At the hardware level of programming, one must understand how hardware devices communicate with one another. The communication path between the CPU and printer is connected through the printer interface which consists of 3 registers for data, control, and status. For details, see text page 460, Table 9.7 and page 466 Direct Input/Output.

The general approach to the CPU to communicate a character to the printer is captured in the following algorithm:

  1. Input printer status register
  2. If printer status indicates not ready go to 1
  3. Output character to printer data register
  4. Output signal to printer control register that new data is available
  5. Go to 1
The purpose of Step 4 is to signal the printer that new data has been placed in the interface data register. To see the necessity of this step consider the scenario where AAAAAA characters were sent to the printer by each being output in turn to the data register. From the printer's point of view, the data register never changed, it remained the character A. Step 4 causes a strobe signal, meaning that the voltage on the connection (wire perhaps) between the CPU and printer changes from a logic 0 to 1 to 0. The printer detects this change or strobe to recognize that new data is available on the connection.
 
Direct Hardware Polled Input/Output
Title   Use of Input/Output instructions and POLLING for printer control
; v:\common\user\c335\assembler
; ml poll.asm
; poll

.data
        Message	db      'Hello world', 10, 13, '$'
.code				;________________ ______ 
     				; Output Data    | 378h | Printer I/O 
PrintChar Proc   Near		; Output Control | 37Ah | Addresses
        Push    Dx		; Input Status   | 379h |
        Push	Ax		;________________|______|
 	PushF
	Push	Ax
        Mov     Dx, 379h	; Do Input and Test Printer Status
@@do:	In	Al, Dx		; While Busy, Paper Out, Not Selected
        And     Al, 10111000B	; Bits 7,       5,        4,       3
        Cmp     Al, 10010000B	; Busy=1, Paper=0, Select=1, Error=0
@@While:Jne     @@do
	Pop	Ax		; Get character from stack to print
        Mov     Dx, 378h 
        Out     Dx, Al		; Print( Al );

        Mov     Dx, 37ah
        Mov     Al, 0dh		; Signal printer character
        Out     Dx, Al		; available to print (Strobe) by
        Mov     Al, 0ch		; sending pulse    __  on Bit 0
        Out     Dx, Al		; ________________|  |_____________
				;   Bit 0 =0       =1       =0
        PopF
	Pop	Ax
	Pop	Dx
        Ret     
PutChar Endp

Main    Proc    Near
        Mov     Ax, @data
        Mov     Ds, Ax		
        Lea     Si, Message
@@While:Lodsb
        Cmp     Al, '$'		; While (Message[Si] != '$') {
        Jne     @@do		;    PutChar(Message[Si]);
        Jmp     @@endWhile      ;    Si := Si + 1
   @@do:  
        Call    PutChar
        Jmp     @@While
@@endWhile:
        Mov    	Ah, 4ch
        Int	21h
Main    Endp
	End	Main

File Input/Output Using DOS Interrupts

BIOS supports generic to input/output devices while DOS supports higher-level notions such as a file system with names, directories and routines for their manipulation. The following is an example of using DOS file functions to copy or display a file to the screen. Note the close similarity between DOS file operations and those used in C++.

DOS Int 21h subfunctions (page 463 of text)
 
    Function 9      Display to standard output string terminated by '$'
    Parameters      Ds:Dx = Segment:Offset of string
    Returns         Nothing
    Function 3D     Open file
    Parameters      Ds:Dx = Segment: Offset of 0 terminated string containing pathname
                    Al = access code
                     0 - open for read
                     1 - open for write
                     2 - open for read/write
    Returns         CF = 0 and Ax = file handle OR CF = 1 and Ax = error code
    Function 3F     Read from file
    Parameters      Bx = file handle
                    Cx = number of bytes to read
                    Ds:Dx = Segment: Offset of memory area for data read from file
    Returns         CF = 0 and Ax = number of bytes read OR CF = 1 and Ax = error code
File Input/Output Using DOS Interrupts
Title    Display File to Screen
; v:\common\user\c335\assembler
; ml copyfile.asm
; copyfile

.model	small
.data       
         Filename  db  'copyfile.asm', 0   ;; Name of file to copy
         Buffer    db  81 dup(?)           ;; Read and write buffer
.code
Main	Proc	near
	Mov	Ax, @data   
	Mov	Ds, Ax          
;______________________________________Bx = fopen(Filename, "r")________
	Mov	Dx, Offset Filename  	;                   		|
	Mov	Al, 0            	;; Open for Reading 		|
	Mov	Ah, 3Dh          	;; DOS Open File Function 	|
	Int	21h              	;; Execute DOS function		|
	Mov	Bx, Ax           	;; Ax contains file handle	|
;_______________________________________________________________________|

@@do:                                	;; do {
;_________________________________________fgetline( Bx, Buffer, 80 )____
	Mov	Cx, 80			;; Number bytes Read from file 	|
	Mov	Dx, Offset Buffer	;; Read 80 chars into Buffer    |
	Mov	Ah, 3Fh			;; DOS Function to Read file	|
	Int	21h			;; Execute DOS function 	|
;_______________________________________________________________________|

;_________________________________________cout << Buffer _______________
	Mov	Si, Ax			;; Ax contains number chars read|
	Mov	Buffer[Si], '$'  	;; Terminate string with '$'	|
	Mov	Ah, 9            	;; DOS Function Display string  |
	Mov	Dx, Offset Buffer	;; Display chars from Buffer    |
	Int	21h              	;; Execute DOS Function 	|
;_______________________________________________________________________|

	Cmp	Si, 0            	;; Quit when zero characters read
@@while:JnZ	@@do               	;; } while !eof()

	Mov	Ah, 4ch
	Int	21h              
Main	Endp

	End	Main

Standard Input and Output

Most operating systems (Unix, DOS, NT, etc.) and many languages support standard input and output. The basic idea is that a program that reads standard input and writes standard output can read the keyboard or a file, write the screen or a file, without any changes to the program. This is performed by the operating system through redirection. For example, the following C++ and Assembler programs reads until a 'Z' is entered, converting characters 'A'-'Z' to lowercase and outputting (note that other characters (e.g. '0'-'9') will be corrupted.

Suppose the name of either program executable is lowcase.exe. Once compiled or assembled, the programs can be executed several ways.

  1. lowcase - The input is from the keyboard and output to the screen.
  2. lowcase < a2z - The input is from the file a2z and output to the screen.
  3. lowcase > z2a - The input is from the keyboard and output to the file z2a.
  4. lowcase < a2z > z2a - The input is from the file a2z and output to the file z2a.
Convert to Lowercase with Standard Input and Output (Page 463 Table 9.11)
#include <iostream.h>
void main(void) {
    char c;

    do {
       cin >> c;
       c = c | 0x20;
       cout << c;
    } while (c != 'z');
}






     .code     
     Main       Proc      near
      do:       Mov       Ah, 8      ; Read standard input
                Int       21H        ; into Al

                Or        Al, 20h
                Mov       Dl, Al     ; Write standard output
                Mov       Ah, 2
                Int       21H

       while:   Cmp       Al, 'z'    
                Jne       do

                Mov       Ah, 4ch
                Int       21H
     Main       Endp
                End       Main
DOS Standard Input and Output Description
         Function                           Inputs           Outputs
  AH
  1    Input one character with echo                         Al = character
  2    Output one character                 Dl=character
 *6    Input one character from             Dl=FFh           Zf=1 (when no input available)
       standard input device.                                Zf=0 and Al=input
  7    Input one character no echo                           Al = character
  8    Input one character, handle Ctrl-C                    Al = character
  9    Print a '$' terminated               Ds=Segment  
       string.                              Dx=Offset

* Windows 2000 behavior is NOT identical to previous DOS/Windows versions in some applications.

For input, functions 1, 7, and 8 are the simplest to use, differing by whether input is echoed (1) or Ctrl-C is handled (1 and 8), or no echo and Ctrl-C is ignored (7). When reading from the keyboard, one key point to note is that the user may not have typed anything when the program executes the read causing the program to block for input (functions 1, 7, or 8). For that reason, it is sometimes necessary to verify that input is available before the read. The standard input function 6 returns the zero flag  to indicate the status, 0 means that input was read and is in Al register, 1 means that input was not available. The wait for input is generally called polling and is an example of a busy/wait loop where the computer is busy waiting. The loop consists of:
 
                Mov       Ah, 6      ; Ah=6 and Dl=FFh
        do2:    Mov       Dl, 0ffh   ; for standard input
                Int       21H        ; into Al
        while2: Jz        do2        ; Wait for input

Document last modified: