Olen kirjoitellut viime aikoina paljon x86-64-arkkitehtuurin ohjelmoinnista Long Modessa eli täydessä 64-bittisessä moodissa. Kokeiluissani pysäytin aina keskeytykset ohjelman alussa (CLI).

Keskeytyksiä kuitenkin tarvitaan heti, kun halutaan esimerkiksi lukea näppäimistöä tai ajastaa toimintoja. IRQ0 (System Timer / INT 0x08) on yksinkertainen kello, joka laukeaa 18.2 kertaa sekunnissa. IRQ1 (Keyboard / INT 0x09) puolestaan laukaistaan aina käyttäjän painaessa jotain näppäintä.

Jos IRQ:t (Interrupt ReQuest) eivät ole tuttuja, niin ne ovat 8259 PIC -kontrollerin generoimia laitteistokeskeytyksiä. IRQ:t 0-7 laukaisevat keskeytykset 0x08-0x0f ja IRQ:t 8-15 taas keskeytykset 0x70-0x77. Näiden lisäksi prosessori käyttää sisäisesti keskeytyksiä 0x00-0x1f. Kaikki muut keskeytykset ovat ohjelmoijan vapaasti käytettävissä ja kutsuttavissa INT-komennolla. Esimerkiksi Linuxin system callit käyttävät keskeytystä INT 0x80.

IDT - Interrupt Descriptor Table

Long Modessa keskeytyksille (INT 0x00-0xFF) rekisteröidään käsittelijät IDT:hen eli Interrupt Descriptor Tableen. Se sisältää 256 kappaletta 128-bittisiä kenttiä, jotka määrittelevät käsittelijöiden muistiosoitteet sekä muutamia lippuja. Kenttien rakenne on kiusallisen hankala (löytyy dokumentaatiosta) ja sen kanssa pitää olla tarkkana. Varsinkin assemblyä ohjelmoidessa bittien nyplääminen on välillä tuskallista.

Tässä on esimerkkikoodia, joka asettaa käsittelijän INT 0x00:lle. Se olettaa, että GDT:hen on määritelty koodisegmentti KERNEL64_CODE, jossa keskeytysten käsittelijät majailevat:

init_idt_64:                            ; setup ISR gate descriptors
        mov rdi, idt64
        mov rax, isr_00
        call set_idt_desc
        lidt [idt64_desc]               ; load 64-bit IDT
        sti
        hlt
        jmp $

set_idt_desc: ; routine to set IDT descriptor at RDI to handler at RAX push rax push rbx push rcx push rdx

    ; Build the lower 64-bit word in RBX

    mov rbx, rax                    ; target offset 31-16 (0x00000000ffff0000) at low 0xffff000000000000
    shl rbx, 32
    mov rcx, 0xffff000000000000
    and rbx, rcx

    mov rcx, 0x00008e0000000000     ; flags (16 bits) at low 0x0000ffff00000000
    or rbx, rcx                     ; flags: P:1(1) DP:2(00) 0:1(0), Type:4(0x0e), Reserved:5(00000), IST:3(000)

    mov rdx, KERNEL64_CODE          ; target selector (16 bits) (0x000000000000ffff) at low 0x00000000ffff0000
    shl rdx, 16
    mov rcx, 0x00000000ffff0000
    and rdx, rcx
    or rbx, rdx

    mov rdx, rax                    ; target offset 15-0 (0x000000000000ffff) at low 0x000000000000ffff
    mov rcx, 0x000000000000ffff
    and rdx, rcx
    or rbx, rdx

    ; Build the higher 64-bit word in RDX

    mov rdx, rax                    ; high 0xffffffff00000000 must be zero
    shr rdx, 32                     ; target offset 64-32 (0xffffffff00000000) at high 0x00000000ffffffff

    mov rax, rbx                    ; First store lower 64-bit word
    stosq

    mov rax, rdx                    ; Then the higher 64-bit word
    stosq

    pop rdx
    pop rcx
    pop rbx
    pop rax
    ret                             ; at end, RDI will point to the next descriptor

align 8 isr_00: iretq

align 8 idt64: ; 64-bit Interrupt Descriptor Table times 256 dq 0x0000000000000000 ; space for 256 x 128-bit descriptors times 256 dq 0x0000000000000000 idt64_end:

align 8 idt64_desc: ; 64-bit Interrupt Descriptor Table info dw idt64_end - idt64 - 1 ; IDT length (16-bit) dq idt64 ; IDT location (64-bit)

IRQ1 - Keyboard Data ready

Laitteiston generoimien keskeytysten käsittely tapahtuu normaalisti näin:

  1. Käsitellään tapahtuma (esim. luetaan näppäimistöltä painettu näppäin)
  2. Kuitataan keskeytys PIC-kontrollerille
  3. Poistutaan käsittelijästä IRETQ-komennolla

Tässä esimerkkikoodi, joka käsittelee IRQ1/INT 0x09:n ja lukee näppäinten painallukset:

isr_09:                                 ; IRQ1 - Keyboard Data Ready
        call read_keyboard
        mov al, 0x20                    ; acknowledge interrupt
        out 0x20, al
        iretq

read_keyboard: push rax push rbx push rsi

    in al, 0x64
    and al, 0x01
    jz .end
    in al, 0x60
    mov bl, al
    and bl, 0x80
    jnz .end
    .keydown:
    mov byte [0x00000000000b8000], al
    <process key press...>
    .end:
    pop rsi
    pop rbx
    pop rax
    ret</code></pre>

Koodi lukee scancodet AL-rekisteriin, ja siitä voi lähteä sitten mäppäämään koodeja merkkeihin ja käsittelemään syötettä. Näytöllehän on tekstitilassa helppo tulostaa käytämällä suoraan 0x00000000000b8000-osoitetta.