인터럽트와 예외(하드웨어 인터럽트)

아래와 같이 소스를 짠다. (파일은 올려둠)


boot.asm


init.inc


kernel.asm


init.inc 는 이전장에서와 동일한 파일을 쓴다.

boot.asm

%include "init.inc"


[org 0]

jmp 07C0h:start

start:

mov ax, cs

mov ds, ax

mov es, ax

reset: ; 플로피 디스크를 리셋한다.

mov ax, 0

mov dl, 0 ; Drive = 0 (A:)

int 13h

jc reset ; 에러가 나면 다시 한다.

mov ax, 0xB800

mov es, ax

mov di, 0

mov ax, word [msgBack]

mov cx, 0x7FF

paint:

mov word [es:di], ax

add di, 2

dec cx

jnz paint

read:

mov ax, 0x1000 ; ES:BX = 1000:1000

mov es, ax

mov bx, 0

mov ah, 2 ; 디스크에 있는 데이터를 es:bx의 주소로

mov al, 1 ; 1섹터를 읽을 것이다.

mov ch, 0 ; 0번째 Cylinder

mov cl, 2 ; 2번째 섹터부터 읽기 시작

mov dh, 0 ; Head = 0

mov dl, 0 ; Drive=0, A: 드라이브

int 13h ; read

jc read ; 에러가 나면 다시함

mov dx, 0x3F2 ; 플로피디스크 드라이브의

xor al, al ; 모터를 끈다.

out dx, al

; 아래 5개 루틴은 ICW1을 마스터 PIC과 슬레이브 PIC에 프로그램 한다.

mov al, 0x11 ; PIC의 초기화    -> 4번 비트 1, 0번 비트(IC4)에 1이 있다. 이것은 PIC 초기화 명령 +
                                          ICW4 명령이 필요하다는 뜻이다.

out 0x20, al ; 마스터 PIC

dw 0x00eb, 0x00eb ; jmp $+2, jmp $+2    -> 하나의 명령을 넣고 약간의 시간 딜레이를 주기 위해 기계
                                                   어로 바꿈, 16비트 리얼모드 명령이므로 $+2로 현재 주소
                                                   에서 2바이트 뒤를 가리키면 다음 명령의 앞을 지칭
, 즉 첫
                                                   번째 0x00eb는 두번째 0x00eb로 점프 , 두번째 0x00eb는
                                                    다음 명령어인 out 0xA0, al 로 점프한다.

out 0xA0, al ; 슬레이브 PIC

dw 0x00eb, 0x00eb


; 아래 6개 루틴은 ICW2 명령

mov al, 0x20 ; 마스터 PIC 인터럽트 시작점(마스터 PIC는 0x20부터 시작), 만약 IRQ 0에 연결된 하
                            드웨어에서 인터럽트 발생 시 PIC이 CPU에 알려주는 IRQ 번호는 0x20, 1일 때 0x21
                            이 된다.

out 0x21, al

dw 0x00eb, 0x00eb

mov al, 0x28 ; 슬레이브 PIC 인터럽트 시작점(슬레이브 PIC는 0x28부터 시작),만약 IRQ 0에 연결된
                            하
드웨어에서 인터럽트 발생 시 PIC이 CPU에 알려주는 IRQ 번호는 0x28, 1일 때
                            0x29
이 된다. 이렇게 IRQ 번호를 바꾸는 것을 다시 매핑한다 하여 리매핑이라고 함

out 0xA1, al

dw 0x00eb, 0x00eb

; ICW3 명령어

mov al, 0x04 ; 마스터 PIC에 IRQ 2번에(마스터 PIC의 0, 1, 2 번째 비트 즉 3번째)

out 0x21, al ; 슬레이브 PIC이 연결되어 있다. (마스터 PIC에 알리는 과정)

dw 0x00eb, 0x00eb

mov al, 0x02 ; 슬레이브 PIC이 마스터 PIC의

out 0xA1, al ; IRQ 2번에 연결되어 있다.(슬레이브 PIC에 알리는 과정)

dw 0x00eb, 0x00eb

; ICW4 명령어

mov al, 0x01 ; 8086 모드를 사용한다.

out 0x21, al

dw 0x00eb, 0x00eb

out 0xA1, al

dw 0x00eb, 0x00eb

; 모든 인터럽트를 막아놓는 부분

mov al, 0xFF

out 0xA1, al

dw 0x00eb, 0x00eb

mov al, 0xFB

out 0x21, al

-> Protected Mode로 넘어가면서 IDT로 설정해야 하고, 하드웨어 인터럽트 관련으로 해야 할 몇가지 일들이 존재한다. 그런 일을 할 동안 하드웨어 인터럽트가 걸린다면 프로그램이 제대로 작동하지 않기 때문에 막아놓는것이 편하다.


lgdt[gdtr]

mov eax, cr0

or eax, 0x00000001

mov cr0, eax

jmp $+2

nop

nop

mov bx, SysDataSelector

mov ds, bx

mov es, bx

mov fs, bx

mov gs, bx

mov ss, bx

jmp dword SysCodeSelector:0x10000

msgBack db '.', 0x67

gdtr:

dw gdt_end - gdt - 1 ; GDT의 Limit

dd gdt+0x7C00 ; GDT의 Base Address

gdt:

dd 0, 0

dd 0x0000FFFF, 0x00CF9A00

dd 0x0000FFFF, 0x00CF9200

dd 0x8000FFFF, 0x0040920B

gdt_end:


times 510-($-$$) db 0

dw 0AA55h


-----------------------------------------------------

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

mov edi, 8*0x20    ; 타이머는 PIC의 IRQ 0번이다. 그러나 부트스트랩 프로그램에서 PIC을 리매핑 하였기 때
                         문에 타이머 인터럽트가 발생했을 때 PIC은 CPU에게 0x20(10진수 32)번의 인터럽트
                          발생했다고 알려줄 것이다. 그러므로 우리는 IDT에 있는 디스크립터 중 0x20번째에 만
                          들어 놓은 디스크립터를 복사
하여 설정해 놓는다.

lea esi, [idt_timer]

mov cx, 8

rep movsb

-> 위 루틴이 실행되면 그 자리에 있던 idt_ignore라는 이름의 디스크립터는 덮어 씌어지게 된다.


; idt_timer 디스크립터 이다.

lidt [idtr]

mov al, 0xFE ; 막아두었던 인터럽트 중(0xFE로써 0번째 비트 0으로 셋팅, 즉 타이머만 유효하게 됨)

out 0x21, al ; 타이머만 다시 유효하게 한다. (0x21은 마스터 PIC)

sti            ; sti도 해주어야 하는데 CPU가 PIC으로부터 인터럽트를 받아들이고, /INTA 신호를 되돌려 주기
                    위함
이다. cli 명령을 내려놓으면 CPU 측에서 /INTA 신호를 되돌려 주기 않게 된다. 그리고
                    jmp $ 명령으로 무한루프를 돌게하여 프로그램은 사실상 여기서 멈추게 된다. 이 후로 타이머
                    인터럽트 핸들러가 계속 호출되어 문장이 나온다. 하단 핸들러의 inc byte !~~ 부분으로 인해
                    서 값이 변경된다.


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


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_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 ; 인터럽트가 발생한 당시의 프로그램의 다음 명령으로 돌아가서 프로그램 재개

idt_ignore:

dw isr_ignore

dw 0x08

db 0

db 0x8E

dw 0x0001

idt_timer:

dw isr_32_timer    -> 핸들러의 물리주소로써 0x10000(최 상단 org 참조) + isr_32_timer 를 지정하고 있다.

dw 0x08

db 0

db 0x8E

db 0x0001

times 512-($-$$) db 0

------------------------------------------------------------

컴파일 후 img 파일을 만든다.

해당 img를 가지고 부팅을 시켜볼 시 2번째 줄 첫번째가 꾸준히 변하는것을 확인할 수 있다.

첫번째가 꾸준히 변하고 있음.. 동작 원리에 대해 이해하기 위해서는 아래를 이해하여야 한다.

PC의 모든 외부로부터의 하드웨어 인터럽트는 8259A 라는 칩을 통하여 입력을 받는다. 이 8258A는 보통 PIC 라고 지칭한다.
이 PIC도 하나의 컨트롤러 개념을 가지고 있어서 조그마한 프로그램을 넣어 조작할 수 있다. 이 프로그램에는 초기화, 여러 개의 PIC 연결방법, 인터럽트를 받아들이는 방법, 받아들인 인터럽트에 대해 CPU에게 알려주는 방법 등이 기재되어야 한다.
우리가 사용하는 PC에서 PIC는 아래 그림과 같이 연결되어 있다. 하나의 PIC은 8개의 IRQ 핀을 가지고 있다.

PIC 두개가 마스터, 슬레이브로 연결되어 있고, 마스터의 INT핀이 CPU의 INT핀으로 연결되어 있다. 슬레이브의 INT핀마스터의 3번째 IRQ(Interrupt Request line) 핀인 2번 핀에 연결되어 있다. 그리고 두 PIC 모두 /INTA 핀이 CPU의 INTA와 연결되어 있다. 각 IRQ 핀에는 다음의 표와 같이 여러 장치들의 인터럽트 선이 각각 연결되어 있다.

마스터 PIC의 동작원리는 아래 순서대로 진행된다.
    1. 마스터 PIC에 연결된 장치 중 하나에서 인터럽트가 발생하면
    2. 마스터 PIC은 자신의 INT핀에 신호를 실어 CPU의 INT핀에 신호를 준다.
    3. CPU는 이것을 받고 EFLAG 레지스터의 IE비트가 1로 세트되어 인터럽트를 받을 수 있는 상황이라면 ?INTA를
      통해 마스터 PIC에 인터럽트를 잘 받았다는 신호를 보낸다.
    4. 마스터 PIC은 /INTA 신호를 받으면 몇 번째 IRQ에서 연결된 장치에서 인터럽트가 발생했는지를 숫자로 데이터
      버스를 통해 CPU로 전달한다.
    5. CPU는 이 데이터를 참조하여 Protected Mode로 실행중이라면 IDT에서 그 번호에 맞는 디스크립터를 찾아 아
      래와 같은 순서를 진행한다.

        (1) 0x20번의 인터럽트가 발생한다.
        (2) CPU는 IDTR을 참조한다.
        (3) IDTR은 IDT의 Base Address를 가지고 있으므로 IDT의 첫 번째 번지 부분을 가리키게 된다.
        (4) 인터럽트 번호가 0x20이므로 IDT의 첫번째 번지에서 0x20번째의 디스크립터를 찾아낸다.
        (5) IDT는 GDT와 달리 테이블의 맨 처음 디스크립터도 사용하므로, 다른 예로 인터럽트 번호 0번이 발생하
           면 맨
 처음 디스크립터를 찾아내게 된다.
        (6) 디스크립터(IDT에 위치한)에는 핸들러가 위치한 세그먼트의 세그먼트 셀렉터와 오프셋이 있다. 이 중에
          서 먼저 세그먼트 셀렉터 값을 가지고 GDT에서 해당하는 디스크립터를 찾아낸다.
        (7) GDT의 디스크립터를 가지고 RAM상의 해당 세그먼트(여기에서는 커널 코드 세그먼트)의 Base Address
           를 찾아낸다. 우리가 작성한 프로그램에서 커널 코드 세그먼트의 Base Address가 물리 주소 0번지에서
           시작하므로 0번지가 된다.
        (8) IDT의 0x20번째 디스크립터에 포함된 오프셋 값을 가지고 인터럽트 핸들러 루틴이 세그먼트 범위 안에서
          실제로 위치한 곳을 찾아낸다.
        (9) 핸들러가 모두 실행되어 iret 명령이 내려지면 처음 인터럽트 걸렸던 명령문 다음 명령으로 돌아간다.

슬레이브 PIC의 동작원리는 아래 순서대로 진행된다.
    1. 슬레이브 PIC에 연결된 장치 중 하나에서 인터럽트가 발생하면
    2. 슬레이브 PIC은 자신의 INT 핀에 신호를 실어 마스터 PIC의 IRQ 2번 핀에 인터럽트 신호를 보낸다.
    3. 마스터 PIC은 자신의 IRQ핀에서 인터럽트가 발생하였으므로 자신의 INT 핀에 신호를 실어 CPU에게 알린다.
    4. CPU가 /INTA 신호를 주면 역시 데이터 베이스에 숫자를 실어 CPU에게 몇 번째 IRQ에서 인터럽트가 발생했는
      지를 알려준다. 이 경우 숫자는 8 ~ 15 사이가 될 것이다.

마스터 PIC과 슬레이브 PIC이 제대로 동작하도록 하기 위해서는 이 PIC들을 초기화시켜 줄 필요가 있다. 마스터 PIC도, 슬레이브 PIC도 자신이 마스터에 해당하는지, 슬레이브에 해당하는지를 알아야 하고, 어떤 모드로 움직일지 등등을 각 PIC에 프로그램 해주어야 한다.

이 프로그램은 ICW1, ICW2, ICW3, ICW4 총 4가지로 구성되어 있다. ICW는 하나의 명령어라고 보면 된다.
프로그램은 ICW1, ICW2, ICW3, ICW4의 순서로 이루어 진다.

ICW의 명령어

ICW1은 PIC을 초기화 하는 명령어이다.

7 ~ 4 비트는 정해진 것이다.
LTIM은 인터럽트가 발생할 때 그 인터럽트 신호의 엣지에서 인터럽트 발생을 인정할 것인지, 혹은 HIGH Level로 신호가 모두 올라온 상태에서 인터럽트를 발생을 인정할 것인지를 나타낸다. 0 이면 엣지 트리거링, 1이면 레벨 트리거링 이다.
SNGL은 이 PIC이 마스터/슬레이브로 구성되어 있는지, 마스터 하나만 사용할지를 나타낸다. 0이면 마스터/슬레이브 형식으로 PIC을 2개 사용, 1이면 마스터 하나만 사용 한다는 뜻이다.
IC4는 ICW4 명령어가 추가적으로 필요한지를 나타낸다. 0 이면 필요하지 않다는 뜻이고, 1 이면 필요하다는 뜻이다.

ICW2는 이 PIC이 인터럽트를 받았을 때 IRQ 번호에 얼마를 더해서 CPU에게 알려줄지를 지정한다.

0 ~ 2 비트가 0인 것은 이 숫자를 8 단위로 기재해야 한다는 뜻 이다.
예를 들어 2진수로 00010000(16진수 0x10)을 넣으면 나중에 인터럽트 0번이 발생했을 때 CPU에게는 0x10의 숫자를 보내준다. IRQ 16번이라고 알려주는 것이다. 인터럽트 1번이 발생했을 때에는 CPU에게 0x11의 숫자를 보내준다.
2진수로 00100000(16진수 0x20)을 넣으면 나중에 인터럽트 0번이 발생했을 때 CPU에게는 0x20의 숫자를 보내준다. 인터럽트 1번이 발생했을때 CPU에게 0x21의 숫자를 보내준다.
일부러 CPU에게 다른 수를 보내주도록 하는 것처럼 보이지만 CPU에 설정된 exception(예외) 번호와 PC의 메인보드에서의 인터럽트 관련 회로 구현에서 충돌이 일어날 수 있기 때문에 하드웨어 인터럽트 번호를 바꾸어 줄 필요가 있다.

ICW3은 각 PIC의 마스터, 슬레이브로서의 연결 방법을 나타낸다.

S0 ~ S7은 마스터 PIC의 각 IRQ 선에 해당된다.
각 비트에 0을 넣으면 그 IRQ 선은 하드웨어 장치에 연결되어 있다는 것을 의미한다.
각 비트중 어느 비트에 1을 넣으면 그 IRQ 선은 슬레이브 PIC에 연결되어 있다는 것을 나타낸다.

3 ~ 7 비트는 0으로 해준다.
ID0 ~ ID2의 3비트를 사용하여 슬레이브 PIC이 마스터 PIC의 몇 번째 IRQ 핀에 연결되어 있는지를 마스터 PIC에서 해당 비트를 1로 세트하는 ICW3과는 달리 여기서는 숫자로 나타낸다.

ICW4는 추가 명령어이다.

SFNM, BUF, M/S의 기능은 현재 우리가 사용하는 PC에는 구현되지 않아도 되므로 0 으로 해둔다.
AEOI 비트는 PIC의 Reset을 자동으로 할 것인지, 수동으로 할 것인지 나타낸다.
UPM 비트에 0을 넣으면 이 PIC이 MCS-80/85 모드로 움직인다는 뜻이고, 1을 넣으면 이 PIC이 8086 모드로 움직인다는 것을 나타낸다. 우리 PC는 8086 계열이므로 1을 넣는다.

PIC에 프로그램 할 때 I/O 명령어 out을 사용하여 마스터 PIC은 I/O 주소 0x20과 0x21에, 슬레이브 PIC은 I/O 주소 0xA0, 0xA1에 프로그램 한다.





+ Recent posts