인터럽트와 예외(예외)
예외의 종류는 아래와 같다.
프로그래머가 지정할 수 있는 인터럽트는 32번부터 255번까지이다.
이전에 PIC에 대해 알아보면서 PIC에 인터럽트가 걸렸을 때 CPU에게 IRQ 번호를 준다고 설명하였다.
PIC을 프로그램하지 않은 상태라면 IRQ는 0부터 시작하므로 이 상태로 만약 0번 인터럽트가 발생한다면 Protected Mde에서는 물론 IDT의 0번째 디스크립터를 참조하여 해당 인터럽트 핸들러를 실행시키므로 핸들러에서는 이것이 하드웨어 인터럽트인지, 예외가 발생한 것인지 모르게 되는 상태가 된다. 이러한 원인은 CPU에 지정된 예외와 PC 메인보드에 PIC이 연결된 방법이 처음부터 충돌이 나도록 디자인 되어 있기 때문이다. 이렇게 사용하는 것은 곤란하므로 하드웨어 인터럽트를 CPU에 지정된 유저 정의 인터럽트 쪽으로 모두 넘겨버리기 위해 PIC을 리맵핑 해주는 것이다.
예외를 발생시키는 프로그램을 작성해 보자.
init.inc 파일과 하단의 2개 파일 사용
위 파일은 섹터 변경된 것이다.
변경 내용 : mov al, 1 -> mov al, 2 ; 2섹터를 읽을 것이다. kernel 맨 마지막 times 1024 로 지정해 주
었는데, 그 이유는 컴파일 도중 512바이트가 넘었다는 에러가 나기 때문이다.
%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으로 나누었을 때 발생하는 예외를 구현한 것이다.
'OS 커널의 구조와 원리' 카테고리의 다른 글
5장 : Task Switching(CALL 이용) (0) | 2015.05.08 |
---|---|
5장 : Task Switching (0) | 2015.05.07 |
4장 : 인터럽트와 예외 - 3(키보드 인터럽트 핸들러 구현) (0) | 2015.05.06 |
4장 : 인터럽트와 예외 - 2(하드웨어 인터럽트) (0) | 2015.05.06 |
4장 : 인터럽트와 예외 - 1 (0) | 2015.05.04 |