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 ดังภาพ
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 ของระบบปฏิบัติการจะทำหน้าที่นี้อยู่แล้ว
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)
...
กรณีที่ท่านต้องการเปลี่ยน 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
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
masm trydelay;
masm delay;
masm atou;
link trydelay+delay+atou,trydelay;