Internet Windows Android

Cum se scrie un nucleu de sistem de operare. Școala de asamblare: Dezvoltarea sistemului de operare

Citind Habr în ultimii doi ani, am văzut doar câteva încercări de a dezvolta sistemul de operare (în special: de la utilizatori și (amânat pe termen nelimitat) și (nu abandonat, dar deocamdată arată mai mult ca o descriere a modului protejat al procesoarelor compatibile x86). , care, fără îndoială, trebuie să știți și pentru a scrie un sistem de operare pentru x86); și o descriere a sistemului finit de la (deși nu de la zero, deși nu este nimic greșit în asta, poate chiar invers)). Din anumite motive, cred că aproape toți programatorii de sistem (și o parte a aplicației) cel puțin o dată, dar s-au gândit să își scrie propriul sistem de operare. În acest sens, 3 OS din comunitatea mare a acestei resurse pare un număr ridicol. Aparent, cei mai mulți dintre cei care se gândesc la propriul sistem de operare nu merg nicăieri mai departe decât ideea, o mică parte se oprește după ce scriu bootloader-ul, puține scriu bucăți din nucleu și doar cei deznădăjduit de încăpățânați creează ceva care seamănă la distanță cu un sistem de operare (în comparație). cu ceva de genul Windows / Linux)... Există multe motive pentru aceasta, dar principalul, în opinia mea, este că oamenii renunță la dezvoltare (unii dintre ei nici măcar nu au timp să înceapă) din cauza numărului mic de descrieri ale procesului de scriere și depanare a sistemului de operare, care este destul de diferit de ceea ce se întâmplă în timpul dezvoltării software-ului aplicat.

Cu această mică notă aș dori să arăt că, dacă porniți corect, atunci nu este nimic deosebit de dificil în a vă dezvolta propriul sistem de operare. Sub tăietură este un ghid scurt și destul de general pentru acțiunea de a scrie un sistem de operare de la zero.

Cum nu este nevoieîncepe
Vă rugăm să nu luați următorul text ca pe o critică explicită a articolelor altcuiva sau a ghidurilor de scriere a sistemului de operare. Doar că prea des în astfel de articole sub titluri zgomotoase, accentul este pus pe implementarea unui fel de pregătire minimă și este prezentat ca un prototip al nucleului. De fapt, ar trebui să vă gândiți la structura nucleului și la interacțiunea părților sistemului de operare în ansamblu, iar acel prototip ar trebui considerat ca un standard „Hello, World!” - o aplicație în lumea software-ului aplicat. Ca o mică scuză pentru aceste remarci, trebuie spus că mai jos există o subsecțiune „Bună, Lume!”

Nu este nevoie să scrieți un bootloader. Oamenii inteligenți au venit cu specificația Multiboot, implementată și descrisă în detaliu ce este și cum să o folosească. Nu vreau să mă repet, voi spune doar că funcționează, ușurează viața și ar trebui aplicat. Specificația, apropo, este mai bine să o citiți în întregime, este scurtă și chiar conține exemple.

Nu este necesar să scrieți sistemul de operare complet în limbaj de asamblare. Nu este chiar atât de rău, ci mai degrabă opusul - programele rapide și mici vor fi întotdeauna ținute la mare stimă. Tocmai din moment ce acest limbaj necesită mult mai mult efort de dezvoltare, utilizarea assemblerului va duce doar la o scădere a entuziasmului și, ca urmare, la aruncarea surselor OS pe o parte din urmă.

Nu este nevoie să încărcați un font personalizat în memoria video și să afișați nimic în rusă. Nu are sens în asta. Este mult mai ușor și mai versatil să folosiți limba engleză și să lăsați schimbarea fontului pentru mai târziu, încărcându-l de pe hard disk prin driverul sistemului de fișiere (în același timp, va exista un stimulent suplimentar să faceți mai mult decât să porniți).

Pregătirea
Pentru început, ca întotdeauna, ar trebui să vă familiarizați cu teoria generală pentru a avea o idee despre volumul viitor de muncă. Surse bune despre problema luată în considerare sunt cărțile lui E. Tanenbaum, care au fost deja menționate în alte articole despre scrierea OS în Habré. Există, de asemenea, articole care descriu sisteme existente și există diverse ghiduri / liste de corespondență / articole / exemple / site-uri cu o părtinire în dezvoltarea sistemului de operare, link-uri către unele dintre ele fiind date la sfârșitul articolului.

După programul educațional inițial, trebuie să decideți asupra principalelor întrebări:

  • Arhitectură țintă - x86 (mod real / protejat / lung), PowerPC, ARM, ...
  • Arhitectură kernel/OS - monolit, monolit modular, microkernel, exokernel, diverși hibrizi
  • limbajul și compilatorul său - C, C++, ...
  • format de fișier kernel - elf, a.out, coff, binar, ...
  • mediu de dezvoltare (da, acesta joacă, de asemenea, un rol important) - IDE, vim, emacs, ...
În continuare, ar trebui să-ți aprofundezi cunoștințele în funcție de cea aleasă și în următoarele domenii:
  • memorie video și lucrați cu ea - concluzia ca dovadă a muncii este necesară încă de la început
  • HAL (Hardware Abstraction layer) - chiar dacă suportul pentru mai multe arhitecturi hardware și nu este planificat să se separe în mod competent părțile de cel mai jos nivel ale nucleului de implementarea unor lucruri abstracte precum procese, semafore și așa mai departe, nu va fi de prisos
  • managementul memoriei - fizice si virtuale
  • controlul execuției - procese și fire, programarea acestora
  • managementul dispozitivelor - drivere
  • sisteme de fișiere virtuale - pentru a oferi o interfață unică pentru conținutul diferitelor sisteme de fișiere
  • API (Application Programming Interface) - cum vor accesa aplicațiile exact kernel-ul
  • IPC (Interprocess Communication) - mai devreme sau mai târziu procesele vor trebui să comunice
Instrumente
Ținând cont de limbajul și instrumentele de dezvoltare alese, ar trebui să alegeți un astfel de set de utilități și setările acestora, care în viitor vor face posibilă, prin scrierea de scripturi, să ușureze și să grăbească maxim asamblarea, pregătirea imaginii și lansarea unui mașină virtuală cu un proiect. Să ne oprim puțin mai detaliat asupra fiecăruia dintre aceste puncte:
  • orice instrument standard este potrivit pentru construirea, cum ar fi make, cmake, ... Aici pot fi folosite scripturi pentru linker și utilitare (scrise special) pentru adăugarea unui antet Multiboot, sume de control sau pentru orice alt scop.
  • pregătirea unei imagini înseamnă montarea acesteia și copierea fișierelor. În consecință, formatul fișierului imagine trebuie selectat astfel încât atât utilitarul de montare/copiere, cât și mașina virtuală să îl accepte. Desigur, nimeni nu interzice efectuarea acțiunilor din acest punct fie ca parte finală a ansamblului, fie ca pregătire pentru lansarea emulatorului. Totul depinde de instrumentele specifice și de opțiunile alese pentru utilizarea lor.
  • pornirea unei mașini virtuale de muncă nu reprezintă, dar nu trebuie să uitați să demontați mai întâi imaginea (demontarea în acest moment, deoarece nu are sens real în această operațiune înainte de a porni mașina virtuală). De asemenea, nu va fi de prisos să aveți un script pentru a porni emulatorul în modul de depanare (dacă există).
Dacă ați parcurs toți pașii anteriori, ar trebui să scrieți un program minim care se încarcă ca nucleu și afișează ceva pe ecran. Dacă se constată inconveniente sau neajunsuri ale mijloacelor selectate, este necesar să le eliminați (deficiențele) sau, în cel mai rău caz, să le luați de la sine înțeles.

La acest pas, trebuie să testați cât mai multe caracteristici ale instrumentelor de dezvoltare pe care intenționați să le utilizați în viitor. De exemplu, încărcarea modulelor în GRUB sau utilizarea unui disc fizic / partiție / unitate flash într-o mașină virtuală în loc de o imagine.

După ce această etapă a trecut cu succes, începe dezvoltarea reală.

Furnizarea de asistență în timpul execuției
Deoarece se propune scrierea în limbaje de nivel înalt, trebuie avut grijă să se ofere suport pentru unele dintre caracteristicile limbajului care sunt de obicei implementate de autorii pachetului compilatorului. De exemplu, pentru C++, aceasta include:
  • funcție pentru alocarea dinamică a unui bloc de date pe stivă
  • lucra cu heap
  • funcția de copiere a blocului de date (memcpy)
  • funcția punct de intrare în program
  • chemări către constructori și destructori de obiecte globale
  • o serie de funcții pentru lucrul cu excepții
  • stub pentru funcții pur-virtuale neimplementate
Când scrii „Bună, lume!” absența acestor funcții poate să nu se facă simțită în niciun fel, dar pe măsură ce codul este adăugat, linkerul va începe să se plângă de dependențe nesatisfăcute.

Desigur, biblioteca standard ar trebui menționată imediat. Nu este necesară o implementare completă, dar un subset major al funcționalității merită implementat. Atunci scrierea codului va fi mult mai familiară și mai rapidă.

Depanare
Nu văd ce spune despre depanare spre sfârșitul acestui articol. De fapt, aceasta este o problemă foarte serioasă și dificilă în dezvoltarea sistemului de operare, deoarece instrumentele obișnuite nu sunt aplicabile aici (cu unele excepții).

Puteți sfătui următoarele:

  • desigur, ieșire de depanare
  • afirmă cu ieșire imediată în „depanator” (vezi paragraful următor)
  • o aparență de depanator de consolă
  • verificați dacă emulatorul vă permite să conectați un depanator, tabele de simboluri sau altceva
Fără un depanator încorporat în nucleu, găsirea erorilor are șanse foarte reale de a deveni un coșmar. Deci, pur și simplu, nu există nicio scăpare de a-l scrie într-un anumit stadiu de dezvoltare. Și deoarece acest lucru este inevitabil, este mai bine să începeți să-l scrieți din timp și astfel să vă ușurați foarte mult dezvoltarea și să economisiți mult timp. Este important să poți implementa depanatorul într-un mod independent de nucleu, astfel încât depanarea să aibă un impact minim asupra funcționării normale a sistemului. Există mai multe tipuri de comenzi care pot fi utile:
  • unele dintre operațiunile standard de depanare: puncte de întrerupere, stivă de apeluri, ieșirea valorilor, imprimarea unui dump, ...
  • comenzi pentru a afișa diverse informații utile, cum ar fi coada de execuție a planificatorului sau diverse statistici (nu este atât de inutil pe cât ar părea la început)
  • comenzi pentru verificarea consistenței stării diferitelor structuri: liste de memorie liberă/utilizată, heap sau coadă de mesaje
Dezvoltare
În continuare, trebuie să scrieți și să depanați principalele elemente ale sistemului de operare, care în acest moment ar trebui să asigure funcționarea sa stabilă, iar în viitor - extensibilitate și flexibilitate ușoară. Pe langa managerii de memorie/procese/(orice altceva), interfata driverelor si sistemelor de fisiere este foarte importanta. Designul lor trebuie abordat cu grijă deosebită, ținând cont de toată varietatea de tipuri de dispozitive / FS. Desigur, le puteți schimba în timp, dar acesta este un proces foarte dureros și predispus la erori (și depanarea nucleului nu este o sarcină ușoară), așa că amintiți-vă - gândiți-vă la aceste interfețe de cel puțin zece ori înainte de a începe să le implementați .
Asemănare cu SDK
Pe măsură ce proiectul se dezvoltă, ar trebui adăugate noi drivere și programe. Cel mai probabil, deja pe cel de-al doilea driver (eventual de un anumit tip)/program, vor fi observate unele caracteristici comune (structura directoarelor, fișierele de control al construirii, specificarea dependențelor dintre module, cod repetat în mainile sau în handlerele de cereri de sistem (de exemplu , dacă driverele înșiși își verifică compatibilitatea cu dispozitivul) )). Dacă da, atunci acesta este un semn al necesității de a dezvolta șabloane pentru diferite tipuri de programe pentru sistemul de operare.

Nu este nevoie de documentație care să descrie procesul de scriere a acestui sau acela tip de program. Dar merită să faceți un gol din elemente standard. Acest lucru nu numai că va facilita adăugarea de programe (ceea ce se poate face prin copierea programelor existente și apoi prin schimbarea lor, dar acest lucru va dura mai mult timp), dar va facilita și actualizarea acestora atunci când apar modificări în interfețe, formate sau altceva. Este clar că astfel de schimbări, în mod ideal, nu ar trebui să fie, dar din moment ce dezvoltarea sistemului de operare este un lucru atipic, există multe locuri pentru decizii potențial greșite. Însă înțelegerea eronată a deciziilor luate, ca întotdeauna, va veni la ceva timp după implementarea lor.

Actiunile urmatoare
Pe scurt, citiți despre sistemele de operare (și în primul rând despre dispozitivul lor), dezvoltați-vă sistemul (ritmul nu este de fapt important, principalul lucru este să nu vă opriți deloc și să reveniți la proiect din când în când cu forțe și idei noi) și este firesc să corectați erorile din acesta (pentru a găsi care este uneori necesar să porniți sistemul și să vă „jucați” cu el). De-a lungul timpului, procesul de dezvoltare va deveni din ce în ce mai ușor, erorile vor fi mai puțin frecvente și veți fi inclus în lista „încăpățânați fără speranță”, acei puțini care, în ciuda unei oarecare absurdități a ideii de a-și dezvolta propriul sistem de operare, tot a făcut-o.

Această serie de articole este dedicată programării de nivel scăzut, adică arhitecturii computerelor, sistemelor de operare, programarii în limbaj de asamblare și domeniilor conexe. Până acum, doi utilizatori habrayus sunt angajați în scris - iley și pehat. Pentru mulți elevi de liceu, studenți și programatori profesioniști, aceste subiecte se dovedesc a fi foarte greu de învățat. Există multă literatură și cursuri despre programarea la nivel scăzut, dar este dificil să obțineți o imagine completă și cuprinzătoare din acestea. Este dificil, după ce am citit una sau două cărți despre asamblare și sisteme de operare, să ne imaginăm, cel puțin în termeni generali, cum funcționează de fapt acest sistem complex de fier, siliciu și multe programe - un computer.

Fiecare rezolvă problema învățării în felul său. Cineva citește multă literatură, cineva încearcă să treacă rapid la practică și să înțeleagă pe parcurs, cineva încearcă să explice prietenilor tot ceea ce studiază. Și am decis să combinăm aceste abordări. Deci, în acest curs de articole, vom demonstra pas cu pas cum să scrieți un sistem de operare simplu. Articolele vor avea un caracter general, adică nu vor conține informații teoretice exhaustive, totuși, vom încerca întotdeauna să oferim link-uri către materiale teoretice bune și să răspundem la toate întrebările care apar. Nu avem un plan clar, așa că multe decizii importante vor fi luate pe parcurs, ținând cont de feedback-ul dumneavoastră.

Poate că vom opri în mod deliberat procesul de dezvoltare pentru a vă permite dvs. și nouă înșine să înțelegeți pe deplin toate consecințele unei decizii greșite, precum și să perfecționăm unele abilități tehnice asupra acesteia. Deci nu ar trebui să iei deciziile noastre ca fiind singurele corecte și să ai încredere orbește în noi. Încă o dată, subliniem că ne așteptăm ca cititorii să fie activi în discutarea articolelor, care ar trebui să influențeze puternic procesul general de dezvoltare și scriere a articolelor ulterioare. În mod ideal, am dori să vedem unii dintre cititori să se alăture dezvoltării sistemului în timp.

Vom presupune că cititorul este deja familiarizat cu elementele de bază ale limbajelor de asamblare și C, precum și cu conceptele de bază ale arhitecturii computerelor. Adică nu vom explica ce este un registru sau, să zicem, o memorie cu acces aleatoriu. Dacă nu aveți suficiente cunoștințe, puteți oricând să consultați literatura suplimentară. O scurtă listă de referințe și link-uri către site-uri cu articole bune se află la sfârșitul articolului. De asemenea, este de dorit să puteți utiliza Linux, deoarece toate instrucțiunile de compilare vor fi date special pentru acest sistem.

Și acum - mai aproape de subiect. În restul articolului, vom scrie un program clasic „Hello World”. Lumea noastră Hello se va dovedi a fi puțin specifică. Nu va rula de pe niciun sistem de operare, ci direct, ca să spunem așa, „pe bare metal”. Înainte de a trece direct la scrierea codului, să ne dăm seama cum încercăm exact să facem acest lucru. Și pentru aceasta trebuie să luați în considerare procesul de pornire a computerului.

Deci, luați computerul preferat și apăsați pe cel mai mare buton de pe unitatea de sistem. Vedem un ecran de splash vesel, unitatea de sistem emite un bip fericit cu un difuzor, iar după un timp sistemul de operare este încărcat. După cum înțelegeți, sistemul de operare este stocat pe hard disk și aici apare întrebarea: cum a pornit magic sistemul de operare în RAM și a început să se execute?

Ar trebui să știți: sistemul care se află pe orice computer este responsabil pentru acest lucru, iar numele său - nu, nu Windows, vă smulge limba - se numește BIOS. Numele său înseamnă Basic Input-Output System, adică sistemul de bază de intrare-ieșire. BIOS-ul se află pe un mic microcircuit de pe placa de bază și pornește imediat după apăsarea butonului mare ON. BIOS-ul are trei sarcini principale:

  1. Detectați toate dispozitivele conectate (procesor, tastatură, monitor, RAM, placă video, cap, brațe, aripi, picioare și cozi...) și verificați-le pentru funcționare. Programul POST (Power On Self Test) este responsabil pentru acest lucru. Dacă hardware-ul vital nu este găsit, atunci niciun software nu va putea ajuta, iar în acest moment difuzorul sistemului va scârțâi ceva sinistru și sistemul de operare nu va ajunge deloc în sistemul de operare. Să nu vorbim despre trist, să presupunem că avem un computer complet funcțional, ne bucurăm și trecem la a doua funcție BIOS:
  2. Furnizarea sistemului de operare cu un set de bază de funcții pentru lucrul cu hardware. De exemplu, prin funcțiile BIOS, puteți afișa text pe ecran sau puteți citi date de la tastatură. Prin urmare, se numește sistemul I/O de bază. De obicei, sistemul de operare accesează aceste funcții prin întreruperi.
  3. Lansarea încărcării sistemului de operare. În acest caz, de regulă, se citește sectorul de boot - primul sector al purtătorului de informații (dischetă, hard disk, CD, unitate flash). Ordinea mediilor de interogare poate fi setată în BIOS SETUP. Sectorul de boot conține un program numit uneori încărcător de pornire primar. În linii mari, sarcina bootloader-ului este să pornească sistemul de operare. Procesul de încărcare a unui sistem de operare poate fi foarte specific și foarte dependent de caracteristicile acestuia. Prin urmare, bootloader-ul primar este scris direct de dezvoltatorii sistemului de operare și este scris în sectorul de pornire în timpul instalării. În momentul în care pornește bootloader-ul, procesorul este în modul real.
Veste tristă: dimensiunea bootloader-ului ar trebui să fie de numai 512 octeți. De ce atât de puțin? Pentru a face acest lucru, trebuie să ne familiarizăm cu dispozitivul dischetei. Iată o poză informativă:

Imaginea arată suprafața unei unități de disc. Discheta are 2 suprafete. Fiecare suprafață are urme în formă de inel. Fiecare pistă este împărțită în bucăți mici, arcuite, numite sectoare. Deci, din punct de vedere istoric, un sector de dischetă are o dimensiune de 512 octeți. Primul sector de pe disc, sectorul de boot, este citit de BIOS în segmentul de memorie zero la offset 0x7C00, iar apoi controlul este transferat la această adresă. Încărcătorul de pornire se încarcă de obicei în memorie nu sistemul de operare în sine, ci un alt încărcător. program stocat pe disc, dar dintr-un anumit motiv (cel mai probabil, acest motiv este dimensiunea) care nu se încadrează într-un singur sector. Și, din moment ce rolul sistemului nostru de operare este îndeplinit până acum de un banal Hello World, scopul nostru principal este să faceți computerul să creadă în existența sistemului nostru de operare, chiar dacă într-un sector, și rulați-l.

Cum funcționează sectorul de boot? Pe un computer, singura cerință pentru sectorul de boot este ca ultimii doi octeți să conțină valorile 0x55 și 0xAA - semnătura sectorului de boot. Deci, este deja mai mult sau mai puțin clar ce trebuie să facem. Să scriem codul! Codul de mai sus este scris pentru asamblatorul yasm.

Secțiunea .text use16 org 0x7C00; programul nostru este încărcat la 0x7C00 start: mov ax, cs mov ds, ax; selectați segmentul de date mov si, mesaj cld; direcție pentru comenzi șir mov ah, 0x0E; Numărul funcției BIOS mov bh, 0x00; pagina memorie video puts_loop: lodsb; încărcați următorul simbol în al test al, al; caracterul nul înseamnă sfârșitul șirului jz puts_loop_exit int 0x10; apelați funcția BIOS jmp puts_loop puts_loop_exit: jmp $; mesaj în buclă eternă: db „Hello World!”, 0 finish: ori 0x1FE-finish + start db 0 db 0x55, 0xAA; semnătura sectorului de boot

Acest scurt program necesită o serie de explicații importante. Linia org 0x7C00 este necesară pentru ca asamblatorul (mă refer la program, nu la limbaj) să calculeze corect adresele pentru etichete și variabile (puts_loop, puts_loop_exit, mesaj). Așa că îl informăm că programul va fi încărcat în memorie la adresa 0x7C00.
În rânduri
mov ax, cs mov ds, ax
segmentul de date (ds) este setat egal cu segmentul de cod (cs), deoarece în programul nostru atât datele, cât și codul sunt stocate într-un singur segment.

Apoi mesajul „Hello World!” este afișat caracter cu caracter în buclă. Funcția 0x0E a întreruperii 0x10 este utilizată pentru aceasta. Are urmatorii parametri:
AH = 0x0E (numărul funcției)
BH = numărul paginii video (nu vă deranjați încă, specificați 0)
AL = cod de caractere ASCII

La linia „jmp $” programul se blochează. Și pe bună dreptate, nu este nevoie ca ea să execute cod suplimentar. Cu toate acestea, pentru ca computerul să funcționeze din nou, va trebui să reporniți.

În linia „times 0x1FE-finish + start db 0”, restul codului programului (cu excepția ultimilor doi octeți) este umplut cu zerouri. Acest lucru se face astfel încât după compilare semnătura sectorului de boot să apară în ultimii doi octeți ai programului.

Am descoperit codul programului, acum haideți să încercăm să compilăm această fericire. Pentru compilare, avem nevoie, de fapt, de un asamblator - yasmul menționat mai sus. Este disponibil în majoritatea depozitelor Linux. Programul poate fi compilat după cum urmează:

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

Fișierul rezultat hello.bin trebuie scris în sectorul de pornire al dischetei. Acest lucru se face cam așa (desigur, în loc de fd, trebuie să înlocuiți numele unității).

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

Deoarece nu toată lumea mai are unități de dischetă și dischete, puteți utiliza o mașină virtuală, de exemplu, qemu sau VirtualBox. Pentru a face acest lucru, trebuie să faceți o imagine a unei dischete cu bootloader-ul nostru și să o introduceți în „unitatea de dischetă virtuală”.
Creați o imagine de disc și umpleți-o cu zerouri:

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

Scriem programul nostru chiar la începutul imaginii:
$ dd if = hello.bin of = disk.img conv = notrunc

Rulați imaginea rezultată în qemu:
$ qemu -fda disk.img -boot a

După lansare, ar trebui să vedeți o fereastră qemu cu o linie plină de bucurie „Hello World!” Astfel se încheie primul articol. Vom fi bucuroși să vedem feedback-ul și sugestiile dvs.

Dezvoltarea unui nucleu este considerată pe bună dreptate o sarcină descurajantă, dar oricine poate scrie cel mai simplu nucleu. Pentru a atinge magia hacking-ului kernel-ului, trebuie doar să respectați unele convenții și să stăpâniți asamblatorul. În acest articol, vă vom arăta cum să o faceți pe degete.


Salut Lume!

Să scriem un nucleu care va porni prin GRUB pe sisteme compatibile x86. Primul nostru nucleu va afișa un mesaj pe ecran și se va opri acolo.

Cum pornesc mașinile x86

Înainte de a ne gândi cum să scrieți un nucleu, să vedem cum pornește computerul și transferă controlul către kernel. Majoritatea registrelor procesorului x86 au valori specifice după încărcare. Instruction Pointer Register (EIP) conține adresa instrucțiunii care va fi executată de procesor. Valoarea sa codificată este 0xFFFFFFF0. Adică, procesorul x86 va începe întotdeauna execuția de la adresa fizică 0xFFFFFFF0. Aceștia sunt ultimii 16 octeți ai spațiului de adrese de 32 de biți. Această adresă se numește „vector de resetare”.

Cardul de memorie conținut în chipset spune că adresa 0xFFFFFFF0 se referă la o anumită parte a BIOS-ului, și nu la RAM. Cu toate acestea, BIOS-ul se copiază pe memoria RAM pentru un acces mai rapid - acest proces se numește shadowing, creând o copie shadow. Deci, adresa 0xFFFFFFF0 va conține doar instrucțiunea de a merge la locația de memorie în care BIOS-ul s-a copiat.

Deci BIOS-ul începe să se execute. În primul rând, caută dispozitive de pe care puteți porni în ordinea specificată în setări. Verifică media pentru prezența unui „număr magic” care distinge discurile bootabile de cele normale: dacă octeții 511 și 512 din primul sector sunt 0xAA55, atunci discul este bootabil.

De îndată ce BIOS-ul găsește dispozitivul de pornire, va copia conținutul primului sector în RAM, începând cu adresa 0x7C00, apoi va transfera execuția la această adresă și va începe să execute codul pe care tocmai l-a încărcat. Acest cod se numește bootloader.

Încărcătorul încarcă nucleul la adresa fizică 0x100000. El este cel care este folosit de majoritatea nucleelor ​​populare pentru x86.

Toate procesoarele compatibile cu x86 pornesc într-un mod primitiv de 16 biți numit „mod real”. Încărcătorul GRUB comută procesorul în modul protejat pe 32 de biți, setând bitul inferior al registrului CR0 la unu. Prin urmare, nucleul începe să se încarce în modul protejat pe 32 de biți.

Rețineți că GRUB, în cazul nucleelor ​​Linux, alege protocolul de boot adecvat și încarcă nucleul în modul real. Nucleele Linux trec la modul protejat.

De ce avem nevoie

  • Computer compatibil X86 (evident)
  • Linux,
  • asamblator NASM,
  • ld (GNU Linker),
  • GRUB.

Punct de intrare în limbajul de asamblare

Desigur, am dori să scriem totul în C, dar nu putem evita complet utilizarea assembler. Vom scrie un fișier mic în asamblatorul x86 care va fi punctul de plecare pentru nucleul nostru. Tot ce va face codul de asamblare este să apeleze o funcție externă, pe care o vom scrie în C și apoi să oprim execuția programului.

Cum să facem ca codul de asamblare să devină punctul de plecare pentru nucleul nostru? Folosim un script de linker care leagă fișierele obiect și creează executabilul final al nucleului (explicat mai detaliat mai jos). În acest script, vom indica în mod direct că dorim ca binarul nostru să se încarce la 0x100000. Aceasta este adresa, așa cum am scris mai devreme, la care bootloader-ul se așteaptă să vadă punctul de intrare în kernel.

Aici este codul de asamblare.

nucleu.asm
bits 32 section .text global start extern kmain start: cli mov esp, stack_space call kmain hlt section .bss resb 8192 stack_space:

Prima instrucțiune pe 32 de biți nu este un asamblator x86, ci o directivă NASM care vă spune să generați cod pentru un procesor care va rula în modul pe 32 de biți. Acest lucru nu este necesar pentru exemplul nostru, dar este o bună practică să o menționăm în mod explicit.

A doua linie începe o secțiune de text, cunoscută și ca secțiune de cod. Tot codul nostru va ajunge aici.

global este o altă directivă NASM, ea declară simbolurile din codul nostru ca fiind globale. Acest lucru va permite linkerului să găsească simbolul de început, care este punctul nostru de intrare.

kmain este o funcție care va fi definită în fișierul nostru kernel.c. extern declară că funcția este declarată în altă parte.

Urmează funcția de pornire, care apelează kmain și oprește procesorul cu instrucțiunea hlt. Întreruperile pot trezi procesorul după hlt, așa că mai întâi dezactivăm întreruperile cu instrucțiunea cli (clear interrupts).

În mod ideal, ar trebui să alocăm o cantitate de memorie pentru stivă și să îndreptăm indicatorul stivei (în special) către acesta. GRUB pare să facă acest lucru pentru noi, iar în acest moment indicatorul de stivă este deja setat. Totuși, pentru orice eventualitate, să alocăm puțină memorie în secțiunea BSS și să îndreptăm indicatorul stivei către începutul său. Folosim instrucțiunea resb - își rezervă memoria specificată în octeți. Apoi se lasă o etichetă îndreptată către marginea porțiunii rezervate de memorie. Chiar înainte de apelul către kmain, indicatorul de stivă (esp) este direcționat către această zonă cu instrucțiunea mov.

nucleul C

În fișierul kernel.asm, am numit funcția kmain (). Deci, în codul C, execuția va începe acolo.

nucleu.c
void kmain (void) (const char * str = "primul meu nucleu"; 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; }

Tot ce va face nucleul nostru este să șterge ecranul și să scoată linia primul meu kernel.

Primul lucru pe care îl facem este să creăm un pointer vidptr care indică adresa 0xb8000. În modul protejat, acesta este începutul memoriei video. Memoria textului ecranului este doar o parte a spațiului de adrese. O secțiune de memorie este alocată pentru I / O ecran, care începe la adresa 0xb8000, - în ea sunt plasate 25 de linii de 80 de caractere ASCII.

Fiecare caracter din memoria text este reprezentat de 16 biți (2 octeți), nu de cei 8 biți (1 octet) cu care suntem obișnuiți. Primul octet este codul de caractere ASCII, iar al doilea octet este octetul-atribut. Aceasta este definiția formatului simbolului, inclusiv culoarea acestuia.

Pentru a imprima s verde pe negru, trebuie să punem s în primul octet al memoriei video și valoarea 0x02 în al doilea octet. 0 aici înseamnă fundal negru și 2 înseamnă verde. Vom folosi o culoare gri deschis, codul ei este 0x07.

În prima buclă while, programul umple toate cele 25 de linii de 80 de caractere cu caractere goale cu atributul 0x07. Acest lucru va șterge ecranul.

În a doua buclă while, caracterele din șirul terminat în nul primul meu nucleu sunt scrise în memoria video și fiecărui caracter i se atribuie un octet de atribut egal cu 0x07. Aceasta ar trebui să scoată linia.

Aspect

Acum trebuie să compilam kernel.asm într-un fișier obiect folosind NASM și apoi să folosim GCC pentru a compila kernel.c într-un alt fișier obiect. Sarcina noastră este să conectăm aceste obiecte într-un nucleu executabil bootabil. Pentru a face acest lucru, trebuie să scrieți un script pentru linker (ld), pe care îl vom transmite ca argument.

link.ld
OUTPUT_FORMAT (elf32-i386) INTRARE (start) SECȚIUNI (. = 0x100000; .text: (* (. Text)) .data: (* (. Data)) .bss: (* (. Bss)))

Aici setăm mai întâi formatul (OUTPUT_FORMAT) al fișierului nostru executabil ca ELF pe 32 de biți (Executable and Linkable Format), formatul binar standard pentru sistemele Unix pentru arhitectura x86.

ENTRY are un singur argument. Specifică numele simbolului care va servi drept punct de intrare pentru fișierul executabil.

SECȚIUNI este cea mai importantă parte pentru noi. Aici definim aspectul executabilului nostru. Putem defini cum vor fi combinate diferite secțiuni și unde va fi plasată fiecare.

În acoladele care urmează instrucțiunii SECȚIUNI, punctul înseamnă contorul de locație. Este inițializat automat la 0x0 la începutul blocului SECȚIUNI, dar poate fi modificat prin alocarea unei noi valori.

Mai devreme am scris că codul kernel-ului ar trebui să înceapă de la 0x100000. Acesta este motivul pentru care setăm contorul de poziție la 0x100000.

Aruncă o privire la rândul .text: (* (. Text)). Un asterisc stabilește o mască care se potrivește cu orice nume de fișier. În consecință, expresia * (. Text) înseamnă toate secțiunile de intrare .text din toate fișierele de intrare.

Ca rezultat, linkerul va îmbina toate secțiunile de text ale tuturor fișierelor obiect în secțiunea de text a fișierului executabil și le va plasa la adresa specificată în contorul de poziții. Secțiunea de cod a executabilului nostru va începe la 0x100000.

După ce linkerul redă secțiunea de text, contorul de poziție este 0x100000 plus dimensiunea secțiunii de text. La fel, secțiunile de date și bss vor fi îmbinate și plasate la adresa dată de contorul de poziții.

GRUB și multibooting

Toate fișierele noastre sunt acum gata pentru a construi nucleul. Dar din moment ce vom încărca nucleul folosind GRUB, mai rămâne un pas.

Există un standard pentru pornirea diferitelor nuclee x86 folosind un bootloader. Aceasta se numește „specificație multiboot”. GRUB va încărca numai nucleele care se potrivesc.

Conform acestei specificații, nucleul poate conține un antet (antet Multiboot) în primii 8 kiloocteți. Acest antet ar trebui să conțină trei câmpuri:

  • magie- contine numarul „magic” 0x1BADB002, prin care se identifica antetul;
  • steaguri- acest câmp nu este important pentru noi, puteți lăsa zero;
  • suma de control- suma de control, ar trebui să dea zero dacă este adăugată la câmpurile magice și flags.

Fișierul nostru kernel.asm va arăta acum astfel.

nucleu.asm
biți 32 secțiune .text; aliniere specificații multiboot 4 dd 0x1BADB002; magic dd 0x00; flags dd - (0x1BADB002 + 0x00); checksum global start extern kmain start: cli mov esp, stack_space call kmain hlt secțiune .bss resb_space 81:92

Instrucțiunea dd specifică un cuvânt dublu de 4 octeți.

Punând miezul împreună

Deci, totul este gata pentru a crea un fișier obiect din kernel.asm și kernel.c și pentru a le lega folosind scriptul nostru. Scriem in consola:

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

Cu această comandă, asamblatorul va crea un fișier kasm.o în format ELF-32 de biți. Acum este rândul GCC:

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

Opțiunea -c indică faptul că fișierul nu trebuie să fie legat după compilare. O vom face singuri:

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

Această comandă va lansa linker-ul cu scriptul nostru și va genera un executabil numit kernel.

AVERTIZARE

Hackingul kernelului se face cel mai bine într-o mașină virtuală. Pentru a rula nucleul în QEMU în loc de GRUB, utilizați comanda qemu-system-i386 -kernel kernel.

Configurarea GRUB și lansarea nucleului

GRUB necesită ca numele fișierului nucleului să respecte convenția nucleului<версия>... Deci, haideți să redenumim fișierul - îl voi numi kernel-701 al meu.

Acum punem nucleul în directorul / boot. Acest lucru va necesita privilegii de superutilizator.

Va trebui să adăugați ceva de genul acesta în fișierul de configurare GRUB grub.cfg:

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

Nu uitați să eliminați directiva hiddenmenu, dacă este specificată.

GRUB 2

Pentru a rula nucleul pe care l-am creat în GRUB 2, care este livrat implicit în noile distribuții, configurația dvs. ar trebui să arate astfel:

Intrarea de meniu „kernel 701” (set root = „hd0, msdos1” multiboot / boot / kernel-701 ro)

Mulțumim lui Ruben Laguana pentru această adăugare.

Reporniți computerul și ar trebui să vedeți kernel-ul listat! Și selectând-o, veți vedea chiar acea linie.



Acesta este miezul tău!

Scrierea unui nucleu cu suport pentru tastatură și ecran

Am terminat de lucrat la un nucleu minim care pornește prin GRUB, rulează în modul protejat și imprimă o singură linie pe ecran. Acum este momentul să-l extindeți și să adăugați un driver de tastatură care va citi caracterele de pe tastatură și le va afișa pe ecran.

Vom comunica cu dispozitivele I/O prin porturile I/O. În esență, sunt doar adrese pe magistrala I/O. Există instrucțiuni speciale de procesor pentru operațiuni de citire și scriere.

Lucrul cu porturi: citire și ieșire

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

Porturile I/O sunt accesate folosind instrucțiunile de intrare și ieșire incluse în suita x86.

În read_port, numărul portului este transmis ca argument. Când compilatorul apelează o funcție, împinge toate argumentele în stivă. Argumentul este copiat în registrul edx folosind un pointer de stivă. Registrul dx este cei 16 biți inferiori ai registrului edx. Instrucțiunea de aici citește numărul portului dat în dx și pune rezultatul în al. Registrul al este cei 8 biți inferiori ai registrului eax. Poate vă amintiți de la cursul de facultate că valorile returnate de funcții sunt trecute prin registrul eax. Astfel, read_port ne permite să citim din porturile I/O.

Funcția write_port funcționează într-un mod similar. Luăm două argumente: numărul portului și datele de scris. Instrucțiunea out scrie date în port.

întreruperi

Acum, înainte de a reveni la scrierea driverului, trebuie să înțelegem cum procesorul știe că un dispozitiv a efectuat o operație.

Cea mai simplă soluție este să sondați dispozitivele - verificați continuu starea acestora într-o buclă. Acest lucru este ineficient și nepractic din motive evidente. Deci aici intră în joc întreruperile. O întrerupere este un semnal trimis procesorului de către un dispozitiv sau un program care indică faptul că a avut loc un eveniment. Folosind întreruperi, putem evita nevoia de a sonda dispozitivele și vom răspunde doar la evenimentele care ne interesează.

Un cip numit Programable Interrupt Controller (PIC) este responsabil pentru întreruperile din arhitectura x86. Se ocupă de întreruperi hardware și rute și le transformă în întreruperi de sistem adecvate.

Când utilizatorul face ceva dispozitivului, un impuls numit cerere de întrerupere (IRQ) este trimis către PIC. PIC-ul traduce întreruperea primită într-o întrerupere de sistem și trimite un mesaj procesorului că este timpul să oprească ceea ce face. Gestionarea ulterioară a întreruperilor este responsabilitatea nucleului.

Fără PIC, ar trebui să interogăm toate dispozitivele prezente în sistem pentru a vedea dacă a avut loc un eveniment care implică vreunul dintre ele.

Să aruncăm o privire la cum funcționează acest lucru în cazul unei tastaturi. Tastatura se blochează pe porturile 0x60 și 0x64. Portul 0x60 trimite date (când este apăsat un buton), iar portul 0x64 trimite starea. Cu toate acestea, trebuie să știm exact când să citim aceste porturi.

Întreruperile sunt utile aici. Când butonul este apăsat, tastatura trimite un semnal PIC pe linia de întrerupere IRQ1. PIС stochează valoarea offset stocată în timpul inițializării sale. Acesta adaugă numărul liniei de intrare la această indentare pentru a forma vectorul de întrerupere. Procesorul caută apoi o structură de date numită Tabel de descrieri de întreruperi (IDT) pentru a oferi funcției de gestionare a întreruperilor o adresă corespunzătoare numărului său.

Apoi codul de la această adresă este executat și se ocupă de întrerupere.

Setarea IDT

struct IDT_entry (unsigned short int offset_lowerbits; unsigned short int selector; unsigned char zero; unsigned char type_attr; unsigned short int offset_higherbits;); struct IDT_entry IDT; void idt_init (void) (adresă_tastatură lungă nesemnată; adresă_idt lungă nesemnată; idt_ptr lung nesemnată; adresa_tastatură = (nesemnată lungă) keyboard_handler; IDT.offset_lowerbits = adresa_tastatură & 0xffff; IDT.selector = 0x08 ; 0x8e; / * INTERRUPT_GATE * / IDT.offset_higherbits = (keyboard_address & 0xffff0000) >> 16; write_port (0x20, 0x11); write_port (0xA0, 0x11); write_port (0x21, 0x20); write_port (0x21, 0x20); write_port (0x20, 0x20); write_port (0x20, 0x11); ); write_port (0xA1, 0x00); write_port (0x21, 0x01); write_port (0xA1, 0x01); write_port (0x21, 0xff); write_port (0xA1, 0xff); idt_address = (unsigned long ) IDT; (idt_ptr) struct IDT_entry) * IDT_SIZE) + ((idt_address & 0xffff)<< 16); idt_ptr = idt_address >> 16; load_idt (idt_ptr); )

IDT este o matrice de structuri IDT_entry. Vom discuta și despre legarea unei întreruperi de tastatură la un handler, dar deocamdată să vedem cum funcționează PIC-ul.

Sistemele x86 moderne au două PIC-uri, fiecare cu opt linii de intrare. Le vom numi PIC1 și PIC2. PIC1 primește de la IRQ0 la IRQ7 și PIC2 primește de la IRQ8 la IRQ15. PIC1 folosește portul 0x20 pentru comenzi și 0x21 pentru date, în timp ce PIC2 folosește portul 0xA0 pentru comenzi și 0xA1 pentru date.

Ambele PIC-uri sunt inițializate cu cuvinte de opt biți numite Initialization command words (ICW).

În modul protejat, ambele PIC-uri trebuie mai întâi să lanseze comanda de inițializare ICW1 (0x11). Îi spune PIC-ului să aștepte încă trei cuvinte de inițializare să vină în portul de date.

Aceste comenzi vor trimite PIC-ul:

  • vector de umplutură (ICW2),
  • care este relația master/slave dintre PIC-uri (ICW3),
  • informații suplimentare despre mediu (ICW4).

A doua comandă de inițializare (ICW2) este de asemenea trimisă la intrarea fiecărui PIC. Acesta atribuie offset, care este valoarea la care adăugăm numărul de linie pentru a obține numărul de întrerupere.

PIC-urile permit ca pinii lor să fie conectați în cascadă la intrările celuilalt. Acest lucru se face folosind ICW3 și fiecare bit reprezintă starea în cascadă pentru IRQ-ul corespunzător. Deocamdată, nu vom folosi redirecționarea în cascadă și o vom seta la zero.

ICW4 setează parametri suplimentari de mediu. Trebuie doar să definim bitul de jos, astfel încât PIC-urile să știe că operăm în modul 80x86.

Ta-dam! PIC-urile sunt acum inițializate.

Fiecare PIC are un registru intern de opt biți numit Interrupt Mask Register (IMR). Stochează un bitmap a liniilor IRQ care merg la PIC. Dacă bitul este setat, PIC-ul ignoră cererea. Aceasta înseamnă că putem activa sau dezactiva o anumită linie IRQ setând valoarea corespunzătoare la 0 sau 1.

Citirea din portul de date returnează valoarea din registrul IMR, iar scrierea modifică registrul. În codul nostru, după inițializarea PIC-ului, setăm toți biții la unul, dezactivând astfel toate liniile IRQ. Mai târziu, activăm liniile care corespund întreruperilor de la tastatură. Dar mai întâi, să o oprim!

Dacă liniile IRQ funcționează, PIC-urile noastre pot primi semnale pe IRQ și le pot converti într-un număr de întrerupere, adăugând un offset. Trebuie să completăm IDT, astfel încât numărul întreruperii care a venit de la tastatură să corespundă adresei funcției de gestionare pe care o vom scrie.

La ce număr de întrerupere avem nevoie pentru a lega gestionarea tastaturii în IDT?

Tastatura folosește IRQ1. Aceasta este linia de intrare 1 și este procesată de PIC1. Am inițializat PIC1 cu offset 0x20 (vezi ICW2). Pentru a obține numărul de întrerupere, trebuie să adăugați 1 și 0x20, obțineți 0x21. Aceasta înseamnă că adresa de gestionare a tastaturii va fi legată în IDT pentru a întrerupe 0x21.

Sarcina se reduce la completarea IDT pentru întrerupere 0x21. Vom mapa această întrerupere la funcția keyboard_handler, pe care o vom scrie în fișierul de asamblare.

Fiecare intrare IDT are 64 de biți. În înregistrarea corespunzătoare întreruperii, nu stocăm întreaga adresă a funcției de gestionare. În schimb, l-am împărțit în două bucăți de 16 biți. Biții inferiori sunt stocați în primii 16 biți ai înregistrării IDT, iar cei 16 biți superiori sunt stocați în ultimii 16 biți ai înregistrării. Toate acestea sunt făcute pentru compatibilitate cu 286 de procesoare. După cum puteți vedea, Intel emite astfel de numere în mod regulat și în multe, multe locuri!

În înregistrarea IDT, ne rămâne să înregistrăm tipul, indicând astfel încât toate acestea să fie făcute pentru a prinde întrerupere. De asemenea, trebuie să setăm offset-ul segmentului de cod al nucleului. GRUB înființează GDT pentru noi. Fiecare înregistrare GDT are o lungime de 8 octeți, unde descriptorul codului de kernel este al doilea segment, deci offset-ul său este 0x08 (detaliile nu vor intra în acest articol). Poarta de întrerupere este reprezentată ca 0x8e. Umpleți cei 8 biți rămași în mijloc cu zerouri. Aceasta va popula intrarea IDT care corespunde întreruperii de la tastatură.

Când se termină maparea IDT, va trebui să spunem procesorului unde se află IDT-ul. Pentru aceasta există un lidt de instrucțiuni de asamblare, este nevoie de un operand. Este un pointer către un mâner către structura care descrie IDT.

Nu există dificultăți cu descriptorul. Conține dimensiunea IDT-ului în octeți și adresa sa. Am folosit o matrice pentru a o face mai compactă. În mod similar, puteți completa un descriptor folosind o structură.

În variabila idr_ptr, avem un pointer pe care îl transmitem instrucțiunii lidt din funcția load_idt ().

Load_idt: mov edx, lidt sti ret

În plus, funcția load_idt () returnează o întrerupere când se utilizează instrucțiunea sti.

După ce am completat și încărcat IDT-ul, putem accesa IRQ-ul tastaturii folosind masca de întrerupere despre care am vorbit mai devreme.

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

0xFD este 11111101 - activați numai IRQ1 (tastatură).

Funcție - manipulator de întreruperi de la tastatură

Deci, am legat cu succes întreruperile de la tastatură la funcția keyboard_handler prin crearea unei intrări IDT pentru întrerupere 0x21. Această funcție va fi apelată de fiecare dată când apăsați orice buton.

Keyboard_handler: apelați keyboard_handler_main iretd

Această funcție apelează o altă funcție scrisă în C și returnează controlul folosind instrucțiuni din clasa iret. Am putea scrie întregul nostru handler aici, dar este mult mai ușor de codat în C, așa că hai să trecem acolo. Instrucțiunile iret / iretd trebuie folosite în loc de ret atunci când controlul revine de la funcția de gestionare a întreruperilor la programul pe care l-a întrerupt. Această clasă de instrucțiuni ridică un registru steag, care este împins în stivă atunci când este apelată o întrerupere.

Void keyboard_handler_main (void) (status caracter nesemnat; cod cheie char; / * Scriem EOI * / write_port (0x20, 0x20); status = read_port (KEYBOARD_STATUS_PORT); / * Bitul de stare inferior va fi setat dacă bufferul nu este gol * / if (stare și 0x01) (keycode = read_port (KEYBOARD_DATA_PORT); if (keycode)< 0) return; vidptr = keyboard_map; vidptr = 0x07; } }

Aici dăm mai întâi semnalul EOI (End Of Interrupt) scriindu-l în portul de comandă PIC. Numai atunci PIC-ul va permite solicitări suplimentare de întrerupere. Trebuie să citim două porturi: portul de date 0x60 și portul de comandă (alias portul de stare) 0x64.

În primul rând, citim portul 0x64 pentru a obține starea. Dacă bitul de stare inferior este zero, atunci tamponul este gol și nu există date de citit. În alte cazuri, putem citi portul de date 0x60. Ne va da codul tastei apăsate. Fiecare cod corespunde unui buton. Folosim matricea de caractere simplă definită în fișierul keyboard_map.h pentru a mapa codurile la caracterele corespunzătoare. Simbolul este apoi afișat pe ecran folosind aceeași tehnică pe care am folosit-o în prima versiune a nucleului.

Pentru a nu complica codul, aici procesez doar litere mici de la a la z și numere de la 0 la 9. Puteți adăuga cu ușurință caractere speciale, Alt, Shift și Caps Lock. Puteți afla că o tastă a fost apăsată sau eliberată de la ieșirea portului de comandă și puteți lua măsurile corespunzătoare. De asemenea, puteți lega orice comenzi rapide de la tastatură la funcții speciale, cum ar fi oprirea.

Acum puteți construi nucleul, îl puteți rula pe o mașină reală sau pe un emulator (QEMU) la fel ca în prima parte.

Sistemul de operare 0 la 1 este publicat pe GitHub și are peste 2.000 de stele și 100 de furcături. După cum sugerează și numele, după ce îl citiți, vă puteți crea propriul sistem de operare - și, poate, puține lucruri din lumea programatorilor pot fi mai cool.

Prin această carte veți învăța următoarele:

  • Aflați cum să creați un sistem de operare bazat pe documentația tehnică hardware. Așa funcționează în lumea reală, nu poți folosi Google pentru răspunsuri rapide.
  • Înțelegeți modul în care componentele computerului interacționează între ele, de la software la hardware.
  • Învață să scrii singur cod. Copierea oarbă a codului nu este o curbă de învățare, veți învăța cu adevărat cum să rezolvați problemele. Apropo, copierea oarbă este și ea periculoasă.
  • Stăpânește instrumentele familiare pentru dezvoltarea la nivel scăzut.
  • Familiarizați-vă cu limbajul de asamblare.
  • Aflați din ce programe sunt făcute și cum le rulează sistemul de operare. Am oferit o mică prezentare generală a acestui subiect pentru curioșii în.
  • Aflați cum să depanați un program direct pe hardware cu GDB și QEMU.
  • Limbajul de programare C. Îl poți stăpâni rapid urmând.
  • Cunoștințe de bază Linux. Este suficient să studiezi pe site-ul nostru.
  • Cunoștințe de bază în fizică: atomi, electroni, protoni, neutroni, tensiune.

Ce trebuie să știi pentru a scrie un sistem de operare

Crearea unui sistem de operare este una dintre cele mai dificile sarcini în programare, deoarece necesită cunoștințe extinse și complexe despre funcționarea unui computer. Care? Să ne uităm la asta mai jos.

Ce este OS

Un sistem de operare (OS) este un software care funcționează cu hardware-ul computerului și resursele acestuia și este puntea dintre hardware-ul și software-ul unui computer.

Calculatoarele din prima generație nu aveau sisteme de operare. Programele de pe primele calculatoare includeau codul pentru funcționarea directă a sistemului, comunicarea cu dispozitivele periferice și calculele pentru executarea cărora a fost scris acest program. Din cauza acestei alinieri, chiar și programele care erau simple din punct de vedere logic au fost greu de implementat în software.

Pe măsură ce computerele au devenit mai diverse și mai complexe, scrierea de programe care funcționau atât ca sistem de operare, cât și ca aplicație a devenit pur și simplu incomod. Prin urmare, pentru a face programele mai ușor de scris, proprietarii de computere au început să dezvolte software. Așa au apărut sistemele de operare.

Sistemul de operare oferă tot ce aveți nevoie pentru a rula programe personalizate. Apariția lor însemna că acum programele nu mai aveau nevoie să controleze întregul volum de lucru al computerului (acesta este un exemplu grozav de încapsulare). Acum programele trebuiau să funcționeze cu sistemul de operare, iar sistemul însuși s-a ocupat de resurse și de a lucra cu perifericele (tastatură, imprimantă).

Pe scurt despre istoria sistemelor de operare

limbajul C

După cum am menționat mai sus, există mai multe limbaje de programare de nivel înalt pentru scrierea unui sistem de operare. Cu toate acestea, cel mai popular dintre acestea este C.

Puteți începe să învățați această limbă de aici. Această resursă vă va prezenta conceptele de bază și vă va pregăti pentru sarcini mai complexe.

Learn C the Hard Way este titlul unei alte cărți. Pe lângă teoria obișnuită, conține multe soluții practice. Acest tutorial va acoperi toate aspectele limbii.

Sau poți alege una dintre aceste cărți:

  • Limbajul de programare C al lui Kernighan și Ritchie;
  • „Ghidul pentru începători absolut de programare în C” de Perry și Miller.

Dezvoltarea sistemului de operare

După ce ați stăpânit tot ce trebuie să știți despre informatică, limbajul de asamblare și C, ar trebui să citiți cel puțin una sau două cărți despre dezvoltarea directă a sistemului de operare. Iată câteva resurse pentru a face acest lucru:

Linux de la zero. Procesul de construire a sistemului de operare Linux este luat în considerare aici (tutorialul a fost tradus în multe limbi, inclusiv rusă). Aici, ca și în alte manuale, vi se vor asigura toate cunoștințele de bază necesare. Bazându-te pe ele, poți încerca singur să creezi un sistem de operare. Pentru ca software-ul să facă parte din sistemul de operare mai profesional, există completări la manual: „