Kako razviti operativni sustav za računalo. Os - Što je potrebno za pisanje operativnog sustava? Kako napraviti vlastiti operativni sustav


Ako prijeđemo na stvar...

OS je stvar koja implementira multitasking (obično) i upravlja raspodjelom resursa između tih zadataka i općenito. Potrebno je osigurati da zadaci ne mogu naštetiti jedni drugima i da rade u različitim područjima memorije i rade s uređajima barem naizmjenično. Također morate osigurati mogućnost prijenosa poruka s jednog zadatka na drugi.

Također, OS, ako postoji dugoročna memorija, mora omogućiti pristup njoj: to jest, osigurati sve funkcije za rad s datotečnim sustavom. To je minimum.

Gotovo posvuda, prvi kod za pokretanje mora biti napisan u asemblerskom jeziku - postoji mnogo pravila o tome gdje bi trebao biti, kako bi trebao izgledati, što bi trebao raditi i koju veličinu ne bi trebao premašiti.

Za PC je potrebno napisati bootloader na ASMA-i, koji će pozvati BIOS i koji bi trebao, ne prelazeći četiri i par stotina bajtova, učiniti nešto i pokrenuti glavni OS - prebaciti kontrolu na glavni kod, koji u bliska budućnost se može napisati u C.

Za ARM je potrebno napraviti tablicu prekida na ACMA (reset, razne greške, IRQ, FIQ prekidi itd.) i prenijeti kontrolu na glavni kod. Iako je u mnogim razvojnim okruženjima takav kod dostupan za gotovo svaki kontroler.

Odnosno, za ovo vam je potrebno:

  1. Poznavati asemblerski jezik ciljane platforme.
  2. Poznavati arhitekturu procesora i sve vrste servisnih naredbi i registara kako biste ga konfigurirali za rad u željenom modu. U PC-u je to prijelaz u zaštićeni mod, na primjer, ili u 64-bitni mod... U ARM-u je to podešavanje takta jezgre i periferije.
  3. Znajte točno kako će se OS pokrenuti, gdje i kako trebate gurati svoj kod.
  4. Poznavanje jezika C - teško je pisati veliki kod u Asmi bez iskustva, održavanje istog bit će još teže. Stoga trebate napisati kernel u C-u.
  5. Poznavati principe rada OS-a. Pa, ima puno knjiga na ruskom o ovoj temi, iako ne znam jesu li sve dobre.
  6. Imajte puno, puno strpljenja i upornosti. Bit će grešaka i trebat će ih pronaći i ispraviti. Također ćete morati puno čitati.
  7. Imajte puno, puno vremena.

Unaprijediti. Recimo da ste nešto napisali. Moramo testirati ovu stvar. Ili vam je potreban fizički uređaj na kojem će se odvijati eksperimenti (ploča za ispravljanje pogrešaka, drugo računalo) ili emulator za to. Drugi je obično lakši i brži za korištenje. Za PC, na primjer, VMWare.

Na Internetu također ima dosta članaka o ovoj temi, ako dobro pretražite. Također postoji mnogo primjera gotovih operativnih sustava s izvornim kodom.

Ako baš želite, možete čak pogledati i izvorni kod stare jezgre NT sustava (Windows), kako zasebno (koji je objavio Microsoft, s komentarima i raznim vrstama referentnih materijala), tako i zajedno sa starim OS (procurio).

Razvoj kernela se s pravom smatra nimalo lakim zadatkom, ali svatko može napisati jednostavan kernel. Da biste iskusili čari hakiranja kernela, samo trebate slijediti neke konvencije i savladati asemblerski jezik. U ovom ćemo vam članku pokazati kako to učiniti.


Pozdrav svijete!

Napišimo kernel koji će se pokretati preko GRUB-a na x86-kompatibilnim sustavima. Naš prvi kernel će prikazati poruku na ekranu i tu će stati.

Kako se pokreću x86 strojevi

Prije nego razmislimo o tome kako napisati kernel, pogledajmo kako se računalo pokreće i prenosi kontrolu na kernel. Većina registara procesora x86 ima specifične vrijednosti nakon pokretanja. Registar pokazivača instrukcija (EIP) sadrži adresu instrukcije koju će izvršiti procesor. Njegova čvrsto kodirana vrijednost je 0xFFFFFFF0. To jest, x86 procesor će uvijek započeti s izvršenjem s fizičke adrese 0xFFFFFFF0. Ovo je zadnjih 16 bajtova 32-bitnog adresnog prostora. Ova adresa se naziva vektor resetiranja.

Memorijska kartica sadržana u čipsetu navodi da se adresa 0xFFFFFFF0 odnosi na određeni dio BIOS-a, a ne na RAM. Međutim, BIOS se kopira u RAM za brži pristup - ovaj se proces naziva "sjenčanje", stvaranje kopije u sjeni. Dakle, adresa 0xFFFFFFF0 sadržavat će samo upute za skok na mjesto u memoriji gdje se BIOS kopirao.

Dakle, BIOS se počinje izvršavati. Prvo traži uređaje s kojih se može pokrenuti prema redoslijedu navedenom u postavkama. Provjerava medij za "magični broj" koji razlikuje diskete za podizanje sustava od običnih: ako su bajtovi 511 i 512 u prvom sektoru 0xAA55, tada se disk može pokrenuti.

Jednom kada BIOS pronađe uređaj za pokretanje, kopirat će sadržaj prvog sektora u RAM, počevši od adrese 0x7C00, a zatim premjestiti izvršenje na tu adresu i započeti s izvršavanjem koda koji je upravo učitao. Taj se kod naziva bootloader.

Bootloader učitava kernel na fizičkoj adresi 0x100000. To je ono što većina popularnih x86 kernela koristi.

Svi procesori kompatibilni s x86 počinju u primitivnom 16-bitnom načinu rada koji se naziva "stvarni način". GRUB pokretački program prebacuje procesor u 32-bitni zaštićeni način rada postavljanjem donjeg bita CR0 registra na jedan. Stoga se kernel počinje učitavati u 32-bitnom zaštićenom načinu rada.

Imajte na umu da GRUB, u slučaju Linux kernela, odabire odgovarajući protokol za pokretanje i pokreće kernel u stvarnom načinu rada. Linux kerneli se automatski prebacuju u zaštićeni način rada.

Što trebamo

  • x86 kompatibilno računalo (očito)
  • Linux
  • NASM asembler,
  • ld (GNU Linker),
  • GRUB.

Ulazna točka asemblerskog jezika

Htjeli bismo, naravno, sve napisati u C-u, ali nećemo moći potpuno izbjeći korištenje asemblera. Napisat ćemo malu datoteku u x86 asembleru koja će postati početna točka za naš kernel. Sve što će asemblerski kod učiniti jest pozvati vanjsku funkciju koju ćemo napisati u C-u, a zatim zaustaviti izvršavanje programa.

Kako asemblerski kod možemo učiniti početnom točkom za naš kernel? Koristimo skriptu povezivača koja povezuje objektne datoteke i stvara konačnu izvršnu datoteku kernela (dolje ću objasniti više). U ovoj skripti ćemo izravno naznačiti da želimo da se naša binarna datoteka preuzme na adresi 0x100000. Ovo je adresa, kao što sam već napisao, na kojoj bootloader očekuje da će vidjeti ulaznu točku u kernel.

Ovdje je asemblerski kod.

kernel.asm
bitovi 32 odjeljak .text global start extern kmain start: cli mov esp, stack_space poziv kmain hlt odjeljak .bss resb 8192 stack_space:

Prva instrukcija od 32 bita nije x86 asembler, već NASM direktiva koja mu govori da generira kod za rad procesora u 32-bitnom načinu rada. Ovo nije potrebno za naš primjer, ali dobra je praksa to eksplicitno naznačiti.

Drugi redak započinje tekstualni odjeljak, također poznat kao odjeljak koda. Sav naš kod ide ovdje.

global je još jedna NASM direktiva, ona proglašava simbole u našem kodu globalnima. Ovo će omogućiti povezivaču da pronađe početni simbol, koji služi kao naša ulazna točka.

kmain je funkcija koja će biti definirana u našoj datoteci kernel.c. extern izjavljuje da je funkcija deklarirana negdje drugdje.

Slijedi start funkcija, koja poziva kmain i zaustavlja procesor s hlt instrukcijom. Prekidi mogu probuditi procesor nakon hlt-a, tako da prvo onemogućujemo prekide cli (clear interrupts) instrukcijom.

U idealnom slučaju, trebali bismo dodijeliti određenu količinu memorije za stog i usmjeriti pokazivač stoga (esp) na njega. Čini se da GRUB to ionako radi za nas, au ovom trenutku je pokazivač stoga već postavljen. Ipak, za svaki slučaj, dodijelimo nešto memorije u BSS odjeljku i usmjerimo pokazivač na stog na njegov početak. Koristimo instrukciju resb - ona rezervira memoriju navedenu u bajtovima. Tada se ostavlja oznaka koja označava rub rezerviranog dijela memorije. Neposredno prije nego što se pozove kmain, pokazivač stoga (esp) usmjerava se na ovo područje instrukcijom mov.

Kernel u C

U datoteci kernel.asm pozvali smo funkciju kmain(). Dakle, u C kodu, izvršenje će započeti od tamo.

jezgra.c
void kmain(void) ( const char *str = "moj prvi kernel"; char *vidptr = (char*)0xb8000; unsigned int i = 0; unsigned int j = 0; while(j< 80 * 25 * 2) { vidptr[j] = " "; vidptr = 0x07; j = j + 2; } j = 0; while(str[j] != "\0") { vidptr[i] = str[j]; vidptr = 0x07; ++j; i = i + 2; } return; }

Sve što će naš kernel učiniti je obrisati ekran i ispisati red moj prvi kernel.

Prvo, kreiramo vidptr pokazivač koji pokazuje na adresu 0xb8000. U zaštićenom načinu rada ovo je početak video memorije. Memorija tekstualnog zaslona jednostavno je dio adresnog prostora. Dio memorije dodijeljen je za ulaz/izlaz ekrana, koji počinje na adresi 0xb8000; u njemu se nalazi 25 redaka od 80 ASCII znakova.

Svaki znak u tekstualnoj memoriji predstavljen je sa 16 bita (2 bajta), umjesto 8 bita (1 bajt) na koje smo navikli. Prvi bajt je ASCII kod znaka, a drugi bajt je bajt atributa. Ovo je definicija formata znakova, uključujući njegovu boju.

Da bismo ispisali znak s zeleno na crno, moramo staviti s u prvi bajt video memorije i vrijednost 0x02 u drugi bajt. 0 ovdje znači crnu pozadinu, a 2 znači zelenu boju. Koristit ćemo svijetlo sivu boju, njen kod je 0x07.

U prvoj while petlji program ispunjava svih 25 redaka od 80 znakova praznim znakovima s atributom 0x07. Ovo će obrisati zaslon.

U drugoj petlji while, null-terminirani niz moj prvi kernel zapisuje se u video memoriju i svaki znak prima bajt atributa 0x07. Ovo bi trebalo ispisati niz.

Izgled

Sada moramo prevesti kernel.asm u objektnu datoteku pomoću NASM-a, a zatim koristiti GCC za prevođenje kernel.c u drugu objektnu datoteku. Naš zadatak je povezati te objekte u izvršni kernel pogodan za učitavanje. Da bismo to učinili, morat ćemo napisati skriptu za povezivač (ld), koju ćemo proslijediti kao argument.

veza.ld
OUTPUT_FORMAT(elf32-i386) ENTRY(start) SECTIONS ( . = 0x100000; .text: ( *(.text) ) .data: ( *(.data) ) .bss: ( *(.bss) ) )

Ovdje prvo postavljamo format (OUTPUT_FORMAT) naše izvršne datoteke na 32-bitni ELF (Executable and Linkable Format), standardni binarni format za sustave temeljene na Unixu za x86 arhitekturu.

ULAZ uzima jedan argument. Određuje naziv simbola koji će služiti kao ulazna točka izvršne datoteke.

SECTIONS je za nas najvažniji dio. Ovdje definiramo izgled naše izvršne datoteke. Možemo odrediti kako će se različiti odjeljci kombinirati i gdje će svaki od njih biti postavljen.

U vitičastim zagradama koje slijede izraz SECTIONS, točka označava brojač mjesta. Automatski se inicijalizira na 0x0 na početku bloka SECTIONS, ali se može promijeniti dodjeljivanjem nove vrijednosti.

Ranije sam napisao da kod kernela treba započeti na adresi 0x100000. Zbog toga brojaču pozicija dodjeljujemo vrijednost 0x100000.

Pogledajte liniju.tekst: ( *(.tekst) ) . Zvjezdica ovdje označava masku koja može odgovarati bilo kojem nazivu datoteke. Sukladno tome, izraz *(.text) označava sve ulazne .text odjeljke u svim ulaznim datotekama.

Kao rezultat toga, povezivač će spojiti sve tekstualne dijelove svih objektnih datoteka u tekstualni odjeljak izvršne datoteke i smjestiti ga na adresu navedenu u brojaču položaja. Dio koda naše izvršne datoteke počet će na adresi 0x100000.

Nakon što povezivač proizvede tekstualni odjeljak, vrijednost brojača položaja bit će 0x100000 plus veličina tekstualnog odjeljka. Slično tome, podaci i bss odjeljci će se spojiti i postaviti na adresu koju je dao brojač položaja.

GRUB i multiboot

Sada su sve naše datoteke spremne za izgradnju kernela. Ali budući da ćemo pokretati kernel koristeći GRUB, preostao je još jedan korak.

Postoji standard za učitavanje različitih x86 kernela pomoću bootloadera. To se naziva "multiboot specifikacija". GRUB će učitati samo kernele koji mu odgovaraju.

Prema ovoj specifikaciji, kernel može sadržavati zaglavlje (Multiboot zaglavlje) u prvih 8 kilobajta. Ovo zaglavlje mora sadržavati tri polja:

  • magija- sadrži “magični” broj 0x1BADB002, kojim se identificira zaglavlje;
  • zastave- ovo polje nam nije važno, možete ga ostaviti na nuli;
  • kontrolni zbroj- kontrolni zbroj, trebao bi dati nulu ako se doda u magična polja i polja zastavica.

Naša datoteka kernel.asm sada će izgledati ovako.

kernel.asm
bitovi 32 odjeljak .text ;multiboot spec align 4 dd 0x1BADB002 ;magic dd 0x00 ;flags dd - (0x1BADB002 + 0x00) ;checksum global start extern kmain start: cli mov esp, stack_space poziv kmain hlt section .bss resb 8192 stack_space:

Instrukcija dd navodi dvostruku riječ od 4 bajta.

Sastavljanje kernela

Dakle, sve je spremno za stvaranje objektne datoteke iz kernel.asm i kernel.c i njihovo povezivanje pomoću naše skripte. U konzoli pišemo:

$ nasm -f elf32 kernel.asm -o kasm.o

Pomoću ove naredbe asembler će kreirati datoteku kasm.o u ELF-32 bitnom formatu. Sada je red na GCC:

$ gcc -m32 -c kernel.c -o kc.o

Parametar -c označava da datoteka ne mora biti povezana nakon kompilacije. Učinit ćemo to sami:

$ ld -m elf_i386 -T link.ld -o kernel kasm.o kc.o

Ova naredba će pokrenuti povezivač s našom skriptom i generirati izvršnu datoteku pod nazivom kernel.

UPOZORENJE

Hakiranje kernela najbolje je raditi u virtualnom okruženju. Za pokretanje kernela u QEMU umjesto GRUB-a, koristite naredbu qemu-system-i386 -kernel kernel .

Postavljanje GRUB-a i pokretanje kernela

GRUB zahtijeva da naziv kernel datoteke slijedi nakon kernel-a<версия>. Dakle, preimenujmo datoteku - ja ću svoju nazvati kernel-701.

Sada stavljamo kernel u /boot direktorij. Ovo će zahtijevati privilegije superkorisnika.

Morat ćete dodati nešto poput ovoga GRUB konfiguracijskoj datoteci grub.cfg:

Naslov myKernel root (hd0,0) kernel /boot/kernel-701 ro

Ne zaboravite ukloniti direktivu skrivenog izbornika ako je uključena.

GRUB 2

Za pokretanje kernela koji smo stvorili u GRUB-u 2, koji se standardno isporučuje u novim distribucijama, vaša bi konfiguracija trebala izgledati ovako:

Stavka izbornika "kernel 701" ( set root="hd0,msdos1" multiboot /boot/kernel-701 ro )

Hvala Rubenu Laguani na ovom dodatku.

Ponovno pokrenite računalo i trebali biste vidjeti svoj kernel na popisu! A kada ga odaberete, vidjet ćete tu istu liniju.



Ovo je vaša srž!

Pisanje kernela s podrškom za tipkovnicu i zaslon

Završili smo rad na minimalnom kernelu koji se pokreće putem GRUB-a, radi u zaštićenom načinu rada i ispisuje jedan redak na ekranu. Vrijeme je da ga proširite i dodate upravljački program za tipkovnicu koji će čitati znakove s tipkovnice i prikazivati ​​ih na ekranu.

Komunicirat ćemo s I/O uređajima preko I/O portova. U biti, to su samo adrese na I/O sabirnici. Postoje posebne upute procesora za operacije čitanja i pisanja.

Rad s portovima: čitanje i izlaz

read_port: mov edx, in al, dx ret write_port: mov edx, mov al, out dx, al ret

I/O priključcima se pristupa pomoću ulaznih i izlaznih uputa uključenih u x86 set.

U read_port, broj porta se prosljeđuje kao argument. Kada prevodilac pozove funkciju, gura sve argumente na stog. Argument se kopira u edx registar pomoću pokazivača stoga. Dx registar je nižih 16 bitova edx registra. Uputa in ovdje čita broj porta zadan u dx i stavlja rezultat u al. Al registar je nižih 8 bitova eax registra. Možda se sjećate s koledža da se vrijednosti koje vraćaju funkcije prosljeđuju kroz eax registar. Dakle read_port nam omogućuje čitanje s I/O portova.

Funkcija write_port radi na sličan način. Uzimamo dva argumenta: broj porta i podatke koji će biti upisani. Out instrukcija upisuje podatke u port.

Prekidi

Sada, prije nego što se vratimo na pisanje upravljačkog programa, moramo razumjeti kako procesor zna da je jedan od uređaja izvršio operaciju.

Najjednostavnije rješenje je anketiranje uređaja - kontinuirano provjeravanje njihovog statusa u krugu. To je, iz očitih razloga, neučinkovito i nepraktično. Dakle, ovdje prekidi stupaju na scenu. Prekid je signal koji uređaj ili program šalje procesoru koji pokazuje da se dogodio događaj. Korištenjem prekida možemo izbjeći potrebu prozivanja uređaja i odgovarat ćemo samo na događaje koji nas zanimaju.

Čip nazvan Programmable Interrupt Controller (PIC) odgovoran je za prekide u x86 arhitekturi. Obrađuje hardverske prekide i rute te ih pretvara u odgovarajuće sistemske prekide.

Kada korisnik učini nešto s uređajem, impuls koji se zove Zahtjev za prekid (IRQ) šalje se na PIC čip. PIC prevodi primljeni prekid u sistemski prekid i šalje poruku procesoru da je vrijeme da zaustavi ono što radi. Daljnje rukovanje prekidima zadatak je kernela.

Bez PIC-a, morali bismo ispitati sve uređaje prisutne u sustavu da vidimo je li se dogodio događaj koji uključuje neki od njih.

Pogledajmo kako to funkcionira s tipkovnicom. Tipkovnica visi na portovima 0x60 i 0x64. Port 0x60 šalje podatke (kada se pritisne gumb), a port 0x64 šalje status. Međutim, moramo znati kada točno čitati te portove.

Prekidi ovdje dobro dođu. Kada se tipka pritisne, tipkovnica šalje PIC signal kroz IRQ1 liniju prekida. PIC pohranjuje vrijednost pomaka spremljenu tijekom inicijalizacije. Dodaje ulazni broj retka ovoj podpuni kako bi formirao vektor prekida. Procesor zatim traži podatkovnu strukturu koja se naziva Tablica deskriptora prekida (IDT) kako bi rukovatelju prekida dao adresu koja odgovara njegovom broju.

Kod na toj adresi se tada izvršava i obrađuje prekid.

Postavite IDT

struct IDT_entry( unsigned short int offset_lowerbits; unsigned short int selektor; unsigned char zero; unsigned char type_attr; unsigned short int offset_higherbits; ); struct IDT_entry IDT; VOID IDT_INIT (VOID) (UNSIGNED LONG KEYboard_ADDRESS; UNSIGNED LONG IDTRA; KEYboard_ADDRESS = (UNSIGNED LONG) KEYboard_HANDER; .OFSET_LWERBITS = keyboard_ADDRESS & 0XFFF; ; IDT.type_attr = 0x8e; /* INTERRUPT_ GATE */ IDT.offset_address & 0xffff0000 >> write_port(0xA0, 0x11); write_port(0xA1, 0xff); IDT_ptr (struct IDT_SIZE) + ((idt_adresa & 0xffff)<< 16); idt_ptr = idt_address >> 16; load_idt(idt_ptr); )

IDT je ​​niz IDT_entry struktura. Kasnije ćemo raspravljati o povezivanju prekida tipkovnice s rukovateljem, ali sada pogledajmo kako radi PIC.

Moderni x86 sustavi imaju dva PIC čipa, svaki s osam ulaznih linija. Nazvat ćemo ih PIC1 i PIC2. PIC1 prima IRQ0 do IRQ7, a PIC2 prima IRQ8 do IRQ15. PIC1 koristi port 0x20 za naredbe i 0x21 za podatke, a PIC2 koristi port 0xA0 za naredbe i 0xA1 za podatke.

Oba PIC-a se inicijaliziraju osmobitnim riječima koje se nazivaju naredbene riječi inicijalizacije (ICW).

U zaštićenom načinu, oba PIC-a prvo trebaju izdati naredbu za inicijalizaciju ICW1 (0x11). Kaže PIC-u da pričeka da na podatkovni port stignu još tri inicijalizacijske riječi.

Ove naredbe će proći PIC:

  • vektor uvlake (ICW2),
  • koji su glavni/podređeni odnosi između PIC-ova (ICW3),
  • dodatne informacije o okolišu (ICW4).

Druga naredba za inicijalizaciju (ICW2) također se šalje na ulaz svakog PIC-a. Dodjeljuje offset, što je vrijednost kojoj dodajemo broj retka da bismo dobili broj prekida.

PIC-ovi dopuštaju da se njihovi pinovi međusobno kaskadno povezuju s ulazima. To se radi pomoću ICW3 i svaki bit predstavlja kaskadni status za odgovarajući IRQ. Sada nećemo koristiti kaskadno preusmjeravanje i postavit ćemo ga na nulu.

ICW4 specificira dodatne parametre okoliša. Trebamo samo definirati niski bit tako da PIC-ovi znaju da radimo u 80x86 modu.

Ta-dam! PIC-ovi su sada inicijalizirani.

Svaki PIC ima interni osmobitni registar koji se naziva registar maske prekida (IMR). Pohranjuje bitmapu IRQ linija koje idu na PIC. Ako je bit postavljen, PIC ignorira zahtjev. To znači da možemo omogućiti ili onemogućiti određenu IRQ liniju postavljanjem odgovarajuće vrijednosti na 0 ili 1.

Čitanje s podatkovnog priključka vraća vrijednost u IMR registar, dok pisanje mijenja registar. U našem kodu, nakon inicijalizacije PIC-a, postavljamo sve bitove na jedan, što deaktivira sve IRQ linije. Kasnije ćemo aktivirati linije koje odgovaraju prekidima tipkovnice. Ali prvo ga isključimo!

Ako IRQ linije rade, naši PIC-ovi mogu primiti signale na IRQ-u i pretvoriti ih u broj prekida, dodajući pomak. Moramo ispuniti IDT na takav način da broj prekida koji dolazi s tipkovnice odgovara adresi funkcije rukovatelja koju ćemo napisati.

Za koji broj prekida trebamo vezati rukovatelja tipkovnice u IDT-u?

Tipkovnica koristi IRQ1. Ovo je ulazni red 1 i obrađuje ga PIC1. Inicijalizirali smo PIC1 s pomakom 0x20 (vidi ICW2). Da biste dobili broj prekida, trebate dodati 1 i 0x20, dobit ćete 0x21. To znači da će adresa rukovatelja tipkovnice biti vezana u IDT-u za prekid 0x21.

Zadatak se svodi na ispunjavanje IDT za prekid 0x21. Preslikat ćemo ovaj prekid u funkciju keyboard_handler, koju ćemo napisati u asemblersku datoteku.

Svaki unos u IDT sastoji se od 64 bita. U unosu koji odgovara prekidu ne spremamo cijelu adresu funkcije rukovatelja. Umjesto toga, podijelili smo ga na dva 16-bitna dijela. Bitovi nižeg reda pohranjeni su u prvih 16 bitova IDT unosa, a 16 bitova visokog reda pohranjeni su u zadnjih 16 bitova unosa. Sve je to učinjeno radi kompatibilnosti s 286 procesorima. Kao što vidite, Intel redovito proizvodi ovakve brojke i to na mnogo, mnogo mjesta!

U IDT unosu, samo moramo registrirati tip, pokazujući da se sve ovo radi kako bi se uhvatio prekid. Također moramo postaviti pomak segmenta kernel koda. GRUB nam postavlja GDT. Svaki GDT unos dugačak je 8 bajtova, gdje je deskriptor koda jezgre drugi segment, tako da će njegov pomak biti 0x08 (pojedinosti su izvan opsega ovog članka). Vrata prekida su predstavljena kao 0x8e. Preostalih 8 bitova u sredini popunjeno je nulama. Na ovaj način ćemo popuniti IDT unos koji odgovara prekidu tipkovnice.

Nakon što smo završili s IDT mapiranjem, moramo reći procesoru gdje je IDT. Za ovo postoji instrukcija asemblera koja se zove lidt; Ovo je pokazivač na deskriptor strukture koja opisuje IDT.

Nema poteškoća s deskriptorom. Sadrži veličinu IDT-a u bajtovima i njegovu adresu. Koristio sam niz da ga učinim kompaktnijim. Na isti način možete ispuniti deskriptor pomoću strukture.

U varijabli idr_ptr imamo pokazivač koji prosljeđujemo instrukciji lidt u funkciji load_idt().

Load_idt: mov edx, lidt sti ret

Dodatno, funkcija load_idt() vraća prekid kada se koristi instrukcija sti.

S IDT-om popunjenim i učitanim, možemo pristupiti IRQ-u tipkovnice pomoću maske prekida o kojoj smo ranije govorili.

Void kb_init(void) (write_port(0x21, 0xFD); )

0xFD je 11111101 - omogući samo IRQ1 (tipkovnica).

Funkcija - rukovatelj prekidima tipkovnice

Dakle, uspješno smo vezali prekide tipkovnice na funkciju keyboard_handler stvaranjem IDT unosa za prekid 0x21. Ova funkcija će se pozvati svaki put kada pritisnete gumb.

Keyboard_handler: poziv keyboard_handler_main iretd

Ova funkcija poziva drugu funkciju napisanu u C-u i vraća kontrolu koristeći iret instrukcije klase. Ovdje bismo mogli napisati cijeli naš rukovatelj, ali mnogo je lakše kodirati u C-u, pa idemo tamo. Naredbe iret/iretd trebale bi se koristiti umjesto ret kada se kontrola vraća s funkcije rukovanja prekidima na prekinuti program. Ova klasa instrukcija podiže registar zastava, koji se gura na stog kada se pozove prekid.

Void keyboard_handler_main(void) ( unsigned char status; char keycode; /* Napiši EOI */ write_port(0x20, 0x20); status = read_port(KEYBOARD_STATUS_PORT); /* Niži bit statusa bit će postavljen ako međuspremnik nije prazan */ if (status & 0x01) ( keycode = read_port(KEYBOARD_DATA_PORT); if(keycode< 0) return; vidptr = keyboard_map; vidptr = 0x07; } }

Ovdje prvo dajemo EOI (End Of Interrupt) signal upisujući ga u PIC port za naredbe. Tek tada će PIC dopustiti daljnje zahtjeve za prekid. Moramo očitati dva porta: podatkovni port 0x60 i naredbeni port (aka statusni port) 0x64.

Prije svega, čitamo port 0x64 da bismo dobili status. Ako je donji bit statusa nula, tada je međuspremnik prazan i nema podataka za čitanje. U drugim slučajevima možemo pročitati podatkovni port 0x60. Dat će nam šifru pritisnute tipke. Svaki kod odgovara jednom gumbu. Koristimo jednostavan niz znakova definiran u keyboard_map.h za preslikavanje kodova na odgovarajuće znakove. Simbol se tada prikazuje na zaslonu koristeći istu tehniku ​​koju smo koristili u prvoj verziji kernela.

Kako bi kod bio jednostavan, obrađujem samo mala slova od a do z i brojeve od 0 do 9. Možete jednostavno dodati posebne znakove, Alt, Shift i Caps Lock. Možete saznati da je tipka pritisnuta ili otpuštena iz izlaza porta za naredbe i izvršiti odgovarajuću akciju. Na isti način možete sve tipkovničke prečace povezati s posebnim funkcijama poput isključivanja.

Sada možete izgraditi kernel i pokrenuti ga na stvarnom stroju ili na emulatoru (QEMU) na isti način kao u prvom dijelu.

Čitajući Habr u posljednje dvije godine, vidio sam samo nekoliko pokušaja razvoja OS-a (konkretno: od korisnika i (odgođen na neodređeno vrijeme) i (nije napušten, ali za sada više izgleda kao opis rada zaštićenog načina rada) x86-kompatibilnih procesora, što nedvojbeno također trebate znati da biste napisali OS za x86 (iako ne od nule, iako u tome nema ništa loše, možda čak i suprotno)) . Iz nekog razloga, mislim da su gotovo svi programeri sustava (i nekih aplikacija) barem jednom razmišljali o pisanju vlastitog operativnog sustava. S tim u vezi, 3 OS-a iz velike zajednice ovog resursa izgledaju smiješno. Očigledno, većina onih koji razmišljaju o vlastitom OS-u ne ide dalje od ideje, mali dio prestane nakon što napiše bootloader, nekolicina napiše dijelove kernela, a samo oni beznadno tvrdoglavi stvaraju nešto što nejasno podsjeća na OS (u usporedbi s nečim poput Windows/Linux) . Postoji mnogo razloga za to, ali glavni je po mom mišljenju taj što ljudi odustaju od razvoja (neki i prije nego što su počeli) zbog malog broja opisa procesa pisanja i debugiranja OS-a, što je sasvim drugačije od onoga što se događa tijekom razvoja aplikacijskog softvera.

Ovom kratkom napomenom želio bih pokazati da ako počnete ispravno, onda nema ništa posebno teško u razvoju vlastitog OS-a. Ispod presjeka je kratak i prilično općenit vodič za pisanje OS-a od nule.

Kako Nema potrebe započeti
Molimo vas da sljedeći tekst ne shvatite kao eksplicitnu kritiku bilo čijih članaka ili vodiča za pisanje OS-a. Samo što se prečesto u takvim člancima pod glasnim naslovima naglasak stavlja na implementaciju nekog minimalnog obradaka, a on se predstavlja kao prototip kernela. Zapravo, trebali biste razmišljati o strukturi kernela i interakciji dijelova OS-a kao cjeline, te taj prototip smatrati standardnom aplikacijom “Hello, World!” u svijetu aplikacijskog softvera. Kao malo opravdanje za ove komentare, treba reći da ispod postoji podsekcija “Hello, World!”, kojoj se u ovom slučaju posvećuje točno onoliko pažnje koliko je potrebno, i ne više.

Nema potrebe pisati bootloader. Pametni ljudi su smislili Multiboot specifikaciju, implementirali je i detaljno opisali što je to i kako se njime koristiti. Ne želim se ponavljati, samo ću reći da djeluje, olakšava život i treba ga koristiti. Usput, bolje je pročitati specifikaciju u cijelosti, mala je i čak sadrži primjere.

Nema potrebe pisati OS u potpunosti u asembleru. To i nije tako loše, naprotiv - brzi i mali programi uvijek će biti na visokoj cijeni. Samo što ovaj jezik zahtijeva znatno više truda za razvoj, korištenje asemblera samo će dovesti do smanjenja entuzijazma i, kao rezultat toga, bacanja OS izvornika u dugu kutiju.

Nema potrebe učitavati prilagođeni font u video memoriju i prikazivati ​​bilo što na ruskom. Ovo nema smisla. Mnogo je lakše i svestranije koristiti engleski, a mijenjanje fonta ostaviti za kasnije, učitavajući ga s tvrdog diska putem upravljačkog programa datotečnog sustava (istovremeno će biti dodatni poticaj da učinite više od samog početka).

Priprema
Za početak, kao i uvijek, trebali biste se upoznati s općom teorijom kako biste imali neku ideju o nadolazećem opsegu posla. Dobri izvori o predmetu koji se razmatra su knjige E. Tanenbauma, koje su već spomenute u drugim člancima o pisanju OS-a na Habréu. Tu su i članci koji opisuju postojeće sustave, a tu su i razni vodiči/mailingovi/članci/primjeri/stranice s naglaskom na razvoj OS-a, od kojih su poveznice na neke navedene na kraju članka.

Nakon početnog obrazovnog programa, trebate odlučiti o glavnim pitanjima:

  • ciljna arhitektura - x86 (stvarni/zaštićeni/dugi način), PowerPC, ARM, ...
  • kernel/OS arhitektura - monolit, modularni monolit, mikrokernel, egzokernel, razni hibridi
  • jezik i njegov kompajler - C, C++, ...
  • Format datoteke kernela - elf, a.out, coff, binary, ...
  • razvojno okruženje (da, i ovo igra važnu ulogu) - IDE, vim, emacs, ...
Zatim biste trebali produbiti svoje znanje prema odabranom i to u sljedećim područjima:
  • video memorija i rad s njom - izlaz kao dokaz rada nužan je od samog početka
  • HAL (sloj hardverske apstrakcije) - čak i ako postoji podrška za nekoliko hardverskih arhitektura i ne postoje planovi za pravilno odvajanje dijelova najniže razine jezgre od implementacije takvih apstraktnih stvari kao što su procesi, semafori i tako dalje, to će ne biti suvišan
  • upravljanje memorijom - fizičkom i virtualnom
  • upravljanje izvršenjem - procesi i niti, njihovo planiranje
  • upravljanje uređajima - upravljački programi
  • virtualni datotečni sustavi - za pružanje jedinstvenog sučelja za sadržaj različitih datotečnih sustava
  • API (Application Programming Interface) - kako će točno aplikacije pristupiti kernelu
  • IPC (Interprocess Communication) - procesi će prije ili kasnije morati komunicirati
Alati
Uzimajući u obzir odabrani jezik i razvojne alate, trebali biste odabrati skup uslužnih programa i njihovih postavki koji će u budućnosti pisanjem skripti omogućiti što lakšu i bržu izgradnju, pripremu slike i pokretanje virtualnog stroja s projekt. Pogledajmo pobliže svaku od ovih točaka:
  • svi standardni alati su prikladni za asembler, kao što su make, cmake,... Ovdje se mogu koristiti skripte za povezivač i (posebno napisani) uslužni programi za dodavanje Multiboot zaglavlja, kontrolnih zbrojeva ili za bilo koje druge svrhe.
  • Pod pripremom slike podrazumijevamo njezino montiranje i kopiranje datoteka. U skladu s tim, format slikovne datoteke mora biti odabran tako da ga podržava i uslužni program za montiranje/kopiranje i virtualni stroj. Naravno, nitko ne zabranjuje izvođenje radnji iz ove točke bilo kao završni dio sklopa, bilo kao pripremu za pokretanje emulatora. Sve ovisi o specifičnim sredstvima i odabranim opcijama za njihovu upotrebu.
  • pokretanje virtualnog stroja nije teško, ali morate zapamtiti da prvo demountate sliku (demontiranje u ovom trenutku, jer nema stvarne svrhe u ovoj operaciji prije pokretanja virtualnog stroja). Također bi bilo korisno imati skriptu za pokretanje emulatora u debug modu (ako je dostupan).
Ako su svi prethodni koraci završeni, trebali biste napisati minimalni program koji će se pokrenuti kao kernel i ispisati nešto na ekran. Ako se otkriju nepogodnosti ili nedostaci odabranog sredstva, potrebno ih je (nedostatke) otkloniti ili, u najgorem slučaju, uzeti zdravo za gotovo.

U ovom koraku morate provjeriti što više značajki razvojnih alata koje planirate koristiti u budućnosti. Na primjer, učitavanje modula u GRUB ili korištenje fizičkog diska/particije/flash pogona u virtualnom stroju umjesto slike.

Nakon što je ova faza uspješna, počinje pravi razvoj.

Pružanje podrške za vrijeme izvođenja
Budući da se predlaže pisanje na jezicima visoke razine, treba voditi računa o pružanju podrške za neke od jezičnih značajki koje obično implementiraju autori paketa prevoditelja. Na primjer, za C++, to uključuje:
  • funkcija za dinamičku alokaciju bloka podataka na stogu
  • rad s gomilom
  • funkcija kopiranja bloka podataka (memcpy)
  • funkcija je ulazna točka u program
  • pozivi konstruktorima i destruktorima globalnih objekata
  • niz funkcija za rad s iznimkama
  • stub za nerealizirane čiste virtualne funkcije
Kada pišete "Hello, World!" Nepostojanje ovih funkcija možda se neće osjetiti, ali kako se kod dodaje, povezivač će se početi žaliti na nezadovoljene ovisnosti.

Naravno, treba spomenuti i standardnu ​​knjižnicu. Potpuna implementacija nije potrebna, ali vrijedan je implementacije temeljnog podskupa funkcionalnosti. Tada će pisanje koda biti mnogo poznatije i brže.

Otklanjanje pogrešaka
Ne gledajte što je rečeno o otklanjanju pogrešaka na kraju članka. Zapravo, ovo je vrlo ozbiljan i težak problem u razvoju OS-a, budući da konvencionalni alati ovdje nisu primjenjivi (uz neke iznimke).

Možemo preporučiti sljedeće:

  • podrazumijeva se, ispravljanje grešaka
  • assert s trenutnim izlazom u "debugger" (pogledajte sljedeći odlomak)
  • neki privid programa za ispravljanje pogrešaka konzole
  • provjerite dopušta li vam emulator povezivanje debuggera, tablica simbola ili nečeg drugog
Bez programa za ispravljanje pogrešaka ugrađenog u kernel, traženje pogrešaka ima vrlo realne šanse da se pretvori u noćnu moru. Dakle, jednostavno nema načina da ga napišete u nekoj fazi razvoja. A budući da je to neizbježno, bolje ga je početi pisati unaprijed i tako si uvelike olakšati razvoj i uštedjeti dosta vremena. Važno je moći implementirati debugger na način neovisan o kernelu tako da debugging ima minimalan utjecaj na normalan rad sustava. Evo nekoliko vrsta naredbi koje mogu biti korisne:
  • dio standardnih operacija otklanjanja pogrešaka: prijelomne točke, stog poziva, ispis vrijednosti, ispis dumpa, ...
  • naredbe za prikaz raznih korisnih informacija, kao što je red čekanja za izvršavanje planera ili razne statistike (nije tako beskorisno kao što se na prvu čini)
  • naredbe za provjeru konzistentnosti stanja različitih struktura: liste slobodne/iskorištene memorije, heap ili red poruka
Razvoj
Zatim je potrebno napisati i otkloniti pogreške glavne elemente OS-a, koji bi u ovom trenutku trebali osigurati njegov stabilan rad, au budućnosti - laku proširivost i fleksibilnost. Uz upravitelje memorije/procesa/(što god) vrlo je važno sučelje upravljačkih programa i datotečnih sustava. Njihovom projektiranju treba pristupiti s posebnom pažnjom, vodeći računa o raznolikosti tipova/FS uređaja. Tijekom vremena, naravno, mogu se promijeniti, ali to je vrlo bolan proces sklon pogreškama (a otklanjanje pogrešaka u kernelu nije lak zadatak), pa zapamtite - razmislite o ovim sučeljima barem deset puta prije nego počnete implementirati ih.
Sličan SDK
Kako se projekt bude razvijao, treba mu dodavati nove upravljačke programe i programe. Najvjerojatnije će već na drugom upravljačkom programu (možda određenog tipa)/programu biti vidljive neke zajedničke značajke (struktura direktorija, kontrolne datoteke sklopa, specifikacija ovisnosti između modula, ponovljeni kod u glavnim ili sustavnim rukovateljima zahtjevima (na primjer, ako sami vozači provjere njihovu kompatibilnost s uređajem )). Ako je to slučaj, onda je to znak potrebe za razvojem predložaka za razne vrste programa za vaš OS.

Nema potrebe za dokumentacijom koja opisuje proces pisanja ove ili one vrste programa. Ali vrijedi napraviti prazninu od standardnih elemenata. Ovo ne samo da će olakšati dodavanje programa (što se može učiniti kopiranjem postojećih programa i njihovom promjenom, ali to će oduzeti više vremena), već će također olakšati njihovo ažuriranje kada se sučelja, formati ili bilo što drugo promijene. Jasno je da do takvih promjena u idealnom slučaju ne bi trebalo doći, ali budući da je razvoj OS-a netipična stvar, ima dosta mjesta za potencijalno pogrešne odluke. Ali razumijevanje pogrešnosti donesenih odluka, kao i uvijek, doći će neko vrijeme nakon njihove provedbe.

Daljnje akcije
Ukratko, dakle: čitajte o operativnim sustavima (i prije svega o njihovom dizajnu), razvijajte svoj sustav (tempo zapravo nije bitan, glavno je ne stati potpuno i vratiti se projektu s vremena na vrijeme s novim snagu i ideje) i Prirodno je ispraviti greške u njemu (da biste ih pronašli ponekad morate pokrenuti sustav i "igrati" se s njim). S vremenom će razvojni proces postajati sve lakši i lakši, pogreške će se događati rjeđe, a vi ćete biti uvršteni na popis “beznadno tvrdoglavih”, onih rijetkih koji, unatoč stanovitoj apsurdnosti ideje da razvijaju svoje vlastiti OS, ipak je uspio.

Izvornik: "Roll your own toy UNIX-clone OS"
Autor: James Molloy
Datum objave: 2008
Prijevod: N. Romodanov
Datum prijevoda: siječanj 2012

Ovaj set tutorijala osmišljen je da vam detaljno pokaže kako programirati jednostavan operacijski sustav sličan UNIX-u za x86 arhitekturu. U ovim tutorialima, odabrani programski jezik je C, dopunjen asemblerskim jezikom gdje je to potrebno. Svrha je tutorijala reći vam o dizajnu i implementaciji rješenja korištenih u stvaranju operativnog sustava OS koji stvaramo, a koji je monolitan u svojoj strukturi (drajveri se učitavaju u modusu kernel modula, a ne u korisničkom modu kao što se događa s programi), budući da je takvo rješenje jednostavnije.

Ovaj set vodiča vrlo je praktičan. Svaki dio pruža teoretske informacije, ali većina priručnika odnosi se na implementaciju apstraktnih ideja i mehanizama o kojima se raspravlja u praksi. Važno je napomenuti da je core implementiran kao trening core. Znam da korišteni algoritmi nisu prostorno najučinkovitiji niti optimalni. Općenito su odabrani zbog svoje jednostavnosti i lakoće razumijevanja. Svrha ovoga je dati vam pravi način razmišljanja i osnovu na kojoj možete raditi. Ova jezgra je proširiva i možete jednostavno povezati najbolje algoritme. Ako imate problema u vezi s teorijom, postoje mnoge stranice koje vam mogu pomoći da to shvatite. Većina pitanja o kojima se raspravlja na OSDev forumu povezana su s implementacijom ("Moja gets funkcija ne radi! pomoć!") i za mnoge je pitanje o teoriji poput daška svježeg zraka. Veze se mogu pronaći na kraju ovoga Uvod.

Preliminarna priprema

Za prevođenje i pokretanje primjera koda, pretpostavljam da trebate samo GCC, ld, NASM i GNU Make. NASM je open source x86 asembler i izbor je mnogih programera x86 OS-a.

Međutim, nema svrhe jednostavno sastavljati i izvoditi primjere ako ih ne razumijete. Morate razumjeti što se kodira, a da biste to učinili morate vrlo dobro poznavati jezik C, posebno kada su u pitanju pokazivači. Također biste trebali razumjeti neke asemblerske jezike (ovi vodiči koriste Intelovu sintaksu), uključujući za što se koristi EBP registar.

Resursi

Postoji mnogo izvora ako znate kako ih tražiti. Posebno će vam biti korisne sljedeće veze:

  • RTFM! Intelovi priručnici su božji dar.
  • Wiki stranice i forum stranice osdev.org.
  • Postoji mnogo dobrih tutorijala i članaka na Osdever.net, a posebno Branovih tutorijala za razvoj jezgre, na čijem sam ranijem kodu ovaj tutorijal i sam koristio te tutorijale za početak, a kod u njima je bio toliko dobar da jesam ne mijenjati ga nekoliko godina.
  • Ako niste početnik, onda u grupi možete dobiti odgovore na mnoga pitanja

Ova serija članaka posvećena je programiranju niske razine, odnosno arhitekturi računala, dizajnu operacijskih sustava, programiranju u asemblerskom jeziku i srodnim područjima. Za sada dva habrauzera rade pisanje - i . Za mnoge srednjoškolce, studente, pa čak i profesionalne programere, ove se teme pokazuju vrlo teškima za naučiti. Postoji mnogo literature i tečajeva posvećenih programiranju niske razine, ali teško je dobiti potpunu i sveobuhvatnu sliku. Teško je, nakon čitanja jedne ili dvije knjige o asemblerskom jeziku i operacijskim sustavima, barem općenito zamisliti kako zapravo funkcionira ovaj složeni sustav željeza, silicija i mnoštva programa - računalo.

Problem učenja svatko rješava na svoj način. Neki ljudi čitaju puno literature, neki pokušavaju brzo prijeći na praksu i u hodu to shvatiti, drugi pokušavaju objasniti svojim prijateljima sve što proučavaju. I odlučili smo kombinirati te pristupe. Dakle, u ovom nizu članaka pokazat ćemo korak po korak kako napisati jednostavan operativni sustav. Članci će biti preglednog karaktera, odnosno neće sadržavati iscrpne teorijske informacije, ali ćemo uvijek nastojati dati poveznice na dobre teorijske materijale i odgovoriti na sva pitanja koja se pojave. Nemamo jasan plan, tako da ćemo mnoge važne odluke donijeti usput, uzimajući u obzir vaše povratne informacije.

Možemo namjerno spriječiti razvojni proces kako bismo vama i sebi omogućili da u potpunosti razumijemo sve posljedice loše odluke, kao i da na tome usavršimo neke tehničke vještine. Stoga ne biste trebali naše odluke doživljavati kao jedine ispravne i slijepo nam vjerovati. Još jednom naglašavamo da od čitatelja očekujemo aktivnu raspravu o člancima, što bi trebalo uvelike utjecati na cjelokupni proces razvoja i pisanja sljedećih članaka. Idealno bi bilo da se s vremenom netko od čitatelja uključi u razvoj sustava.

Pretpostavit ćemo da je čitatelj već upoznat s osnovama asemblera i jezika C, kao i s elementarnim konceptima arhitekture računala. Odnosno, nećemo objašnjavati što je registar ili, recimo, RAM. Ukoliko nemate dovoljno znanja uvijek se možete obratiti dodatnoj literaturi. Kratak popis referenci i poveznice na stranice s dobrim člancima nalaze se na kraju članka. Također je preporučljivo znati kako koristiti Linux, jer će sve upute za kompilaciju biti dane posebno za ovaj sustav.

A sada - bliže stvari. U ostatku članka napisat ćemo klasični program “Hello World”. Naš Helloworld bit će malo specifičan. Neće se pokretati iz bilo kojeg operativnog sustava, već izravno, da tako kažem, “na golom metalu”. Prije nego počnemo pisati kod, shvatimo kako to točno pokušavamo učiniti. A za ovo moramo razmotriti postupak pokretanja računala.

Dakle, uzmite svoje omiljeno računalo i pritisnite najveći gumb na jedinici sustava. Vidimo veseli čuvar zaslona, ​​sistemska jedinica radosno zazvoni svojim zvučnikom i nakon nekog vremena učitava se operativni sustav. Kao što razumijete, operativni sustav je pohranjen na tvrdom disku i ovdje se postavlja pitanje: kako se operativni sustav magično učitao u RAM i počeo izvršavati?

Znajte ovo: sustav koji se nalazi na bilo kojem računalu odgovoran je za to, a njegovo ime - ne, ne Windows, vrh jezika - zove se BIOS. Njegov naziv je kratica za Basic Input-Output System, odnosno osnovni ulazno-izlazni sustav. BIOS se nalazi na malom čipu na matičnoj ploči i pokreće se odmah nakon pritiska na veliku tipku ON. BIOS ima tri glavna zadatka:

  1. Detektirajte sve povezane uređaje (procesor, tipkovnicu, monitor, RAM, video karticu, glavu, ruke, krila, noge i repove...) i provjerite njihovu funkcionalnost. Za to je zaslužan program POST (Power On Self Test). Ako se vitalni hardver ne detektira, tada nikakav softver neće moći pomoći, au tom će trenutku zvučnik sustava zaškripati nešto zlokobno i OS uopće neće prijeći na stvar. Nemojmo govoriti o tužnim stvarima, pretpostavimo da imamo potpuno radno računalo, radujmo se i prijeđimo na razmatranje druge BIOS funkcije:
  2. Opskrba operativnog sustava osnovnim skupom funkcija za rad s hardverom. Na primjer, putem funkcija BIOS-a možete prikazati tekst na ekranu ili pročitati podatke s tipkovnice. Zato se i zove osnovni ulazno/izlazni sustav. Obično operativni sustav pristupa ovim funkcijama putem prekida.
  3. Pokretanje programa za učitavanje operativnog sustava. U ovom se slučaju u pravilu čita sektor za pokretanje - prvi sektor medija za pohranu (disketa, tvrdi disk, CD, flash pogon). Redoslijed prozivanja medija može se postaviti u BIOS SETUP-u. Sektor za podizanje sustava sadrži program koji se ponekad naziva primarnim programom za podizanje sustava. Grubo rečeno, posao bootloadera je pokretanje operativnog sustava. Proces učitavanja operativnog sustava može biti vrlo specifičan i jako ovisi o njegovim značajkama. Stoga primarni bootloader izravno pišu programeri OS-a i zapisuje se u boot sektor tijekom instalacije. Kada se bootloader pokrene, procesor je u stvarnom načinu rada.
Tužna vijest je da bi bootloader trebao biti veličine samo 512 bajtova. Zašto tako malo? Da bismo to učinili, moramo se upoznati sa strukturom diskete. Evo informativne slike:

Slika prikazuje površinu pogona diska. Disketa ima 2 površine. Svaka površina ima prstenaste staze (staze). Svaka je staza podijeljena na male dijelove u obliku luka koji se nazivaju sektori. Dakle, povijesno, sektor diskete ima veličinu od 512 bajtova. Prvi sektor na disku, sektor za pokretanje, BIOS čita u nulti segment memorije na pomaku 0x7C00, a zatim se kontrola prenosi na tu adresu. Program za pokretanje sustava obično ne učitava sam OS, već drugi program za pokretanje pohranjen na disku, ali iz nekog razloga (najvjerojatnije je to veličina) ne stane u jedan sektor. A budući da za sada ulogu našeg OS-a igra banalni Helloworld, naš glavni cilj je napraviti računalo povjerovati u postojanje našeg OS-a, makar samo u jednom sektoru, i pokrenuti ga.

Kako je strukturiran boot sektor? Na osobnom računalu, jedini uvjet za boot sektor je da njegova posljednja dva bajta sadrže vrijednosti 0x55 i 0xAA - potpis sektora za pokretanje. Dakle, već je manje-više jasno što nam je činiti. Napišimo kod! Navedeni kod je napisan za yasm asembler.

Odjeljak .text use16 org 0x7C00 ; naš program se učitava na adresi 0x7C00 start: mov ax, cs mov ds, ax ; odaberite segment podataka mov si, poruka cld ; smjer za niz naredbi mov ah, 0x0E ; BIOS funkcija broj mov bh, 0x00 ; stranica video memorije puts_loop: lodsb ; učitaj sljedeći simbol u al test al, al ; null znak označava kraj retka jz puts_loop_exit int 0x10 ; pozovite BIOS funkciju jmp puts_loop puts_loop_exit: jmp $ ; poruka vječne petlje: db "Hello World!", 0 završetak: puta 0x1FE-završetak+početak db 0 db 0x55, 0xAA ; potpis sektora za pokretanje

Ovaj kratki program zahtijeva neka važna objašnjenja. Linija org 0x7C00 je potrebna kako bi asembler (što znači program, ne jezik) ispravno izračunao adrese za oznake i varijable (puts_loop, puts_loop_exit, message). Stoga ga obavještavamo da će program biti učitan u memoriju na adresi 0x7C00.
U redovima
mov sjekira, cs mov ds, sjekira
segment podataka (ds) je postavljen jednak segmentu koda (cs), budući da su u našem programu i podaci i kod pohranjeni u istom segmentu.

Sljedeća u petlji, znak po znak prikazuje se poruka "Hello World!". U tu svrhu koristi se funkcija 0x0E prekida 0x10. Ima sljedeće parametre:
AH = 0x0E (broj funkcije)
BH = broj video stranice (nemojte se još truditi, označite 0)
AL = ASCII znakovni kod

Na liniji "jmp$" program se zamrzava. I ispravno, nema potrebe da izvršava dodatni kod. Međutim, kako bi računalo ponovno radilo, morat ćete ga ponovno pokrenuti.

U retku “times 0x1FE-finish+start db 0” ostatak programskog koda (osim posljednja dva bajta) ispunjen je nulama. To se radi tako da nakon kompilacije posljednja dva bajta programa sadrže potpis boot sektora.

Čini se da smo sredili programski kod, pokušajmo sada sastaviti ovu sreću. Za kompilaciju će nam trebati, strogo govoreći, asembler - gore spomenuti yasm. Dostupan je u većini Linux repozitorija. Program se može sastaviti na sljedeći način:

$ yasm -f bin -o zdravo.bin zdravo.asm

Rezultirajuća datoteka hello.bin mora biti zapisana u sektor za pokretanje diskete. To se radi otprilike ovako (naravno, umjesto fd trebate zamijeniti naziv vašeg pogona).

$ dd if=hello.bin of=/dev/fd

Budući da nemaju svi diskovne pogone i diskete, možete koristiti virtualni stroj, na primjer, qemu ili VirtualBox. Da biste to učinili, morat ćete napraviti sliku diskete s našim bootloaderom i umetnuti je u "virtualni disketni pogon".
Napravite sliku diska i ispunite je nulama:

$ dd if=/dev/zero of=disk.img bs=1024 count=1440

Naš program pišemo na samom početku slike:
$ dd if=hello.bin of=disk.img conv=notrunc

Pokrećemo rezultirajuću sliku u qemu:
$ qemu -fda disk.img -boot a

Nakon pokretanja, trebali biste vidjeti qemu prozor sa sretnom linijom "Hello World!" Ovdje završava prvi članak. Bit će nam drago vidjeti vaše povratne informacije i želje.