인터럽트와 예외(예외)

예외의 종류는 아래와 같다.

프로그래머가 지정할 수 있는 인터럽트는 32번부터 255번까지이다.
이전에 PIC에 대해 알아보면서 PIC에 인터럽트가 걸렸을 때 CPU에게 IRQ 번호를 준다고 설명하였다.
PIC을 프로그램하지 않은 상태라면 IRQ는 0부터 시작하므로 이 상태로 만약 0번 인터럽트가 발생한다면 Protected Mde에서는 물론 IDT의 0번째 디스크립터를 참조하여 해당 인터럽트 핸들러를 실행시키므로 핸들러에서는 이것이 하드웨어 인터럽트인지, 예외가 발생한 것인지 모르게 되는 상태가 된다. 이러한 원인은 CPU에 지정된 예외와 PC 메인보드에 PIC이 연결된 방법이 처음부터 충돌이 나도록 디자인 되어 있기 때문이다. 이렇게 사용하는 것은 곤란하므로 하드웨어 인터럽트를 CPU에 지정된 유저 정의 인터럽트 쪽으로 모두 넘겨버리기 위해 PIC을 리맵핑 해주는 것이다.

예외를 발생시키는 프로그램을 작성해 보자.

init.inc 파일과 하단의 2개 파일 사용

boot.asm

위 파일은 섹터 변경된 것이다. 
    변경 내용 : mov al, 1    -> mov al, 2    ; 2섹터를 읽을 것이다. kernel 맨 마지막 times 1024 로 지정해 주
               었는데, 그 이유는 컴파일 도중 512바이트가 넘었다는 에러가 나기 때문
이다.

kernel.asm

%include "init.inc"


[org 0x10000]

[bits 32]


PM_Start:

mov bx, SysDataSelector

mov ds, bx

mov es, bx

mov fs, bx

mov gs, bx

mov ss, bx

lea esp, [PM_Start]

mov edi, 0

lea esi, [msgPMode]

call printf

cld

mov ax, SysDataSelector

mov es, ax

xor eax, eax

xor ecx, ecx

mov ax, 256 ; IDT 영역에 256개의

mov edi, 0 ; 디스크립터를 복사한다.

loop_idt:

lea esi, [idt_ignore]

mov cx, 8 ; 디스크립터 하나는 8바이트 이다.

rep movsb

dec ax

jnz loop_idt

; 먼저 디스크립터를 IDT에 포함시킨다. 0으로 나누는 예외는 IRQ 0번이므로 IDT의 맨 처음 부분에 디스크립터  를 포함시킨다.

mov edi, 0     ; IDT 덮어 쓴다.

lea esi, [idt_zero_divide]

mov cx, 8 ; 디스크립터 하나는 8바이트 이다.

rep movsb

mov edi, 8*0x20

lea esi, [idt_timer]

mov cx, 8

rep movsb

mov edi, 8*0x21 ; 키보드 IDT 디스크립터를 복사한다.

lea esi, [idt_keyboard]

mov cx, 8

rep movsb

lidt [idtr]

mov al, 0xFC ; 막아두었던 인터럽트 중

out 0x21, al ; 타이머와 키보드만 다시 유효하게 한다.

sti


mov edx, 0

mov eax, 0x100

mov ebx, 0

div ebx ; 인터럽트 발생

jmp $


printf:

push eax

push es

mov ax, VideoSelector

mov es, ax

printf_loop:

mov al, byte [esi]

mov byte [es:edi], al

inc edi

mov byte [es:edi], 0x06

inc esi

inc edi

or al, al

jz printf_end

jmp printf_loop

printf_end:

pop es

pop eax

ret


msgPMode db "We are in Protected Mode", 0

msg_isr_ignore db "This is an ignorable interrupt", 0

msg_isr_32_timer db ".This is the timer interrupt", 0

msg_isr_33_keyboard db ".This is the keyboard interrupt", 0

msg_isr_zero_divide db "Zero Devide Exception!", 0


idtr:

dw 256*8-1 ; IDT의 Limit

dd 0 ; IDT의 Base Address


isr_ignore:

push gs

push fs

push es

push ds

pushad

pushfd

mov al, 0x20

out 0x20, al

mov ax, VideoSelector

mov es, ax

mov edi, (80*7*2)

lea esi, [msg_isr_ignore]

call printf

popfd

popad

pop ds

pop es

pop fs

pop gs

iret

isr_zero_divide:

push gs

push fs

push es

push ds

pushad

pushfd

mov al, 0x20

out 0x20, al

mov ax, VideoSelector

mov es, ax

mov edi, (80*6*2)

lea esi, [msg_isr_zero_divide]

call printf

jmp $    ; 여기서 무한루프가 걸려 더이상 진행이 되지 않는다. 타이머 인터럽트, 키보드 핸들러가 작동 안하
               는 이유이다.

popfd

popad

pop ds

pop es

pop fs

pop gs

iret

isr_32_timer:

push gs

push fs

push es

push ds

pushad

pushfd

mov al, 0x20

out 0x20, al

mov ax, VideoSelector

mov es, ax

mov edi, (80*2*2)

lea esi, [msg_isr_32_timer]

call printf

inc byte [msg_isr_32_timer]

popfd

popad

pop ds

pop es

pop fs

pop gs

iret ; 인터럽트가 발생한 당시의 프로그램의 다음 명령으로 돌아가서 프로그램 재개


isr_33_keyboard:

pushad

push gs

push fs

push es

push ds

pushfd

in al, 0x60

mov al, 0x20

out 0x20, al

mov ax, VideoSelector

mov es, ax

mov edi, (80*4*2)

lea esi, [msg_isr_33_keyboard]

call printf

inc byte [msg_isr_33_keyboard]

popfd

pop ds

pop es

pop fs

pop gs

popad

iret

idt_ignore:

dw isr_ignore

dw 0x08

db 0

db 0x8E

dw 0x0001

idt_zero_divide:

dw isr_zero_divide

dw 0x08

db 0

db 0x8E

dw 0x0001

idt_timer:

dw isr_32_timer

dw 0x08

db 0

db 0x8E

db 0x0001


idt_keyboard:

dw isr_33_keyboard

dw 0x08

db 0

db 0x8E

dw 0x0001

times 1024-($-$$) db 0


위와 같이 소스를 짠 후 컴파일 및 실행 시 아래 그림과 같이 exception 이 발생된 것을 확인할 수 있다.


devide -> divide 로 알아봐주길.. 오타임..


위 내용은 0으로 나누었을 때 발생하는 예외를 구현한 것이다.

+ Recent posts