Să scriem un nucleu! Crearea celui mai simplu nucleu de sistem de operare care funcționează

În ultimul articol v-am spus, dar astăzi este timpul să vorbim despre cum să creați nucleul semantic al unui site folosind programe și servere speciale.

Vine vara și cumva devine din ce în ce mai greu să blog, așa că rezultatul este că articolele sunt publicate rar, ceea ce probabil nu te face fericit. Nu-i așa?

Sincer să fiu, nu este vorba deloc despre vară, ci despre „prostia” mea de a crea un blog la etapa inițială. În general, la crearea unui blog, am plecat de la gândurile mele, și nu de la solicitările utilizatorilor. În general, s-a dovedit că îmi răspundeam la întrebări, amuzant.

Într-una dintre postările mele anterioare, am scris deja despre ceea ce este necesar SY la site și ce este acesta, unde am dezvăluit baza fundamentelor creării sale, probabil că este timpul să trecem la practica creării unui nucleu semantic.

cum să creați un nucleu semantic pentru un site web

În ultimul articol, am vorbit în principal despre teorie, dar după cum arată practica, nu toți utilizatorii înțeleg întotdeauna un astfel de material, așa că vă prezint instrucțiuni pas cu pas pe care le puteți lua ca bază și vă puteți crea propriul nucleu semantic fără prea mult efort. . Da, exact ca bază, deoarece progresul nu stă pe loc și, poate, există deja modalități mai avansate de a crea nucleul pentru un site.

Căutăm concurenți pentru a compila nucleul semantic al site-ului

Deci, în primul rând, trebuie să decidem despre ce va fi viitorul nostru site web. Voi lua ca bază tema „ecologia mediului” » și voi selecta cuvinte cheie pentru el. Pentru a determina cheile principale, voi lua în primul rând site-ul concurentului și Google.

După cum înțelegeți, voi lua ca bază primele trei site-uri din rezultatele căutării Yandex.

anotimpuri-an.rf
zeleneet.com
biofile.ru

Selectăm cererile concurenților - automat

La acest pas, orice site care poate identifica automat interogări de site care sunt potrivite pentru promovarea acestuia în top este bun pentru noi. Prefer să folosesc ce server preferi - aceasta este pur și simplu părerea ta, pe care mi-o poți scrie în comentarii și să o discuti.

Adăugăm donatorul nostru, nu site-ul nostru!

După cum probabil ați ghicit, am luat unul dintre cele trei site-uri, dar ar trebui să luați mai multe. Faceți clic pe următorul...

După cum puteți vedea, am selectat doar primele 10 solicitări pentru un exemplu vizual. Ar trebui să colectați cât mai multe cuvinte cheie posibil și să nu fiți prea leneși să vă gândiți la modul în care ați cerut Google să găsească răspunsul la întrebarea dvs., pe care o veți publica pe internet pe blogul sau site-ul dvs. web. Să trecem la următoarea etapă de formare a unui nucleu semantic cu propriile noastre mâini.

După cum ați înțeles deja, tot ce trebuie să faceți este să exportați baza de date de cuvinte cheie și să treceți la pasul următor spre crearea nucleului semantic al site-ului, pe care îl veți face singur. Nu uitați să bifați casetele pentru cuvintele cheie pe care încercați să le salvați în fișierul excel.

Selectarea cererilor de joasă și de înaltă frecvență

În această etapă serverul ne va ajuta, fiți atenți la linkul de afiliat, Inregistreaza-te la rookee.ru. Trecem la acesta și creăm o nouă campanie publicitară folosind site-ul web al concurenței noastre.

Adăugați un site și urmați instrucțiunile pas cu pas ale resursei online.

Acum trebuie să așteptați puțin ca robotul să scaneze site-ul și să vă ofere cele mai interesante interogări, pe care le puteți aplica și mai târziu. Iată rezultatul meu.

După cum puteți vedea, sistemul a găsit 299 de solicitări, dar nu mă interesează și le voi șterge și le voi adăuga pe cele 10 pe care mi le-a dat seopult în prima etapă. Apropo, trebuie să rămână o singură solicitare, altfel nu veți putea șterge totul.
Asta mi s-a întâmplat după îndepărtare. Când adaug baza de date principală de interogări, voi șterge această cheie, astfel încât să nu mă deranjeze, deoarece nu este relevantă pentru mine. Acum adăugăm cererile pe care le-am primit mai devreme.

Acum trebuie să mergem în folderul „cereri” și să așteptăm puțin ca robotul de căutare să scaneze cererile. În cele din urmă, s-a dovedit că toate cuvintele s-au dovedit a fi NK (low-competitive), care sunt destul de potrivite pentru mine pentru a crea un site web și a atrage primii cititori pe blog.

Dacă aveți cuvinte cu semnificația VC (concurență înaltă) sau SC (competiție medie), atunci în prima etapă de formare a nucleului ar trebui să le abandonați și să alegeți doar concurență scăzută.

Exportăm cuvintele și trecem la următorul pas final. Deoarece toate cuvintele mele au fost o concurență scăzută, nu am șters nimic, situația ta poate fi diferită. Dar vă rog să ștergeți interogările medii și extrem de competitive, deoarece acestea sunt costuri inutile pentru dvs.

Creăm un nucleu semantic pentru site

Pentru etapa finală, vom avea nevoie de programul Key Collector Voi folosi prototipul său gratuit, pe care îl puteți găsi cu ușurință pe Internet. Probabil că vă voi lăsa numele acestui program drept teme. Deci, deschideți programul și introduceți cheile găsite, am 9 dintre ele până acum.

După cum vedem, din 9 solicitări, este mai bine să scriem despre un aparat de măsurare a umidității aerului. Acesta este un exemplu cu un cuvânt cheie care este relevant pentru interogări cu frecvență joasă pe subiectul specificat.

Nu uitați că programul este, de asemenea, capabil să selecteze 1 mie de cuvinte pentru interogarea principală cu formele sale de cuvinte pentru a face acest lucru, pur și simplu introduceți cuvintele în fila roșie „colecție de cuvinte din Yandex worstat”.

După ce ați selectat numărul maxim de cuvinte relevante, nu uitați să le sortați după importanță, începând cu cel mai mic. Încercați, de asemenea, să faceți legături bune către articolul dvs. - acest lucru va îmbunătăți faptele comportamentale din resursa dvs.

Ei bine, asta-i tot, am amânat munca mea. Cum creezi SY pentru magazinul tău online? scrie-mi despre asta în comentarii.

P.S. De asemenea, puteți primi noile mele articole prin e-mail simplu prin abonarea la buletinul informativ RSSși fii primul care află despre secretele și metodele mele. Cum creezi nucleul semantic al unui site? Scrie-mi despre metoda ta în comentarii, hai să discutăm.

Sistemul de operare asemănător UNIX este interesant de disecat și, de asemenea, de a scrie propriul kernel care va imprima un mesaj. Ei bine, scriem?

Sistem de operare asemănător UNIX și pornirea unei mașini x86

Ce este un sistem de operare asemănător UNIX? Acesta este un sistem de operare inspirat de UNIX. Dar înainte de a începe să scriem un nucleu pentru el, să ne uităm la modul în care mașina pornește și transferă controlul către nucleu.

Majoritatea registrelor procesorului x86 au valori clar definite după pornire. Registrul indicator al instrucțiunii (EIP) conține adresa de memorie pentru instrucțiunea executată de procesor. EIP este codificat la valoarea 0xFFFFFFF0. Astfel, procesorul are instrucțiuni clare la adresa fizică 0xFFFFFFF0, care este în esență ultimii 16 octeți ai spațiului de adrese de 32 de biți. Această adresă se numește vector de resetare.

Harta memoriei chipset-ului asigură acum că 0xFFFFFFF0 este mapat la o anumită parte a BIOS-ului și nu la RAM. Între timp, BIOS-ul se copiază pe RAM pentru un acces mai rapid. Aceasta se numește umbrire. Adresa 0xFFFFFFF0 va conține doar o instrucțiune pentru a sări la adresa din memorie unde s-a copiat BIOS-ul.

Astfel, codul BIOS își începe execuția. BIOS-ul caută mai întâi un dispozitiv de pornire în conformitate cu ordinea configurată a dispozitivului de pornire. Caută un anumit număr magic pentru a determina dacă dispozitivul este bootabil sau nu (octeții 511 și 512 ai primului sector sunt 0xAA55).

Odată ce BIOS-ul a detectat un dispozitiv de boot, acesta copiază conținutul primului sector al dispozitivului în RAM, începând cu adresa fizică 0x7c00; apoi merge la adresa și execută codul care tocmai a fost încărcat. Acest cod se numește bootloader de sistem.

Bootloader-ul încarcă apoi nucleul la adresa fizică 0x100000. Adresa 0x100000 este folosită ca adresă de pornire pentru toate nucleele mari de pe mașinile x86.

Toate procesoarele x86 pornesc într-un mod simplificat de 16 biți numit modul adresă reală. Bootloader-ul GRUB comută la modul protejat pe 32 de biți setând bitul cel mai puțin semnificativ al registrului CR0 la 1. Acest lucru pornește nucleul în modul protejat pe 32 de biți.

Rețineți că, dacă este detectat un nucleu Linux, GRUB va primi protocolul de pornire și va porni nucleul Linux în modul real. Și nucleul Linux va trece în modul protejat.

De ce avem nevoie?

  • computer x86 (desigur)
  • asamblator NASM
  • ld (GNU Linker)
  • Sursă

Ei bine, ar fi frumos să aveți o idee despre cum funcționează un sistem de operare asemănător UNIX. Codul sursă poate fi găsit în depozitul Github.

Punctul de intrare și lansarea nucleului

Mai întâi, să scriem un fișier mic în asamblatorul x86, care va fi punctul de pornire pentru lansarea nucleului. Acest fișier va apela o funcție C externă și apoi va opri fluxul programului.

Cum vă puteți asigura că acest cod va servi drept punct de plecare pentru kernel?

Vom folosi un script de linker care leagă fișierele obiect pentru a crea executabilul final al nucleului. În acest script, vom indica în mod explicit că fișierul binar trebuie descărcat la adresa 0x100000. Această adresă este locul unde ar trebui să fie nucleul.

Iată codul de asamblare:

;;kernel.asm biți 32 ;directiva nasm - secțiune pe 32 de biți .text global start extern kmain ;kmain este definit în fișierul C start: cli ;blocarea întreruperii mov esp, stack_space ;setarea pointerului stivei apelul kmain hlt ;oprirea procesorului secțiunea .bss resb 8192 ;8KB per stivă stack_space:

Prima instrucțiune de biți 32 nu este o instrucțiune de asamblare x86. Aceasta este o directivă pentru asamblatorul NASM care specifică că ar trebui să genereze cod pentru a rula pe un procesor care rulează în modul pe 32 de biți. Acest lucru nu este neapărat necesar în exemplul nostru, dar este o bună practică să specificați astfel de lucruri în mod explicit.

A doua linie începe cu o secțiune de text. Aici vom plasa tot codul nostru.

global este o altă directivă NASM, folosită pentru a seta simbolurile codului sursă ca fiind globale.

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

Funcția de pornire apelează funcția kmain și oprește CPU folosind comanda hlt. Întreruperile pot trezi CPU de la executarea instrucțiunii hlt. Prin urmare, mai întâi dezactivăm întreruperile folosind instrucțiunea cli.

În mod ideal, ar trebui să alocați puțină memorie pentru stivă și să indicați spre ea folosind un indicator de stivă (în special). Cu toate acestea, GRUB face acest lucru pentru noi și indicatorul de stivă este deja setat. Cu toate acestea, pentru a fi sigur, vom aloca ceva spațiu în secțiunea BSS și vom plasa indicatorul de stivă la începutul memoriei alocate. Pentru a face acest lucru, folosim comanda resb, care rezervă memorie în octeți. După aceasta, rămâne un semn care indică marginea fragmentului de memorie rezervat. Înainte ca kmain să fie apelat, indicatorul de stivă (esp) este folosit pentru a indica acest spațiu folosind comanda mov.

Kernel în C

În kernel.asm am făcut un apel la funcția kmain(). Astfel, codul C va începe să se execute în kmain():

/* * kernel.c */ void kmain(void) ( const char *str = "primul meu nucleu"; char *vidptr = (char*)0xb8000; //memoria video începe aici unsigned int i = 0; unsigned int j = 0; /* această buclă șterge ecranul */ while(j< 80 * 25 * 2) { /* пустой символ */ vidptr[j] = " "; /* байт атрибутов */ vidptr = 0x07; j = j + 2; } j = 0; /* в этом цикле строка записывается в видео память */ while(str[j] != "\0") { /* ascii отображение */ vidptr[i] = str[j]; vidptr = 0x07; ++j; i = i + 2; } return; }

*kernel.c

void kmain(void)

const char * str = "primul meu nucleu" ;

unsigned int i = 0 ;

unsigned int j = 0 ;

/* această buclă șterge ecranul */

în timp ce (j< 80 * 25 * 2 ) {

/* caracter gol */

vidptr[j] = " ";

/* octet de atribut */

vidptr[j+1] = 0x07;

j = j + 2;

j = 0;

/* în această buclă șirul este scris în memoria video */

în timp ce (str [ j ] != "\0" ) (

/* afișaj ascii */

vidptr[i] = str[j];

vidptr[i+1] = 0x07;

i = i + 2 ;

întoarcere ;

Nucleul nostru va șterge ecranul și va afișa pe el linia „primul meu nucleu”.

Pentru început, creăm un pointer vidptr care indică adresa 0xb8000. Această adresă este începutul memoriei video în modul protejat. Memoria text de pe ecran este doar o bucată de memorie din spațiul nostru de adrese. I/O pentru ecranul de pe cardul de memorie începe de la 0xb8000 și acceptă 25 de linii a câte 80 de caractere ascii fiecare.

Fiecare element de caracter din această memorie de text este reprezentat de 16 biți (2 octeți), mai degrabă decât de cei 8 biți (1 octet) cu care suntem obișnuiți. Primul octet trebuie să aibă o reprezentare de caractere, ca în ASCII. Al doilea octet este octetul de atribut. Descrie formatarea unui caracter, inclusiv diverse atribute, cum ar fi culoarea.

Pentru a imprima un caracter cu culoare verde pe un fundal negru, stocăm caracterul s în primul octet al adresei memoriei video și valoarea 0x02 în al doilea octet.

0 este un fundal negru, iar 2 este un fundal verde.

Mai jos este un tabel de coduri pentru diferite culori:

0 - Negru, 1 - Albastru, 2 - Verde, 3 - Cyan, 4 - Roșu, 5 - Magenta, 6 - Maro, 7 - Gri deschis, 8 - Gri închis, 9 - Albastru deschis, 10/a - Verde deschis, 11/b - Cyan deschis, 12/c - Roșu deschis, 13/d - Magenta deschis, 14/e - Maro deschis, 15/f - Alb.

0 - Negru, 1 - Albastru, 2 - Verde, 3 - Cyan, 4 - Roșu, 5 - Magenta, 6 - Maro, 7 - Gri deschis, 8 - Gri închis, 9 - Albastru deschis, 10/a - Verde deschis, 11/b - Cyan deschis, 12/c - Roșu deschis, 13/d - Magenta deschis, 14/e - Maro deschis, 15/f –Alb.

În nucleul nostru vom folosi caractere gri deschis pe un fundal negru. Prin urmare, octetul nostru de atribut ar trebui să aibă valoarea 0x07.

În prima buclă while, programul scrie caracterul gol cu ​​atributul 0x07 pe toate cele 80 de coloane de 25 de rânduri. Acest lucru șterge ecranul.

În a doua buclă while, caracterele liniei „primul meu nucleu” sunt scrise într-o bucată de memorie video. Pentru fiecare caracter, octetul de atribut conține valoarea 0x07.

În acest fel șirul va fi afișat pe ecran.

Piesa de conectare

Compilăm kernel.asm și NASM într-un fișier obiect, apoi folosim GCC pentru a compila kernel.c într-un alt fișier obiect. Acum sarcina noastră este să asociem aceste obiecte cu nucleul de boot executabil.

Pentru a face acest lucru, folosim un script de linker explicit, care poate fi transmis ca argument la ld (linkerul nostru).

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

*link.ld

OUTPUT_FORMAT (elf32 - i386)

INTRARE (început)

SECȚIUNI

0x100000;

Text: (*(.text))

Date: (*(.data))

Bss: (*(. bss))

Mai întâi, setăm formatul executabil de ieșire ca executabil pe 32 de biți (ELF). ELF este un format de fișier binar standard pentru sistemele x86 asemănătoare Unix.

ENTRY are un singur argument. Specifică numele simbolului, care ar trebui să fie punctul de intrare al executabilului nostru.

SECȚIUNI este cea mai importantă parte în care definim aspectul fișierului executabil. Acesta specifică cum ar trebui combinate diferitele secțiuni și unde vor fi amplasate.

În acoladele după instrucțiunea SECȚIUNI, simbolul punctului (.) reprezintă contorul de locație.

Contorul de locație este întotdeauna inițializat la 0x0 la începutul blocului SECȚIUNI. Poate fi schimbat prin atribuirea unei noi valori.

După cum sa menționat deja, codul kernel-ului trebuie să înceapă la adresa 0x100000. Deci, setăm contorul de locație la 0x100000.

Uită-te la rândul următor .text: (*(.text))

Asteriscul (*) este un caracter special care se va potrivi cu orice nume de fișier. Adică, expresia *(.text) înseamnă toate secțiunile input.text din toate fișierele de intrare.

Astfel, linkerul combină toate secțiunile text ale fișierelor obiect în secțiunea text a fișierului executabil la adresa stocată în contorul de locații. Secțiunea de cod a executabilului începe la 0x100000.

După ce linkerul plasează secțiunea de ieșire a textului, contorul de locație este setat la 0x1000000 + dimensiunea secțiunii de ieșire a textului.

La fel, secțiunile de date și bss sunt combinate și plasate pe valorile contorului de locație.

Grub și Multiboot

Acum toate fișierele necesare pentru a construi nucleul sunt gata. Dar din moment ce intenționăm să pornim nucleul folosind GRUB, avem nevoie de încă un lucru.

Există un standard pentru pornirea diferitelor nuclee x86 folosind un bootloader numit specificația Multiboot.

GRUB va porni nucleul numai dacă îndeplinește specificația Multiboot.

Potrivit acestuia, nucleul trebuie să conțină un antet în primii 8 kiloocteți.

În plus, acest antet trebuie să conțină încă 3 câmpuri:

  • câmp de număr magic: conține numărul magic 0x1BADB002 pentru a identifica antetul.
  • câmpul flags: nu avem nevoie de el acum, doar setați-i valoarea la zero.
  • câmpul sumă de control: atunci când este dat, trebuie să returneze zero pentru suma cu primele două câmpuri.

Deci, kernel.asm va arăta astfel:

;;kernel.asm ;directiva nasm - 32 biți biți 32 secțiune .text ;multiboot spec align 4 dd 0x1BADB002 ;numere magice dd 0x00 ;dd stegulețe - (0x1BADB002 + 0x00) ;checksum. mch+f+ks trebuie să fie egal cu zero global start extern kmain ;kmain este definit într-un fișier extern start: cli ;blocarea întreruperi mov esp, stack_space ;stack pointer call kmain hlt ;processor stop section .bss resb 8192 ;8KB on stack stack_space:

; ; nucleu. asm

; directiva nasm - 32 de biți

biții 32

secțiune. text

; specificații multiboot

aliniază 4

dd 0x1BADB002 ; numere magice

dd 0x00; steaguri

dd - (0x1BADB002 + 0x00); verifica suma. mch+ f+ x ar trebui să fie egal cu zero

început global

extern kmmain ; kmain este definit într-un fișier extern

start :

cli ; blocarea întreruperii

mov esp, stack_space; indicator de stivă

apelați kmain

hlt; oprirea procesorului

Dezvoltarea unui nucleu nu este considerată pe bună dreptate o sarcină ușoară, dar oricine poate scrie un nucleu simplu. Pentru a experimenta magia hacking-ului kernel-ului, trebuie doar să urmați câteva convenții și să stăpâniți limbajul de asamblare. În acest articol vă vom arăta cum să faceți acest lucru.


Salut Lume!

Să scriem un nucleu care va porni prin GRUB pe sisteme compatibile cu 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 la cum să scriem un nucleu, să ne uităm la modul în care un computer pornește și transferă controlul către kernel. Majoritatea registrelor procesorului x86 au valori specifice după pornire. Registrul indicator al instrucțiunii (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 afirmă că adresa 0xFFFFFFF0 se referă la o anumită parte a BIOS-ului, și nu la RAM. Cu toate acestea, BIOS-ul se copiază pe sine în RAM pentru un acces mai rapid - acest proces se numește „shadowing”, creând o copie umbră. Deci adresa 0xFFFFFFF0 va conține doar o instrucțiune pentru a sări la locația din memorie în care BIOS-ul s-a copiat.

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

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

Bootloader-ul încarcă nucleul la adresa fizică 0x100000. Acesta este ceea ce folosesc cele mai populare nuclee x86.

Toate procesoarele compatibile cu x86 pornesc într-un mod primitiv de 16 biți numit „mod real”. Bootloader-ul GRUB comută procesorul în modul protejat pe 32 de biți, setând bitul de jos 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, selectează protocolul de boot adecvat și încarcă nucleul în modul real. Nucleele Linux trec automat în modul protejat.

De ce avem nevoie

  • computer compatibil x86 (evident)
  • Linux
  • asamblator NASM,
  • ld (GNU Linker),
  • GRUB.

Punct de intrare în limbajul de asamblare

Ne-am dori, desigur, să scriem totul în C, dar nu vom putea evita complet utilizarea assembler. Vom scrie un fișier mic în asamblatorul x86 care va deveni 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ă oprească executarea programului.

Cum putem face din codul de asamblare punctul de plecare pentru nucleul nostru? Folosim un script de linker care leagă fișierele obiect și creează fișierul executabil final al nucleului (voi explica mai multe mai jos). În acest script, vom indica în mod direct că dorim ca binarul nostru să se descarce la adresa 0x100000. Aceasta este adresa, așa cum am scris deja, 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 îi spune să genereze cod pentru ca procesorul să ruleze în modul pe 32 de biți. Acest lucru nu este necesar pentru exemplul nostru, dar este o bună practică să îl indicați în mod explicit.

A doua linie începe secțiunea de text, cunoscută și sub denumirea de 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 servește drept punct 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 oricum și î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. Se lasă apoi un semn care indică marginea piesei rezervate de memorie. Chiar înainte ca kmain să fie apelat, indicatorul de stivă (esp) este direcționat către această zonă de către instrucțiunea mov.

Kernel în C

În fișierul kernel.asm am numit funcția kmain(). Deci, în codul C, execuția va începe de 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ă imprime linia primul meu kernel.

Mai întâi, creăm un pointer vidptr care indică adresa 0xb8000. În modul protejat, acesta este începutul memoriei video. Memoria ecranului text este pur și simplu o parte a spațiului de adrese. O secțiune de memorie este alocată pentru I/O ecran, care începe la adresa 0xb8000, sunt plasate în ea 25 de linii de 80 de caractere ASCII.

Fiecare caracter din memoria text este reprezentat de 16 biți (2 octeți), mai degrabă decât de cei 8 biți (1 octet) cu care suntem obișnuiți. Primul octet este codul ASCII al caracterului, iar al doilea octet este octetul-atribut. Aceasta este o definiție a formatului caracterului, inclusiv a culorii acestuia.

Pentru a scoate caracterul 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ă culoare 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, șirul terminat în nul primul meu nucleu este scris în memoria video și fiecare caracter primește un octet de atribut de 0x07. Acesta ar trebui să scoată un șir.

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 adecvat pentru încărcare. Pentru a face acest lucru, va trebui să scriem un script pentru linker (ld), pe care îl vom transmite ca argument.

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

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

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

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

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

Am scris mai devreme că codul kernel-ului ar trebui să înceapă la adresa 0x100000. Acesta este motivul pentru care atribuim contorului de poziții valoarea 0x100000.

Aruncă o privire la line.text: ( *(.text) ) . Asteriscul specifică aici o mască care se poate potrivi cu orice nume de fișier. În consecință, expresia *(.text) înseamnă toate secțiunile .text de intrare 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 o va plasa la adresa specificată în contorul de poziții. Secțiunea de cod a executabilului nostru va începe la adresa 0x100000.

După ce linkerul produce o secțiune de text, valoarea contorului de poziție va fi 0x100000 plus dimensiunea secțiunii de text. În mod similar, secțiunile de date și bss vor fi îmbinate și plasate la adresa specificată de contorul de poziții.

GRUB și multiboot

Acum toate fișierele noastre sunt gata pentru a construi nucleul. Dar din moment ce vom porni nucleul folosind GRUB, mai rămâne un pas.

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

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

  • magie- conține numărul „magic” 0x1BADB002, prin care este identificat antetul;
  • steaguri- acest câmp nu este important pentru noi, îl 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 steagurilor.

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

nucleu.asm
biți 32 secțiune .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 call kmain hlt secțiune resbspace _8bs2 resbspace _8bs2 ;

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

Asamblarea nucleului

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

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

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

Parametrul -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 rula linker-ul cu scriptul nostru și va genera un executabil numit kernel.

AVERTIZARE

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

Configurarea GRUB și pornirea nucleului

GRUB necesită ca numele fișierului kernel să urmeze kernel-<версия>. Deci, să redenumim fișierul - îl voi apela 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 de meniu ascuns dacă este inclusă.

GRUB 2

Pentru a rula nucleul pe care l-am creat în GRUB 2, care este furnizat 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 în listă! Și când îl selectați, veți vedea aceeași linie.



Acesta este miezul tău!

Scrierea unui nucleu cu suport pentru tastatură și ecran

Am finalizat lucrul la un nucleu minim care pornește prin GRUB, rulează în modul protejat și imprimă o singură linie pe ecran. Este timpul 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 de pe magistrala I/O. Există instrucțiuni speciale ale procesorului pentru operațiunile 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 setul 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 facultate că valorile returnate de funcții sunt trecute prin registrul eax. Deci 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 care vor fi scrise. Instrucțiunea out scrie date pe un port.

întreruperi

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

Cea mai simplă soluție este să sondați dispozitivele - verificați continuu starea acestora într-un cerc. Acest lucru este, din motive evidente, ineficient și impractic. 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 cu dispozitivul, un impuls numit cerere de întrerupere (IRQ) este trimis către cipul 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 sarcina 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ă vedem cum funcționează asta cu o tastatură. 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 prin linia de întrerupere IRQ1. PIC-ul stochează valoarea offset-ului salvată în timpul inițializării sale. Adaugă numărul liniei de intrare la această umplutură pentru a forma un vector de întrerupere. Procesorul caută apoi o structură de date numită Interrupt Descriptor Table (IDT) pentru a oferi operatorului de întrerupere adresa corespunzătoare numărului său.

Codul de la acea adresă este apoi executat și se ocupă de întrerupere.

Setați 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) (UNSIGNED LONG KEYboard_ADDRESS; UNSIGNED LONG IDTRESS; UNSIGNED LONG IDT_PTR; KEYboard_ADDRESS = (UNSIGNED LONG) Keyboard_HANDER; .OFSET_LWERBITS = keyboard_ADDRESS & 0XPTFFF_8; / IDT.offset_higherbits = ( keyboard_address & 0xffff0000) >> 16 write_port(0xA0, 0x11); * IDT_SIZE) + ((idt_address și 0xffff)<< 16); idt_ptr = idt_address >> 16;

IDT este o matrice de structuri IDT_entry. Vom discuta mai târziu legarea unei întreruperi de tastatură la un handler, dar acum să vedem cum funcționează PIC-ul.

Sistemele moderne x86 au două cipuri PIC, fiecare cu opt linii de intrare. Le vom numi PIC1 și PIC2. PIC1 primește IRQ0 la IRQ7, iar PIC2 primește IRQ8 la IRQ15. PIC1 folosește portul 0x20 pentru comenzi și 0x21 pentru date, iar 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ă ajungă pe portul de date.

Aceste comenzi vor trece PIC-ul:

  • vector indentare (ICW2),
  • care sunt relațiile 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. Acum nu vom folosi redirecționarea în cascadă și o vom seta la zero.

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

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

Fiecare PIC are un registru intern de opt biți numit Registrul masca de întrerupere (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, în timp ce scrierea modifică registrul. În codul nostru, după inițializarea PIC-ului, setăm toți biții la unul, ceea ce dezactivează toate liniile IRQ. Mai târziu vom activa 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 offset. Trebuie să completăm IDT-ul în așa fel încât numărul de întrerupere care vine de la tastatură să se potrivească cu adresa 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 din IDT constă din 64 de biți. În intrarea 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 de ordin inferior sunt stocați în primii 16 biți ai intrării IDT, iar cei 16 biți de ordin înalt sunt stocați în ultimii 16 biți ai intrării. Toate acestea sunt făcute pentru compatibilitate cu 286 de procesoare. După cum puteți vedea, Intel produce numere ca acestea în mod regulat și în multe, multe locuri!

În intrarea IDT, trebuie doar să înregistrăm tipul, indicând astfel că toate acestea se fac pentru a prinde întrerupere. De asemenea, trebuie să setăm offset-ul segmentului de cod al nucleului. GRUB stabilește GDT-ul pentru noi. Fiecare intrare GDT are 8 octeți, unde descriptorul codului kernelului este al doilea segment, deci offset-ul său va fi 0x08 (detaliile depășesc domeniul de aplicare al acestui articol). Poarta de întrerupere este reprezentată ca 0x8e. Restul de 8 biți din mijloc sunt umpluți cu zerouri. În acest fel vom popula intrarea IDT care corespunde întreruperii de la tastatură.

Odată ce am terminat cu maparea IDT, trebuie să spunem procesorului unde este IDT. Există o instrucțiune de asamblare numită lidt pentru aceasta; Acesta este un pointer către un descriptor al structurii 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 același mod, 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.

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

Void kb_init(void) ( write_port(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 un 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țiunile clasei 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 utilizate în loc de ret atunci când controlul revine de la funcția de gestionare a întreruperilor la programul î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) (starea caracterului nesemnat; codul cheii char; /* Scrieți EOI */ write_port(0x20, 0x20); status = read_port(KEYBOARD_STATUS_PORT); /* Bitul de stare inferior va fi setat dacă buffer-ul nu este gol */ if (stare și 0x01) (keycode = read_port(KEYBOARD_DATA_PORT); if(keycode)< 0) return; vidptr = keyboard_map; vidptr = 0x07; } }

Aici oferim mai întâi un semnal 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 jos al stării 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 o matrice de caractere simplă definită în 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 păstra codul simplu, 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 efectua acțiunea corespunzătoare. În același mod, puteți lega orice comenzi rapide de la tastatură la funcții speciale, cum ar fi oprirea.

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

Cea mai de bază componentă a sistemului de operare Linux este nucleul. Este nucleul care acționează ca o legătură intermediară între programele utilizatorului și hardware-ul computerului. În toate distribuțiile binare, nu trebuie să ne îngrijorăm cu privire la asamblarea și configurarea nucleului, dezvoltatorii de distribuție au făcut deja totul pentru noi. Dar dacă dorim să ne construim singuri distribuția sau să instalăm cea mai recentă versiune a nucleului, va trebui să construim nucleul manual.

Prima opțiune era relevantă pentru cei care doreau să obțină performanțe maxime din echipamentele lor, dar acum, având în vedere creșterea rapidă a puterii computerului, creșterea performanței la asamblarea nucleului este complet de neobservat. În zilele noastre, construirea nucleului poate fi necesară pentru utilizatorii distribuțiilor non-binare precum Gentoo, cei care doresc să facă unele modificări la kernel, să obțină cea mai recentă versiune a nucleului și, desigur, cei care doresc să înțeleagă pe deplin funcţionarea sistemului lor.

În acest tutorial ne vom uita la cum să construim nucleul Linux. Prima parte vă va spune cum să configurați nucleul în modul automat. Ca să spunem așa, pentru cei care nu doresc să înțeleagă cum funcționează, care trebuie doar să obțină produsul finit ca rezultat - nucleul asamblat. În a doua parte ne vom uita la pașii principali de configurare manuală a nucleului, acest proces este complex și lent, dar voi încerca să dau elementele de bază, astfel încât să vă puteți da seama singur.

Primul lucru pe care trebuie să-l faceți este să descărcați sursele nucleului. Cel mai bine este să luați sursele de pe site-ul web al distribuției dvs., dacă sunt acolo, sau de pe site-ul oficial al nucleului: kernel.org. Ne vom uita la descărcarea surselor de pe kernel.org.

Înainte de a descărca sursele, trebuie să decidem asupra versiunii nucleului pe care o vom construi. Există două versiuni principale de versiuni - stabile (stabile) și candidate de lansare (rc), există, desigur, și versiuni stabile cu o perioadă lungă de suport (pe termen lung), dar este important acum să le înțelegem pe primele două. Cele stabile, de regulă, nu sunt cele mai noi, dar sunt deja nuclee bine testate, cu un număr minim de erori. Cele de testare, dimpotrivă, sunt cele mai noi, dar pot conține diverse erori.

Deci, când ne-am hotărât asupra versiunii, accesați kernel.org și descărcați sursele necesare în format tar.xz:

Acest articol va folosi cea mai nouă versiune instabilă în prezent, 4.4.rc7.

De asemenea, puteți obține sursele kernel-ului Linux folosind utilitarul git. Mai întâi, să creăm un folder pentru surse:

mkdir kernel_sources

Pentru a descărca cea mai recentă versiune, tastați:

git clone https://github.com/torvalds/linux

Dezambalarea surselor de kernel

Acum avem surse salvate. Accesați folderul sursă:

cd linux_sources

Sau dacă ați descărcat nucleul Linux folosind un browser, creați mai întâi acest folder și copiați arhiva în el:

mkdir linux_sources

cp ~/Downloads/linux* ~/linux_sources

Despachetați arhiva folosind utilitarul tar:

Și mergi la folderul cu nucleul dezambalat, am asta:

cd linux-4.4-rc7/

Configurarea automată a construirii kernelului Linux

Înainte de a începe să construim kernel-ul Linux, va trebui să-l configuram. După cum am spus, mai întâi ne vom uita la opțiunea automată pentru configurarea construirii nucleului. Sistemul dumneavoastră are deja un nucleu asamblat, configurat de producătorul distribuției și un nucleu complet funcțional. Dacă nu doriți să vă ocupați de complexitățile configurației nucleului, puteți pur și simplu să extrageți setările gata făcute ale vechiului nucleu și să generați setări pentru cel nou pe baza acestora. Va trebui doar să specificăm valorile pentru noii parametri. Având în vedere că nu au existat și nu sunt planificate modificări majore în cele mai recente versiuni, puteți răspunde la toți acești parametri așa cum sugerează scriptul de configurare.

Parametrii nucleului utilizat sunt stocați într-o arhivă la /proc/config.gz. Să despachetăm configurația și să o plasăm în folderul nostru folosind utilitarul zcat:

În timpul procesului, va trebui să răspundeți la mai multe întrebări. Acestea sunt opțiuni noi care s-au schimbat sau au fost adăugate la noul nucleu și suport pentru hardware nou, în cele mai multe cazuri puteți alege opțiunea implicită. De obicei, există trei opțiuni: y - activare, n - nu include, m - activare ca modul. Opțiunea recomandată este scrisă cu majuscule pentru a o selecta, pur și simplu apăsați Enter;

Îți va lua aproximativ 10 minute pentru a face totul. Odată ce procesul este finalizat, nucleul este gata pentru a fi construit. În continuare, ne vom uita la configurarea manuală a nucleului, dar puteți sări direct la construirea nucleului Linux.

Reglarea manuală a kernelului Linux

Configurarea manuală este un proces complex și consumator de timp, dar vă permite să înțelegeți cum funcționează sistemul dvs., ce funcții sunt utilizate și să creați un nucleu cu setul minim necesar de funcții pentru a se potrivi nevoilor dumneavoastră. Vom lua în considerare doar pașii principali care trebuie parcurși pentru ca nucleul să fie asamblat și să funcționeze. Va trebui să vă dați seama de orice altceva pe baza documentației kernelului. Din fericire, utilitarul de configurare are o documentație extinsă pentru fiecare parametru, care vă va ajuta să înțelegeți ce alte setări trebuie să activați.

Sa incepem. Pentru a lansa meniul de setări kernel Linux, tastați:

Aceasta va deschide un utilitar cu interfața ncurses:

După cum puteți vedea, unele opțiuni necesare sunt deja incluse pentru a vă ușura procesul de configurare. Să începem cu cele mai de bază setări. Pentru a activa parametrul apăsați y, pentru a-l activa prin modul - m, pentru a vă deplasa utilizați tastele săgeți și Enter, puteți reveni la nivelul superior folosind butonul Ieșire Deschideți elementul Configurare generală.

Aici setăm următorii parametri:

Versiunea locală- versiunea locală a nucleului, va crește cu unul cu fiecare build, astfel încât nucleele noi să nu le înlocuiască pe cele vechi în timpul instalării, setați valoarea la 1.

Adăugați automat informațiile despre versiune la șirul de versiuni- adăugați versiunea la numele fișierului kernel.

Modul de compresie a nucleului- modul de compresie a imaginii nucleului, cel mai eficient lzma.

Nume de gazdă implicit- numele computerului afișat la promptul de intrare

Cozi de mesaje POSIX- suport pentru cozile POSTIX

Suport pentru paginarea memoriei anonime - activați suportul de schimb

Suport Grup de control- suport pentru un mecanism de distribuire a resurselor între grupuri de procese

Suport Kernel.configȘi Activați accesul la .config prin /proc/config.gz- activați capacitatea de a extrage configurația kernelului prin /proc/config.gz

Asta e, du-te înapoi la un nivel și pornește-l Activați suportul pentru module încărcate, Această funcție permite încărcarea modulelor externe, apoi deschiderea meniului și activarea:

suport pentru dezactivarea modulelor

oprirea forțată a modulelor

Din nou ne întoarcem și deschidem Tipul și caracteristicile procesorului:

Familia de procesoare (Opteron/Athlon64/Hammer/K8)- selectați tipul dvs. de procesor.

Să revenim din nou și să mergem la secțiune Sisteme de fișiere, bifați aici toate casetele necesare.

Asigurați-vă că îl porniți Sistemul de fișiere Extended 3 (ext3).Și Sistemul de fișiere Extended 4 (ext4).- pentru a suporta sistemele de fișiere standard ext3 și ext4

Ne întoarcem și mergem la Hacking kernel.

Aici includem Tasta Magic SysRq- suport pentru funcțiile magice SysRq, nu un lucru esențial, dar uneori util.

Mai a mai rămas un punct, cel mai dificil, pentru că va trebui să treci singur prin el. Drivere de dispozitiv- trebuie să parcurgeți secțiunile și să activați driverele pentru echipamentul dvs. Prin echipament mă refer la hard disk-uri non-standard, mouse-uri, dispozitive USB, camere web, Bluetooth, adaptoare WIFI, imprimante etc.

Puteți vedea ce echipament este conectat la sistemul dvs. cu comanda:

Odată ce ați finalizat toți pașii, nucleul este gata de construit, dar probabil că veți avea multe de aflat.

Pentru a ieși apăsați butonul de câteva ori Ieșire.

Construirea nucleului Linux

După ce toate pregătirile sunt finalizate, nucleul Linux poate fi construit. Pentru a începe procesul de construire, rulați:

face && face module

Acum poți merge să bei o cafea sau să faci o plimbare, pentru că procesul de asamblare este lung și va dura aproximativ o jumătate de oră.

Instalarea unui nucleu nou

Când nucleul și modulele sunt asamblate, noul nucleu poate fi instalat. Puteți copia manual fișierul kernel în folderul bootloader:

cp arch/x86_64/boot/bzImage /boot/vmlinuz

Sau pur și simplu puteți executa scriptul de instalare, instalând imediat modulele în același timp:

sudo make install && sudo make modules_install

După instalare, nu uitați să actualizați configurația Grub bootloader:

grub-mkconfig -o /boot/grub/grub.cfg

Și reporniți computerul pentru a vedea noul nucleu în acțiune:

concluzii

Asta e tot. În acest articol, am analizat în detaliu cum să construim nucleul Linux din sursă. Acest lucru va fi util pentru oricine dorește să-și înțeleagă mai bine sistemul și pentru cei care doresc să obțină cea mai recentă versiune a nucleului pe sistemul lor. Dacă aveți întrebări, întrebați în comentarii!

Această serie de articole este dedicată programării de nivel scăzut, adică arhitecturii computerelor, proiectării sistemelor de operare, programării în limbaj de asamblare și domeniilor conexe. Până acum, doi habrauseri scriu - și . Pentru mulți elevi de liceu, studenți și chiar programatori profesioniști, aceste subiecte se dovedesc a fi foarte greu de învățat. Există o mulțime de literatură și cursuri dedicate programării de nivel scăzut, dar este dificil să obțineți o imagine completă și atotcuprinzătoare. Este dificil, după ce am citit una sau două cărți despre limbajul de asamblare și sistemele 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. Unii oameni citesc multă literatură, unii încearcă să treacă rapid la exersare și să-și dea seama pe măsură ce merg, alții î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 de recenzie, adică nu vor conține informații teoretice exhaustive, dar 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ă.

Este posibil să împiedicăm în mod deliberat procesul de dezvoltare pentru a vă permite dvs. și nouă înșine să înțelegeți pe deplin consecințele unei decizii proaste, precum și pentru a perfecționa unele abilități tehnice asupra acesteia. Deci nu ar trebui să percepeți deciziile noastre ca fiind singurele corecte și să ne credeți orbește. Să subliniem încă o dată că ne așteptăm ca cititorii să fie activi în discutarea articolelor, ceea ce ar trebui să influențeze foarte mult procesul general de dezvoltare și scriere a articolelor ulterioare. În mod ideal, în timp, aș dori ca unii dintre cititori să se alăture dezvoltării sistemului.

Vom presupune că cititorul este deja familiarizat cu elementele de bază ale limbajelor de asamblare și C, precum și cu conceptele elementare ale arhitecturii computerelor. Adică nu vom explica ce este un registru sau, să zicem, RAM. Dacă nu aveți suficiente cunoștințe, puteți oricând să apelați la literatură 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 recomandabil să știți cum să utilizați 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 clasicul program „Hello World”. Helloworld va fi puțin specific. Nu va fi lansat de pe niciun sistem de operare, ci direct, ca să spunem așa, „pe bare metal”. Înainte de a începe să scriem codul, să ne dăm seama exact cum încercăm să facem acest lucru. Și pentru aceasta trebuie să luăm î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 screensaver vesel, unitatea de sistem emite un bip fericit cu difuzorul și după ceva timp sistemul de operare se încarcă. După cum înțelegeți, sistemul de operare este stocat pe un hard disk și aici apare întrebarea: cum s-a încărcat magic sistemul de operare în RAM și a început să se execute?

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

  1. Detectează toate dispozitivele conectate (procesor, tastatură, monitor, RAM, placă video, cap, brațe, aripi, picioare și cozi...) și verifică funcționalitatea acestora. Programul POST (Power On Self Test) este responsabil pentru acest lucru. Dacă hardware-ul vital nu este detectat, atunci niciun software nu va putea ajuta, iar în acest moment difuzorul sistemului va scârțâi ceva de rău augur și sistemul de operare nu va ajunge deloc la obiect. Să nu vorbim despre lucruri triste, să presupunem că avem un computer pe deplin funcțional, să ne bucurăm și să 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ă. De aceea se numește sistem de bază de intrare/ieșire. 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 mediului de stocare (dischetă, hard disk, CD, unitate flash). Ordinea de interogare media 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, încărcătorul de pornire principal este scris direct de dezvoltatorii sistemului de operare și este scris în sectorul de pornire în timpul instalării. Când pornește bootloader-ul, procesorul este în modul real.
Vestea tristă este că bootloader-ul ar trebui să aibă o dimensiune de numai 512 octeți. De ce atât de puțini? Pentru a face acest lucru, trebuie să ne familiarizăm cu structura dischetei. Iată o poză informativă:

Imaginea arată suprafața unei unități de disc. O dischetă are 2 suprafețe. Fiecare suprafață are trasee (piese) în formă de inel. Fiecare pistă este împărțită în bucăți mici în formă de arc 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 pornire, este citit de BIOS în segmentul de memorie zero la offset 0x7C00, iar apoi controlul este transferat la această adresă stocate pe disc, dar dintr-un motiv oarecare (cel mai probabil, acest motiv este dimensiunea) nu se încadrează într-un singur sector Și deoarece deocamdată rolul sistemului nostru de operare este jucat de un Helloworld banal, scopul nostru principal este să facem computerul credeți în existența sistemului nostru de operare, chiar și într-un singur sector, și lansați-l.

Cum este structurat 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 cod! Codul dat este scris pentru asamblatorul yasm.

secțiune. text

folosi16

org 0x7C00 ; programul nostru este încărcat la adresa 0x7C00

start:

mov ax, cs

mov ds, ax ; selectați un segment de date



mov si , mesaj

cld ; direcție pentru comenzi șir

mov ah , 0x0E ; Numărul caracteristicii BIOS

mov bh , 0x00 ; pagina de memorie video

puts_loop:

lodsb ; încărcați următorul simbol în al

test al, al ; caracterul nul înseamnă sfârșitul liniei

jz puts_loop_exit

int 0x10 ; apelați funcția BIOS

jmp puts_loop

puts_loop_exit:

jmp$; ciclu etern



mesaj:

db „Bună lume!” , 0

finalizarea:

ori 0x1FE - terminare + start db 0

db 0x55, 0xAA ; semnătura sectorului de boot

Acest scurt program necesită câteva explicații importante. Linia org 0x7C00 este necesară pentru ca asamblatorul (adică programul, nu limbajul) 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 în același segment.

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

La linia „jmp$” programul se blochează. Și pe bună dreptate, nu este nevoie ca acesta 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, ultimii doi octeți ai programului să conțină semnătura sectorului de boot.

Se pare că am rezolvat codul programului, să încercăm acum să compilăm această fericire. Pentru compilare avem nevoie, strict vorbind, de un asamblator - cel menționat mai sus