ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [OS] 18. Interrupts
    OS/OS from Scratch 2021. 7. 3. 16:43

    서론

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

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

     

    cfenollosa/os-tutorial

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

    github.com


    이론

    ISR (Interrupt Service Routine)

    CPU가 A 작업을 하고 있다가 키보드 입력과 같은 Interrupt을 받으면 CPU는 현재 진행 중인 A 작업을 멈추고 Interrupt Sercie Routine으로 점프해 Interrupt에 관한 Routine을 처리합니다. Interrupt가 처리되면 원래 A 작업으로 돌아갑니다. 

    IDT (Interrupt Descriptor Table)

    GDT와 매우 유사한 구조를 가지고 있는 IDT는 CPU에게 256개의 Interrupt에 대한 Handler을 찾게 하며, 중요한 첫 32개 Interrupt는 다음과 같습니다.

    • 0 - Division by zero exception
    • 1 - Debug exception
    • 2 - Non maskable interrupt
    • 3 - Breakpoint exception
    • 4 - 'Into detected overflow'
    • 5 - Out of bounds exception
    • 6 - Invalid opcode exception
    • 7 - No coprocessor exception
    • 8 - Double fault (pushes an error code)
    • 9 - Coprocessor segment overrun
    • 10 - Bad TSS (pushes an error code)
    • 11 - Segment not present (pushes an error code)
    • 12 - Stack fault (pushes an error code)
    • 13 - General protection fault (pushes an error code)
    • 14 - Page fault (pushes an error code)
    • 15 - Unknown interrupt exception
    • 16 - Coprocessor fault
    • 17 - Alignment check exception
    • 18 - Machine check exception
    • 19-31 - Reserved

    #ifndef, #define

    복잡한 프로그램의 경우 여러 헤더 파일이 꼬여, 헤더 파일의 중복 선언이 일어날 수 있습니다. 이를 막기 위해 해당 헤더 파일이 이미 선언된 경우, 중복된 선언을 방지하기 위해 #ifndef, #define을 사용합니다. 

    #ifndef TYPES_H
    #define TYPES_H
    
    ... 정의
    
    #endif

    위 코드(types.h)에서 만약 types.h가 이미 선언된 경우 재선언하지 않고, 선언되지 않는 경우 #define과 #endif 사이의 types.h의 내용을 선언합니다.

    매크로 함수

    매크로 함수는 #define 부분에 인자를 전달해 함수처럼 사용할 수 있는 매크로를 의미합니다. 

    #define SUM(X,Y) ((X)+(Y))

    이때 매크로 함수의 인자는 어떤 타입이나 가능하며, 매크로 함수 내부에서 인자를 사용할 때는 괄호를 반드시 사용해야 합니다.

    __attribute__((packed))

    typedef struct {
        char c;
        int x;
    } myStruct;

    상식적으로, myStruct의 크기는 char 1byte + int 4byte = 5byte이어야 합니다. 하지만 실제로 sizeof(myStruct)는 8입니다. 이는 CPU가 메모리에 4byte 단위로 접근할 시 가장 속도가 빠르기 때문에, 4byte 단위로 데이터를 저장하기 때문입니다. 이때 char는 4byte 공간을 차지하게 되고, 1byte를 제외한 나머지 3byte는 의미 없는 데이터로 채워지게 됩니다.

    typedef struct {
        char c;
        int x;
    } __attribute__((packed)) myStruct;

    myStruct가 메모리를 낭비하지 않고 5byte가 되도록 하기 위해서는 __attribute__((packed)) 특성을 설정해 컴파일러가 구조체의 실제 크기만큼 메모리를 할당하게 합니다.

    __volatile__

    __volatile__은 컴파일러가 코드를 최적화하지 않고, 있는 그대로 실행하게 합니다. 예를 들어, 컴파일러는 Inline Assembly에서 이후의 코드에서 사용하지 않는 변수를 변경하면, 해당 부분의 코드를 최적화해 수행하지 않습니다. __volatile__을 설정하면 이처럼 컴파일러에 의해 코드의 최적화가 발생하지 않습니다.

    lidtl

    lidtl Assembly 명령어는 IDT를 로딩하는 명령어입니다. lidtl 명령어는 IDT Description Structure의 주소를 인자로 받습니다.

    Assembly에서 인자가 있는 함수 사용

    Assembly에서 C 코드에 선언된 함수를 사용하기 위해서는 extern <함수 이름> 명령어로 해당 함수를 불러와 call <함수 이름> 명령어로 함수를 실행합니다. 이때 함수의 인자들은 Stack에 push 되어 전달됩니다. 즉, 다음과 같은 함수 func가 있을 때,

    func(arg1, arg2, arg3)

    func의 인자들은 오른쪽 인자들부터 Stack에 push 되어 사용합니다.

    sti, cli

    Assembly에서 sti는 Interrupt Flag (IF)를 IF=1인 상태로 만들고, cli는 IF=0인 상태를 만듭니다. IF=0이면 Interrupt가 발생하지 않게 억제되며, IF=1이면 Interrupt를 처리합니다.


    코드

    cpu/types.h

    C 코드에서 char, int 데이터형을 통해 데이터의 크기를 지정하지 않고 직접 자료형을 만들어 사용하려 합니다. low_16은 메모리 주소에서 하위 16byte, high_16은 상위 주소를 계산하는 매크로 함수입니다.

    #ifndef TYPES_H
    #define TYPES_H
    
    // 데이터 크기에 맞는 선언문
    typedef unsigned int u32;
    typedef int s32;
    typedef unsigned short u16;
    typedef short s16;
    typedef unsigned char u8;
    typedef char s8;
    
    #define low_16(address) (u16)((address) & 0xffff)
    #define high_16(address) (u16)((address) >> 16 & 0xffff)
    
    #endif

    cpu/idt.h

    각 IDT는 크기가 256인 idt_gate_t 구조체 배열로 저장하고, idt_register_t는 GDT 레지스터와 비슷한 역할로 BIOS에 IDT를 로드합니다. 이때 모든 Interrupt의 구조를 나타낸 idt_gat_t 구조체는 handler의 주소, Kernel Segment Selector, Flags들로 이루어져 있습니다.

    #ifndef IDT_H
    #define IDT_H
    
    #include "types.h"
    
    // Segment Selector
    #define KERNEL_CS 0x08
    
    // 모든 Interrupt의 구조
    typedef struct {
        // Handler 함수의 하위 16bit 주소 
        u16 low_offset;
        // Kernel Segment Selector
        u16 sel;
        // 무조건 0
        u8 always0;
        // 7번 bit: Interrupt 존재
        // 5-6번 bit: caller의 권한 (0=kernel, ..., 3=user)
        // 4번 bit: 0=Interrupt Gate
        // 3-0 bit: 1110 = 32bit Interrupt Gate
        u8 flags;
        // Handler 함수의 상위 16bit 주소 
        u16 high_offset;
    } __attribute__((packed)) idt_gate_t;
    
    // Interrupt Handler의 배열에 대한 포인터
    // Assembly의 lidt으로 읽음
    typedef struct {
        // 크기
        u16 limit;
        // 주소
        u32 base;
    } __attribute__((packed)) idt_register_t;
    
    // 256아니면 CPU 패닉
    #define IDT_ENTRIES 256
    idt_gate_t idt[IDT_ENTRIES];
    idt_register_t idt_reg;
    
    void set_idt_gate(int n, u32 handler);
    void set_idt();
    
    #endif

    cpu/idt.c

    idt.c에서 set_idt_gate 함수는 idt 배열의 원소를 채우는 역할을 하고, set_idt 함수는 idt_reg에 대해 lidtl Assembly 명령어를 불러 IDT를 탑재합니다.

    #include "idt.h"
    #include "../kernel/util.h"
    
    void set_idt_gate(int n, u32 handler) {
        idt[n].low_offset = low_16(handler);
        idt[n].sel = KERNEL_CS;
        idt[n].always0 = 0;
        idt[n].flags = 0b10001110;
        idt[n].high_offset = high_16(handler);
    }
    
    void set_idt() {
        idt_reg.base = (u32) &idt;
        idt_reg.limit = IDT_ENTRIES*sizeof(idt_gate_t)-1;
        __asm__ __volatile__("lidtl (%0)" : : "r"(&idt_reg));
    }

     cpu/isr.h

    isr.h와 isr.c에서는 32개의 가장 기본적인 Interrupt Service들을 정의하고 Handler들을 설정합니다. 이때 ISR들은 외부 Assembly 코드들에서 설정할 것이며, popa과정에서 나오는 레지스터들을 저장할 수 있도록 registers_t 구조체를 정의하겠습니다.

    #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();
    
    // 여러 레지스터 포함한 구조체
    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);
    
    #endif

    cpu/isr.c

    isr.c에서는 32개의 Interrupt Service Routine을 설정하고 ISR이 발생했을 때 어떤 Interrupt가 발생했는지 출력하는 Handler를 구현합니다.

     

    #include "isr.h"
    #include "idt.h"
    #include "../drivers/screen.h"
    #include "../kernel/util.h"
    
    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);
    
        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");
    }

    cpu/interrupt.asm

    interrupt.asm에서는 각 ISR들을 구현합니다. 먼저 isr<isr 번호>에서 각 ISR마다 에러코드와 Interrupt번호를 설정하고 isr_common_stub를 호출해 isr.c의 isr_handler함수에 인자를 전달해 호출합니다. isr_handler함수의 처리가 완료되면 원래 상태로 레지스터 값들을 되돌려 놓습니다.

    ; isr.c의 isr_handler()
    [extern isr_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
    
    ; 여러 handler에 대한 interrupt handler 정의
    ; 일부 interrupt는 에러코드를 stack에 저장
    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
    
    ; 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

    kernel/kernel.c

    kernel.c에서 Interrupt를 호출합니다.

    #include "../drivers/screen.h"
    #include "util.h"
    #include "../cpu/isr.h"
    #include "../cpu/idt.h"
    
    void main() {
        isr_install();
        __asm__ __volatile__("int $2");
        __asm__ __volatile__("int $3");
    }

    boot/bootsect.asm

    bootsect.asm에서 kernel이 차지하는 sector를 31로 늘려 공간을 확보합니다.

    [bits 16]
    load_kernel:
        mov bx, MSG_LOAD_KERNEL
        call print
        call print_nl
    
        mov bx, KERNEL_OFFSET
        mov dh, 31
        mov dl, [BOOT_DRIVE]
        call disk_load
        ret

    Makefile

    Makefile에 cpu path를 추가해줍니다.

    C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c)
    HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h)

    실행 결과

    make로 실행하면, 2, 3 Interrupt가 발생한 것을 확인할 수 있습니다..

    실행결과

     

    'OS > OS from Scratch' 카테고리의 다른 글

    [OS] 20. Interrupt-timer  (0) 2021.07.18
    [OS] 19. Interrupt-irqs  (0) 2021.07.12
    [OS] 17. Video Scroll  (0) 2021.07.02
    [OS] 16. Video Driver  (0) 2021.07.02
    [OS] 15. Video Ports  (0) 2021.06.30
Designed by Tistory.