|
- Programming4Fun http://www.programming4fun.pun.pl/index.php - Poradniki http://www.programming4fun.pun.pl/viewforum.php?id=19 - Pisanie systemów operacyjnych cz. III - przerwania, wyjątki, GRUB. http://www.programming4fun.pun.pl/viewtopic.php?id=8 |
| arnon - 2014-07-10 12:06:41 |
Jak napisać własny, PRAWDZIWY SYSTEM OPERACYJNY - TAKI CO MA JĄDRO I MOŻNA NORMALNIE ZAINSTALOWAĆ NA KOMPUTERZE!!!!!! Kod:#define KERNEL_PAGEDIR 0x0 #define KERNEL_PAGETAB 0x1000 #define KERNEL_GDT 0x2000 #define KERNEL_IDT 0x2500 Aby poprawnie załadować GDT i IDT musimy jeszcze stworzyć nagłowek dla naszych tablic. Możemy je umieścić w pliku entry.asm. Kod:gdt_descr: .size: dw 2048 .addr: dd 0x2000 idt_descr: .size: dw 2048 .addr: dd 0x2500 Nie przejmuj się, że nie stworzymy po 255 wpisów w GDT, jeśli nie użyjemy błędnego selektora nic złego się nie stanie. Kod:[GLOBAL load_gdt] load_gdt: lgdt [gdt_descr] ret [GLOBAL load_idt] load_idt: lidt [idt_descr] ret Od razu podam struktury, które będziemy za chwilę używać. Kod:typedef struct {
unsigned short offset_0;
unsigned short selector;
unsigned short type;
unsigned short offset_16;
} gate_desc;
typedef struct {
unsigned short limit;
unsigned short base_0_15;
unsigned char base_16_23;
unsigned char dpl_type;
unsigned char gav_lim;
unsigned char base_24_31;
} sys_desc;Pierwsza jest wpisem w IDT, druga w GDT, którego format już znasz. Kod:typedef struct {
unsigned short offset_0;
unsigned short selector;
unsigned short type;
unsigned short offset_16;
} gate_desc;Gdy już znamy format takiej tablicy możemy napisać funkcje w C do podczepiania funkcji przerwań (ISRów). Kod:void setup_int(int i, unsigned long p_hand, unsigned short type)
{
gate_desc *idt = (gate_desc*)KERNEL_IDT;
idt[i].offset_0 = p_hand;
idt[i].selector = 0x08;
idt[i].type = type;
idt[i].offset_16 = (p_hand >> 16);
}Funkcja podczepi nam ISRa podanego w p_hand do przerwania i, który jest typem type. Kod:void setup_seg(int i, unsigned long base, unsigned long limit, char type)
{
sys_desc *desc = (sys_desc*)KERNEL_GDT;
desc[i].limit = limit & 0xFFFF;
desc[i].base_0_15 = base << 16;
desc[i].base_16_23 = (base & 0xFF0000) >> 16;
desc[i].dpl_type = 0x90 | type;
desc[i].gav_lim = ((limit & 0xF0000) >> 16) | 0xC0;
desc[i].base_24_31 = 0;
}Funkcja zawsze ustawia bit granularity jako zapalony, więc nie możemy jej użyć do ustawienia NULL Descriptor, musimy również zapamiętać, że limit jest mnożony przez 4KiB (lub jak kto woli podajemy ilość stron w segmencie). Kod:[GLOBAL keyb_isr] keyb_isr: pusha push gs push fs push es push ds [EXTERN keyb] call keyb ;jakas funkcja w C pop ds pop es pop fs pop gs popa iret W naszym OSie będziemy obsługiwać kilka przerwań, większość wyjątków i testowe przerwanie 0x80. Kod:#define PIC1 0x20
#define PIC2 0xA0
#define ICW1 0x11
#define ICW4 0x01
void init_irq(int pic1, int pic2)
{
outb(PIC1, ICW1);
outb(PIC2, ICW1);
outb(PIC1 + 1, pic1);
outb(PIC2 + 1, pic2);
outb(PIC1 + 1, 4);
outb(PIC2 + 1, 2);
outb(PIC1 + 1, ICW4);
outb(PIC2 + 1, ICW4);
outb(PIC1 + 1, 0);
outb(PIC2 + 1, 0);
}Funkcja ta odblokuje wszystkie przerwania sprzętowe lecz lepiej będzie jak w przyszłości odblokujesz tylko te przerwania, których będziesz używał. Aby to zrobić musisz wysłać odpowiednią maske do portu PIC1 + 1 lub PIC2 + 1 w zależności od numeru przerwania, po więcej odsyłam do manuala Intela. Kod:#define KBD_SPECIAL 200
#define ENTER 10
#define F1 201
#define F2 202
#define F3 203
#define F4 204
#define F5 205
#define F6 206
#define F7 207
#define F8 208
#define F9 209
#define F10 210
#define F11 211
#define F12 212
#define PAUSE 213
char scancode_ascii[0x100] = {
KBD_SPECIAL, KBD_SPECIAL,
'1','2','3','4','5','6','7','8','9','0','-','=',
KBD_SPECIAL, KBD_SPECIAL,
'q','w','e','r','t','y','u','i','o','p','[',']','\n',KBD_SPECIAL,
'a','s','d','f','g','h','j','k','l',';','\'',KBD_SPECIAL,'\\',
'<','z','x','c','v','b','n','m',',','.','/',KBD_SPECIAL,KBD_SPECIAL,KBD_SPECIAL,
' ',KBD_SPECIAL,F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12,PAUSE,KBD_SPECIAL,KBD_SPECIAL,
};Następnie napiszemy bardzo prostą funkcje do odczytu tych danych. Kod:void keyb()
{
char key;
char kbd_scancode;
kbd_scancode = inb(0x60);
key = scancode_ascii[kbd_scancode];
if (key == 'f')
{
print("nacinales f!");
}
outb(0x20, 0x20);
}Powyższy kod jest bardzo prymitywny bo nie ma w nim obsługi SHIFTa ani CAPS LOCKa ale przynajmniej wiesz od czego zacząć. Zwróce jeszcze uwage na to, że przerwanie wykonuje się zarówno przy przycisnięciu jakiegoś klawisza jak i przy jego zwolnieniu, więc musisz to odpowiednio zinterpretować. Kod:void exception_handler( unsigned long nr )
{
print("System halted.", 14);
__asm__ __volatile__ ("cli\n"
"hlt");
}Makro tworzące ISRy dla wyjątków (patrz plik isr.asm) podaje w parametrze handlera numer wyjątku, więc możesz to odpowiednio zainterpretować, np Kod:if (nr == 1)
{
print("nie dziel cholero przez zero");
}Inicjatywa należy oczywiście do Ciebie. Kod:MULTIBOOT_PAGE_ALIGN equ 1<<0 MULTIBOOT_MEMORY_INFO equ 1<<1 MULTIBOOT_AOUT_KLUDGE equ 1<<16 MULTIBOOT_HEADER_MAGIC equ 0x1BADB002 MULTIBOOT_HEADER_FLAGS equ MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO | MULTIBOOT_AOUT_KLUDGE CHECKSUM equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS) align 4 ; wartosci z linkera [extern code] [extern edata] [extern end] mboot: dd MULTIBOOT_HEADER_MAGIC dd MULTIBOOT_HEADER_FLAGS dd CHECKSUM ;MULTIBOOT_AOUT_KLUDGE dd mboot ; dd code ; start of kernel .text (code) section dd edata ; end of kernel .data section dd end ; end of kernel BSS dd start ; kernel entry point (initial EIP) Możemy zrezygnować z części MULTIBOOT_AOUT_KLUDGE jeśli nasz kernel jest w formacie elf. wartości poszczególnych elementów podajemy z etykiem assemblera lub wartości linkera (patrz, plik link.ld). Proponuje nie zwracać na format tego nagłówka większej uwagi, jednak jeśli koniecznie chcesz wiedzieć co to wszystko oznacza to polecam dokumentacje GRUBa lub inne materiały dotyczące standardu multiboot. Kod:#include "draco.h"
void k_main(multiboot_info_t *multiboot_info)
{
print("Draco (minix) compilation "__DATE__" "__TIME__" is starting.", 8);
#ifdef DEBUG
print("System is in DEBUG mode.", 8);
#endif
print_cpuid();
init_gdt();
init_idt();
init_irq(0x20, 0x28);
sti();
init_paging();
/* testujemy przerwania */
__asm__ __volatile__ ("int $0x80");
print("System is running.", 8);
while(1);
}Powinieneś zauważyć pewną rzecz, która nie była wyjaśniona. Tak więc jak wiesz lub nie, ilość pamięci najlepiej sprawdzać w 16 bitowym trybie rzeczywistym gdzie mamy łatwy dostęp do BIOSa poprzez przerwania, nasz system od samego początku jest 32 bitowy więc tego zrobić nie może, z pomocą przychodzi GRUB i standard multiboot. GRUB tworzy dla nas strukture zdefiniowaną przez nas jako multiboot_info_t i po uruchomieniu kernela podaje jej adres w rejestrze EBX. Kod:-fno-leading-underscore Niech ktos tylko powie, że żadna nowość... pytałem o to :> |