Interrupt

Interrupt มีสองประเภท คือ software interrupt (หรือ trap) และ hardware interrupt ไม่ว่าจะเป็น interrupt แบบใด จะมีขบวนการจัดการกับ interrupt เหล่านั้นด้วยการจดจำตำแหน่งของคำสั่งที่จะ execute ถัดไป ไว้บนกองซ้อน (push CS, push IP) แล้วกระโดดไปยัง interrupt handler routine (เปลี่ยนค่าใน CS, IP) เพื่อบริการ interrupt ดังกล่าว เมื่อบริการ interrupt handler routine เสร็จ ก็จะกลับมาทำงานที่คำสั่งที่ตำแหน่งซึ่งได้เคยจดจำไว้ (pop IP, pop CS) แสดงดังภาพ

ตำแหน่งของ interrupt handler routine จะเก็บอยู่ใน interrupt vector table ซึ่งอยู่ที่หน่วยความจำเลขที่ 000000H-0003FFH ขนาด 1K สำหรับเก็บ interrupt vector หรือ CS:IP ขนาด 4 bytes ซึ่งเป็นตำแหน่งของ interrupt handler routine จำนวน 256 vectors ดังภาพ

12.1 Hardware Interrupt

8086/8088 มี hardware interrupt ที่เป็น input สองประเภท คือ non-maskable interrupt (NMI) และ interrupt request (INTR) ดังภาพ

NMI ปกติใช้สำหรับ parity error หรือ เกิดความผิดพลาดของระบบ เช่น ไฟดับ เป็นต้น interrupt ประเภทนี้ไม่สามารถบังคับไม่ให้เกิดได้ (คำสั่ง cli ไม่มีผลกับ NMI) ถ้าเกิด NMI interrupt การบริการจะใช้ interrupt type 2 เป็น interrupt handler routine ซึ่งการทำงานจะต้องรีบเก็บข้อมูลที่สำคัญลง CMOS ซึ่งเป็นหน่วยความจำที่มีแบตเตอรี่สำรอง เป็นต้น

สำหรับ INTR , Intel 8086/8088 มี 8259 Interrupt Controller เป็นตัวควบคุมการเกิด interrupt อุปกรณ์ตัวนี้มี Interrupt Mask Register (IMR) ขนาด 8 bits (b7b6b5b4b3b2b1b0) และมีเลขที่ port หมายเลข 21h บิตที่ 7 จะเกี่ยวข้องกับ interrupt หมายเลข 7 บิตที่ 6 จะเกี่ยวข้องกับ interrupt หมายเลข 6 ตามลำดับ ถ้ากำหนดให้บิตใดมีค่าเป็น 1 (unmask) เมื่อเกิด interrupt จากอุปกรณ์ที่เกี่ยวข้องกับบิตนั้น 8259 จะไม่ส่งสัญญาณให้ 8086/8088 ทราบนั้นคือ interrupt นั้นจะถูกละเลย ถ้าต้องการให้ 8259 ส่งสัญญาณให้ 8086/8066 ทราบเพื่อให้ interrupt นั้นมีผล ต้องกำหนดให้บิตที่เกี่ยวข้องมีค่าเป็น 0 (mask) อย่างไรก็ตาม interrupt จะมีผลถ้า IF (Interrupt Flag) มีค่าเป็น 1 (enable) ไม่ใช่ 0 (disable) ดูคำสั่ง cli และ sti ในบทที่ 9

อุปกรณ์ 8259 กำหนด priority หรือ level ให้กับแต่ละบิตที่กล่าวมา ผู้ใช้ไม่สามารถเปลี่ยนแปลง priority ดังกล่าว interrupt บิต 0 จะมี priority สูงสุด และ interrupt บิต 7 จะมี priority ต่ำสุด

ในบางช่วงเวลาอาจจะมี interrupt มากกว่าหนึ่ง level เกิดขึ้นพร้อมกัน (ขณะที่กำลังบริการอุปกรณ์ที่มี priority ต่ำกว่า แล้วเกิด interrupt จากอุปกรณ์ที่มี priority สูงกว่า) อุปกรณ์ที่มี priority สูงกว่าจะได้รับการบริการก่อน ส่วนอุปกรณ์ที่มี priority ต่ำกว่าจะต้องรอ เมื่อบริการ interrupt เสร็จผู้เขียนโปรแกรมจะต้องบอกการสิ้นสุดของการบริการ interrupt ให้ 8259 รู้ ด้วยการ ใส่ค่า 20h ไปที่ port หมายเลข 20h (เรียกว่า End Of Interrupt (EOI) command) เขียนเป็นภาษาแอสเซมบลีได้ ดังนี้

mov al,20h

out 20h,al

เพื่อที่อุปกรณ์ที่มี priority ต่ำกว่าที่กำลังรออยู่จะได้รับการบริการต่อไป

ขณะที่กำลังบริการ interrupt อยู่ 8259 จะป้องกันไม่ให้ interrupt จากอุปกรณ์ที่มี priority ต่ำกว่าหรือเท่ากับ มีผล โดยไม่ส่งสัญญาณไปบอกให้ 8086/8088 รู้ หรือละเลย interrupt ดังกล่าวนั่นเอง

8259 จะใช้ Interrupt Mask Register (IMR) , Interrupt Request Register(IRR), และ Interrupt Service Register (ISR) เพื่อจัดการกับเรื่องที่กล่าวมาทั้งหมดข้างต้น ดังนี้

การป้องกันไม่ให้ interrupt ที่มี priority ต่ำกว่า มีผล ทำได้โดยการตรวจสอบบิตใน IRR กับบิตใน ISR ถ้าบิตที่มี priority สูงกว่าใน ISR อย่างน้อยหนึ่งบิตเป็น 1 แสดงว่ากำลังบริการ interrupt ที่มี priority สูงกว่าอยู่ จะละเลย interrupt ที่ร้องขอ อย่างลืมว่าเมื่อบริการ interrupt ใดเสร็จ เป็นหน้าที่ของผู้เขียนโปรแกรมที่จะต้องใช้ EOI command เพื่อบอกให้ 8259 รู้ จะได้กำหนดให้บิตของ interrupt ที่บริการเสร็จใน ISR เป็น 0 เพื่อที่จะให้ interrupt ที่มี priority ต่ำกว่าสามารถร้องขอได้

ถ้า interrupt ที่เป็น INTR มีผล 8086/8088 จะเปลี่ยนค่า CS และ IP โดยใช้ interrupt vector type 8 –15 ใน interrupt vector table สำหรับ interrupt level 0-7 ตามลำดับ

หมายเหตุ ในกรณีที่ลืม EOI command หลังจากสิ้นสุดการบริการ timer interrupt ซึ่งเป็น priority สูงสุด เครื่องจะ hang เพราะไม่สามารถเกิด interrupt ที่มี priority ต่ำกว่าหรือเท่ากับได้อีก การส่ง EOI command สองครั้งก็อาจจะทำให้การทำงานผิดพลาดได้ ระบบปฏิบัติการจะเป็นตัวจัดการกับทรัพยากรที่เกี่ยวข้องกับอุปกรณ์เหล่านี้ ท่านจึงไม่ควรใช้ EOI command ในโปรแกรมของท่าน เพราะ interrupt handler routine ของระบบปฏิบัติการจะทำหน้าที่นี้อยู่แล้ว

12.2 Software Interrupt

Intel 8086/8088 มีคำสั่ง int (ดูบทที่ 9) ให้ผู้ใช้สามารถทำ software interrupt ได้ หลังคำสั่ง int คือหมายเลข interrupt ซึ่งมีค่าได้ตั้งแต่ 0 – 255 สำหรับที่อยู่ของ interrupt handler routine จะอยู่ใน interrupt vector table ตามที่กล่าวมาแล้ว สรุป interrupt vector table ที่ใช้สำหรับทั้ง hardware และ software interrupt ที่สำคัญมีดังนี้

int 00h : divide by zero

int 01h : single step (use by DEBUG)

int 02h: nonmaskable interrupt (ดู hardware interrupt, NMI)

int 03h: break point (use by DEBUG)

int 04h: overflow

int 05h : print screen

...

int 08h-0eh : INTR interrupt (ดู hardware interrupt, INTR)

int 10h-1ah : see ROM BIOS Service Summary

...

int 1ch: get control of timer interrupt, called by int 08h

...

int 21h : the universal DOS functions (see DOS Service Summary)

...

int 23h: Ctrl/break address

int 27h :terminate but stay resident (see DOS Service Summary)

...

12.3 Change Interrupt Vector

กรณีที่ท่านต้องการเปลี่ยน interrupt vector เช่น ต้องการเขียนโปรแกรม screen saver ท่านอาจมีความจำเป็นต้องเปลี่ยน interrupt vector ของ timer interrupt (ควรใช้ int 1ch) และ keyboard interrupt (int 09h) หลักการทำดังนี้

เปลี่ยน interrupt vector ใน interrupt vector table ให้ชี้มาที่ interrupt handle ของท่าน ใน interrupt handler routine ของท่าน อย่าลืมเรียกใช้ interrupt handler routine เดิมด้วย เพื่อให้การทำงานยังคงเป็นปกติเหมือนเดิม ส่วนท่านจะเขียนอะไรเพิ่มเติมก็เขียนส่วนที่เพิ่มเติมเฉพาะใน interrupt handler routine ของท่าน

การเปลี่ยน interrupt vector ทำได้สองแบบ คือ เรียกใช้บริการของ DOS (ดู interrupt 21h function 25h และ 35h) หรือเปลี่ยนที่ interrupt vector table โดยตรง

ตัวอย่าง ต้องการเขียน ฟังก์ชัน delay เหมือนในภาษา C , void delay (unsigned millisecond);

หมายเหตุ ส่งผ่าน parameter ในที่นี้คือ millisecond ทางกองซ้อน ฉะนั้นวิธีเรียกใช้ต้องทำดังนี้

	mov ax,500		; millisecond = 500
	push ax
	call _delay
 -----------------------------------------------------
_TEXT   segment word public 'CODE'
_TEXT   ends
_DATA   segment word public 'DATA'
_DATA   ends
STACK   segment para stack 'STACK'
STACK   ends
DGROUP  group   _DATA,STACK
	assume  cs:_TEXT,ds:DGROUP,ss:DGROUP,es:DGROUP
_TEXT   segment
	public _delay       
;void delay(unsigned  times);
@DLYTIM EQU     WORD PTR [bp+4]
_delay  proc    near
	push    bp
	mov     bp,sp
	push    ax
	push    bx
	push    cx
	push    dx
	mov     ax,@DLYTIM
	cmp     ax,0
	je      @DLY10
	mov     cx,182
	mul     cx
	mov     cx,10000
	div     cx
	cmp     ax,0
	je      @DLY10
	mov     @TCOUNT,ax      ; keep delay times in @TCOUNT
        	mov     @TFLAG,0        ; set out of delay time to false
	mov     WORD PTR cs:@TDS+1,ds   ; store ds for new timer intr.
	mov     WORD PTR cs:@CDS+1,ds   ; store ds for new ctrlc intr.
	mov     ax,351ch        ; get address of time interrupt
	int     21h
	mov     @OLD_T,bx       ; offset address of old interrupt
	mov     @OLD_T+2,es     ; and segment
	mov     WORD PTR cs:@JMPOT+1,bx ; save offset of old timer intr.
	mov     WORD PTR cs:@JMPOT+3,es ; and segment
	mov     ax,3523h        ; get address of ctrlc interrupt
	int     21h
	mov     @OLD_C,bx       ; offset address of old interrupt
	mov     @OLD_C+2,es     ; and segment
	mov     bx,ds
	push    cs
	pop     ds
	mov     dx,offset _new_ctrlc
	mov     ax,2523h        ; set new ctrlc interrupt
	int     21h
	mov     dx,offset _new_time
	mov     ax,251ch        ; set new timer interrupt
	int     21h
	mov     ds,bx
@DLY05: cmp     @TFLAG,1        ; Is out of delay time?
	jne     @DLY05          ; no -> wait
	mov     cx,ds           ; keep ds in cx
	mov     ax,251ch        ; restore old timer interrupt
	lds     dx,DWORD PTR @OLD_T
	int     21h
	mov     ax,2523h        ; restore old ctrlc interrupt
	lds     dx,DWORD PTR @OLD_C
	int     21h
	mov     ds,cx           ; restore ds
@DLY10:
	pop     dx
	pop     cx
	pop     bx
	pop     ax
        	mov     sp,bp
	pop     bp
	ret     2
_delay  endp

_new_time       proc far
	push    ax              ; keep ax because it is changed at @TDS
	push    ds              ; keep ds of old timer intr.
@TDS:   mov     ax,0000h        ; 0000h is replaced by ds from _delay
	mov     ds,ax           ; now ds is correct for @TCOUNT and @TFLAG
	dec     @TCOUNT
	jnz     @NWT10
        mov     @TFLAG,1        ; signal out of delay timer
@NWT10: pop     ds      ; pop value offset of ds (reset to old value)
	pop     ax      ; get ax from stack again
@JMPOT: db      0eah,0,0,0,0    ; far jump to old timer interrupt
_new_time       endp

_new_ctrlc      proc far
@CDS:   mov     ax,0000h        ; 0000h is replaced by ds from _delay
	mov     ds,ax           ; now ds is correct for @OLD_C
        	mov     @TFLAG,1        ; protect any error signal out of delay timer
	; restore old timer interrupt
	mov     ax,251ch
	lds     dx,DWORD PTR @OLD_T
	int     21h
	; exit
	mov     ax,4c00h        ; end program
	int     21h
_new_ctrlc      endp
_TEXT   ends

_DATA   segment
@OLD_T  dw      (?),(?) ; address of old timer interrupt
@OLD_C  dw      (?),(?) ; address of old ctrlc interrupt
@TCOUNT dw      0
@TFLAG  db      ?
_DATA   ends
	end

ตัวอย่างโปรแกรมที่เรียกใช้ _delay และ atou

	title	TRYDELAY -- Demonstration Program for _delay and atou
	page	55,132


_TEXT	segment 	word public 'CODE'
_TEXT	ends
_DATA	segment	              word public 'DATA'                               
_DATA	ends                                                                           
STACK	segment	              para stack 'STACK'                              
STACK	ends                                                                            
DGROUP	group	_DATA,STACK
	assume	cs:_TEXT,ds:DGROUP,ss:DGROUP,es:DGROUP

cr	equ	0dh			; ASCII carriage return
lf	equ	0ah			; ASCII line feed

	extrn	_delay:near
	extrn	atoi:near

_TEXT	segment
main	proc	near

	mov	ax,_DATA		; or mov ax,DGROUP , adjust DS
	mov	ds,ax		; addressable...
	mov	es,ax

main1:	mov	dx,offset prompt	; display a prompt
	mov	cx,p_len		; to the user...
	mov	bx,1		; 'Enter delay time in ,millisecond: '
	mov	ah,40h
	int	21h
	jc	main2			; if error, just exit

	mov	dx,offset inbuff		; read keyboard entry
	mov	cx,80			; from the user...
	mov	bx,0
	mov	ah,3fh
	int	21h
	jc	main2			; if error, just exit

	cmp	ax,2			; did he enter anything?
	je	main2			; empty line, exit

	mov	si,offset inbuff		; convert convert user's
	call	atou			; input to binary in AX

	push ax				; pass parameter via stack

	mov	dx,offset display		; show 'Delay started:'
	mov	cx,d_len			
	mov	bx,1
	mov	ah,40h
	int	21h
	jc	main2			; if error, just exit

	call _delay

	mov	dx,offset expired		; show 'Delay ended:'
	mov	cx,e_len			
	mov	bx,1
	mov	ah,40h
	int	21h
	jc	main2			; if error, just exit

	jmp	main1			; do it again...


main2:	mov	ax,4c00h		; final exit to MS-DOS
	int	21h

main	endp

_TEXT	ends


_DATA	segment  

prompt	db	cr,lf,lf,'Enter delay time in ,millisecond: '
p_len	equ	$-prompt

display db	cr,lf,lf,'Delay started:'
d_len	equ	$-display

expired db	cr,lf,lf,'Delay ended:'
e_len	equ	$-expired

inbuff	db	80 dup (?)

_DATA	ends

STACK	segment para stack 'STACK'
	db	128 dup (?)
STACK	ends
        end main

File : atou.asm

	title	ATOU - ASCII to unsigned integer
	page	55,132

; ATOU.ASM --- Convert ASCII string to 16-bit decimal unsigned integer.
;
;
; Call with:	DS:SI = address of string,
;		where 'string' is in the form
;		   [whitespace][sign][digits]	Note! ignore sign
;
; Returns:	AX    = result
;		DS:SI = address+1 of terminator
;
; Destroys:	Nothing
;
; no warning of overflow, and terminates on the first invalid character.
_TEXT	segment 	word public 'CODE'
_TEXT	ends
_DATA	segment	              word public 'DATA'                               
_DATA	ends                                                                           
STACK	segment	              para stack 'STACK'                              
STACK	ends                                                                            
DGROUP	group	_DATA,STACK
	assume	cs:_TEXT,ds:DGROUP,ss:DGROUP,es:DGROUP


blank	equ	20h		; ASCII blank character
tab	equ	09h		; ASCII tab character

_TEXT	segment

	public	atou
atou	proc	near

	push	bx			; save registers
	push	cx
	push	dx

	xor	bx,bx			; initialize forming answer
	xor	cx,cx			; initialize sign flag

atoi1:	lodsb				; scan off whitespace
	cmp	al,blank			; ignore leading blanks
	je	atoi1
	cmp	al,tab			; ignore leading tabs
	je	atoi1

	cmp	al,'+'			; if + sign, proceed
	je	atoi2
	cmp	al,'-'			; is it - sign?
jne	atoi3			; no, test if numeric
; ignore sign	dec	cx		; was - sign, set flag
					; for negative result
; display error here if you want
atoi2:	lodsb				; get next character

atoi3:	cmp	al,'0'			; is character valid?
	jb	atoi4			; jump if not '0' to '9'
	cmp	al,'9'
	ja	atoi4			; jump if not '0' to '9'

	and	ax,0fh			; isolate lower four bits

	xchg	bx,ax			; previous answer x 10
	mov	dx,10
	mul	dx

	add	bx,ax			; add this digit

	jmp	atoi2			; convert next digit

atoi4:	mov	ax,bx			; put result into AX
; ignore sign	jcxz	atoi5			; jump if sign flag clear
; ignore sign	neg	ax			; make result negative

atoi5:	pop	dx			; restore registers
	pop	cx
	pop	bx
	ret				; back to caller

atou	endp

_TEXT	ends

	end

How to compile and link

masm trydelay;

masm delay;

masm atou;

link trydelay+delay+atou,trydelay;