How to write your own Operating System (x86_64): Interrupts

Πως να γράψετε το δικό σας Λειτουργικό Σύστημα (x86_64): Διακοπές

· Coding Προγραμματισμός · assembly assembly os λειτουργικό σύστημα

IntroductionΕισαγωγή

In our previous articles, we showed how to implement a bootloader and how to enter the long mode. In this part, we will discuss the Interrupts. An Interrupt is a signal emitted by hardware or software when a process or an event needs attention. Usually, an Interrupt Vector Table (IVT) is used to determine the proper response to three types of events: Hardware Interrupts, Software Interrupts, and Processor Exceptions. In the x86_64 architecture, this data structure is named Interrupt Descriptor Table (IDT) and consists of 256 interrupt vectors, the first 32 of which (0–31 or 0x00–0x1F) are reserved for the processor exceptions.

Σε προηγούμενα άρθρα μας, είδαμε πώς να υλοποιήσουμε έναν εκκινητή και πώς να μπούμε στη λειτουργία μακράς κατάστασης. Σε αυτό το μέρος, θα συζητήσουμες τις Διακοπές. Μια Διακοπή είναι ένα σήμα που παράγεται από το υλικό ή το λογισμικό όταν μια διεργασία ή ένα συμβάν χρειάζεται προσοχή. Σύνηθως, ένας Διανυσματικός Πίνακας Διακοπών (Interrupt Vector Table, IVT) χρησιμοποιείται για να καθοριστεί η κατάλληλη απάντηση σε τρια ήδη συμβάντων: Διακοπές Υλικού (Hardware Interrupts), Διακοπές Λογισμικού (Software Interrupts) και Εξαιρέσεις Επεξεργαστή (Processor Exceptions). Στην αρχιτεκτονική x86_64, αυτή η δομή δεδομένων ονομάζεται Πίνακας Περιγραφής Διακοπών (Interrupt Descriptor Table, IDT) και αποτελείται από 256 διανύσματα Διακοπών, από τα οποία τα 32 πρώτα (0–31 ή 0x00–0x1F) είναι δεσμευμένα για τις Εξαιρέσεις του Επεξεργαστή.

Kernel Code: InterruptsΚώδικας του Πυρήνα: Διακοπές

kernel/idt.asm

In this file, we define our Interrupt Descriptor Table (IDT). For the time being, we have set up Interrupt Services Routines (ISR) for some CPU Exceptions ("Division by zero", "General Protection Fault" and "Page Fault") and for two IRQs, IRQ0 (Programmable Interval Timer) and IRQ1 (Keyboard). The value 0x8F means a "Trap Gate", while the value 0x8E means an "Interrupt gate". The important difference between trap and interrupt gates is that interrupt gates will disable further processor handling of maskable interrupts.

Σε αυτό το αρχείο, ορίζουμε τον Πίνακα Περιγραφής Διακοπών (Interrupt Descriptor Table, IDT). Προς το παρόν, έχουμε ορίσει Ρουτίνες Εξυπηρέτησης Διακοπών (Interrupt Services Routines, ISR) για μερικες Εξαιρέσεις του Επεξεργαστή ("Διαίρεση με το μηδέν", "Γενικό Σφάλμα Προστασίας" και "Σφάλμα Σελίδας") και για δύο IRQs, τα IRQ0 (Χρονιστής Προγραμματιζόμενης Περιόδου) και IRQ1 (Πληκτρολόγιο). Η τιμή 0x8F σημαίνει μια "Πύλη Παγίδευσης (Trap Gate)", ενώ η τιμή 0x8E σημαίνει μια "Πύλη Διακοπής (Interrupt Gate)". Η σημαντική διαφορά ανάμεσα στις πύλες Παγίδευσης και Διακοπής είναι ότι οι Πύλες Διακοπών απενεργοποιούν τον χειρισμό επιπλέον παρεμποδιζόμενων/αποκρύψιμων (maskable) Διακοπών.

;**************************************************************************************;
; Interrupt Descriptor Table (IDT)                                                     ;
;**************************************************************************************;
; The Interrupt Descriptor Table (IDT) is a data structure used to implement an        ; 
; Interrupt Vector Table (IVT), i.e. to determine the proper response to three types   ; 
; of events: hardware interrupts, software interrupts, and processor exceptions.       ;
; The IDT consists of 256 interrupt vectors, the first 32 (0–31 or 0x00–0x1F) of which ;
; are used for processor exceptions.                                                   ;   
;**************************************************************************************;

BASE_OF_SECTION equ 0x8000

; We use a macro to simplify a little each IDT entry 
%macro .idtentry 3
dw ((BASE_OF_SECTION + %1 - $$) & 0xFFFF) - 1024    ; Low word bits (0-15) of offset
dw %2                                               ; Code-Segment-Selector
db 0                                                ; Always zero
db %3                                               ; Type and Attributes
dw ((BASE_OF_SECTION + %1 - $$) >> 16) & 0xFFFF     ; Middle bits (16-31) of offset
dd ((BASE_OF_SECTION + %1 - $$) >> 32) & 0xFFFFFFFF ; High bits (32-64) of offset
dd 0                                                ; Reserved
%endmacro

IDT_START:
;*************************************************************************************
; IDT Entry: Address of Interrupt Service Routine, Code Segment Selector, Attributes ;
;*************************************************************************************
  .idtentry ISR_Division_by_Zero, CODE_SEG, 0x8F  ;0 (Division by zero)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;1 (Debug Exception)     
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;2 (NMI, Non-Maskable Interrupt)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;3 (Breakpoint Exception)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;4 (INTO Overflow)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;5 (Out of Bounds)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;6 (Invalid Opcode)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;7 (Device Not Available)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;8 (Double Fault) 
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;9 (Deprecated)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;10 (Invalid TSS) 
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;11 (Segment Not Present)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;12 (Stack-Segment Fault)
  .idtentry ISR_GPF             , CODE_SEG, 0x8F  ;13 (General Protection Fault)
  .idtentry ISR_Page_Fault      , CODE_SEG, 0x8F  ;14 (Page Fault)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;15 (Reserved)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;16 (x87 Floating-Point Exception)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;17 (Alignment Check Exception)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;18 (Machine Check Exception)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;19 (SIMD Floating-Point Exception)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;20 (Virtualization Exception)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;21 (Control Protection Exception)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;22 (Reserved)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;23 (Reserved)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;24 (Reserved)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;25 (Reserved)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;26 (Reserved)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;27 (Reserved)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;28 (Hypervisor Injection Exception)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;29 (VMM Communication Exception)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;30 (Security Exception)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;31 (Reserved)
  .idtentry ISR_systimer        , CODE_SEG, 0x8F  ;32 (IRQ0: Programmable Interval Timer)
  .idtentry ISR_keyboard        , CODE_SEG, 0x8E  ;33 (IRQ1: Keyboard)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;34 (IRQ2: PIC Cascade, used internally)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;35 (IRQ3: COM2, if enabled)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;36 (IRQ4: COM1, if enabled)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;37 (IRQ5: LPT2, if enabled)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;38 (IRQ6: Floppy Disk)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;39 (IRQ7: LPT1)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;40 (IRQ8: CMOS real-time clock)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;41 (IRQ9: Free)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;42 (IRQ10: Free)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;43 (IRQ11: Free)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;44 (IRQ12: PS2 Mouse)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;45 (IRQ13: Coprocessor)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;46 (IRQ14: Primary ATA Hard Disk)
  .idtentry ISR_dummy           , CODE_SEG, 0x8F  ;47 (IRQ15: Secondary ATA Hard Disk)
; ...
; Although the IDT can contain less than 256 entries, any entries that are not present
; will generate a General Protection Fault when an attempt to access them is made.
IDT_END:


; The IDTR is the argument for the LIDT assembly instruction
; which loads the location of the IDT to the IDT Register. 
ALIGN 4
IDTR:
  .Length dw IDT_END-IDT_START-1 ; One less than the size of the IDT in bytes.
  .Base   dd IDT_START           ; The linear address of the Interrupt Descriptor Table 
                                 ; (not the physical address, paging applies).

kernel/video.asm

Before we go any further, in order to get some feedback, we have to write some code that print messages on the screen. We cannot depend on the BIOS services any more, because the BIOS is designed to run in real mode (and we are now in long mode). Thus, we have to write our own graphics driver that access the video memory directly:

Πριν προχωρήσουμε παρακάτω, για να μπορούμε να έχουμε κάποια ανάδραση για το τι συμβαίνει, πρέπει να γράψουμε κάποιον κώδικα που θα εκτυπώνει μηνύματα στην οθόνη μας. Δεν μπορούμε να βασιζόμαστε πλέον στις υπηρεσίες του BIOS, μιας και το BIOS έχει σχεδιαστεί για να τρέχει σε λειτουργία πραγματικής κατάστασης (real mode), ενώ εμείς βρισκόμαστε τώρα σε λειτουργία μακράς κατάστασης (long mode). Οπότε, πρέπει να γράψουμε τον δικό μας οδηγό γραφικών (graphics driver) ο οποίος θα επεξεργάζεται απευθείας τη μνήμη των γραφικών:

BITS 64

;---Initialized data----------------------------------------------------------------
VRAM dq 0xB8000

;---Constants-----------------------------------------------------------------------
VGA_WIDTH equ 80
VGA_HEIGHT equ 25
; Colors
VGA_COLOR_BLACK equ 0
VGA_COLOR_BLUE equ 1
VGA_COLOR_GREEN equ 2
VGA_COLOR_CYAN equ 3
VGA_COLOR_RED equ 4
VGA_COLOR_MAGENTA equ 5
VGA_COLOR_BROWN equ 6
VGA_COLOR_LIGHT_GREY equ 7
VGA_COLOR_DARK_GREY equ 8
VGA_COLOR_LIGHT_BLUE equ 9
VGA_COLOR_LIGHT_GREEN equ 10
VGA_COLOR_LIGHT_CYAN equ 11
VGA_COLOR_LIGHT_RED equ 12
VGA_COLOR_LIGHT_MAGENTA equ 13
VGA_COLOR_LIGHT_BROWN equ 14
VGA_COLOR_WHITE equ 15


;---Code---------------------------------------------------------------------------
Fill_screen:
;*********************************************************************************;
; Fill screen                                                                     ;
;---------------------------------------------------------------------------------;
; rax (XY__XY__XY__XY__): X -> Background color, Y -> Character color             ;
; rax (__ZZ__ZZ__ZZ__ZZ): ASCII code(s) of character(s) to use to fill the screen ;
;*********************************************************************************;
    mov rdi, [VRAM]
    mov rcx, 500 ; 80*25 / 4 = 500 (we set 4 characters each time).
    rep stosq    ; Clear the entire screen.
    ret


Print:
;**********************************************************************************;
; Prints a string                                                                  ;
;----------------------------------------------------------------------------------;
; rsi: pointer to string (first 16 bits = the number of characters in the string.) ;
;  ah: Color attributes                                                            ;
;  r8: x                                                                           ;
;  r9: y                                                                           ;
;**********************************************************************************;
    push rsi
    push rax
    push r8
    push r9
    dec r8
    add r8, r8
    dec r9
    mov rdi, [VRAM]
    push rax
    mov rax, VGA_WIDTH*2
    mul r9
    add r8, rax
    pop rax
    mov cx, word [rsi] ; first 16 bits = the number of characters in the string
    inc rsi
   .string_loop: ; print all the characters in the string
        lodsb
        mov al, byte [rsi]
        mov [rdi+r8], ax
        add rdi, 2
    loop .string_loop
    pop r9
    pop r8
    pop rax
    pop rsi
    ret


Print_hex:
;**********************************************************************************;
; Prints a 16-digit hexadecimal value                                              ;
;----------------------------------------------------------------------------------;
; r10: value to be printed                                                         ;
;  ah: Color attributes                                                            ;
;**********************************************************************************;
    push r10
    sub rsp, 20         ; make space for the string length (2 bytes) and 18 characters
    mov rsi, rsp
    push rsi            ; store rsi (string address)
    mov [rsi], word 18  ; string length = 17
    mov [rsi+2], word "0x"  
    add rsi, 19         ; point rsi to the end of the string
    mov ecx, 16        ; loop 16 times (one for each digit)
   .digit:
        push r10             ; store rax
        and r10, 0Fh         ; isolate digit
        add r10b,'0'         ; convert to ascii
        cmp r10b,'9'         ; is hex?
        jbe .nohex          
        add r10b, 7          ; hex
       .nohex:
        mov [rsi], byte r10b ; store result
        dec rsi              ; next position
        pop r10              ; restore rax
        shr r10, 4           ; right shift by 4 
    loop .digit         
    pop rsi       ; restore rsi (string address)
    call Print
    add rsp, 20
    pop r10
    ret

kernel/isr.asm

Now, let's examine some of our Interrupt Service Routines:

Τώρα, ας εξετάσουμε τις Ρουτίνες Εξυπηρέτησης Διακοπών (Interrupt Service Routines):

; INTERRUPT SERVICE ROUTINES

BITS 64 

;---Initialized data----------------------------------------------------------
systimer_ticks dq 0
keyboard_scancode dq 0
error_code_low dw 0
error_code_high dw 0

int_message dw 17
db 'Interrupt raised!'

division_by_zero_message dw 17
db 'Division by zero!'

gpf_message dw 25
db 'General Protection Fault!'

pf_message dw 11
db 'Page Fault!'


;---Code------------------------------------------------------------------------
ISR_dummy:
;***************************************************************************;
; Just a dummy generic handler. It prints the message "Interrupt raised!".  ;
;***************************************************************************;
    cli
    push rax
    push r8
    push r9
    push rsi
    mov ah, (VGA_COLOR_RED << 4) | VGA_COLOR_LIGHT_BROWN
    mov r8, 1
    mov r9, 1
    mov rsi, int_message
    Call Print
    pop rsi
    pop r9
    pop r8
    pop rax
    .halt: hlt
    jmp .halt ; Infinite loop
    iretq
    

ISR_Division_by_Zero:
;***************************************************************************;
; Divizion by zero handler                                                  ;
;***************************************************************************;
    cli
    push rax
    push r8
    push r9
    push rsi
    mov ah, (VGA_COLOR_RED << 4) | VGA_COLOR_LIGHT_BROWN
    mov r8, 1
    mov r9, 1
    mov rsi, division_by_zero_message
    Call Print
    pop rsi
    pop r9
    pop r8
    pop rax
    .halt: hlt
    jmp .halt ; Infinite loop
    iretq


ISR_GPF:
;***************************************************************************;
; General Protection Fault handler                                                  ;
;***************************************************************************;
    cli
    push rax
    push r8
    push r9
    push rsi
    mov ah, (VGA_COLOR_RED << 4) | VGA_COLOR_LIGHT_BROWN
    mov r8, 1
    mov r9, 1
    mov rsi, gpf_message
    Call Print
    pop rsi
    pop r9
    pop r8
    pop rax
    .halt: hlt
    jmp .halt ; Infinite loop
    iretq


ISR_Page_Fault:
;***************************************************************************;
; Page Fault handler                                                  ;
;***************************************************************************;
    cli
    pop word [error_code_high]
    pop word [error_code_low]
    push rax
    push r8
    push r9
    push rsi
    mov ah, (VGA_COLOR_RED << 4) | VGA_COLOR_LIGHT_BROWN
    mov r8, 1
    mov r9, 1
    mov rsi, pf_message
    call Print
    pop rsi
    pop r9
    pop r8
    pop rax
    .halt: hlt 
    jmp .halt ; Infinite loop
    iretq


ISR_systimer:
;*****************************************************************************;
; System Timer Interrupt Service Routine (IRQ0 mapped to INT 0x20)            ;
;*****************************************************************************;
    push rax
    inc qword [systimer_ticks]
    mov al, PIC_EOI      ; Send EOI (End of Interrupt) command
    out PIC1_COMMAND, al
    pop rax
    iretq


ISR_keyboard:
;*****************************************************************************;
; Keyboard Controller Interrupt Service Routine (IRQ1 mapped to INT 0x21)     ;
;*****************************************************************************;
    push rax
    xor rax, rax         
    in al, 0x60          ; MUST read byte from keyboard (else no more interrupts).
    mov [keyboard_scancode], al
    mov al, PIC_EOI      ; Send EOI (End of Interrupt) command
    out PIC1_COMMAND, al
    pop rax
    iretq

kernel/kernel.asm

Here, we added some code to see our Interrupt Service Routines in action:

Εδώ, προσθέσαμε κώδικα για να δούμε τις Ρουτίνες Εξυπηρέτησης Διακοπών σε δράση:

BITS 64  ; We have entered the long mode! :)

;---Define----------------------------------------------------------------------
%define DATA_SEG   0x0010

;---Initialized data------------------------------------------------------------
hello_world_message dw 12
db 'Hello World!'

;---Include---------------------------------------------------------------------
%include "kernel/idt.asm"
%include "kernel/isr.asm"
%include "kernel/video.asm"

;---Code------------------------------------------------------------------------
Kernel:
    lidt [IDTR] ; Load our IDT

    mov al, 0x80       ; OCW1: Unmask all interrupts at master PIC
    out PIC1_DATA, al
    mov al, 0x80       ; OCW1: Unmask all interrupts at master PIC
    out PIC2_DATA, al

    ; Set all segments registers to DATA_SEG
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    
    ; Clear the screen.
    mov rax, 0x0020002000200020 ; Set background color to black (0) and
                                ; character to blank space (20).
    call Fill_screen            

    ; Print "Hello World!" at the upper right corner
    mov ah, 0x1E
    mov r8, 69
    mov r9, 1
    mov rsi, hello_world_message
    call Print

    ; Uncomment the following lines if you want to trigger a "Division by zero" exception.
    ; mov eax, 1
    ; mov ecx, 0
    ; div ecx   
    
   .loop:
        ; Print system timer ticks.
        mov ah, VGA_COLOR_LIGHT_GREEN     
        mov r8, 1
        mov r9, 2
        mov r10, [systimer_ticks]
        call Print_hex
        ; Print keyboard scan code.
        mov r10, [keyboard_scancode]
        mov ah, VGA_COLOR_LIGHT_CYAN    
        mov r8, 1
        mov r9, 4
        call Print_hex
        jmp .loop ; Infinite loop.

Running our codeΕκτέλεση του κώδικά μας

We can test our code very easily using an emulator like QEMU:

Μπορούμε να δοκιμάσουμε τον κώδικά μας πολύ εύκολα, χρησιμοποιώντας έναν εξομοιωτή όπως ο QEMU:

$ make
$ qemu-system-x86_64 -drive format=raw,file=os.bin

(Press some keys to watch the scan code change.)

(Πατήστε μερικά πλήκτρα για να δείτε τον κωδικό του πλήκτρου να αλλάζει.)

IRQ ISR example

Now, we have a way to measure time and we can get input from our keyboard! What a progress! :D

Τώρα, λοιπόν, έχουμε έναν τρόπο για να μετρήσουμε τον χρόνο και μπορούμε να πάρουμε είσοδο από το πληκτρολόγιό μας! Τι μεγάλη πρόοδος! :D


To see the "Division by zero" ISR in action, uncomment the relevant lines:

Για να δειτε την εξαίρεση της "Διαίρεση με το μηδέν" σε δράση, αφαιρέστε τα σχόλια από τις σχετικές γραμμές:

; Uncomment the following lines if you want to trigger a "Division by zero" exception.
mov eax, 1
mov ecx, 0
div ecx   

Divizion by zero ISR example

Full codeΠλήρης κώδικας

You can download the full code from here: code

Μπορείτε να κατεβάσετε τον πλήρη κώδικα απο εδώ: κώδικας

ReferencesΑναφορές

See also...

Δείτε επίσης...