OS/OS from Scratch

[OS] 19. Interrupt-irqs

jschang 2021. 7. 12. 00:57

서론

이번 강의에서는 Interrupt Request를 구현하겠습니다. 해당 Github 강의는 다음과 같습니다.

https://github.com/cfenollosa/os-tutorial/tree/master/19-interrupts-irqs

 

cfenollosa/os-tutorial

How to create an OS from scratch. Contribute to cfenollosa/os-tutorial development by creating an account on GitHub.

github.com


이론

Computer Buses

버스에 대한 좋은 동영상이 있어 첨부합니다.

Computer Bus

버스는 데이터 버스, 주소 버스, 제어 버스로 나뉩니다.

데이터 버스, 주소 버스, 제어 버스

  • 데이터 버스: 데이터 버스는 데이터가 이동할 수 있도록 하며 32, 64개의 선들로 구성되어 있다.
  • 주소 버스: 데이터의 출발지나 도착지의 메모리 주소를 전달한다.
  • 제어 버스: 데이터 버스와 주소 버스를 제어한다.
    • Read
    • Write
    • Transfer Acknowledgment (데이터가 잘 보내졌는지)
    • Clock Signal (언제 데이터 보냈는지 표시)

데이터가 전송되는 방식에는 Serial, Parallel 방식이 있습니다. 현대 CPU의 대부분의 경우 Serial 방식을 사용합니다.

  • Serial: 데이터가 순차적으로 하나의 Channel을 통해 전송된다.
  • Parallel: 데이터가 여러 Channel을 통해 동시에 전송된다. 이때 각 Channel을 통해 들어오는 데이터가 동시에 도달해야 한다.

IRQ (Interrupt Request)

IRQ는 컴퓨터의 주변 기기가 CPU에게 어떤 이벤트가 발생했다는 것을 알리는 line입니다. 즉, CPU가 어떤 일이 처리하다가도 주변 기기에서 Interrupt가 들어오면 하던 일을 중단하고 해당 Interrupt을 처리하게 됩니다. IRQ 번호에 따른 Interrupt 내용은 다음과 같습니다.

0 IRQ 타이머
1 키보드
2 IRQ 캐스 케이드 공유
3 시리얼포트 2, COM2, COM4 ( 모뎀 / 마우스)
4 시리얼포트 1, COM1, COM3 ( 모뎀 / 마우스)
5 LPT2 병렬포트 혹은
6 FDD 컨트롤러
7 LPT1 병열 포트 등 혹은 사운드카드
8 RTC( Real Time Clock)
9 예비, 주로 미디 카드(MPU401)에서 사용 , SOUND ,VGA . USB, MPEG II
10 예비 , SOUND ,VGA . USB,MPEG II,
11 예비, SCSI 아답터, SOUND ,VGA . USB,MPEG II
12 PS/2 마우스
13 코프로세서 (수치처리보조연산자 )
14 IDE 하드 컨트롤러 Primary
15 IDE 하드 컨트롤러 Secondary

PCI (Peripheral Component Interconnect)

PCI는 CPU와 컴퓨터 주변기기를 연결하는 Local Bus입니다. PCI는 Bridge, Master, Slave으로 구분됩니다.

  • Bridge: 시스템과 PCI 버스를 연결해준다
  • Master: 버스 전송을 초기화하고 주소, 데이터 전송, 제어 신호들을 조정한다
  • Slave: 마스터에 의존하는 수동적인 메모리 디바이스이다

CPU가 boot 되면 PIC는 IRQ 0-7을 INT 0x8-0xF에, IRQ 8-15을 INT 0x70-0x77에 맵핑합니다. 지난 강의들에서 ISR를 0-31에 맵핑하였으므로 IRQ를 ISR 32-47에 맵핑해야 겹치지 않습니다.

PIC는 I/O port을 통해 통신하고, 이때 Master PIC는 0x20을 통해 명령, 0x21을 통해 데이터를 처리합니다. Slave PIC는 0xA0을 통해 명령, 0xA1을 통해 데이터를 처리합니다.


코드

이번 강의의 코드는 지난 강의에서 ISR을 설정했던 것과 같은 방법으로 IRQ를 설정해주는 것이 전부입니다. 설명은 주석으로 대체하겠습니다.

cpu/interrupt.asm

; isr.c의 isr_handler() & irq_handler()
[extern isr_handler]
[extern irq_handler]

; Common ISR Code
isr_common_stub:
    ; 1. CPU상태 저장
    ; (인자) edi, esi, ebp, esp, ebx, edx, ecx, eax push 
    pusha
    ; EAX의 하위 16bit = ds
    mov ax, ds
    ; (인자) Data Segment Descriptor 저장
    push eax
    ; Kernel Data Segment Descriptor
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ; 2. C handler 호출
    call isr_handler

    ; 3. 원래 상태로 되돌려놓음
    pop eax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    popa
    ; push된 에러코드와 ISR 번호 삭제
    add esp, 8
    sti
    ; pop cs, eip, eflags, ss, esp
    iret

; Common IRQ Code
irq_common_stub:
    ; 1. CPU상태 저장
    pusha
    mov ax, ds
    push eax
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ; 2. C handler 호출
    call irq_handler

    ; 3. 원래 상태로 되돌려놓음
    pop ebx
    mov ds, bx
    mov es, bx
    mov fs, bx
    mov gs, bx
    popa
    add esp, 8
    sti
    iret

; 여러 handler에 대한 interrupt handler 정의
; 일부 interrupt는 에러코드를 stack에 저장
; ISR
global isr0
global isr1
global isr2
global isr3
global isr4
global isr5
global isr6
global isr7
global isr8
global isr9
global isr10
global isr11
global isr12
global isr13
global isr14
global isr15
global isr16
global isr17
global isr18
global isr19
global isr20
global isr21
global isr22
global isr23
global isr24
global isr25
global isr26
global isr27
global isr28
global isr29
global isr30
global isr31
; IRQ
global irq0
global irq1
global irq2
global irq3
global irq4
global irq5
global irq6
global irq7
global irq8
global irq9
global irq10
global irq11
global irq12
global irq13
global irq14
global irq15

; 0: Divide by zero division
isr0:
    cli
    ; (인자) 에러 코드 (더미)
    push byte 0
    ; (인자) interrupt 번호
    push byte 0
    jmp isr_common_stub

; 1: Debug Exception
isr1:
    cli
    push byte 0
    push byte 1
    jmp isr_common_stub

; 2: Non Maskable Interrupt Exception
isr2:
    cli
    push byte 0
    push byte 2
    jmp isr_common_stub

; 3: Int 3 Exception
isr3:
    cli
    push byte 0
    push byte 3
    jmp isr_common_stub

; 4: INTO Exception
isr4:
    cli
    push byte 0
    push byte 4
    jmp isr_common_stub

; 5: Out of Bounds Exception
isr5:
    cli
    push byte 0
    push byte 5
    jmp isr_common_stub

; 6: Invalid Opcode Exception
isr6:
    cli
    push byte 0
    push byte 6
    jmp isr_common_stub

; 7: Coprocessor Not Available Exception
isr7:
    cli
    push byte 0
    push byte 7
    jmp isr_common_stub

; 8: Double Fault Exception
isr8:
    cli
    ; (인자) 에러 코드 Stack에 이미 push됨
    push byte 8
    jmp isr_common_stub

; 9: Coprocessor Segment Overrun Exception
isr9:
    cli
    push byte 0
    push byte 9
    jmp isr_common_stub

; 10: Bad TSS Exception
isr10:
    cli
    ; (인자) 에러 코드 Stack에 이미 push됨
    push byte 10
    jmp isr_common_stub

; 11: Segment Not Present Exception
isr11:
    cli
    ; (인자) 에러 코드 Stack에 이미 push됨
    push byte 11
    jmp isr_common_stub

; 12: Stack Fault Exception
isr12:
    cli
    ; (인자) 에러 코드 Stack에 이미 push됨
    push byte 12
    jmp isr_common_stub

; 13: General Protection Fault Exception
isr13:
    cli
    ; (인자) 에러 코드 Stack에 이미 push됨
    push byte 13
    jmp isr_common_stub

; 14: Page Fault Exception
isr14:
    cli
    ; (인자) 에러 코드 Stack에 이미 push됨
    push byte 14
    jmp isr_common_stub

; 15: Reserved Exception
isr15:
    cli
    push byte 0
    push byte 15
    jmp isr_common_stub

; 16: Floating Point Exception
isr16:
    cli
    push byte 0
    push byte 16
    jmp isr_common_stub

; 17: Alignment Check Exception
isr17:
    cli
    push byte 0
    push byte 17
    jmp isr_common_stub

; 18: Machine Check Exception
isr18:
    cli
    push byte 0
    push byte 18
    jmp isr_common_stub

; 19: Reserved
isr19:
    cli
    push byte 0
    push byte 19
    jmp isr_common_stub

; 20: Reserved
isr20:
    cli
    push byte 0
    push byte 20
    jmp isr_common_stub

; 21: Reserved
isr21:
    cli
    push byte 0
    push byte 21
    jmp isr_common_stub

; 22: Reserved
isr22:
    cli
    push byte 0
    push byte 22
    jmp isr_common_stub

; 23: Reserved
isr23:
    cli
    push byte 0
    push byte 23
    jmp isr_common_stub

; 24: Reserved
isr24:
    cli
    push byte 0
    push byte 24
    jmp isr_common_stub

; 25: Reserved
isr25:
    cli
    push byte 0
    push byte 25
    jmp isr_common_stub

; 26: Reserved
isr26:
    cli
    push byte 0
    push byte 26
    jmp isr_common_stub

; 27: Reserved
isr27:
    cli
    push byte 0
    push byte 27
    jmp isr_common_stub

; 28: Reserved
isr28:
    cli
    push byte 0
    push byte 28
    jmp isr_common_stub

; 29: Reserved
isr29:
    cli
    push byte 0
    push byte 29
    jmp isr_common_stub

; 30: Reserved
isr30:
    cli
    push byte 0
    push byte 30
    jmp isr_common_stub

; 31: Reserved
isr31:
    cli
    push byte 0
    push byte 31
    jmp isr_common_stub

; IRQ Handler
irq0:
	cli
	push byte 0
	push byte 32
	jmp irq_common_stub

irq1:
	cli
	push byte 1
	push byte 33
	jmp irq_common_stub

irq2:
	cli
	push byte 2
	push byte 34
	jmp irq_common_stub

irq3:
	cli
	push byte 3
	push byte 35
	jmp irq_common_stub

irq4:
	cli
	push byte 4
	push byte 36
	jmp irq_common_stub

irq5:
	cli
	push byte 5
	push byte 37
	jmp irq_common_stub

irq6:
	cli
	push byte 6
	push byte 38
	jmp irq_common_stub

irq7:
	cli
	push byte 7
	push byte 39
	jmp irq_common_stub

irq8:
	cli
	push byte 8
	push byte 40
	jmp irq_common_stub

irq9:
	cli
	push byte 9
	push byte 41
	jmp irq_common_stub

irq10:
	cli
	push byte 10
	push byte 42
	jmp irq_common_stub

irq11:
	cli
	push byte 11
	push byte 43
	jmp irq_common_stub

irq12:
	cli
	push byte 12
	push byte 44
	jmp irq_common_stub

irq13:
	cli
	push byte 13
	push byte 45
	jmp irq_common_stub

irq14:
	cli
	push byte 14
	push byte 46
	jmp irq_common_stub

irq15:
	cli
	push byte 15
	push byte 47
	jmp irq_common_stub

cpu/isr.h

#ifndef ISR_H
#define ISR_H

#include "types.h"

// CPU Exception들
extern void isr0();
extern void isr1();
extern void isr2();
extern void isr3();
extern void isr4();
extern void isr5();
extern void isr6();
extern void isr7();
extern void isr8();
extern void isr9();
extern void isr10();
extern void isr11();
extern void isr12();
extern void isr13();
extern void isr14();
extern void isr15();
extern void isr16();
extern void isr17();
extern void isr18();
extern void isr19();
extern void isr20();
extern void isr21();
extern void isr22();
extern void isr23();
extern void isr24();
extern void isr25();
extern void isr26();
extern void isr27();
extern void isr28();
extern void isr29();
extern void isr30();
extern void isr31();

// IRQ
extern void irq0();
extern void irq1();
extern void irq2();
extern void irq3();
extern void irq4();
extern void irq5();
extern void irq6();
extern void irq7();
extern void irq8();
extern void irq9();
extern void irq10();
extern void irq11();
extern void irq12();
extern void irq13();
extern void irq14();
extern void irq15();

#define IRQ0 32
#define IRQ1 33
#define IRQ2 34
#define IRQ3 35
#define IRQ4 36
#define IRQ5 37
#define IRQ6 38
#define IRQ7 39
#define IRQ8 40
#define IRQ9 41
#define IRQ10 42
#define IRQ11 43
#define IRQ12 44
#define IRQ13 45
#define IRQ14 46
#define IRQ15 47

// 여러 레지스터 포함한 구조체
typedef struct {
    // Data Segment
    u32 ds;
    // pusha로 pushed되는 레지스터
    u32 edi, esi, ebp, esp, ebx, edx, ecx, eax;
    // Interrupt 번호와 에러 코드
    u32 int_no, err_code;
    // CPU에 의해 자동적으로 pushed 됨
    u32 eip, cs, eflags, useresp, ss;
} registers_t;

void isr_install();
void isr_handler(registers_t r);

// isr_t: registers_t를 인자로 받는 void 함수 포인터 
typedef void (*isr_t)(registers_t);
void register_interrupt_handler(u8 n, isr_t handler);

#endif

cpu/isr.c

#include "isr.h"
#include "idt.h"
#include "../drivers/screen.h"
#include "../kernel/util.h"
#include "../drivers/ports.h"

isr_t interrupt_handlers[256];

void isr_install() {
    set_idt_gate(0, (u32)isr0);
    set_idt_gate(1, (u32)isr1);
    set_idt_gate(2, (u32)isr2);
    set_idt_gate(3, (u32)isr3);
    set_idt_gate(4, (u32)isr4);
    set_idt_gate(5, (u32)isr5);
    set_idt_gate(6, (u32)isr6);
    set_idt_gate(7, (u32)isr7);
    set_idt_gate(8, (u32)isr8);
    set_idt_gate(9, (u32)isr9);
    set_idt_gate(10, (u32)isr10);
    set_idt_gate(11, (u32)isr11);
    set_idt_gate(12, (u32)isr12);
    set_idt_gate(13, (u32)isr13);
    set_idt_gate(14, (u32)isr14);
    set_idt_gate(15, (u32)isr15);
    set_idt_gate(16, (u32)isr16);
    set_idt_gate(17, (u32)isr17);
    set_idt_gate(18, (u32)isr18);
    set_idt_gate(19, (u32)isr19);
    set_idt_gate(20, (u32)isr20);
    set_idt_gate(21, (u32)isr21);
    set_idt_gate(22, (u32)isr22);
    set_idt_gate(23, (u32)isr23);
    set_idt_gate(24, (u32)isr24);
    set_idt_gate(25, (u32)isr25);
    set_idt_gate(26, (u32)isr26);
    set_idt_gate(27, (u32)isr27);
    set_idt_gate(28, (u32)isr28);
    set_idt_gate(29, (u32)isr29);
    set_idt_gate(30, (u32)isr30);
    set_idt_gate(31, (u32)isr31);

    // Remap PIC (설명 추가 필요)
    port_byte_out(0x20, 0x11);
    port_byte_out(0xA0, 0x11);
    port_byte_out(0x21, 0x20);
    port_byte_out(0xA1, 0x28);
    port_byte_out(0x21, 0x04);
    port_byte_out(0xA1, 0x02);
    port_byte_out(0x21, 0x01);
    port_byte_out(0xA1, 0x01);
    port_byte_out(0x21, 0x0);
    port_byte_out(0xA1, 0x0); 

    // Install the IRQs
    set_idt_gate(32, (u32)irq0);
    set_idt_gate(33, (u32)irq1);
    set_idt_gate(34, (u32)irq2);
    set_idt_gate(35, (u32)irq3);
    set_idt_gate(36, (u32)irq4);
    set_idt_gate(37, (u32)irq5);
    set_idt_gate(38, (u32)irq6);
    set_idt_gate(39, (u32)irq7);
    set_idt_gate(40, (u32)irq8);
    set_idt_gate(41, (u32)irq9);
    set_idt_gate(42, (u32)irq10);
    set_idt_gate(43, (u32)irq11);
    set_idt_gate(44, (u32)irq12);
    set_idt_gate(45, (u32)irq13);
    set_idt_gate(46, (u32)irq14);
    set_idt_gate(47, (u32)irq15);

    set_idt();
}

char *exception_messages[] = {
    "Division By Zero",
    "Debug",
    "Non Maskable Interrupt",
    "Breakpoint",
    "Into Detected Overflow",
    "Out of Bounds",
    "Invalid Opcode",
    "No Coprocessor",

    "Double Fault",
    "Coprocessor Segment Overrun",
    "Bad TSS",
    "Segment Not Present",
    "Stack Fault",
    "General Protection Fault",
    "Page Fault",
    "Unknown Interrupt",

    "Coprocessor Fault",
    "Alignment Check",
    "Machine Check",
    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved",

    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved"
};

void isr_handler(registers_t r) {
    kprint("Received Interrupt: ");
    char s[3];
    int_to_ascii(r.int_no, s);
    kprint(s);
    kprint("\n");
    kprint(exception_messages[r.int_no]);
    kprint("\n");
}

void register_interrupt_handler(u8 n, isr_t handler) {
    interrupt_handlers[n] = handler;
}

void irq_handler(registers_t r) {
    // Interrupt를 받은 후 PIC에 EOI(End of Interrupt)을 전송해야
    // 다시 Interrupt를 보내지 않는다
    // EOI Interrupt: 0x20
    if(r.int_no>=40) { // Slave PCI
        port_byte_out(0xA0, 0x20);
    }
    // Master PCI
    port_byte_out(0x20, 0x20);

    if(interrupt_handlers[r.int_no]!=0) {
        isr_t handler = interrupt_handlers[r.int_no];
        handler(r);
    }
}

실행 결과

다음 강의에서 구현한 IRQ들을 테스트해보겠습니다.