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:
- Käsitellään tapahtuma (esim. luetaan näppäimistöltä painettu näppäin)
- Kuitataan keskeytys PIC-kontrollerille
- 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.