Pisanie OS/Obsługa przerwań i konsola tekstowa

Wstęp edytuj

Witamy kolejnej części kursu pt. "Jak zostać Billem Gatesem". Ups, to nie ten tekst :) W poprzedniej części dowiedziałeś się dość dużo o programowaniu OS-ów. Teraz przyszła kolej na trudniejsze rzeczy, czyli praktykę. Napisaliśmy także najprostsze jądro systemu, które tylko wyświetla napis "Hello World !!!" w lewym górnym rogu ekranu. Większość kursów niestety kończyła się na tym, ale mój kurs będzie lepszy i opiszę więcej rzeczy :)

Najpierw zajmiemy się rozszerzeniem naszego kernela o obsługę przerwań. Tym razem nie będzie o obsłudze wyjątków. Będzie o tym w 3 części, ponieważ będzie ona stosunkowo krótka, więc postanowiłem przesunąć ten temat, aby ta część była krótsza.

Dodajemy obsługę przerwań edytuj

Jak już wspomniałem w pierwszej części, adresy przerwań są zapisane w IDT. IDT należy załadować instrukcją lidt. Do naszego kodu wstawiamy następujące linijki:

lidt [idt_descr]

zaraz po instrukcji lgdt. Na końcu pliku dopisujemy:

idt_descr: 
dw 256*8-1 
dd idt 

GLOBAL idt 
idt: 
times 256 dd 0,0

Jak na razie to nasz kod startowy załaduje do rejestru IDTR położenie naszej tablicy. Na razie nie możemy wywołać żadnych przerwań, ponieważ procesor by się zresetował. Teraz wypadałoby stworzyć sobie jakieś procedury do "grzebania" w IDT. Na początek może jakiś plik nagłówkowy (intr.h):

#ifndef __INTR_H 
#define __INTR_H 

typedef struct desc_struct 
{ 
long a,b; 
} desc_table[256]; 

extern desc_table gdt,idt; 

void set_intr_gate(int n,void * addr); 
void set_trap_gate(int n,void * addr); 
void set_system_gate(int n,void * addr); 

#endif

W powyższym pliku mamy zadeklarowany typ desc_struct jako jeden deskryptor oraz desc_table jako tablicę deskryptorów. W kodzie C mamy dostęp do tablic gdt oraz idt, ponieważ w pliku start.asm zadeklarowaliśmy je jako etykiety globalne (czyli można uzyskać adres do nich poza plikiem). Teraz stwórzmy sobie procedury do ustawiania wpisów w IDT. Będzie to plik intr.c:

#include "intr.h" 

#define _set_gate(gate_addr,type,dpl,addr) \
     __asm__("movw %%dx,%%ax\n\t" \
           "movw %0,%%dx\n\t" \
           "movl %%eax,%1\n\t" \
           "movl %%edx,%2\n\t" \
           ::"i"((short)(0x8000+(dpl<<13)+(type<<8))), \
             "o"(*((char *)(gate_addr))), \
             "o"(*(4+(char *)(gate_addr))), \
             "d"((char *)(addr)),"a"(0x00080000)) 

void set_intr_gate(int n,void * addr) 
{ 
_set_gate(&idt[n],14,0,addr); 
} 

void set_trap_gate(int n,void * addr) 
{ 
_set_gate(&idt[n],15,0,addr); 
} 

void set_system_gate(int n,void * addr) 
{ 
_set_gate(&idt[n],15,3,addr); 
}


Funkcja set_intr_gate ustawia adres do "normalnego" przerwania, czyli bez dostępnego kodu błędu. set_trap_gate ustawia adres do "pułapki", czyli przerwania z kodem błędu (kod błędu po wywołaniu przerwania jest dostępny w [esp]). Funkcja set_system_gate przyda nam się w przyszłości, ponieważ zostanie użyta do ustawienia przerwania, za pomocą którego programy użytkownika będą się komunikować z kernelem (tzw. system calls). I właściwie obsługę przerwań mamy już dodaną. Jeszcze jedna rzecz do wyjaśnienia: zastosowałem tu tzw. assembler AT&T. Jest on bardzo wygodny w użyciu do zastosowania go w kodzie C. Dla początkującego programisty może się on wydawać dziwny, bo np. w assemblerze Intela to:

mov eax,ebx 
mov ebx,[esp+4] 
mov ecx,[ebx+edx*4] 
inc dword [licznik] 
mov edi,licznik

w assemblerze AT&T wygląda następująco:

movl %ebx,%eax 
movl 4(%esp),%ebx 
movl (%ebx,%edx,4),%ecx 
incl licznik 
movl $licznik,%edi

Asembler AT&T powinien być opisany w dokumentacji do assemblera as (info as). Plik intr.c kompilujemy poleceniem:

gcc intr.c -c -o intr.o -m32 -O2 -fomit-frame-pointer

Przy następnych kompilacjach plików w C będziemy używać opcji kompilatora w postaci:

gcc -c plik.c -o plik.o -O2 -fomit-frame-pointer -m32 -nostdinc -fno-builtin

Można też sobie napisać skrypt Makefile, który miałby postać:

.SUFFIXES: .asm; 
OUTFILE = kernel.bin 
OBJS = start.o main.o intr.o 
CFLAGS = -O2 -fomit-frame-pointer -nostdinc -fno-builtin -m32 

$(OUTFILE): $(OBJS) 
           ld -Tkernel.ld -o $(OUTFILE) $(OBJS) 

.asm.o: 
           nasm $*.asm -f elf32 -o $*.o

Resztę kompilujemy poleceniami (jeśli nie zrobiliśmy pliku dla make) jak zostało to opisane pod koniec pierwszej części. Potem wszystko linkujemy:

ld -Tkernel.ld -o kernel.bin start.o main.o intr.o

Na razie się nic nie zmieniło, tylko plik wynikowy powiększył się o kilkaset bajtów. Później napiszemy prostą procedurę obsługi przerwania.

Piszemy obsługę ekranu edytuj

Pisanie obsługi ekranu jest bardzo proste. Istnieją 2 rodzaje obsługi kart graficznych: Hercules i VGA. Herculesa już dzisaj nikt nie używa. Wspomnę tylko, że procedury obsługi różnią się tylko adresem pamięci karty graficznej i portem do obsługi położenia kursora. Pamięć tekstu karty VGA jest zlokalizowana pod adresem liniowym 0xB8000. Uważam, że w naszym OSie nie warto pisać obsługi karty Hercules, ponieważ każdy dzisiaj ma kartę kompatybilną z VGA. Najpierw napiszmy sobie procedury do robienia operacji na portach. Plik io.h:

#ifndef __IO_H 
#define __IO_H 

static inline void outportb(unsigned short port,unsigned char val) 
{ 

__asm__("outb %%al,%%dx"::"a"(val),"d"(port)); 
} 

static inline unsigned char inportb(unsigned short port) 
{ 
unsigned char __ret; 
__asm__("inb %%dx,%%al":"=a"(__ret):"d"(port)); 
return __ret; 
} 

#endif

Te instrukcje posłużą nam do wysyłania danych do portów karty VGA, aby ta zmieniła położenie kursora. Kursor można przesunąć poniższą procedurą:

void przesun_kursor(int x,int y) 
{ 
int temp; 
temp=(y*80+x); 
outportb(0x3D4+0,14); 
outportb(0x3D4+1,temp>>8); 
outportb(0x3D4+0,15); 
outportb(0x3D4+1,temp); 
}

Tutaj przyjmujemy, że ekran tekstowy ma wymiary 80x25. Teraz piszemy właściwy program obsługi ekranu. Plik cons.c:

#include"io.h" 

#define __wmemcpy(dst,src,count) \
{ \
int d0,d1,d2; \
__asm__ __volatile__("cld;rep;movsw":"=&S"(d0),"=&D"(d1),"=&  c" (d2):"0"(src),"1"(dst),"2"(count):"memory"); \
} 

#define __wmemset(dst,val,count) \
{ \
int d0,d1; \
__asm__ __volatile__("cld;rep;stosw":"=&D"(d0),"=&c"(d1):"0" (dst),"a"(val),"1"(count):"memory"); \
} 

typedef struct { 
unsigned long esc,attrib,csr_x,csr_y,esc1,esc2,esc3; 
unsigned short * fb_adr; 
} console_t; 

static console_t * _curr_vc; 
static console_t cons[1]; 

static unsigned short *_vga_fb_adr; 
static unsigned _crtc_io_adr, _vc_width, _vc_height; 
static int _num_vcs=1; 

static void scroll(console_t *con) 
{ 
unsigned short *fb_adr; 
unsigned blank, temp; 
blank = 0x20 | ((unsigned)con->attrib << 8); 
fb_adr = con->fb_adr; 
if(con->csr_y >= _vc_height) 
{ 
 temp = con->csr_y - _vc_height + 1; 
 __wmemcpy(fb_adr, fb_adr + temp * _vc_width,(_vc_height - temp) * _vc_width); 
 __wmemset(fb_adr + (_vc_height - temp) * _vc_width,blank, _vc_width); 
 con->csr_y = _vc_height - 1; 
} 
} 

static void set_attrib(console_t *con, unsigned att) 
{ 
static const unsigned ansi_to_vga[] = { 
 0 , 4, 2, 14, 1, 5, 3, 7 
}; 
unsigned new_att; 
new_att = con->attrib; 
if(att == 0) new_att &= ~0x08; 
 else if(att == 1) new_att |= 0x08; 
  else if(att >= 30 && att <= 37) 
  { 
   att = ansi_to_vga[att - 30]; 
   new_att = (new_att & ~0x07) | att; 
  } else if(att >= 40 && att <= 47) 
  { 
   att = ansi_to_vga[att - 40] << 4; 
   new_att = (new_att & ~0x70) | att; 
  } 
con->attrib = new_att; 
} 

static void move_csr(void) 
{ 
unsigned temp; 
temp = (_curr_vc->csr_y * _vc_width + _curr_vc->csr_x) + 
     (_curr_vc->fb_adr - _vga_fb_adr); 
outportb(_crtc_io_adr + 0, 14); 
outportb(_crtc_io_adr + 1, temp >> 8); 
outportb(_crtc_io_adr + 0, 15); 
outportb(_crtc_io_adr + 1, temp); 
} 

#define isdigit(c)      ((c)>='0' && (c)<='9') 

void select_vc(unsigned which_vc) 
{ 
unsigned i; 
if(which_vc >= _num_vcs) 
 return; 
_curr_vc = cons + which_vc; 
i = _curr_vc->fb_adr - _vga_fb_adr; 
outportb(_crtc_io_adr + 0, 12); 
outportb(_crtc_io_adr + 1, i >> 8); 
outportb(_crtc_io_adr + 0, 13); 
outportb(_crtc_io_adr + 1, i); 
move_csr(); 
} 

static void putch_help(console_t *con, unsigned c) 
{ 
unsigned short *fb_adr; 
unsigned att; 
att = (unsigned)con->attrib << 8; 
fb_adr = con->fb_adr; 
/* maszyna stanów do obsługi sekwencji escape */ 
if(con->esc == 1) 
{ 
 if(c == '[') 
 { 
  con->esc++; 
  con->esc1 = 0; 
  return; 
 } 
 /* inaczej idź do końca: zeruj esc i wypisz c */ 
} /* ESC[ */ 
else if(con->esc == 2) 
{ 
 if(isdigit(c)) 
 { 
  con->esc1 = con->esc1 * 10 + c - '0'; 
  return; 
 } else if(c == ';') 
 { 
  con->esc++; 
  con->esc2 = 0; 
 return; 
} 
/* ESC[2J -- wyczyść ekran */ 
 else if(c == 'J') 
 { 
  if(con->esc1 == 2) 
  { 
   __wmemset(fb_adr, (' ' | att)&0xffff, _vc_height * _vc_width); 
   con->csr_x = con->csr_y = 0; 
  } 
 } 
/* ESC[num1m -- ustaw atrybut num1 */ 
 else if(c == 'm') 
  set_attrib(con, con->esc1); 
 con->esc = 0; 
 return; 
} 
/* ESC[num1; */ 
else if(con->esc == 3) 
{ 
 if(isdigit(c)) 
 { 
  con->esc2 = con->esc2 * 10 + c - '0'; 
  return; 
 } 
 else if(c == ';') 
 { 
  con->esc++;      /* ESC[num1;num2; */ 
  con->esc3 = 0; 
  return; 
 } 
 else if(c == 'H') 
 { 
  if(con->esc2 < _vc_width) 
   con->csr_x = con->esc2; 
  if(con->esc1 < _vc_height) 
   con->csr_y = con->esc1; 
 } 
 else if(c == 'm') 
 { 
  set_attrib(con, con->esc1); 
  set_attrib(con, con->esc2); 
 } 
 con->esc = 0; 
 return; 
} else if(con->esc == 4) 
{ 
 if(isdigit(c)) 
 { 
  con->esc3 = con->esc3 * 10 + c - '0'; 
  return; 
 } else if(c == 'm') 
 { 
  set_attrib(con, con->esc1); 
  set_attrib(con, con->esc2); 
  set_attrib(con, con->esc3); 
 } 
 con->esc = 0; 
 return; 
} 
con->esc = 0; 
if(c == 0x1B) 
{ 
 con->esc = 1; 
 return; 
} 
if(c == 0x08) 
{ 
 if(con->csr_x != 0) 
  con->csr_x--; 
} else if(c == 0x09) con->csr_x = (con->csr_x + 8) & ~(8 - 1); 
else if(c == '\r') 
 con->csr_x = 0; 
else if(c == '\n') 
{ 
 con->csr_x = 0; 
 con->csr_y++; 
} else if(c >= ' ') 
{ 
 unsigned short *where; 
 where = fb_adr + (con->csr_y * _vc_width + con->csr_x); 
 *where = (c | att); 
 con->csr_x++; 
} 
if(con->csr_x >= _vc_width) 
{ 
 con->csr_x = 0; 
 con->csr_y++; 
} 
scroll(con); 
if(_curr_vc == con) 
 move_csr(); 
} 

void putch(char c) 
{ 
putch_help(_curr_vc,c); 
} 

void puts(char * s) 
{ 
for(;s && *s;s++) putch(*s); 
} 

void init_video(void) 
{ 
_vga_fb_adr=(unsigned short *)0xb8000; 
_crtc_io_adr=0x3D4; 
_vc_width=80; 
_vc_height=25; 
cons[0].attrib=1; 
cons[0].fb_adr=_vga_fb_adr; 
_curr_vc=&cons[0]; 
puts("\x1B[33;44m\x1B[2J"); 
}

Teraz mamy gotową procedurę obsługi ekranu. Zastosowana tu implementacja tylko częściowo wykorzystuje standard ANSI. Rozkazy ANSI składają się z ciągów sekwencji ESC. Są one dokładnie takie same jak te używane przez sterownik ANSI.SYS (kto używał DOSa, to powinien go pamiętać). UWAGA: Wszystko w tym rozdziale jest prawdziwe tylko i wyłącznie dla karty VGA i kompatybilnej. Na karcie Hercules inne są tylko adresy pamięci video, porty I/O oraz informacje o kolorze są opisane w inny sposób (Hercules jest kartą monochromatyczną). Na początek wypadałoby opisać, w jaki sposób tekst jest umieszczony w pamięci video.

Każdy znak zapisany jest w postaci jednego słowa (16-bit). Dolna połowa (bity 0-7) jest to po prostu kod znaku ASCII (można także zmieniać znaki zapisane w generatorze znaków karty graficznej, ale o tym kiedy indziej). W górnej połowie słowa (bity 8-15) jest zapisany kolor znaku. W jednobajtowym kolorze znaku zapisane są kolor tła znaku oraz kolor znaku.

Bity koloru znaku:

bity     opis 
----------------------------- 
b7       migotanie 
b6:b4    kolor tła (0-7) 
b3:b0    kolor znaku (0-15) 

Wartości kolorów powinny się już kojarzyć programiście z Turbo Pascalem i stałymi opisującymi kolor ;). Są one dokładnie takie same jak w TP.

  • 0 - czarny
  • 1 - niebieski
  • 2 - zielony
  • 3 - cyan
  • 4 - czerwony
  • 5 - magenta
  • 6 - brązowy
  • 7 - biały
  • 8 - ciemno-szary
  • 9 - jasny niebieski
  • 10 - jasny zielony
  • 11 - jasny cyan
  • 12 - różowy
  • 13 - jasny magenta
  • 14 - żółty
  • 15 - jasny biały

Teraz opiszę tutaj kilka sekwencji ANSI escape:

  • esc[nA - przesuwa kursor n wierszy w górę
  • esc[nB - przesuwa kursor n wierszy w dół
  • esc[nC - przesuwa kursor n wierszy w prawo
  • esc[nD - przesuwa kursor n wierszy w lewo
  • esc[y;xH - przesuwa kursor na pozycję (x,y). Górny lewy róg ekranu to (1,1)
  • esc[2J - czyści ekran i umieszcza kursor w (1,1)
  • esc[K - czyści linię od pozycji kursora do końca linii
  • esc[y;xf - to samo co H
  • esc[s - zapamiętuje pozycję kursora na wewnętrznym stosie
  • esc[u - odczytuje pozycję kursora zapisaną przez esc[s i przesuwa go tam
  • esc[im
  • esc[i;jm
  • esc[i;j;km - ustawienie atrybutów znaków
    • i,j,k - atrybuty:
      • 0 - wyłącza wszystkie atrybuty
      • 1 - włącza pogrubienie
      • 4 - włącza podkreślenie (może nie być obsługiwane)
      • 5 - migotanie
      • 7 - odwrócone video
      • 30 - kolor znaku: czarny
      • 31 - kolor znaku: czerwony
      • 32 - kolor znaku: zielony
      • 33 - kolor znaku: żółty
      • 34 - kolor znaku: niebieski
      • 35 - kolor znaku: magenta
      • 36 - kolor znaku: cyan
      • 37 - kolor znaku: biały
      • 40 - kolor tła: czarny
      • 41 - kolor tła: czerwony
      • 42 - kolor tła: zielony
      • 43 - kolor tła: zółty
      • 44 - kolor tła: niebieski
      • 45 - kolor tła: magenta
      • 46 - kolor tła: cyan
      • 47 - kolor tła: biały

Tak więc aby wyczyścić ekran wystarczy wyświetlić sekwencję escape esc[2J (oczywiście esc to znak Escape o kodzie 0x1B, czyli 27. Nie piszemy dosłownie esc !!!). Aby wyczyścić zmienić kolor tła na niebieski, kolor liter na żółty oraz wyczyścić ekran piszemy:

puts("\x1B[33;44m\x1B[2J");

Znaki te nie zostaną nigdy wyświetlone na ekranie, ponieważ analiza kodów ANSI została zrobiona za pomocą tzw. maszyny stanów. Przykładowo dopóki putch_help nie spotka znaku Escape i flaga Escape nie została ustawiona, to znaki są wyświetlane normalnie. Powyższego kodu nie ma za bardzo sensu omawiać. Zrozumieć można go analizując kod źródłowy. Jeśli ktoś by chciał, żeby lepiej wytłumaczyć, proszę pisać komentarze (e-mail na samym początku tekstu).

Teraz zmieniamy main: zostawiamy tylko funkcję start kernel oraz przed puts("Hello ... dajemy init_video();. Wszystko można obejrzeć w załączonym pliku .zip. Jest tam pokazane, jak zmieniać kolor tekstu. Zachęcam do samodzielnych prób.

Nareszcie obsługę ekranu tekstowego mamy napisaną. Tryb graficzny to inna bajka i będzie dużo, dużo później.

Procedura przekierowania przerwań sprzętowych edytuj

Wiem, że to było w I części. Małe wyjaśnienie: to co było w I części, może nie zadziałać na nowych komputerach. Prezentuję procedurę, która zadziała na 100%. Jeśli ktoś nie wie, po co przekierowywać przerwania, zachęcam do przeczytania I części dokładniej. Procedurę należy kompilować pod NASM.

Plik irqroute.asm:

[SECTION .text] 
[BITS 32] 

GLOBAL reroute_irqs 
reroute_irqs: 
  
       cli 
       in      al,0x21 
       mov     ah,al 
       in      al,0xA1 
       mov     cx,ax 
  
       mov     al,0x11 
       out     0x20,al 
       out     0xEB,al 
       out     0xA0,al 
       out     0xEB,al 
  
       mov     al,0x20 
       out     0x21,al 
       out     0xEB,al 
  
       add     al,0x8 
       out     0xA1,al 
       out     0xEB,al 
  
       mov     al,0x04 
       out     0x21,al 
       out     0xEB,al 
       shr     al,1 
       out     0xA1,al 
       out     0xEB,al 
       shr     al,1 
       out     0x21,al 
       out     0xEB,al 
       out     0xA1,al 
       out     0xEB,al 
  
       mov     ax,cx 
       out     0xA1,al 
       mov     al,ah 
       out     0x21,al 
       mov     ecx,0x1000 
       cld 
picl1:
       out     0xEB,al 
       loop    picl1 
  
       cli 
       mov     al,255 
       out     0xa1,al 
       out     0x21,al 
  
       ret

Teraz należy wprowadzić odpowiednią poprawkę do start.asm. Ma być tak:

jmp .1 
.1: 
extern reroute_irqs 
call reroute_irqs 
push dword 0 
push dword 0 
push dword 0 
push dword L6 
EXTERN start_kernel 
push dword start_kernel

Piszemy procedurę obsługi przerwania edytuj

Pisanie obsługi procedur przerwania jest bardzo proste. Przykładowa procedura:

global irq0 
irq0: 
push gs 
push fs 
push es 
push ds 
pusha 
mov ax,0x10 
mov ds,ax 
mov es,ax 
mov al,0x60 
outb 0x20,al 
call jakas_procedura_obslugi_przerwania_w_c 
popa 
pop ds 
pop es 
pop fs 
pop gs 
iret

To jest cała procedura w assemblerze! Nie ma tutaj żadnych cudów. Najpierw zapisujemy na stos rejestry segmentowe ds, es, fs oraz gs. O cs i ss nie musimy się martwić (patrz część I punkty 5.7 oraz 4.3). Potem zapamiętujemy wszystkie rejestry użytkownika instrukcją pusha. Teraz ustawiamy ds oraz es tak, żeby wskazywały na segmenty danych jądra.

Potem można wywołać jakąś procedurę obsługi przerwania w C. Następnie zdejmujemy ze stosu rejestry użytkownika (popa) oraz rejestry segmentowe i wracamy do programu instrukcją iret.

Wysyłanie EOI do kontrolera przerwań (PIC) edytuj

Sposób wysłania EOI zależy od numeru przerwania. Gdy są to przerwania sprzętowe od 0-7 to wysyłamy do portu 0x20 wartość (0x60+numer_przerwania), np. gdy jest to przerwanie 3 to piszemy (np. w asm):

mov al,0x63 
out 0x20,al

Kiedy jest to przerwanie o numerze 8 lub większym, to procedura jest trochę inna. Wysyłamy do portu 0xA0 wartość (0x60+(numer_przerwania&7)), a do portu 0x20 wysyłamy wartość 0x62. Np. dla przerwania IDE0, czyli 14 procedura jest następująca:

mov al,0x66 
out 0xA0,al 
mov al,0x62 
out 0x20,al

Blokowanie i odblokowywanie przerwań sprzętowych edytuj

Kiedy wywołamy procedurę reroute_irqs (tę z irqroute.asm), to wszystkie przerwania sprzętowe zostaną zablokowane. W rejestrach 0x21 oraz 0xA1 wpisana zostaje tzw. maska przerwań. Maska przerwań to jedno słowo (16 bitów, ponieważ jest 16 przerwań sprzętowych). Maska ta jest zanegowana, czyli gdy chcemy odblokować przerwanie, czyścimy bit maski a gdy chcemy zablokować przerwanie, "zapalamy" go. Dodajemy teraz do intr.c następujący tekst:

static unsigned int cached_irq_mask = 0xffff; 

#define __byte(x,y)       (((unsigned char *)&(y))[x]) 
#define cached_21      (__byte(0,cached_irq_mask)) 
#define cached_A1      (__byte(1,cached_irq_mask)) 

void disable_irq(unsigned int irq) 
{ 
unsigned int mask = 1 << irq; 
cached_irq_mask |= mask; 
if (irq & 8) 
{ 
 outb(cached_A1,0xA1); 
} else { 
 outb(cached_21,0x21); 
} 
} 

void enable_irq(unsigned int irq) 
{ 
unsigned int mask = ~(1 << irq); 
cached_irq_mask &= mask; 
if (irq & 8) { 
 outb(cached_A1,0xA1); 
} else { 
 outb(cached_21,0x21); 
} 
}

Procedura enable_irq służy do odblokowywania przerwań, a disable_irq do blokowania przerwań. Procedury te są na tyle proste, że nie wymagają tłumaczenia. Jedynie ten fragment:

#define __byte(x,y)       (((unsigned char *)&(y))[x]) 
#define cached_21      (__byte(0,cached_irq_mask)) 
#define cached_A1      (__byte(1,cached_irq_mask))

może się wydać skomplikowany, ale takie konstrukcje często się stosuje przy pisaniu systemów. __byte(x,y) robi z y tablicę bajtów a x jest indeksem. Kompilator zamieni to na odpowiednie offsety w zmiennej cached_irq_mask, zamiast stosować przesunięcia rejestrów i operację and.

Trochę o klawiaturze edytuj

Jak wiadomo istnieją różne typy klawiatur. Są klawiatury na standardowe złącze DIN (które wyszło już z użycia), PS/2 (nadal popularne) oraz USB. Różnią się one jedynie złączem oraz liczbą bajerów, w jakie wyposażył je producent. Programowanie klawiatur jest takie same.

Klawiatura wykorzystuje przerwanie sprzętowe 1. Nieważne, czy to jest klawiatura USB czy na złącze PS/2, przerwanie 1 było, jest i będzie zarezerwowane dla klawiatury. Klawiatura używa także portów 0x60 i 0x64. Port 0x60 służy do przesyłania danych, a 0x64 do przesyłania poleceń.

Klawiatury używają mikroprocesora 8048 (w nowszych modelach może być inny, ale kompatybilny układ). Na płycie głównej jest układ 8042 (w nowszych komputerach jest pewnie inny układ do tego). Kiedy się naciśnie lub zwolni przycisk, klawiatura przesyła 0 lub więcej bajtów. Wtedy generowane jest także przerwanie sprzętowe IRQ1. Dana wysłana przez klawiaturę nazywana jest scan code. Program obsługi przerwania klawiatury musi pobrać te dane z portu 0x60.

Można także używać oczekiwania zamiast przerwań, aby odczytać dane z klawiatury.

Wyróżniamy 3 zestawy kodów wysyłanych przez klawiaturę:

  1. IBM PC XT
  2. IBM PC AT
  3. IBM PS/2

Tylko zestaw 2 jest poprawnie zaimplementowany we wszystkich kontrolerach klawiatury. Pozostałe zestawy mogą zawierać bugi oraz mogą być niepoprawnie zaimplementowane.

Mapa zestawu 2 (przetłumaczona):
Kody "Make" są generowane, gdy klawisz zostanie wciśnięty. Kody "Break" są generowane, gdy klawisz zostanie zwolniony.

Większość klawiszy:

  • jedno bajtowy kod make = nn
  • jedno bajtowy kod powtórzenia = nn
  • dwu bajtowy kod break = f0nn

"Szare" klawisze (nie ma ich na oryginalnej 84-klawiszowej klawiaturze):

  • dwu bajtowy kod make = e0nn
  • dwu bajtowy kod powtórzenia = e0nn
  • trzy bajtowy kod break = 0ef0nn

"Szare" klawisze są zaznaczone przez [1] i są zależne od NumLock:

Kiedy NumLock na klawiaturze jest włączone:

  • czterobajtowy kod make = e012e0nn
  • dwubajtowy kod powtórzenia = e0nn
  • sześciobajtowy kod break = e0f0nne0f012
 ___    _______________    _______________    _______________ 
|   |  |   |   |   |   |  |   |   |   |   |  |   |   |   |   | 
|Esc|  |F1 |F2 |F3 |F4 |  |F5 |F6 |F7 |F8 |  |F9 |F10|F11|F12| 
| 76|  | 05| 06| 04| 0C|  | 03| 0B| 83| 0A|  | 01| 09| 78| 07| 
|___|  |___|___|___|___|  |___|___|___|___|  |___|___|___|___| 

 _____________________________________ ___________________________________ 
|    |    |    |    |    |    |    |    |    |    |    |    |    |    |    | 
|~   |!   |@   |#   |$   |%   |^   |&   |*   |(   |)   |_   |+   ||   |bksp| 
|`   |1   |2   |3   |4   |5   |6   |7   |8   |9   |0   |-   |=   |\   |    | 
|  0E|  16|  1E|  26|  25|  2E|  36|  3D|  3E|  46|  45|  4E|  55|  5D|  66| 
|____|____|____|____|____|____|____|___ |____|____|____|____|____|____|____| 
|    |    |    |    |    |    |    |    |    |    |    |    |    |         | 
|Tab |Q   |W   |E   |R   |T   |Y   |U   |I   |O   |P   |{   |}   |         | 
|    |    |    |    |    |    |    |    |    |    |    |[   |]   |         | 
|  0D|  15|  1D|  24|  2D|  2C|  35|  3C|  43|  44|  4D|  54|  5B|         | 
|____|____|____|____|____|____|____|___ |____|____|____|____|____|         | 
|    |    |    |    |    |    |    |    |    |    |    |    |              | 
|Caps|A   |S   |D   |F   |G   |H   |J   |K   |L   |:   |"   |     Enter    | 
|    |    |    |    |    |    |    |    |    |    |;   |'   |              | 
|  58|  1C|  1B|  23|  2B|  34|  33|  3B|  42|  4B|  4C|  52|            5A| 
|____|____|____|____|____|____|____|___ |____|____|____|____|______________| 
|         |    |    |    |    |    |    |    |    |    |    |              | 
| L Shift |Z   |X   |C   |V   |B   |N   |M   |<   |>   |?   |   R Shift    | 
|         |    |    |    |    |    |    |    |,   |.   |/   |              | 
|       12|  1A|  22|  21|  2A|  32|  31|  3A|  41|  49|  4A|            59| 
|_________|____|____|____|____|____|___ |____|____|____|____|______________| 
|       |       |       |                  |       |       |       |       | 
|L Ctrl | L win | L Alt |       space      | R Alt | R win | menu  |R Ctrl | 
|       |[1]    |       |                  |       |[1]    |[1]    |       | 
|     14|   E01F|     11|                29|   E011|   E027|   E02F|   E014| 
|_______|_______|_______|______________ ___|_______|_______|_______|_______| 

Dla klawisza PrintScreen/SysRq:

  • kod make = e012e07c
  • kod powtózenia = e07c
  • kod break = e0f07ce0f012

Klawisz Pause/Break nie generuje powtórzeń ani kodu break. Jego kod make to e11477e1f014f077

 ____ ____ ____ 
|    |    |    | 
|Prt |Scrl|Paus| 
|Scrn|Lock|Brk | 
| [2]|  7E| [3]| 
|____|____|____| 

 ____ ____ ____   ____ ____ ____ ____ 
|    |    |    | |    |    |    |    | 
|Ins |Home|PgUp| |Num |/   |*   |-   | 
|[1] |[1] |[1] | |Lock|    |    |    | 
|E070|E06C|E07D| |  77|E04A|  7C|  7B| 
|____|____|____| |____|____|____|____| 
|    |    |    | |    |    |    |    | 
|Del |End |PgDn| |7   |8   |9   |    | 
|[1] |[1] |[1] | |Home|(U) |PgUp|    | 
|E071|E069|E07A| |  6C|  75|  7D|    | 
|____|____|____| |____|____|____|    | 
                 |    |    |    |+   | 
                 |4   |5   |6   |    | 
                 |(L) |    |(R) |    | 
                 |  6B|  73|  74|  79| 
      ____       |____|____|____|____| 
     |    |      |    |    |    |    | 
     |(U) |      |1   |2   |3   |    | 
     |[1] |      |End |(D) |PgDn|    | 
     |E075|      |  69|  72|  7A|Ent | 
 ____|____|____  |____|____|____|    | 
|    |    |    | |         |    |    | 
|(L) |(D) |(R) | |0        |.   |    | 
|[1] |[1] |[1] | |Ins      |Del |    | 
|E06B|E072|E074| |       70|  71|E05A| 
|____|____|____| |_________|____|____| 


kod  klawisz    kod  klawisz    kod  klawisz    kod  klawisz 
---  -------    ---  -------    ---  -------    ---  ------- 
01   F9                                         66   BackSpace 
                21   C          41   ,< 
03   F5         22   X          42   K          69   End 1 
04   F3         23   D          43   I 
05   F1         24   E          44   O          6B   (left) 4 
06   F2         25   4$         45   0)         6C   Home 7 
07   F12        26   3#         46   9( 
                                                70   Ins 0 
09   F10        29   Space      49   .>         71   Del . 
0A   F8         2A   V          4A   /?         72   (down) 2 
0B   F6         2B   F          4B   L          73   5 
0C   F4         2C   T          4C   ;:         74   (right) 6 
0D   Tab        2D   R          4D   P          75   (up) 8 
0E   `~         2E   5%         4E   -_         76   Esc 
                                                77   NumLock 
11   L Alt      31   N          52   '"         78   F11 
12   L Shift    32   B                          79   + 
                 33   H          54   [{         7A   PageDown 3 
14   L Ctrl     34   G          55   =+         7B   - 
15   Q          35   Y                          7C   * 
16   1!         36   6^         58   CapsLock   7D   PageUp 9 
                                59   R Shift    7E   ScrollLock 
1A   Z          3A   M          5A   Enter 
1B   S          3B   J          5B   ]}         83   F7 
1C   A          3C   U 
1D   W          3D   7&         5D   \| 
1E   2@         3E   8* 


kod             klawisz 
---             ------- 
E011            R Alt 
E012E07C        Kod make dla PrintScreen 
E014            R Ctrl 
E01F            L Win 
E027            R Win 
E02F            Menu 
E04A            / 
E05A            Enter (na klawiaturze numerycznej) 
E069            End 
E06B            (w lewo) 
E06C            Home 
E070            Ins 
E071            Del 
E072            (w dół) 
E074            (w prawo) 
E075            (do góry) 
E07A            PageDown 
E07C            Kod powtórzenia PrintScreen 
E07D            PageUp 
E0F07CE0F012    Kod break PrintScreen 
E11477E1F014F077 Pause 

Oczywiście powyższej mapy klawiatury nie uczymy się na pamięć! :) Jest ona nam potrzebna tylko przy pisaniu sterownika klawiatury.

Sterownik klawiatury może pełnić również inne funkcje. Służy on także do obsługi portu PS/2 myszy (jeśli jest zamontowany), kontrolowania linii A20 oraz resetowania komputera :P

Przykładowy kod w assemblerzem jak zresetować komputer:

reset: 
     call kbd 
     mov al,0xfe 
     out 0x64,al 

kbd0:   
     jmp short $+2 
     in al,60h 
kbd:    jmp short $+2 
     in al,64h 
     test al,1 
     jnz kbd0 
     test al,2 
     jnz kbd 
     ret

Piszemy kod przerwania klawiatury edytuj

Do pliku start.asm należy dodać następujący kod:

GLOBAL irq1 
irq1: 
     push gs 
     push fs 
     push es 
     push ds 
     pusha 
     mov ax,0x10 
     mov ds,ax 
     mov es,ax 
     mov al,0x61 
     out 0x20,al 
EXTERN do_irq1 
     call do_irq1 
     popa 
     pop ds 
     pop es 
     pop fs 
     pop gs 
     iret

Już powinniście wiedzieć o co robi ten kod. Jeśli nie, patrz punkt 5. Procedura jest podobna, tylko wywołuje naszą funkcję w C, która ma deklarację:

void do_irq1(void);

My dopiero w procedurze do_irq1() robimy obsługę klawiatury. Procedura w assemblerze inicjalizuje rejestry segmentowe oraz kładzie na stos rejestry użytkownika, czego kompilator C za nas nie może zrobić. Nie ma co się rozpisywać o kodzie w assemblerze.

Bufor danych dla klawiatury edytuj

Moglibyśmy stworzyć zmienną, w której zapisany jest kod ostatnio wciśniętego klawisza, ale byłoby to bardzo niedoskonałe, ponieważ gdy naciśniemy kilka klawiszy, a program ich nie zdąży odczytać, to po prostu zostaną "zgubione". Dla tego stosuje się bufory dla klawiatury. My zastosujemy listę cykliczną. Jest to najprostszy sposób implementacji bufora FIFO (First In First Out).

typedef struct 
{ 
/* dane w buforze */ 
unsigned char *data; 
/* rozmiar, początek bufora, koniec bufora */ 
unsigned size, in_ptr, out_ptr; 
} queue_t; 

static int inq(queue_t *q, unsigned data) 
{ 
unsigned temp; 
temp = q->in_ptr + 1; 
if(temp >= q->size) temp = 0; 
/* jeśli in_ptr+1==out_ptr to znaczy, że kolejka jest pełna */ 
if(temp == q->out_ptr) return -1; 
q->data[q->in_ptr] = data; 
q->in_ptr = temp; 
return 0; 
} 

static int deq(queue_t *q, unsigned char *data) 
{ 
/* gdy out_ptr==in_ptr, znaczy że kolejka jest pusta */ 
if(q->out_ptr == q->in_ptr) return -1; 
*data = q->data[q->out_ptr++]; 
if(q->out_ptr >= q->size) q->out_ptr = 0; 
return 0; 
} 

static int empty(queue_t *q) 
{ 
return q->out_ptr == q->in_ptr; 
}

Funkcja inq służy do wstawiania danych do kolejki, a deq do pobierania danych z kolejki. inq i deq zwracają  , gdy kolejka jest odpowiednio pełna lub pusta, lub   gdy wstawiono znak poprawnie.

Nie będę tutaj opisywał zasady działania kolejki, ponieważ nie ma tu na to miejsca.

Obsługa przerwania klawiatury edytuj

Nareszcie. Teraz napiszemy właściwy kod w C.

#define KBD_QUEUE_SIZE            64 
static unsigned char kbd_queue_data[KBD_QUEUE_SIZE]; 

static queue_t kbd_buf={ 
     kbd_queue_data, 
     KBD_QUEUE_SIZE, 
     0, 
     0 
}; 


static int read_kbd(void) 
{ 
unsigned long timeout; 
unsigned stat, data; 
for(timeout = 500000L; timeout != 0; timeout--) 
{ 
 stat = inportb(0x64); 
/* czekaj gdy bufor klawiatury jest pełny */ 
 if(stat & 0x01) 
 { 
  data = inportb(0x60); 
/* pętla, gdt błąd parzystości, lub koniec czasu oczekiwania */ 
  if((stat & 0xC0) == 0) return data; 
 } 
} 
return -1; 
} 

static void write_kbd(unsigned adr, unsigned data) 
{ 
unsigned long timeout; 
unsigned stat; 
for(timeout = 500000L; timeout != 0; timeout--) 
{ 
 stat = inportb(0x64); 
/* czekaj gdy bufor klawiatury nie zrobi się pusty */ 
 if((stat & 0x02) == 0) break; 
} 
if(timeout == 0) 
{ 
 puts("write_kbd: timeout\n"); 
 return; 
} 
outportb(adr, data); 
} 

#define      KEY_F1            0x80 
#define      KEY_F2            (KEY_F1 + 1) 
#define      KEY_F3            (KEY_F2 + 1) 
#define      KEY_F4            (KEY_F3 + 1) 
#define      KEY_F5            (KEY_F4 + 1) 
#define      KEY_F6            (KEY_F5 + 1) 
#define      KEY_F7            (KEY_F6 + 1) 
#define      KEY_F8            (KEY_F7 + 1) 
#define      KEY_F9            (KEY_F8 + 1) 
#define      KEY_F10            (KEY_F9 + 1) 
#define      KEY_F11            (KEY_F10 + 1) 
#define      KEY_F12            (KEY_F11 + 1) 
#define      KEY_INS            0x90 
#define      KEY_DEL            (KEY_INS + 1) 
#define      KEY_HOME      (KEY_DEL + 1) 
#define      KEY_END            (KEY_HOME + 1) 
#define      KEY_PGUP      (KEY_END + 1) 
#define      KEY_PGDN      (KEY_PGUP + 1) 
#define      KEY_LFT            (KEY_PGDN + 1) 
#define      KEY_UP            (KEY_LFT + 1) 
#define      KEY_DN            (KEY_UP + 1) 
#define      KEY_RT            (KEY_DN + 1) 
#define      KEY_PRNT      (KEY_RT + 1) 
#define      KEY_PAUSE      (KEY_PRNT + 1) 
#define      KEY_LWIN      (KEY_PAUSE + 1) 
#define      KEY_RWIN      (KEY_LWIN + 1) 
#define      KEY_MENU      (KEY_RWIN + 1) 

#define      KBD_META_ALT      0x0200 
#define      KBD_META_CTRL      0x0400 
#define      KBD_META_SHIFT      0x0800 
#define      KBD_META_ANY      (KBD_META_ALT | KBD_META_CTRL | KBD_META_SHIFT) 
#define      KBD_META_CAPS      0x1000 
#define      KBD_META_NUM      0x2000 
#define      KBD_META_SCRL      0x4000 

#define      RAW1_LEFT_CTRL            0x1D 
#define      RAW1_LEFT_SHIFT            0x2A 
#define      RAW1_CAPS_LOCK            0x3A 
#define      RAW1_LEFT_ALT            0x38 
#define      RAW1_RIGHT_ALT            0x38 
#define      RAW1_RIGHT_CTRL            0x1D 
#define      RAW1_RIGHT_SHIFT      0x36 
#define      RAW1_SCROLL_LOCK      0x46 
#define      RAW1_NUM_LOCK            0x45 
#define      RAW1_DEL            0x53 

static int set1_scancode_to_ascii(unsigned code) 
{ 
     static const unsigned char map[] = 
     { 
/* 00 */0, 0x1B, '1', '2', '3', '4', '5', '6', 
/* 08 */'7', '8', '9', '0', '-', '=', '\b', '\t', 
/* 10 */'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 
/* 1Dh to lewy Ctrl */ 
/* 18 */'o', 'p', '[', ']', '\n', 0, 'a', 's', 
/* 20 */'d', 'f', 'g', 'h', 'j', 'k', 'l', ';', 
/* 2Ah to lewy Shift */ 
/* 28 */'''', '`', 0, '\\', 'z', 'x', 'c', 'v', 
/* 36h to prawy Shift */ 
/* 30 */'b', 'n', 'm', ',', '.', '/', 0, 0, 
/* 38h to lewy Alt, 3Ah to Caps Lock */ 
/* 38 */0, ' ', 0, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, 
/* 45h to Num Lock, 46h to Scroll Lock */ 
/* 40 */KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10,0, 0,KEY_HOME, 
/* 48 */KEY_UP, KEY_PGUP,'-', KEY_LFT,'5', KEY_RT, '+', KEY_END, 
/* 50 */KEY_DN, KEY_PGDN,KEY_INS,KEY_DEL,0, 0, 0, KEY_F11, 
/* 58 */KEY_F12 
     }; 
     static const unsigned char shift_map[] = 
     { 
/* 00 */0, 0x1B, '!', '@', '#', '$', '%', '^', 
/* 08 */'&', '*', '(', ')', '_', '+', '\b', '\t', 
/* 10 */'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 
/* 18 */'O', 'P', '{', '}', '\n', 0, 'A', 'S', 
/* 20 */'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', 
/* 28 */'"', '~', 0, '|', 'Z', 'X', 'C', 'V', 
/* 30 */'B', 'N', 'M', '<', '>', '?', 0, 0, 
/* 38 */0, ' ', 0, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, 
/* 40 */KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10,0, 0, KEY_HOME, 
/* 48 */KEY_UP, KEY_PGUP,'-', KEY_LFT,'5', KEY_RT, '+', KEY_END, 
/* 50 */KEY_DN, KEY_PGDN,KEY_INS,KEY_DEL,0, 0, 0, KEY_F11, 
/* 58 */KEY_F12 
     }; 
static unsigned saw_break_code, kbd_status; 
unsigned temp; 
/* sprawdź czy kod break (np. gdy klawisz został zwolniony) */ 
if(code >= 0x80) 
{ 
 saw_break_code = 1; 
 code &= 0x7F; 
} 
/* kod break, które nas na razie interesują 
  to Ctrl, Shift, Alt */ 
if(saw_break_code) 
{ 
 if(code == RAW1_LEFT_ALT || code == RAW1_RIGHT_ALT) 
  kbd_status &= ~KBD_META_ALT; 
 else if(code == RAW1_LEFT_CTRL || code == RAW1_RIGHT_CTRL) 
  kbd_status &= ~KBD_META_CTRL; 
 else if(code == RAW1_LEFT_SHIFT || code == RAW1_RIGHT_SHIFT) 
  kbd_status &= ~KBD_META_SHIFT; 
 saw_break_code = 0; 
 return -1; 
} 
/* jeśli to kod make: sprawdź klawisze "meta" 
  podobnie jak powyżej */ 
if(code == RAW1_LEFT_ALT || code == RAW1_RIGHT_ALT) 
{ 
 kbd_status |= KBD_META_ALT; 
 return -1; 
} 
if(code == RAW1_LEFT_CTRL || code == RAW1_RIGHT_CTRL) 
{ 
 kbd_status |= KBD_META_CTRL; 
 return -1; 
} 
if(code == RAW1_LEFT_SHIFT || code == RAW1_RIGHT_SHIFT) 
{ 
 kbd_status |= KBD_META_SHIFT; 
 return -1; 
} 
/* Scroll Lock, Num Lock, i Caps Lock ustawiają diody LED. */ 
if(code == RAW1_SCROLL_LOCK) 
{ 
 kbd_status ^= KBD_META_SCRL; 
 goto LEDS; 
} 
if(code == RAW1_NUM_LOCK) 
{ 
 kbd_status ^= KBD_META_NUM; 
 goto LEDS; 
} 
if(code == RAW1_CAPS_LOCK) 
{ 
 kbd_status ^= KBD_META_CAPS; 
LEDS:             
 write_kbd(0x60, 0xED);      /* komenda "set LEDS" */ 
 temp = 0; 
 if(kbd_status & KBD_META_SCRL) 
  temp |= 1; 
 if(kbd_status & KBD_META_NUM) 
  temp |= 2; 
 if(kbd_status & KBD_META_CAPS) 
  temp |= 4; 
 write_kbd(0x60, temp); 
 return -1; 
} 
/* brak konwersji, gdy Alt jest naciśnięty */ 
if(kbd_status & KBD_META_ALT) 
 return code; 
/* konwertuj A-Z[\]^_ na kody sterowania */ 
if(kbd_status & KBD_META_CTRL) 
{ 
 if(code >= sizeof(map) / sizeof(map[0])) 
  return -1; 
 temp = map[code]; 
 if(temp >= 'a' && temp <= 'z') 
  return temp - 'a'; 
 if(temp >= '[' && temp <= '_') 
  return temp - '[' + 0x1B; 
 return -1; 
} 
/* konwertuj kod skanowania na kod ASCII */ 
if(kbd_status & KBD_META_SHIFT) 
{ 
/* ignoruj niepoprawne kody */ 
 if(code >= sizeof(shift_map) / sizeof(shift_map[0])) return -1; 
 temp = shift_map[code]; 
 if(temp == 0) return -1; 
/* caps lock? */ 
 if(kbd_status & KBD_META_CAPS) 
 { 
  if(temp >= 'A' && temp <= 'Z') 
   temp = map[code]; 
 } 
} else { 
 if(code >= sizeof(map) / sizeof(map[0])) 
  return -1; 
 temp = map[code]; 
 if(temp == 0) 
  return -1; 
 if(kbd_status & KBD_META_CAPS) 
 { 
  if(temp >= 'a' && temp <= 'z') 
   temp = shift_map[code]; 
 } 
} 
return temp; 
} 

void do_irq1(void) 
{ 
int klawisz=inportb(0x60); 
klawisz=set1_scancode_to_ascii(klawisz); 
if(klawisz>0 && klawisz<256) 
{ 
 inq(&kbd_buf,klawisz); 
} 
}

Uff... Trochę tego było. Kod powinien być łatwy do zrozumienia, ponieważ miejscami jest skomentowany. read_kbd i write_kbd są używane do sterowania klawiaturą. Normalnie nie ma sensu ich używać. set1_scancode_to_ascii konwertuje kod podany przez klawiaturę na kody ASCII. Jak zapewne zauważyliście, klawiatura nie wysyła znaków ASCII tylko kody klawiszy, które następnie trzeba przekształcić na kody ASCII. Jeszcze trzeba napisać procedury, które nam zainicjalizują klawiaturę. Nie ma tak dobrze ;)

extern void irq1(void); 

void kbd_init(void) 
{ 
set_intr_gate(0x21,&irq1); 
enable_irq(1); 
}

Zmodyfikowałem jeszcze main.c i intr.h, ale to można sobie obejrzeć w dołączonym pliku .zip. Sterownik klawiatury jest w pliku kbd.c. Cały kod został przetestowany pod emulatorem PC Bochs.

Proponuję uruchomić teraz naszego kernela i pobawić się klawiaturą ;)

Zakończenie edytuj

Najpierw sprawy organizacyjne: Od teraz będziemy używać skryptów Makefile. Jeśli ktoś nie wie co to jest, niech poczyta o programie make, lub kompiluje wszystko ręcznie. Dla "zielonych" ułatwiłem sprawę i dodałem skrypt make i powiem tylko. Dodawanie nowych plików do Makefile jest dziecinnie proste. W naszym przypadku dodajemy pliki na końcu linijki OBJS = ....

Teraz piszemy make i naciskamy Enter. Kompilują nam się tylko te pliki, które zostały zmienione, albo nie były jeszcze skompilowane.