Internet Windows Android

Configurarea și compilarea nucleului Linux. Arhive de categorii: Programarea driverelor care definesc în mod explicit funcțiile de finalizare și inițializare

De ce să compilați singur nucleul?
Poate că întrebarea principală pusă despre compilarea unui nucleu este: „De ce ar trebui să fac asta?”
Mulți consideră că este o pierdere inutilă de timp pentru a se dovedi a fi un „Linuxoid” inteligent și avansat. De fapt, compilarea nucleului este foarte importantă. Să presupunem că ați cumpărat un laptop nou și camera web nu funcționează. Actiunile tale? Te uiți într-un motor de căutare și cauți o soluție la o problemă legată de această problemă. Destul de des se poate dovedi că camera web rulează pe un nucleu mai nou decât al tău. Dacă nu știți ce versiune aveți, introduceți uname -r în terminal, ca urmare veți obține versiunea de kernel (de exemplu, linux-2.6.31-10). Compilarea kernel-ului este, de asemenea, utilizată pe scară largă pentru a crește performanța: fapt este că în mod implicit în distribuții kernel-urile sunt compilate „pentru toată lumea”, motiv pentru care include un număr mare de drivere de care s-ar putea să nu aveți nevoie. Deci, dacă cunoașteți bine hardware-ul utilizat, puteți dezactiva driverele inutile în etapa de configurare. De asemenea, este posibil să activați suportul pentru mai mult de 4 Gigaocteți de RAM fără a modifica adâncimea de biți a sistemului. Deci, dacă mai trebuie să aveți propriul kernel, să începem compilarea!

Obținerea sursei kernelului.
Primul lucru de făcut este să obțineți codul sursă pentru versiunea corectă a nucleului. De obicei, trebuie să obțineți cea mai recentă versiune stabilă. Toate versiunile oficiale ale nucleului sunt disponibile la kernel.org. Dacă aveți deja instalat un server X (calculatorul de acasă), atunci puteți accesa site-ul în browserul dvs. preferat și puteți descărca versiunea necesară în arhiva tar.gz (gzip comprimat). Dacă lucrați în consolă (de exemplu, nu ați instalat încă serverul X sau configurați serverul), puteți utiliza un browser de text (de exemplu, elinks). De asemenea, puteți utiliza managerul standard de descărcare wget:
wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.33.1.tar.gz
Dar vă rugăm să rețineți că trebuie să cunoașteți numărul exact de versiune pe care îl doriți.

Despachetarea arhivei sursă.
După ce ați primit arhiva cu codul sursă, trebuie să despachetați arhiva într-un folder. Acest lucru se poate face din manageri de fișiere grafice (delfin, nautilus etc.) sau prin mc. Alternativ, utilizați comanda tradițională tar:
tar -zxvf calea_la_arhivă
Acum aveți un folder și un cod sursă, navigați la el folosind comanda cd director_cod_sursă_nucleu(utilizați comanda ls pentru a lista directoarele dintr-un folder).

Configurația kernelului.
Odată ce ați trecut la directorul sursă al nucleului, trebuie să efectuați o configurație de „20 de minute” a nucleului. Scopul său este de a lăsa doar driverele și funcțiile necesare. Toate comenzile trebuie deja executate în numele superutilizatorului.

make config - modul consola al configuratorului.

face menuconfig - modul console ca o listă.

make xconfig - modul grafic.

După efectuarea modificărilor necesare, salvați setările și părăsiți configuratorul.

Compilare.
A sosit timpul pentru etapa finală a asamblarii - compilare. Acest lucru se face cu două comenzi:
make && make install
Prima comandă va compila toate fișierele în codul mașinii, iar a doua va instala noul nucleu pe sistemul dumneavoastră.
Așteptăm de la 20 de minute la câteva ore (în funcție de puterea computerului). Nucleul este instalat. Pentru a-l face să apară în lista grub (2), tastați (ca rădăcină)
update-grub
Acum, după repornire, apăsați „Escape” și veți vedea noul nucleu listat. Dacă nucleul nu pornește, atunci pur și simplu porniți cu nucleul vechi și configurați mai atent.

KernelCheck - compilarea nucleului fără a intra în consolă.
vă permite să construiți nucleul într-un mod complet grafic pentru Debian și distribuțiile bazate pe acesta... După lansare, KernelCheck va oferi cele mai recente versiuni ale nucleului și patch-urilor, iar după acordul dumneavoastră, va descărca codul sursă, va lansa configuratorul grafic. Programul va compila nucleul în pachete .deb și le va instala. Trebuie doar să reporniți.

Despre: „Pe baza traducerii” Linux Device Driver ediția a 2-a. Traducere: Knyazev Alexey [email protected] Data ultimei modificări: 03.08.2004 Plasare: http://lug.kmv.ru/index.php?page=knz_ldd2

Acum să începem programarea! Acest capitol oferă o privire de ansamblu asupra modulelor și programării nucleului.
Aici vom construi și rula un modul cu drepturi depline, a cărui structură corespunde oricărui driver de modul real.
În același timp, ne vom concentra pe pozițiile principale fără a ține cont de specificul dispozitivelor reale.

Toate părțile nucleului, cum ar fi funcțiile, variabilele, anteturile și macrocomenzile menționate aici, vor fi
sunt detaliate la sfârșitul capitolului.

Salut Lume!

În timp ce revizuiam materialul original scris de Alessndro Rubini & Jonathan Corbet, am găsit un exemplu oarecum nefericit dat ca Hello world! Prin urmare, vreau să ofer cititorului, după părerea mea, o versiune mai bună a primului modul. Sper că nu vor fi probleme cu compilarea și instalarea lui sub nucleul 2.4.x. Modulul propus și modul în care este compilat îi permit să fie utilizat în nuclee, atât suportând, cât și nesuportand controlul versiunilor. Mai târziu te vei familiariza cu toate detaliile și terminologia, acum deschid vim și încep să lucrez!

==================================================== // hello_knz.c fișier #include #include <1>Bună ziua, lume \ n "); return 0;); void cleanup_module (void) (printk ("<1>La revedere lume crudă \ n ");) MODULE_LICENSE (" GPL "); =============================== == ================

Următorul Makefile poate fi folosit pentru a compila un astfel de modul. Nu uitați să puneți un caracter tabulator în fața liniei care începe cu $ (CC)...

==================================================== FLAGS = -c -Wall -D__KERNEL__ -DMODULE PARAM = -I / lib / modules / $ (shell uname -r) / build / include hello_knz.o: hello_knz.c $ (CC) $ (FLAGS) $ (PARAM) - o [email protected] $^ =================================================

Aici sunt folosite două caracteristici, în comparație cu codul original Hello world propus de Rubini & Corbet. În primul rând, modulul va avea aceeași versiune ca și nucleul. Acest lucru se realizează prin setarea variabilei PARAM în scriptul de compilare. În al doilea rând, acum modulul va fi licențiat sub GPL (folosind macrocomanda MODULE_LICENSE ()). Dacă acest lucru nu se face, atunci când instalați modulul în nucleu, este posibil să vedeți aproximativ următorul avertisment:

# insmod hello_knz.o Atenție: încărcarea hello_knz.o va afecta nucleul: fără licență Consultați http://www.tux.org/lkml/#export-tainted pentru informații despre modulele contaminate Modulul hello_knz încărcat, cu avertismente

Să explicăm acum opțiunile de compilare a modulelor (macro-urile vor fi explicate mai târziu):

-cu- dacă această opțiune este prezentă, compilatorul gcc va opri procesul de compilare a fișierului imediat după crearea fișierului obiect, fără a încerca să creeze un binar executabil.

-Perete- nivelul maxim de ieșire de avertizare în timp ce gcc rulează.

-D- definițiile macro-simbolurilor. La fel ca directiva #define din fișierul compilat. Nu are nicio diferență cum definiți macrocomenzile utilizate în acest modul folosind #define în fișierul sursă sau folosind opțiunea -D pentru compilator.

-Eu- căi de căutare suplimentare pentru fișierele incluse. Observați utilizarea substituției „uname -r”, care vă va oferi numele exact al versiunii de kernel utilizate în prezent.

Următoarea secțiune oferă un alt exemplu de modul. De asemenea, explică în detaliu cum să îl instalați și să îl descărcați din kernel.

Lumea originală Hello!

Acum, iată codul original pentru un modul simplu „Hello, World” oferit de Rubini & Corbet. Acest cod poate fi compilat sub nucleele 2.0 până la 2.4. Acest exemplu, ca și celelalte din carte, este disponibil pe site-ul FTP O'Reilly (vezi Capitolul 1).

// fișier hello.c #define MODULE #include int init_module (void) (printk ("<1>Bună ziua, lume \ n "); return 0;) void cleanup_module (void) (printk ("<1>La revedere lume crudă \ n ");)

Funcţie printk () definit în nucleul Linux și funcționează ca o funcție standard de bibliotecă printf ()în limbajul C. Nucleul are nevoie de propria sa funcție de ieșire, de preferință mică, conținută direct în nucleu, nu în bibliotecile la nivel de utilizator. Un modul poate apela o funcție printk () deoarece după încărcarea modulului cu comanda insmod modulul comunică cu nucleul și are acces la funcțiile și variabilele nucleului publicate (exportate).

Parametru șir „<1>„Trimit la funcția printk () este prioritatea mesajului. Sursele originale în limba engleză folosesc termenul loglevel, adică nivelul de înregistrare a mesajelor. Aici, vom folosi termenul de prioritate în locul „loglevel” inițial. În acest exemplu, folosim prioritate mare pentru mesaj, care corespunde numărului scăzut. Prioritatea ridicată a mesajului este setată în mod deliberat, deoarece mesajul cu prioritate implicită poate să nu fie afișat în consola de pe care a fost instalat modulul. Direcția de ieșire a mesajelor de kernel cu o prioritate implicită depinde de versiunea nucleului care rulează, versiunea demonului klogd, și configurația dvs. Mai detaliat, lucrul cu o funcție printk () vom explica în Capitolul 4, „Tehnici de depanare”.

Puteți testa modulul folosind comanda insmod pentru a instala un modul în nucleu și comenzi rmmod pentru a elimina un modul din nucleu. Mai jos vă vom arăta cum se poate face acest lucru. În acest caz, punctul de intrare init_module () este executat când un modul este instalat în nucleu, iar cleanup_module () este executat când este eliminat din nucleu. Amintiți-vă că doar un super utilizator poate încărca și descărca module.

Exemplul de modul de mai sus poate fi folosit numai cu un nucleu care a fost construit cu steag-ul „modul version support” dezactivat. Din păcate, majoritatea distribuțiilor folosesc nuclee versiuni (discutate în secțiunea „Controlul versiunilor în module” din Capitolul 11, „kmod și modularizarea avansată”). Deși versiunile mai vechi ale pachetului modutile permite încărcarea unor astfel de module în nuclee compilate cu controlul versiunilor, acum acest lucru nu este posibil. Amintiți-vă că pachetul modutils conține un set de programe care includ programele insmod și rmmod.

Sarcină: Determinați numărul versiunii și conținutul pachetului modutils din distribuția dvs.

Când încercați să introduceți un astfel de modul într-un nucleu care acceptă controlul versiunilor, este posibil să vedeți un mesaj de eroare similar cu următorul:

# insmod hello.o hello.o: kernel-module version nepotrivire hello.o a fost compilat pentru versiunea kernel-ului 2.4.20, în timp ce acest nucleu este versiunea 2.4.20-9asp.

In catalog diverse module Pentru exemple de pe ftp.oreilly.com veți găsi programul de exemplu hello.c original, care are puțin mai multe linii și poate fi instalat în nuclee cu sau fără control al versiunii. Cu toate acestea, vă recomandăm insistent să vă construiți propriul kernel fără suport pentru controlul versiunilor. În același timp, se recomandă să luați sursele originale ale kernel-ului pe site-ul www.kernel.org

Dacă sunteți nou în construirea de nuclee, încercați să citiți articolul pe care Alessandro Rubini (unul dintre autorii cărții originale) l-a postat la http://www.linux.it/kerneldocs/kconf, care ar trebui să vă ajute să stăpâniți procesul.

Rulați următoarele comenzi într-o consolă de text pentru a compila și testa modulul eșantion original de mai sus.

Root # gcc -c hello.c root # insmod ./hello.o Salut, rădăcină mondială # rmmod salut Adio rădăcină mondială crudă #

În funcție de mecanismul pe care sistemul dumneavoastră îl utilizează pentru trimiterea șirurilor de mesaje, direcția de ieșire a mesajelor trimise de funcție printk () Poate diferi. În exemplul dat de compilare și testare a modulului, mesajele transmise de la funcția printk () au fost afișate în aceeași consolă, de unde au fost date comenzile pentru instalarea și rularea modulelor. Acest exemplu a fost filmat dintr-o consolă text. Dacă executați comenzile insmodși rmmod de sub program xterm atunci cel mai probabil nu vei vedea nimic pe terminalul tău. În schimb, mesajul poate ajunge într-unul dintre jurnalele de sistem, de exemplu în / var / jurnal / mesaje. Numele exact al fișierului depinde de distribuție. Uitați-vă la ora de modificare a fișierelor jurnal. Mecanismul folosit pentru a transmite mesaje de la funcția printk () este descris în secțiunea „Cum se înregistrează mesajele” din Capitolul 4, „Tehnica
depanare”.

Pentru a vizualiza mesajele modulului în fișierul jurnal de sistem / val / log / mesaje, este convenabil să utilizați utilitarul de coadă a sistemului, care, implicit, afișează ultimele 10 rânduri ale fișierului transferat la acesta. O opțiune interesantă a acestui utilitar este opțiunea -f, care pornește utilitarul în modul de urmărire a ultimelor linii ale fișierului, adică. când apar linii noi în fișier, acestea vor fi afișate automat. Pentru a opri executarea comenzii în acest caz, apăsați Ctrl + C. Astfel, pentru a vizualiza ultimele zece linii ale fișierului jurnal de sistem, introduceți următoarele la linia de comandă:

Rădăcină # coadă / var / jurnal / mesaje

După cum puteți vedea, scrierea unui modul nu este atât de dificilă pe cât ar părea. Cea mai grea parte este să înțelegeți cum funcționează dispozitivul dvs. și cum să creșteți performanța modulului. Pe parcursul acestui capitol, vom afla mai multe despre scrierea modulelor simple, lăsând dispozitivul specific pentru capitolele următoare.

Diferențele dintre modulele kernelului și aplicațiile

O aplicație are un singur punct de intrare, care începe să se execute imediat după ce aplicația lansată este plasată în memoria RAM a computerului. Acest punct de intrare este descris în C ca funcția principală (). Terminarea funcției principale () înseamnă terminarea aplicației. Modulul are mai multe puncte de intrare care sunt executate la instalarea și eliminarea modulului din kernel, precum și la procesarea cererilor primite de la utilizator. De exemplu, punctul de intrare init_module () este executat atunci când un modul este încărcat în nucleu. Funcția cleanup_module () este executată când modulul este descărcat. În viitor, ne vom familiariza cu alte puncte de intrare în modul, care sunt executate atunci când se fac diverse solicitări către modul.

Capacitatea de a încărca și descărca module sunt doi piloni ai mecanismului de modularizare. Ele pot fi evaluate în diferite chei. Pentru un dezvoltator, asta înseamnă, în primul rând, o reducere a timpului de dezvoltare, din moment ce puteți testa funcțiile driverului fără un proces lung de repornire.

Ca programator, știi că o aplicație poate apela o funcție care nu a fost declarată în aplicație. În etapele de legare statică sau dinamică se determină adresele unor astfel de funcții din bibliotecile corespunzătoare. Funcţie printf () una dintre aceste funcții apelabile, care este definită în bibliotecă libc... Un modul, pe de altă parte, este asociat doar cu nucleul și poate apela doar funcții care sunt exportate de nucleu. Codul executabil kernel nu poate folosi biblioteci externe. Deci, de exemplu, funcția printk () care a fost folosit în exemplu buna, este un analog al funcției binecunoscute printf () disponibil în aplicațiile la nivel de utilizator. Funcţie printk () este situat în nucleu și ar trebui să fie cât mai mic posibil. Prin urmare, spre deosebire de printf (), are suport foarte limitat pentru tipurile de date și, de exemplu, nu acceptă deloc numere în virgulă mobilă.

Implementarea nucleelor ​​2.0 și 2.2 nu a suportat specificatorii de tip Lși Z... Au fost introduse doar în nucleul 2.4.

Figura 2-1 prezintă implementarea mecanismului de apelare a funcțiilor care sunt puncte de intrare într-un modul. De asemenea, această figură arată mecanismul de interacțiune a unui modul instalat sau în curs de instalare cu nucleul.

Orez. 2-1. Conectarea modulului la kernel

Una dintre particularitățile sistemelor de operare Unix / Linux este că nu există biblioteci care să poată fi legate la modulele kernelului. După cum știți deja, modulele, atunci când sunt încărcate, sunt legate în nucleu, prin urmare toate funcțiile externe modulului dumneavoastră trebuie să fie declarate în fișierele de antet ale nucleului și să fie prezente în nucleu. Sursele modulelor nu nu ar trebui să includă fișiere de antet obișnuite din bibliotecile spațiului utilizatorului. În modulele nucleului, puteți utiliza numai funcții care fac parte de fapt din nucleu.

Întreaga interfață a nucleului este descrisă în fișierele de antet aflate în directoare include / linuxși include / asmîn interiorul surselor nucleului (de obicei situate în /usr/src/linux-x.y.z(x.y.z este versiunea dvs. de kernel)). Distribuții mai vechi (bazate pe libc versiunile 5 sau mai puțin) au folosit legături simbolice / usr / include / linuxși / usr / include / asm către directoarele corespunzătoare din sursele nucleului. Aceste legături simbolice oferă posibilitatea de a utiliza interfețele kernelului în aplicații personalizate, după cum este necesar.

Deși interfața bibliotecii spațiu utilizator este acum separată de interfața nucleului, uneori devine necesar ca procesele utilizatorului să folosească interfețele nucleului. Cu toate acestea, multe dintre legăturile din anteturile nucleului sunt specifice nucleului însuși și nu ar trebui să fie disponibile pentru aplicațiile utilizatorului. Prin urmare, aceste reclame sunt protejate. #ifdef __KERNEL__ blocuri. Acesta este motivul pentru care driverul dvs., ca și alt cod de kernel, trebuie să fie compilat cu simbolul macro declarat __NUCLEU__.

Rolul antetelor individuale ale nucleului va fi discutat pe parcursul cărții, după cum este necesar.

Dezvoltatorii care lucrează la orice proiect software mare (cum ar fi nucleul) ar trebui să ia în considerare și să evite „poluarea spațiului de nume”... Această problemă apare atunci când există un număr mare de funcții și variabile globale ale căror nume nu sunt suficient de expresive (se pot distinge). Programatorul care ulterior trebuie să se ocupe de astfel de aplicații este forțat să petreacă mult mai mult timp memorând nume „rezervate” și să vină cu nume unice pentru elemente noi. Ciocnirile de nume (ambiguitățile) pot crea o gamă largă de probleme, de la erori la încărcarea unui modul, până la comportamentul instabil sau inexplicabil al programului care poate apărea utilizatorilor care utilizează un nucleu construit într-o configurație diferită.

Dezvoltatorii nu își pot permite să facă astfel de greșeli atunci când scriu codul nucleului, deoarece chiar și cel mai mic modul va fi legat de întregul nucleu. Cea mai bună soluție pentru a evita coliziunile de nume este, în primul rând, să declarați obiectele programului dvs. ca staticși, în al doilea rând, utilizarea unui prefix unic la nivelul întregului sistem pentru denumirea obiectelor globale. În plus, în calitate de dezvoltator de module, puteți controla domeniul de aplicare al obiectelor din codul dvs., așa cum este descris mai târziu în secțiunea „Tabel de legare a nucleului”.

Majoritatea (dar nu toate) versiunile comenzii insmod exportați toate obiectele modul care nu sunt declarate ca static, implicit, i.e. cu excepția cazului în care în modul sunt definite instrucțiuni specifice. Prin urmare, este perfect logic să declari obiecte modul pe care nu le vei exporta static.

Utilizarea unui prefix unic pentru obiectele locale într-un modul poate fi o practică bună, deoarece facilitează depanarea. În timpul testării driverului, poate fi necesar să exportați obiecte suplimentare în nucleu. Folosind un prefix unic pentru a desemna nume, nu riscați să introduceți coliziuni în spațiul de nume al nucleului. Prefixele nucleului folosesc caractere minuscule prin convenție și ne vom menține la această convenție.

O altă diferență semnificativă între procesele kernel și utilizator este mecanismul de gestionare a erorilor. Nucleul controlează execuția procesului utilizatorului, astfel încât o eroare în procesul utilizatorului are ca rezultat un mesaj, inofensiv pentru sistem: eroare de segmentare. În acest caz, un depanator poate fi întotdeauna utilizat pentru a urmări o eroare în codul sursă al unei aplicații utilizator. Erorile care apar în nucleu sunt fatale - dacă nu pentru întregul sistem, atunci cel puțin pentru procesul curent. În secțiunea „Depanarea erorilor de sistem” din Capitolul 4, „Tehnici de depanare”, vom analiza modalități de a urmări erorile de kernel.

Spațiu utilizator și spațiu kernel

Modulul este executat în așa-numitul spațiu kernelîn timp ce aplicațiile rulează. Acest concept este fundamentul teoriei sistemelor de operare.

Unul dintre scopurile principale ale sistemului de operare este de a oferi utilizatorului și programelor utilizatorului resurse de calculator, majoritatea fiind reprezentate de dispozitive externe. Sistemul de operare nu trebuie doar să ofere acces la resurse, ci și să controleze alocarea și utilizarea acestora, prevenind coliziunile și accesul neautorizat. În plus, sistemul de operare poate crea operațiuni independente pentru programe și poate proteja împotriva accesului neautorizat la resurse. Soluția la această sarcină non-trivială este posibilă numai dacă procesorul protejează programele de sistem de aplicațiile utilizatorului.

Aproape fiecare procesor modern este capabil să ofere această separare prin implementarea diferitelor niveluri de privilegii pentru codul executabil (sunt necesare cel puțin două niveluri). De exemplu, procesoarele I32 au patru niveluri de privilegii de la 0 la 3. Mai mult, nivelul 0 are cele mai mari privilegii. Pentru astfel de procesoare, există o clasă de instrucțiuni privilegiate care pot fi executate doar la niveluri privilegiate. Sistemele Unix folosesc două niveluri de privilegii de procesor. Dacă procesorul are mai mult de două niveluri de privilegii, se utilizează cel mai mic și cel mai înalt nivel. Nucleul Unix operează la cel mai înalt nivel de privilegii, oferind control asupra hardware-ului și proceselor utilizatorului.

Când vorbim despre spațiu kernelși spațiu de proces al utilizatorului Mă refer nu numai la diferite niveluri de privilegii ale codului executabil, ci și la diferite spații de adrese.

Unix transferă execuția din spațiul de proces al utilizatorului în spațiul kernel de două ori. În primul rând, când aplicația utilizator efectuează un apel către nucleu (apel de sistem) și, în al doilea rând, în timp ce întreține întreruperile hardware. Codul kernelului executat la un apel de sistem rulează în contextul unui proces, adică lucrând în interesul procesului de apelare de la are acces la datele spațiului de adrese al procesului. Pe de altă parte, codul executat la întreruperea hardware este asincron în raport cu procesul și nu aparține niciunui proces special.

Scopul modulelor este de a extinde funcționalitatea nucleului. Codul modulului este executat în spațiul kernel. De obicei, un modul îndeplinește ambele sarcini menționate mai devreme: unele dintre funcțiile modulului sunt executate ca parte a apelurilor de sistem, iar unele sunt responsabile pentru gestionarea întreruperilor.

Paralelizarea în nucleu

La programarea driverelor de dispozitiv, spre deosebire de programarea aplicațiilor, problema paralelizării codului executabil este deosebit de acută. În mod obișnuit, o aplicație rulează de la început până la sfârșit într-o manieră secvențială, fără să-și facă griji cu privire la schimbarea mediului în care se află. Codul nucleului ar trebui să funcționeze cu presupunerea că poate fi accesat de mai multe ori în același timp.

Există multe motive pentru paralelizarea codului kernelului. De obicei, există multe procese care rulează în Linux și unele dintre ele pot încerca să acceseze codul modulului în același timp. Multe dispozitive pot declanșa întreruperi hardware pe procesor. Operatorii de întrerupere sunt apelați asincron și pot fi apelați în timp ce șoferul dumneavoastră este ocupat să execute o altă solicitare. Unele abstracții software (cum ar fi cronometrele kernelului, explicate în Capitolul 6, „Fluxul timpului”), de asemenea, rulează asincron. În plus, Linux poate rula pe un sistem multiprocesor simetric (SMP), prin care codul driverului poate rula în paralel pe mai multe procesoare în același timp.

Din aceste motive, codul kernel-ului Linux, inclusiv codurile driverului, trebuie să fie reintrant, de exemplu. trebuie să poată lucra cu mai mult de un context de date în același timp. Structurile de date ar trebui proiectate cu mai multe fire care rulează în paralel. La rândul său, codul nucleului trebuie să poată gestiona mai multe fluxuri de date paralele fără a le deteriora. Scrierea unui cod care se poate executa în paralel și evitarea situațiilor în care o secvență diferită de execuție ar putea duce la un comportament nedorit al sistemului este consumatoare de timp și, posibil, dificilă. Fiecare exemplu de driver din această carte este scris cu concurența în minte. Dacă este necesar, vom explica caracteristicile tehnicii de scriere a unui astfel de cod.

Cea mai frecventă greșeală pe care o fac programatorii este că presupun că concurența nu este o problemă, deoarece unele segmente de cod nu pot dormi. Într-adevăr, nucleul Linux nu este paginat, cu o excepție importantă în ceea ce privește gestionatorii de întreruperi, care nu pot fi recepționați de procesor în timp ce se execută codul critic al nucleului. Recent, neîncărcarea a fost suficientă pentru a preveni paralelismul nedorit în majoritatea cazurilor. Pe sistemele SMP, totuși, descărcarea codului nu este necesară din cauza calculului paralel.

Dacă codul dvs. presupune că nu va fi aruncat, atunci nu va funcționa corect pe sistemele SMP. Chiar dacă nu aveți un astfel de sistem, altcineva l-ar putea avea folosind codul dvs. De asemenea, este posibil ca nucleul să folosească paginarea în viitor, așa că chiar și sistemele uniprocesor vor trebui să se ocupe de paralelism peste tot. Există deja opțiuni pentru implementarea unor astfel de nuclee. Astfel, un programator prudent va scrie codul nucleului presupunând că va rula pe un sistem SMP.

Aproximativ. traducător: Scuze, dar ultimele două paragrafe nu sunt clare pentru mine. Poate că acesta este rezultatul unei traduceri incorecte. Prin urmare, dau textul original.

O greșeală comună făcută de programatorii driverului este să presupună că concurența nu este o problemă atâta timp cât un anumit segment de cod
nu se culcă (sau „blochează”). Este adevărat că nucleul Linux este nepreemptiv; cu excepţia importantă a
întreruperi de service, nu va îndepărta procesorul de codul nucleului care nu cedează de bunăvoie. În vremuri trecute, acest lucru nonpreemptiv
comportamentul a fost suficient pentru a preveni concurența nedorită de cele mai multe ori. Pe sistemele SMP, totuși, nu este necesară preempțiunea
execuție concomitentă.

Dacă codul dvs. presupune că nu va fi preemptat, nu va rula corect pe sistemele SMP. Chiar dacă nu aveți un astfel de sistem,
alții care rulează codul dvs. pot avea unul. În viitor, este, de asemenea, posibil ca nucleul să treacă la un mod de operare preventiv,
moment în care chiar și sistemele uniprocesor vor trebui să se confrunte cu concurența peste tot (unele variante ale nucleului deja implementează
aceasta).

Informații despre procesul curent

Deși codul modulului nucleului nu este executat secvenţial ca aplicaţiile, majoritatea apelurilor către nucleu sunt efectuate în raport cu procesul de apelare. Codul nucleului poate identifica procesul care l-a numit referindu-se la indicatorul global care indică structură struct task_struct definit pentru nucleele 2.4 din fișier inclus în ... Indicator actual indică procesul utilizatorului care rulează în prezent. Când executați apeluri de sistem, cum ar fi deschis () sau închide (), trebuie să existe un proces care le-a provocat. Codul kernelului, dacă este necesar, poate apela informații specifice despre procesul de apelare printr-un pointer actual... Pentru exemple de utilizare a acestui indicator, consultați „Controlarea accesului la fișiere dispozitiv” în Capitolul 5, „Operațiuni îmbunătățite ale driverului Char”.

Până în prezent, indicator actual nu este o variabilă mai globală ca în versiunile anterioare ale nucleului. Dezvoltatorii au acces optimizat la structura care descrie procesul curent, transferându-l pe pagina stivei. Puteți vedea detaliile de implementare a curentului în fișier ... Codul pe care îl veți vedea acolo poate să nu vi se pară simplu. Rețineți că Linux este un sistem orientat spre SMP și o variabilă globală pur și simplu nu va funcționa atunci când aveți de-a face cu mai multe procesoare. Detaliile de implementare rămân ascunse altor subsisteme ale nucleului, iar driverul de dispozitiv poate accesa pointerul actual doar prin interfață .

Din punct de vedere al modulului, actual arată ca un link extern printk ()... Modulul poate folosi actual oriunde ai nevoie. De exemplu, următoarea bucată de cod tipărește identificatorul (ID-ul procesului - PID) și numele comenzii procesului care a apelat modulul, obținându-le prin câmpurile corespunzătoare ale structurii struct task_struct:

Printk ("Procesul este \"% s \ "(pid% i) \ n", curent-> com, curent-> pid);

Câmpul curent-> comm este numele de fișier al comenzii care a generat procesul curent.

Compilarea și încărcarea modulelor

Restul acestui capitol este dedicat redactării unui modul complet, deși atipic. Acestea. modulul nu aparține niciunei dintre clasele descrise în secțiunea Clase de dispozitive și module din Capitolul 1, Introducere în driverele de dispozitiv. Exemplul de driver prezentat în acest capitol se va numi skull (Simple Kernel Utility for Loading Localities). Puteți folosi modulul scull ca șablon pentru a scrie propriul cod local.

Folosim termenul „cod local” pentru a sublinia modificările codului personal, în vechea tradiție Unix (/ usr / local).

Cu toate acestea, înainte de a completa funcțiile init_module () și cleanup_module () cu conținut, vom scrie un script Makefile pe care make îl va folosi pentru a construi codul obiect al modulului.

Înainte ca preprocesorul să proceseze includerea oricărui fișier antet, simbolul macro __KERNEL__ trebuie definit de directiva #define. După cum am menționat mai devreme, un context specific nucleului poate fi definit în fișierele de interfață kernel, vizibil numai dacă simbolul __KERNEL__ este predefinit în preprocesare.

Un alt simbol important definit de directiva #define este simbolul MODULE. De la trebuie să fie definit pentru a activa interfața (excluzând acele drivere care vor fi compilate cu nucleul). Driverele compilate în nucleu nu vor fi descrise în această carte, așa că simbolul MODULE va fi prezent în toate exemplele noastre.

Dacă construiți un modul pentru un sistem SMP, trebuie să definiți și macro-ul __SMP__ înainte de a activa interfețele kernelului. În nucleul 2.2, alegerea între un sistem uniprocesor și un sistem multiprocesor a fost introdusă ca element separat în configurația nucleului. Prin urmare, includerea următoarelor linii ca primele linii ale modulului dvs. va avea ca rezultat suport pentru multiprocesor.

#include #ifdef CONFIG_SMP # define __SMP__ #endif

Dezvoltatorii modulului trebuie să definească, de asemenea, indicatorul de optimizare -O pentru compilator, deoarece multe funcții sunt declarate inline în anteturile kernelului. Compilatorul gcc nu efectuează extinderea inline a funcțiilor până când optimizarea este activată. Activarea extensiei substituțiilor inline cu opțiunile -g și -O vă va permite să depanați mai târziu codul folosind funcțiile inline din depanator. Deoarece nucleul folosește pe scară largă funcțiile inline, este foarte important ca acestea să fie extinse corect.

Rețineți, totuși, că orice optimizare peste nivelul -O2 este riscantă, deoarece compilatorul poate extinde funcții care nu sunt descrise ca fiind inline. Acest lucru poate duce la probleme. un cod de funcție se așteaptă să-și găsească stiva de apeluri standard. Extinderea în linie este înțeleasă ca inserarea codului funcției în punctul invocarii acestuia în locul instrucțiunii de apelare a funcției corespunzătoare. În consecință, în acest caz, deoarece nu există un apel de funcție, atunci nu există nicio stivă de apeluri.

Poate fi necesar să verificați dacă utilizați același compilator pentru a compila modulele care au fost folosite pentru a construi nucleul în care ar trebui să fie instalat modulul. Pentru detalii, consultați documentul original din dosar Documentație/Modificări situat în directorul surselor nucleului. Dezvoltarea kernelului și a compilatorului sunt în general sincronizate între echipele de dezvoltare. Pot exista cazuri în care actualizarea unuia dintre aceste elemente expune erori în celălalt. Unii furnizori de distribuție livrează versiuni ultra-noi ale compilatorului care nu se potrivesc cu nucleul pe care îl folosesc. În acest caz, de obicei oferă un pachet separat (deseori numit kgcc) cu un compilator special conceput pentru
compilarea nucleului.

În cele din urmă, pentru a preveni erorile enervante, vă sugerăm să utilizați opțiunea de compilare -Perete(toate avertismentele - toate avertismentele). Poate fi necesar să vă schimbați stilul obișnuit de programare pentru a îndeplini toate aceste avertismente. Când scrieți codul nucleului, este de preferat să folosiți stilul de codare sugerat de Linus Torvalds. Deci, documentul Documentare/CodingStyle, din directorul sursă al nucleului, este destul de interesant și recomandat tuturor celor interesați de programarea la nivel de kernel.

Este recomandat să plasați setul de steaguri de compilare ale modulului, pe care l-am văzut recent, în variabilă CFLAGS Makefile-ul tău. Pentru utilitatea make, aceasta este o variabilă specială, a cărei utilizare va deveni clară din următoarea descriere.

În afară de steaguri din variabilă CFLAGS, Makefile-ul dvs. poate avea nevoie de o țintă pentru a concatena diferite fișiere obiect. Acest obiectiv este necesar numai atunci când codul modulului este împărțit în mai multe fișiere sursă, ceea ce nu este neobișnuit în general. Fișierele obiect sunt combinate cu comanda ld -r, care nu este o operație de legătură în sensul convențional, în ciuda utilizării linkerului ( ld). Rezultatul executării comenzii ld -r este un alt fișier obiect care combină codurile obiect ale fișierelor de intrare ale linkerului. Opțiune -r mijloace " relocabil - relocare”, adică mutăm fișierul de ieșire al comenzii în spațiul de adrese, deoarece nu are încă adrese absolute de apel pentru funcții.

Următorul exemplu arată Makefile minim necesar pentru a compila un modul cu două fișiere sursă. Dacă modulul este format dintr-un fișier sursă, atunci din exemplul dat trebuie să eliminați ținta care conține comanda ld -r.

# Calea către directorul surselor de kernel poate fi schimbată aici, # sau o puteți trece ca parametru atunci când apelați „make” KERNELDIR = / usr / src / linux include $ (KERNELDIR) /. Config CFLAGS = -D__KERNEL__ -DMODULE - I $ (KERNELDIR) / include \ -O -Wall ifdef CONFIG_SMP CFLAGS + = -D__SMP__ -DSMP endif all: skull.o skull.o: skull_init.o skull_clean.o $ (LD) -r $ ^ -o [email protected] curat: rm -f * .o * ~ miez

Dacă sunteți nou în utilitarul make, ați putea fi surprins că nu există reguli pentru compilarea fișierelor * .c în fișiere obiect * .o. Definirea unor astfel de reguli nu este necesară, deoarece utilitarul make, dacă este necesar, convertește fișierele * .c în fișiere * .o folosind compilatorul implicit sau compilatorul specificat de variabilă $ (CC)... În acest caz, conținutul variabilei $ (CFLAGS) folosit pentru a specifica steaguri de compilare.

Următorul pas după construirea modulului este încărcarea acestuia în nucleu. Am spus deja că pentru aceasta vom folosi utilitarul insmod, care leagă toate simbolurile nedefinite (apeluri de funcții etc.) ale modulului la tabelul de simboluri al nucleului care rulează. Totuși, spre deosebire de un linker (de exemplu, cum ar fi ld), acesta nu modifică fișierul disc al modulului, ci încarcă obiectul modulului legat de nucleu în RAM. Utilitarul insmod acceptă mai multe opțiuni de linie de comandă. Detaliile pot fi vizualizate prin intermediul om insmod... Folosind aceste opțiuni, puteți, de exemplu, să atribuiți anumite variabile întregi și șir din modulul dvs. unor valori specifice înainte de a conecta modulul la nucleu. Astfel, dacă un modul este proiectat corespunzător, acesta poate fi configurat în momentul pornirii. Acest mod de configurare a unui modul oferă utilizatorului mai multă flexibilitate decât configurarea în timpul compilării. Configurația de pornire este explicată în secțiunea „Configurare manuală și automată” mai târziu în acest capitol.

Unii cititori vor fi interesați de detaliile modului în care funcționează utilitarul insmod. Implementarea insmod se bazează pe mai multe apeluri de sistem definite în kernel / module.c. Funcția sys_create_module () alocă cantitatea necesară de memorie în spațiul de adrese kernel pentru a încărca un modul. Această memorie este alocată utilizând funcția vmalloc () (vezi secțiunea „vmalloc și prietenii” din capitolul 7 „Păstrarea memoriei”). Apelul de sistem get_kernel_sysms () returnează tabelul de simboluri kernel, care va fi folosit pentru a determina adresele reale ale obiectelor la conectarea. Funcția sys_init_module () copiază codul obiect al modulului în spațiul de adrese kernel și apelează funcția de inițializare a modulului.

Dacă vă uitați la sursele pentru codul kernelului, veți găsi acolo nume de apeluri de sistem care încep cu prefixul sys_. Acest prefix este folosit doar pentru apelurile de sistem. Nicio altă funcție nu ar trebui să-l folosească. Țineți cont de acest lucru când procesați sursele kernelului cu grep.

Dependențe de versiune

Dacă nu știți nimic mai mult decât ceea ce s-a spus aici, atunci cel mai probabil modulele pe care le creați vor trebui recompilate pentru fiecare versiune a nucleului la care vor fi legate. Fiecare modul trebuie să definească un simbol numit __module_kernel_version a cărui valoare
este comparată cu versiunea nucleului actual cu utilitarul insmod. Acest simbol se află în secțiune .modinfo Fișiere ELF (Executable and Linking Format). Acest lucru este explicat mai detaliat în Capitolul 11, „kmod și modularizare avansată”. Vă rugăm să rețineți că această metodă de control al revizuirii este aplicabilă numai pentru nucleele 2.2 și 2.4. În nucleul 2.0, acest lucru se face într-un mod ușor diferit.

Compilatorul va defini acest simbol oriunde este inclus fișierul antet ... Prin urmare, în exemplul de mai sus hello.c, nu am descris acest simbol. Aceasta înseamnă, de asemenea, că, dacă modulul dvs. este format din mai multe fișiere sursă, trebuie să includeți fișierul în codul tău o singură dată. O excepție este cazul utilizării definiției __NU_VERSIUNE__, pe care ne vom întâlni mai târziu.

Mai jos este definiția simbolului descris din fișierul module.h extras din codul kernel-ului 2.4.25.

Static const char __module_kernel_versio / PRE__attribute __ ((section ("". Modinfo"))) = "kernel_version =" UTS_RELEASE;

Dacă modulul nu se încarcă din cauza nepotrivirii versiunii, puteți încerca să încărcați acest modul trecând cheia șirului de parametri al utilitarului insmod -f(forta). Acest mod de încărcare a modulului nu este sigur și nu întotdeauna de succes. Este dificil de explicat motivele posibilelor eșecuri. Este posibil ca modulul să nu fie încărcat deoarece simbolurile nu pot fi rezolvate la conectare. În acest caz, veți primi un mesaj de eroare corespunzător. Motivele eșecului pot sta, de asemenea, în schimbări în activitatea sau structura nucleului. În acest caz, încărcarea modulului poate duce la erori severe de rulare, precum și la panica sistemului. Acesta din urmă ar trebui să fie un bun stimulent pentru a utiliza un sistem de control al versiunilor. Nepotrivirea versiunilor poate fi gestionată mai elegant utilizând controlul versiunilor în kernel. Vom vorbi despre asta în detaliu în secțiunea „Controlul versiunilor în module” din Capitolul 11 ​​„kmod și modularizare avansată”.

Dacă doriți să vă compilați modulul pentru o anumită versiune de kernel, trebuie să includeți fișierele de antet din acea versiune de kernel. În exemplul de mai sus Makefile, variabila a fost folosită pentru a determina directorul în care se află aceste fișiere KERNELDIR... Această compilare personalizată nu este neobișnuită atunci când sursele kernel-ului sunt disponibile. De asemenea, nu este neobișnuit să aveți versiuni diferite ale nucleului în arborele de directoare. Toate exemplele de module din această carte folosesc variabila KERNELDIR pentru a indica locația directorului sursă al versiunii kernel-ului, în care se presupune că va lega modulul asamblat. Puteți utiliza o variabilă de sistem pentru a specifica acest director sau puteți transmite locația acestuia prin parametrii din linia de comandă pentru a face.

Când un modul este încărcat, utilitarul insmod folosește propriile căi de căutare pentru fișierele obiect ale modulului, uitându-se la directoarele dependente de versiune începând de la punctul / lib / module... Deși versiunile mai vechi ale utilitarului au inclus directorul curent în căile de căutare, acest comportament este acum considerat inacceptabil din motive de securitate (aceleași probleme ca utilizarea variabilei de sistem CALE). Astfel, dacă doriți să încărcați un modul din directorul curent, îl puteți specifica în stil ./modul.o... Această indicație a poziției modulului va funcționa pentru orice versiune a utilitarului insmod.

Uneori este posibil să întâlniți interfețe de kernel care diferă între versiunile 2.0.x și 2.4.x. În acest caz, va trebui să recurgeți la utilizarea unei macrocomenzi care determină versiunea curentă a nucleului. Această macrocomandă se află în fișierul antet ... Vom evidenția cazurile de diferențe între interfețe atunci când le folosim. Acest lucru se poate face fie chiar pe parcurs, fie la sfârșitul secțiunii, într-o secțiune specială despre dependențele versiunii. Mutarea detaliilor într-o secțiune separată, în unele cazuri, va permite să nu se complice descrierea conform versiunii de kernel 2.4.x care face profil pentru această carte.

În fișierul antet linux / versiunea.h următoarele macrocomenzi sunt definite în legătură cu definiția versiunii de kernel.

UTS_RELEASE O macrocomandă care se extinde într-un șir care descrie versiunea de kernel a curentului
arborele sursă. De exemplu, o macrocomandă se poate extinde în acest sens
linia: "2.3.48" . LINUX_VERSION_CODE Această macrocomandă se extinde într-o reprezentare binară a versiunii de kernel, prin
câte un octet pentru fiecare parte a numărului. De exemplu, binarul
reprezentarea pentru versiunea 2.3.48 va fi 131888 (zecimal
reprezentare pentru hex 0x020330). Posibil binar
priveliștea ți se va părea mai convenabilă decât cea cu șnur. Observați ce este
vizualizarea vă permite să descrieți nu mai mult de 256 de opțiuni în fiecare
părți ale camerei. KERNEL_VERSION (major, minor, lansare) Această macrocomandă vă permite să construiți kernel_version_code
a elementelor individuale care alcătuiesc versiunea de nucleu. De exemplu,
următoarea macro KERNEL_VERSION (2, 3, 48)
se extinde la 131888. Această macrocomandă este foarte utilă când
comparând versiunea actuală a nucleului cu cea necesară. Vom fi în mod repetat
utilizați această macrocomandă pe tot parcursul cărții.

Să dăm conținutul fișierului linux / versiunea.h pentru nucleul 2.4.25 (textul fișierului antet este dat în întregime).

#define UTS_RELEASE "2.4.25" #define LINUX_VERSION_CODE 132121 #define KERNEL_VERSION (a, b, c) (((a)<< 16) + ((b) << 8) + (c))

Fișierul antet version.h este inclus în fișierul module.h, așa că de obicei nu trebuie să includeți în mod explicit version.h în codul modulului. Alternativ, puteți împiedica includerea fișierului antet version.h în module.h prin declararea unei macrocomenzi __NU_VERSIUNE__... Vei folosi __NU_VERSIUNE__, de exemplu, în cazul în care trebuie să activați în mai multe fișiere sursă, care, ulterior, vor fi legate într-un singur modul. Anunţ __NU_VERSIUNE__înainte de a include fișierul antet module.h previne
descrierea liniei automate __module_kernel_version sau echivalentul acestuia în fișierele sursă. Este posibil să aveți nevoie de acest lucru pentru a aborda plângerile linkerului când ld -r cărora nu le vor plăcea multiplele descrieri ale simbolurilor din tabelele de linkuri. De obicei, dacă codul modulului este împărțit în mai multe fișiere sursă, inclusiv fișierul antet apoi ad __NU_VERSIUNE__ se face în toate aceste fișiere, cu excepția unuia. La sfârșitul cărții, există un exemplu de utilizare a unui modul __NU_VERSIUNE__.

Majoritatea dependențelor asociate cu versiunea de kernel pot fi gestionate folosind logica construită pe directivele preprocesorului folosind macrocomenzi KERNEL_VERSIONși LINUX_VERSION_CODE... Cu toate acestea, verificarea dependențelor de versiune poate complica foarte mult lizibilitatea codului modulului din cauza directivelor pestrițe #ifdef... Prin urmare, poate cea mai bună soluție este să puneți verificarea dependenței într-un fișier de antet separat. Acesta este motivul pentru care exemplul nostru include fișierul antet sysdep.h folosit pentru a conține toate macrocomenzile asociate cu verificările dependenței de versiune.

Prima dependență de versiune pe care vrem să o prezentăm este în declarația țintă " face instalarea"script pentru a compila driverul nostru. După cum v-ați putea aștepta, directorul de instalare, care se modifică în funcție de versiunea de kernel utilizată, este selectat pe baza vizualizării fișierului version.h. Iată un fragment de cod din fișier Reguli.face care este folosit de toate Makefile-urile nucleului.

VERSIONFILE = $ (INCLUDEDIR) /linux/version.h VREION = $ (shell awk -F \ "" / REL / (printați $$ 2) "$ (VERSIONFILE)) INSTALLDIR = / lib / module / $ (VERSIUNE) / misc

Rețineți că folosim directorul misc pentru a instala toate driverele noastre (declarația INSTALLDIR din exemplul Makefile de mai sus). Începând cu kernel-ul 2.4, acest director este directorul recomandat pentru plasarea driverelor personalizate. În plus, atât versiunile vechi, cât și cele noi ale pachetului modutils conțin un director misc în căile de căutare.

Folosind definiția INSTALLDIR dată mai sus, ținta de instalare din Makefile ar putea arăta astfel:

Instalare: install -d $ (INSTALLDIR) install -c $ (OBJS) $ (INSTALLDIR)

Dependența de platformă

Fiecare platformă de calcul are propriile caracteristici care trebuie luate în considerare de către dezvoltatorii de kernel pentru a obține cele mai înalte performanțe.

Dezvoltatorii de kernel au mult mai multă libertate în alegerea și luarea deciziilor decât /PCLASS = „western” și dezvoltatorii de aplicații. Această libertate vă permite să vă optimizați codul, strângând maximum din fiecare platformă specifică.

Codul modulului trebuie compilat folosind aceleași opțiuni de compilare care au fost utilizate la compilarea nucleului. Acest lucru se aplică atât utilizării acelorași modele de utilizare a registrului procesorului, cât și efectuării aceluiași nivel de optimizare. Fişier Reguli.face, situat la rădăcina arborelui sursă a nucleului, include definițiile specifice platformei care trebuie incluse în toate Makefile-urile de compilare. Toate scripturile de compilare specifice platformei sunt denumite Makefile. platformăși conțin valorile variabilelor pentru utilitarul make în funcție de configurația curentă a nucleului.

O altă caracteristică interesantă a Makefile este suportul pentru multi-platformă sau doar pentru compilarea încrucișată. Acest termen este folosit atunci când trebuie să compilați cod pentru o altă platformă. De exemplu, folosind platforma i86, veți genera cod pentru platforma M68000. Dacă intenționați să utilizați compilarea încrucișată, atunci trebuie să înlocuiți instrumentele de compilare ( gcc, ld, etc.) cu un alt set de instrumente adecvate
(de exemplu, m68k-linux-gcc, m68k-linux-ld). Prefixul utilizat poate fi specificat fie de variabila $ (CROSS_COMPILE) Makefile, de un parametru de linie de comandă pentru utilitarul make, fie de o variabilă de mediu de sistem.

Arhitectura SPARC este un caz special care trebuie tratat corespunzător în Makefile. Programele de utilizator rulate pe platforma SPARC64 (SPARC V9) sunt binare, de obicei concepute pentru platforma SPARC32 (SPARC V8). Prin urmare, compilatorul implicit de pe platforma SPARC64 (gcc) generează codul obiect pentru SPARC32. Pe de altă parte, un nucleu proiectat să ruleze pe SPARC V9 trebuie să conțină codul obiect pentru SPARC V9, așa că, chiar și așa, este necesar un compilator încrucișat. Toate distribuțiile GNU/Linux care vizează SPARC64 includ un compilator încrucișat corespunzător, care trebuie selectat în Makefile al scriptului de compilare a nucleului.

Deși lista completă a dependențelor de versiuni și platforme este puțin mai complexă decât cea descrisă aici, este totuși suficientă pentru compilarea încrucișată. Pentru mai multe informații, puteți consulta scripturile de compilare Makefile și fișierele sursă ale nucleului.

Caracteristicile Kernel 2.6

Timpul nu sta pe loc. Și acum asistăm la apariția unei noi generații a nucleului 2.6. Din păcate, originalul acestei cărți nu acoperă noul nucleu, așa că traducătorul își va lua libertatea de a adăuga noi cunoștințe la traducere.

Puteți utiliza IDE-uri precum TimeSys ’TimeStorm, care va scheleta și va compila corect scripturile pentru modulul dvs., în funcție de versiunea de kernel necesară. Dacă aveți de gând să scrieți singuri toate acestea, atunci veți avea nevoie de câteva informații suplimentare despre principalele diferențe introduse de noul nucleu.

Una dintre caracteristicile nucleului 2.6 este necesitatea de a utiliza macrocomenzile module_init () și module_exit () pentru a înregistra în mod explicit numele funcțiilor de inițializare și terminare.

Macro-ul MODULE_LISENCE (), introdus în nucleul 2.4, este încă necesar dacă nu doriți să vedeți avertismentele corespunzătoare la încărcarea unui modul. Puteți selecta următoarele șiruri de licență pentru transfer la macro: „GPL”, „GPL v2”, „GPL și drepturi suplimentare”, „Dual BSD / GPL” (alegeți între licențe BSD sau GPL), „Dual MPL / GPL” ( alegerea între licențe Mozilla sau GPL) și
"Proprietate".

Mai importantă pentru noul nucleu este o nouă schemă de compilare a modulelor, care implică nu numai modificări în codul modulului în sine, ci și în Makefile-ul scriptului său de compilare.

Astfel, definirea simbolului macro MODULE nu mai este necesară nici în codul modulului, nici în Makefile. Dacă este necesar, noua schemă de compilare va determina ea însăși acest simbol macro. De asemenea, nu trebuie să definiți în mod explicit macrocomenzi __KERNEL__ sau altele mai noi, cum ar fi KBUILD_BASENAME și KBUILD_MODNAME.

De asemenea, nu ar trebui să specificați nivelul de optimizare în timp de compilare (-O2 sau altele), deoarece modulul tău va fi compilat cu tot acel set de steaguri, inclusiv steaguri de optimizare cu care sunt compilate toate celelalte module din nucleul tău - utilitarul make va folosi automat tot setul necesar de steaguri.

Din aceste motive, Makefile pentru compilarea unui modul pentru nucleul 2.6 este mult mai ușor. Deci, pentru modulul hello.c, Makefile va arăta astfel:

Obj-m: = salut.o

Cu toate acestea, pentru a compila modulul, veți avea nevoie de acces de scriere la arborele sursă a nucleului, unde vor fi create fișiere și directoare temporare. Prin urmare, comanda pentru compilarea modulului în nucleul 2.6, dată din directorul curent care conține codul sursă al modulului, ar trebui să arate astfel:

# make -C /usr/src/linux-2.6.1 SUBDIRS = module `pwd`

Deci, avem sursa modulului salut-2.6.c, pentru a compila într-un nucleu 2.6:

//hello-2.6.c #include #include #include MODULE_LICENSE ("GPL"); static int __init my_init (void) (printk ("Bună lume \ n"); return 0;); static void __exit my_cleanup (void) (printk ("La revedere \ n"); module_init (my_init); ieșire_modul (curățarea_mea);

În consecință, avem un Makefile:

Obj-m: = salut-2.6.o

Apelăm utilitarul make pentru a procesa fișierul Makefile cu următorii parametri:

# make -C / usr / src / linux-2.6.3 SUBDIRS = module `pwd`

Procesul normal de compilare va continua cu următoarea ieșire standard:

Make: Introduceți directorul `/usr/src/linux-2.6.3" *** Avertisment: Suprascrierea SUBDIRS pe linia de comandă poate cauza *** inconsecvențe make: `arch / i386 / kernel / asm-offsets.s" necesită actualizare . CHK include / asm-i386 / asm_offsets.h CC [M] /home/knz/j.kernel/3/hello-2.6.o Construirea modulelor, etapa 2. /usr/src/linux-2.6.3/scripts/Makefile .modpost: 17: *** Uh-oh, ai intrări în modul învechite. Te-ai încurcat cu SUBDIRS, /usr/src/linux-2.6.3/scripts/Makefile.modpost:18: nu te plânge dacă ceva nu merge bine. MODPOST CC /home/knz/j.kernel/3/hello-2.6.mod.o LD [M] /home/knz/j.kernel/3/hello-2.6.ko make: Exit directory `/ usr / src / linux-2.6.3 "

Rezultatul final al compilației va fi un fișier modul hello-2.6.ko care poate fi instalat în kernel.

Rețineți că în nucleul 2.6 fișierele modulului au sufixul .ko, nu .o ca în nucleul 2.4.

Tabelul simbolurilor nucleului

Am vorbit deja despre modul în care utilitarul insmod folosește tabelul public de simboluri al nucleului atunci când conectează un modul la nucleu. Acest tabel conține adresele obiectelor globale ale nucleului - funcții și variabile - care sunt necesare pentru implementarea variantelor de driver modulare. Tabelul de simboluri publice al nucleului poate fi citit sub formă de text din fișierul / proc / ksyms, cu condiția ca nucleul dumneavoastră să accepte sistemul de fișiere / proc.

În nucleul 2.6, fișierul / proc / ksyms a fost redenumit în / proc / modules.

Când un modul este încărcat, simbolurile exportate de modul devin parte a tabelului de simboluri kernel și le puteți vizualiza din / proc / ksyms.

Noile module pot folosi simboluri exportate de modulul dumneavoastră. Deci, de exemplu, modulul msdos se bazează pe simbolurile exportate de modulul fat și fiecare dispozitiv USB utilizat în modul de citire utilizează simbolurile din modulele usbcore și de intrare. Această relație, implementată prin încărcarea secvențială a modulelor, se numește stivă de module.

Stiva de module este utilă atunci când se creează proiecte complexe de module. Această abstractizare este utilă pentru a separa codul driverului dispozitivului în părți dependente de hardware și independente de hardware. De exemplu, setul de drivere video-pentru-linux constă dintr-un modul principal care exportă simboluri pentru un driver de nivel scăzut care ține cont de specificul hardware-ului utilizat. În funcție de configurația dvs., încărcați modulul video principal și modulul specific hardware-ului dvs. De asemenea, se oferă suport pentru porturile paralele și o gamă largă de dispozitive plug-in, cum ar fi dispozitivele USB. Stiva de sistem cu porturi paralele este prezentată în Fig. 2-2. Săgețile indică interacțiunea dintre module și API-ul kernelului. Interacțiunea poate avea loc atât la nivelul funcțiilor, cât și la nivelul structurilor de date controlate de funcții.

Figura 2-2. Stiva de module cu port paralel

Când utilizați module de stivuire, este convenabil să utilizați utilitarul modprobe. Funcționalitatea utilitarului modprobe este în multe privințe similară cu utilitarul insmod, dar atunci când un modul este încărcat, acesta verifică dependențele sale subiacente și, dacă este necesar, încarcă modulele necesare până când stiva de module necesară este umplută. Astfel, o singură comandă modprobe poate duce la apeluri multiple la comanda insmod. Putem spune că modprobe este un înveliș inteligent peste insmod. Puteți folosi modprobe în loc de insmod peste tot, cu excepția cazului în care încărcați module personalizate din directorul curent. modprobe se uită doar la locații speciale ale modulelor și nu va putea satisface posibilele dependențe.

Împărțirea modulelor în părți ajută la reducerea timpului de dezvoltare prin simplificarea enunțului problemei. Aceasta este similară cu separarea dintre mecanismul de implementare și politica de control, care este discutată în Capitolul 1, Introducere în driverele de dispozitiv.

De obicei, un modul își implementează funcționalitatea fără a fi nevoie să exporte deloc simboluri. Va trebui să exportați simboluri dacă alte module pot beneficia de ele. Poate fi necesar să includeți o directivă specială pentru a preveni exportul simbolurilor nestatice. majoritatea implementărilor de moduls le exportă pe toate în mod implicit.

Antetele nucleului Linux oferă o modalitate convenabilă de a controla vizibilitatea simbolurilor dvs., prevenind astfel poluarea spațiului de nume al tabelului de simboluri al nucleului. Mecanismul descris în acest capitol funcționează în nuclee începând cu versiunea 2.1.18. Nucleul 2.0 avea un mecanism de control complet diferit
vizibilitatea simbolurilor, care va fi descrisă la sfârșitul capitolului.

Dacă modulul dvs. nu ar trebui să exporte deloc simboluri, puteți plasa în mod explicit următorul apel macro în fișierul sursă al modulului:

EXPORT_NO_SYMBOLS;

Acest apel macro, definit în fișierul linux / module.h, este extins într-o directivă de asamblare și poate fi specificat oriunde în modul. Cu toate acestea, atunci când creați cod care este portabil la diferite nuclee, este necesar să plasați acest apel macro în funcția de inițializare a modulului (init_module), deoarece versiunea acestei definiții macro pe care am definit-o în fișierul nostru sysdep.h pentru mai vechi. versiunile nucleului vor funcționa doar aici.

Pe de altă parte, dacă trebuie să exportați o anumită parte a simbolurilor din modulul dvs., atunci trebuie să utilizați simbolul macro
EXPORT_SYMTAB... Această macrocomandă trebuie definită față inclusiv modulul fișierului antet.h. Este o practică general acceptată
definirea acestui macro simbol prin steag -Dîn Makefile.

Dacă caracterul macro EXPORT_SYMTAB este definit, simbolurile individuale pot fi exportate folosind câteva macrocomenzi:

EXPORT_SYMBOL (nume); EXPORT_SYMBOL_NOVERS (nume);

Oricare dintre aceste două macrocomenzi va face simbolul dat disponibil în afara modulului. Diferența este că macro EXPORT_SYMBOL_NOVERS exportă un simbol fără informații despre versiune (vezi capitolul 11 ​​„kmod și modularizare avansată”). Pentru mai multe informatii
verificați fișierul antet , deși cele de mai sus sunt destul de suficiente pentru utilizare practică
macro-uri.

Inițializarea și completarea modulelor

După cum sa menționat, funcția init_module () înregistrează componentele funcționale ale unui modul în kernel. După o astfel de înregistrare, pentru aplicația care utilizează modulul, punctele de intrare în modul vor fi disponibile prin interfața furnizată de kernel.

Modulele pot înregistra multe componente diferite, care, atunci când sunt înregistrate, sunt numele funcțiilor modulului. Un pointer către o structură de date care conține pointeri către funcții care implementează funcționalitatea propusă este transmis funcției de înregistrare de bază.

Capitolul 1, „Introducere în driverele de dispozitiv”, a menționat o clasificare a principalelor tipuri de dispozitive. Puteți înregistra nu numai tipurile de dispozitive menționate acolo, ci și orice altele, până la abstracții software, cum ar fi, de exemplu, fișiere ale sistemului de fișiere / proc etc. Orice poate funcționa în kernel prin API-ul driverului poate fi înregistrat ca șofer.

Dacă doriți să aflați mai multe despre tipurile de drivere înregistrate pentru exemplul dvs. de kernel, puteți implementa o căutare pentru subșirul EXPORT_SYMBOL în sursele kernelului și puteți găsi punctele de intrare oferite de diferite drivere. De regulă, funcțiile de înregistrare folosesc prefixul în numele lor Inregistreaza-te_,
deci un alt mod posibil de a le găsi este să căutați un subșir Inregistreaza-te_în fișierul / proc / ksyms folosind utilitarul grep. După cum sa menționat, în nucleul 2.6.x fișierul / proc / ksyms este înlocuit cu / proc / modules.

Gestionarea erorilor în init_module

Dacă apare o eroare de orice fel în timpul inițializării modulului, atunci trebuie să anulați inițializarea deja făcută înainte de a opri încărcarea modulului. O eroare poate apărea, de exemplu, din cauza memoriei insuficiente în sistem la alocarea structurilor de date. Din păcate, acest lucru se poate întâmpla și un cod bun ar trebui să poată face față acestor situații.

Orice lucru care a fost înregistrat sau alocat înainte ca eroarea să apară în funcția de inițializare init_module () trebuie anulat sau eliberat singur, deoarece nucleul Linux nu urmărește erorile de inițializare și nu anulează împrumutul și alocarea resurselor deja executate de codul modulului. Dacă nu ați derulat înapoi sau nu ați putut derula înapoi înregistrarea, atunci nucleul va rămâne într-o stare instabilă și atunci când modulul este reîncărcat
nu veți putea reînregistra articolele deja înregistrate și nu veți putea anula o înregistrare făcută anterior, deoarece într-o instanță nouă a funcției init_module (), nu veți avea valoarea corectă pentru adresele funcțiilor înregistrate. Restabilirea sistemului la starea anterioară va necesita diverse trucuri complexe și, mai des, acest lucru se face printr-o simplă repornire a sistemului.

Implementarea restabilirii stării anterioare a sistemului în cazul erorilor de inițializare a modulelor este cel mai bine implementată folosind instrucțiunea goto. De obicei, acest operator este tratat extrem de negativ, și chiar cu ură, dar tocmai în această situație se dovedește a fi foarte util. Prin urmare, în nucleu, instrucțiunea goto este adesea folosită pentru a gestiona erorile de inițializare a modulelor.

Următorul cod simplu demonstrează acest mod de a gestiona erorile folosind funcțiile fictive de înregistrare și anulare ca exemplu.

Int init_module (void) (int err; / * înregistrarea ia un pointer și un nume * / err = register_this (ptr1, "craniu"); if (err) goto fail_this; err = register_that (ptr2, "craniu"); dacă (err) merge la fail_that; err = register_those (ptr3, "craniu"); if (err) merge to fail_those; return 0; / * succes * / fail_those: unregister_that (ptr2, "craniu"); fail_that: unregister_this (ptr1, " craniu "); fail_this: return err; / * propaga eroarea * /)

Acest exemplu încearcă să înregistreze trei componente ale modulului. Instrucțiunea goto este utilizată atunci când apare o eroare de înregistrare și face ca componentele înregistrate să fie dezregistrate înainte ca modulul să se oprească din încărcare.

Un alt exemplu de utilizare a instrucțiunii goto pentru cod ușor de citit este trucul de a „rememora” operațiunile de înregistrare finalizate cu succes ale unui modul și de a apela cleanup_module () cu aceste informații transmise atunci când apare o eroare. Funcția cleanup_module () este concepută pentru a anula operațiunile de inițializare executate și este apelată automat când modulul este descărcat. Valoarea returnată de funcția init_module () trebuie
reprezintă codul de eroare de inițializare a modulului. În nucleul Linux, codul de eroare este un număr negativ din multe definiții făcute în fișierul antet. ... Includeți acest fișier antet în modulul dvs. pentru a utiliza mnemonicii simbolice ale codurilor de eroare rezervate, cum ar fi -ENODEV, -ENOMEM etc. Este considerat un stil de programare bun să folosești astfel de mnemonici. Cu toate acestea, trebuie remarcat faptul că unele versiuni ale utilităților din pachetul modutils nu procesează corect codurile de eroare returnate și afișează mesajul „Dispozitiv ocupat”
ca răspuns la o grămadă de erori complet diferite returnate de init_modules (). În cele mai recente versiuni ale pachetului, aceasta
bug-ul enervant a fost remediat.

Codul funcției cleanup_module () pentru cazul de mai sus poate fi, de exemplu, astfel:

Void cleanup_module (void) (unregister_those (ptr3, „craniu”); unregister_that (ptr2, „skull”); unregister_this (ptr1, „craniu”); return;)

Dacă codul dvs. de inițializare și terminare este mai complex decât cel descris aici, atunci folosirea instrucțiunii goto poate duce la un cod de program greu de citit, deoarece codul de terminare trebuie repetat în funcția init_module () folosind mai multe etichete pentru salturile goto . Din acest motiv, se folosește un truc mai complicat folosind apelul la funcția cleanup_module () din funcția init_module (), pasând informații despre cantitatea de inițializare reușită atunci când apare o eroare de încărcare a modulului.

Mai jos este un exemplu despre modul în care init_module () și cleanup_module () sunt scrise în acest fel. Acest exemplu utilizează indicatori definiți la nivel global pentru a furniza informații despre cantitatea de inițializare reușită.

Structurați ceva * item1; struct somethingelse * item2; int chestii_ok; void cleanup_module (void) (dacă (item1) release_thing (item1); if (item2) release_thing2 (item2); if (stuff_ok) unregister_stuff (); return;) int init_module (void) (int err = -ENOMEM; item1 = allocate_thing (argumente); item2 = allocate_thing2 (arguments2); if (! item2 ||! item2) a eșuat; err = register_stuff (element1, item2); dacă (! err) stuff_ok = 1; altfel nu a mers; returnează 0; / * succes * / fail: cleanup_module (); return err;)

În funcție de complexitatea operațiunilor de inițializare ale modulului dumneavoastră, puteți utiliza una dintre metodele descrise aici pentru a controla erorile de inițializare a modulului.

Contor de utilizare a modulului

Sistemul conține un contor de utilizare pentru fiecare modul pentru a determina dacă modulul poate fi descărcat în siguranță. Sistemul are nevoie de aceste informații, deoarece modulul nu poate fi descărcat dacă este ocupat cu cineva sau ceva - nu puteți elimina driverul sistemului de fișiere dacă acest sistem de fișiere este montat sau nu puteți descărca modulul dispozitivului de caractere dacă un proces folosește acest dispozitiv. In caz contrar,
acest lucru poate duce la blocarea sistemului - eroare de segmentare sau panică a nucleului.

În nucleele moderne, sistemul vă poate oferi un contor automat pentru utilizarea modulelor folosind un mecanism pe care îl vom analiza în capitolul următor. Indiferent de versiunea de kernel, puteți utiliza controlul manual al acestui contor. Deci, codul care ar trebui să fie utilizat în versiunile mai vechi ale nucleului trebuie să utilizeze modelul de contabilizare a utilizării modulelor construit pe următoarele trei macrocomenzi:

MOD_INC_USE_COUNT Mărește contorul de utilizare al modulului curent MOD_DEC_USE_COUNT Reduce contorul de utilizare al modulului curent MOD_IN_USE Returnează true dacă contorul de utilizare pentru acest modul este zero

Aceste macrocomenzi sunt definite în , și manipulează o structură internă specială de date pe care nu este de dorit să o acceseze direct. Faptul este că structura internă și modul de gestionare a acestor date se pot schimba de la o versiune la alta, în timp ce interfața externă pentru utilizarea acestor macro-uri rămâne neschimbată.

Rețineți că nu trebuie să verificați MOD_IN_USEîn codul pentru funcția cleanup_module (), deoarece această verificare se face automat înainte de a apela cleanup_module () în apelul de sistem sys_delete_module (), care este definit în kernel / module.c.

Gestionarea corectă a contorului de utilizare a modulului este esențială pentru stabilitatea sistemului. Amintiți-vă că nucleul poate decide să descarce automat un modul neutilizat în orice moment. O greșeală comună în programarea modulelor este gestionarea defectuoasă a acestui contor. De exemplu, ca răspuns la o solicitare, codul modulului efectuează unele acțiuni și, la finalizarea procesării, crește contorul de utilizare a modulului. Acestea. Un astfel de programator presupune că acest contor este destinat să colecteze statistici privind utilizarea modulelor, în timp ce, de fapt, este, de fapt, contorul de ocupare curentă a modulului, adică. ține evidența numărului de procese care utilizează în prezent codul modulului. Astfel, atunci când procesați o cerere către un modul, trebuie să sunați MOD_INC_USE_COUNTînainte de a lua orice măsură și MOD_DEC_USE_COUNT după completarea acestora.

Pot exista situații în care, din motive evidente, nu veți putea descărca un modul dacă pierdeți controlul contorului său de utilizare. Această situație este adesea întâlnită în faza de dezvoltare a unui modul. De exemplu, un proces se poate întrerupe atunci când încearcă să anuleze referința unui pointer NULL și nu puteți descărca un astfel de modul până când nu resetați contorul de utilizare la zero. Una dintre posibilele soluții la această problemă în etapa de depanare a unui modul este abandonarea completă a gestionării contorului de utilizare a modulului prin suprascriere. MOD_INC_USE_COUNTși MOD_DEC_USE_COUNTîn cod gol. O altă soluție este să efectuați un apel ioctl () pentru a forța contorul de utilizare a modulului la zero. Vom acoperi acest lucru în secțiunea „Utilizarea argumentului ioctl” din Capitolul 5, „Operațiuni îmbunătățite ale driverului Char”. Desigur, într-un driver gata de utilizare, astfel de manipulări frauduloase cu contorul ar trebui excluse, cu toate acestea, în etapa de depanare, economisesc timpul dezvoltatorului și sunt destul de acceptabile.

Puteți găsi valoarea curentă a contorului de utilizare a sistemului pentru fiecare modul în al treilea câmp al fiecărei intrări din fișierul / proc / modules. Acest fișier conține informații despre modulele încărcate curent - o linie pentru fiecare modul. Primul câmp al liniei conține numele modulului, al doilea câmp - dimensiunea ocupată de modul în memorie, iar al treilea câmp - valoarea curentă a contorului de utilizare. Aceste informații, în formă formatată,
poate fi obținut apelând utilitarul lsmod. Mai jos este un exemplu de fișier / proc / modules:

Parport_pc 7604 1 (autoclean) lp 4800 0 (nefolosit) parport 8084 1 lockd 33256 1 (autoclean) sunrpc 56612 1 (autoclean) ds 6252 1 i82365 22304_core 1 412804 1 pcm

Aici vedem mai multe module încărcate în sistem. În câmpul steagurilor (ultimul câmp al liniei), stiva de dependențe de modul este afișată între paranteze drepte. Printre altele, puteți observa că modulele cu port paralel comunică prin stiva de module, așa cum se arată în Fig. 2-2. Steagul (autoclean) marchează modulele gestionate de kmod sau kerneld. Acest lucru va fi tratat în Capitolul 11, „kmod și modularizare avansată”). Indicatorul (neutilizat) înseamnă că modulul nu este utilizat în prezent. În nucleul 2.0, câmpul dimensiune afișează informații nu în octeți, ci în pagini, a căror dimensiune pentru majoritatea platformelor este de 4kBt.

Descărcarea unui modul

Pentru a descărca un modul, utilizați utilitarul rmmod. Descărcarea unui modul este o sarcină mai simplă decât încărcarea lui, care îl leagă dinamic la kernel. Când un modul este descărcat, este executat apelul de sistem delete_module (), care fie apelează funcția cleanup_module () a modulului descărcat dacă contorul său de utilizare este zero, fie se încheie cu o eroare.

După cum sa menționat deja, în funcția cleanup_module (), operațiunile de inițializare efectuate când modulul a fost încărcat sunt anulate de funcția cleanup_module (). De asemenea, simbolurile exportate ale modulului sunt șterse automat.

Definirea explicită a funcțiilor de finalizare și inițializare

După cum sa menționat deja, la încărcarea unui modul, nucleul apelează funcția init_module (), iar când o descarcă, apelează cleanup_module (). Cu toate acestea, în versiunile moderne ale nucleului, aceste funcții au adesea nume diferite. Începând cu nucleul 2.3.23, a devenit posibil să se definească în mod explicit un nume pentru funcția de încărcare și descărcare a modulului. Acum se recomandă stilul de programare să denumească în mod explicit aceste funcții.

Să dăm un exemplu. Dacă doriți să declarați funcția my_init () ca funcție de inițializare a modulului dvs. și funcția my_cleanup () ca finală, în loc de init_module () și, respectiv, cleanup_module (), atunci va trebui să adăugați următoarele două macrocomenzi cu textul modulului (de obicei sunt inserate la sfârșit
fișier sursă al modulului):

Module_init (my_init); ieșire_modul (curățarea_mea);

Rețineți că pentru a utiliza aceste macrocomenzi va trebui să includeți fișierul antet în modulul dvs. .

Comoditatea utilizării acestui stil este că fiecare funcție de inițializare și completare a modulului din nucleu poate avea propriul nume unic, ceea ce ajută foarte mult la depanare. Mai mult, utilizarea acestor funcții simplifică depanarea, indiferent dacă implementați codul driverului ca modul sau dacă îl veți încorpora direct în nucleu. Desigur, nu trebuie să utilizați macrocomenzi module_init și module_exit dacă funcțiile de inițializare și terminare au nume rezervate, de exemplu. init_module () și, respectiv, cleanup_module ().

Dacă sunteți familiarizat cu sursele pentru nucleele 2.2 sau mai recente, este posibil să vedeți o formă ușor diferită de descriere pentru funcția de inițializare și completare. De exemplu:

Static int __init my_init (void) (....) static void __exit my_cleanup (void) (....)

Utilizarea atributelor __init va duce la faptul că după finalizarea inițializării, funcția de inițializare va fi descărcată din memorie. Cu toate acestea, acest lucru funcționează numai pentru driverele de nucleu încorporate și va fi ignorat pentru module. De asemenea, pentru driverele încorporate în nucleu, atributul __Ieșire va face ca întreaga funcție marcată cu acest atribut să fie ignorată. Pentru module, acest flag va fi, de asemenea, ignorat.

Utilizarea atributelor __init(și __initdata pentru descrierea datelor) poate reduce cantitatea de memorie folosită de nucleu. Semnalizarea __init funcția de inițializare a modulului nu va aduce niciun beneficiu sau rău. Controlul acestui mod de inițializare nu a fost încă implementat pentru module, deși poate fi făcut în viitor.

Rezumând

Deci, ca urmare a materialului prezentat, putem prezenta următoarea versiune a modulului „Hello world”:

Codul fișierului sursă al modulului ============================================== # include #include #include static int __init my_init_module (void) (EXPORT_NO_SYMBOLS; printk ("<1>Bună lume \ n "); return 0;); static void __exit my_cleanup_module (void) (printk ("<1>La revedere \ n ");); module_init (my_init_module); module_exit (my_cleanup_module); MODULE_LICENSE (" GPL "); ======================== ====================== Makefile pentru a compila modulul ======================= = ===================== CFLAGS = -Wall -D__KERNEL__ -DMODULE -I / lib / modules / $ (shell uname -r) / build / include hello. o: ================================================

Rețineți că atunci când scriem Makefile, am folosit convenția conform căreia GNU poate determina în mod independent cum se generează un fișier obiect pe baza variabilei CFLAGS și a compilatorului disponibil pe sistem.

Utilizarea resurselor

Un modul nu își poate finaliza sarcina fără a utiliza resurse de sistem, cum ar fi memoria, porturile I/O, memoria I/O, liniile de întrerupere și canalele DMA.

Ca programator, ar trebui să fiți deja familiarizați cu gestionarea heap-ului. Managementul heap-ului în nucleu nu este fundamental diferit. Programul dvs. poate obține memorie folosind funcția kmalloc ()și eliberează-o cu kfree ()... Aceste funcții sunt foarte asemănătoare cu familiarele malloc () și free (), cu excepția faptului că un argument de prioritate suplimentar este transmis către kmalloc (). De obicei, prioritatea este GFP_KERNEL sau GFP_USER. GFP este un acronim pentru „get free page” - ia o pagină gratuită. Gestionarea heap-ului kernelului este detaliată în Capitolul 7, „Obținerea memoriei”.

Un dezvoltator de driver începător poate fi surprins de necesitatea de a aloca în mod explicit porturi I/O, memorie I/O și linii de întrerupere. Numai atunci modulul kernel poate accesa cu ușurință aceste resurse. Deși memoria de sistem poate fi alocată de oriunde, memoria I/O, porturile și liniile de întrerupere joacă un rol special și sunt alocate diferit. De exemplu, un driver trebuie să aloce anumite porturi, nu
totul, cu excepția celor de care are nevoie pentru a controla dispozitivul. Dar șoferul nu poate folosi aceste resurse până nu se asigură că nu sunt folosite de altcineva.

Zona de memorie care aparține unui periferic este de obicei numită memorie I/O, pentru a o distinge de RAM de sistem (RAM), numită pur și simplu memorie.

Porturi I/O și memorie

Un loc de muncă tipic al șoferului constă în cea mai mare parte în citirea și scrierea de porturi și memorie I/O. Porturile și memoria I/O sunt denumite în mod colectiv regiune (sau zonă) I/O.

Din păcate, nu orice arhitectură de magistrală poate defini clar regiunea I/O aparținând fiecărui dispozitiv și este posibil ca șoferul să fie nevoit să ghicească locația regiunii care îi aparține sau chiar să încerce operațiuni de citire/scriere a posibilelor spații de adrese. . Această problemă este mai ales
se referă la magistrala ISA, care este încă folosită pentru a instala dispozitive simple într-un computer personal și este foarte populară în lumea industrială în implementarea PC / 104 (vezi secțiunea „PC / 104 și PC / 104 +” din Capitolul 15 „Prezentare generală asupra autobuzelor periferice” ).

Indiferent de magistrala utilizată pentru a conecta un dispozitiv hardware, driverului de dispozitiv trebuie să i se garanteze acces exclusiv la regiunea sa I/O pentru a preveni coliziunile între drivere. Dacă un modul, referindu-se la dispozitivul său, scrie pe un dispozitiv care nu îi aparține, atunci acest lucru poate duce la consecințe fatale.

Dezvoltatorii Linux au implementat un mecanism de solicitare/eliberare pentru regiunile I/O, în principal pentru a preveni coliziunile între diferite dispozitive. Acest mecanism a fost folosit de multă vreme pentru porturile I/O și recent a fost generalizat la un mecanism de gestionare a resurselor în general. Rețineți că acest mecanism este o abstractizare software și nu se extinde la capabilitățile hardware. De exemplu, accesul neautorizat la porturile I/O la nivel hardware nu provoacă nicio eroare similară cu o „defecțiune de segmentare”, deoarece hardware-ul nu alocă și nu-și autorizează resursele.

Informațiile despre resursele înregistrate sunt disponibile sub formă de text în fișierele / proc / ioports și / proc / iomem. Aceste informații au fost furnizate în Linux începând cu kernel-ul 2.3. Reamintim că această carte se concentrează în primul rând pe nucleul 2.4, iar notele de compatibilitate vor fi furnizate la sfârșitul capitolului.

Porturi

Mai jos este conținutul tipic al fișierului / proc / ioports:

0000-001f: dma1 0020-003f: pic1 0040-005f: temporizator 0060-006f: tastatură 0080-008f: dma page reg 00a0-00bf: pic2 00c0-00df: fpuide: f0-2017070: f0-2007070: : ide0 02f8-02ff: serial (set) 0300-031f: NE2000 0376-0376: ide1 03c0-03df: vga + 03f6-03f6: ide0 03f8-03ff: serial (set) 03c0-03df: Intel-1 03f6-03f6: ide0 03f8-03ff: serial (set) 1003 : acpi 1004-1005: acpi 1008-100b: acpi 100c-100f: acpi 1100-110f: Intel Corporation 82371AB PIIX4 IDE 1300-131f: pcnet_cs 1400-100f: Intel Corporation 82371AB PIIX4 IDE 1300-131f: pcnet_cs 1400-1400-1400 PCI818181410018181810 - 1cff: PCI CardBus # 04 5800-581f: Intel Corporation 82371AB PIIX4 USB d000-dfff: PCI Bus # 01 d000-d0ff: ATI Technologies Inc 3D Rage LT Pro AGP-133

Fiecare linie a acestui fișier afișează în hexazecimal gama de porturi asociate unui driver sau proprietar de dispozitiv. În versiunile anterioare ale nucleului, fișierul are același format, cu excepția faptului că ierarhia portului nu este afișată.

Fișierul poate fi folosit pentru a evita coliziunile de porturi atunci când adăugați un nou dispozitiv la sistem. Acest lucru este deosebit de convenabil atunci când configurați manual echipamentul instalat prin comutarea jumperelor (jampers). În acest caz, utilizatorul poate vizualiza cu ușurință lista de porturi utilizate și poate selecta o zonă liberă pentru dispozitivul instalat. Și, deși majoritatea dispozitivelor moderne nu folosesc deloc jumperi manuali, ele sunt totuși utilizate în fabricarea de componente la scară mică.

Mai important, există o structură de date accesibilă programatic asociată cu fișierul / proc / ioports. Prin urmare, atunci când driverul de dispozitiv se inițializează, acesta poate afla intervalul ocupat de porturi I/O. Aceasta înseamnă că dacă este necesară scanarea porturilor în căutarea unui nou dispozitiv, driverul poate evita situația de a scrie în porturile ocupate de dispozitive străine.

Scanarea magistralei ISA este cunoscută a fi o sarcină riscantă. Prin urmare, unele drivere distribuite cu nucleul Linux oficial evită această scanare la încărcarea modulului. Astfel, ei evită riscul de a deteriora sistemul care rulează prin scrierea în porturile folosite de alte echipamente. Din fericire, arhitecturile moderne de autobuz sunt imune la aceste probleme.

Interfața de programare utilizată pentru a accesa registrele I/O constă din următoarele trei funcții:

Int check_region (început lung nesemnat, len lung nesemnat); struct resource * request_region (unsigned long start, unsigned long len, char * name); void release_region (început lung nesemnat, len lung nesemnat);

Funcţie check_region () poate fi apelat pentru a verifica dacă intervalul de porturi specificat este ocupat. Returnează un cod de eroare negativ (cum ar fi -EBUSY sau -EINVAL) dacă răspunsul este negativ.

Funcţie cerere_regiune () efectuează alocarea intervalului specificat de adrese, returnând un pointer non-nul la succes. Driverul nu trebuie să stocheze sau să utilizeze indicatorul returnat. Tot ce trebuie să faceți este să verificați pentru NULL. Codul care ar trebui să funcționeze numai cu nucleul 2.4 (sau mai mare) nu trebuie să apeleze deloc check_region (). Nu există nicio îndoială avantajul acestei metode de distribuție, deoarece
nu se știe ce s-ar putea întâmpla între apelurile la check_region () și request_region (). Dacă doriți să mențineți compatibilitatea cu versiunile mai vechi ale nucleului, atunci este necesară apelarea check_region () înainte de request_region ().

Funcţie regiune_eliberare () ar trebui apelat atunci când driverul eliberează porturile utilizate anterior.

Valoarea reală a pointerului returnată de request_region () este utilizată numai de alocatorul de resurse care rulează în nucleu.

Aceste trei funcții sunt de fapt macro-uri definite în .

Următorul este un exemplu de utilizare a secvenței de apeluri utilizate pentru a înregistra porturile. Exemplul este preluat din codul șoferului craniului. (Codul pentru funcția skull_probe_hw () nu este afișat aici, deoarece conține cod dependent de hardware.)

#include #include static int skull_detect (unsigned int port, unsigned int range) (int err; if ((err = check_region (port, interval))< 0) return err; /* busy */ if (skull_probe_hw(port,range) != 0) return -ENODEV; /* not found */ request_region(port,range,"skull"); /* "Can"t fail" */ return 0; }

Acest exemplu verifică mai întâi disponibilitatea intervalului de porturi necesar. Dacă porturile nu sunt disponibile, atunci nu este posibil să accesați hardware-ul.
Locațiile reale ale portului dispozitivului pot fi verificate prin scanare. Funcția request_region () nu ar trebui, în acest exemplu,
se va sfârşi cu eşec. Nucleul nu poate încărca mai mult de un modul simultan, deci nu există coliziuni de porturi.
trebuie sa.

Orice porturi I/O alocate de driver trebuie ulterior eliberate. Driverul nostru craniu face acest lucru în funcția cleanup_module ():

Static void skull_release (port int nesemnat, interval int nesemnat) (release_region (port, interval);)

Mecanismul de solicitare / eliberare a resurselor este similar cu mecanismul de înregistrare / anulare a înregistrării modulelor și este perfect implementat pe baza instrucțiunii goto descrise mai sus.

Memorie

Informațiile de memorie I/O sunt disponibile prin fișierul / proc / iomem. Mai jos este un exemplu tipic de astfel de fișier pentru un computer personal:

00000000-0009fbff: Sistem de RAM 0009fc00-0009ffff: 000a0000-000bffff rezervate: Video Zona RAM 000c0000-000c7fff: Video ROM 000f0000-000fffff: Sistem ROM 00100000-03feffff: Sistem de RAM 00100000-0022c557: Kernel Cod 0022c558-0024455f: Kernel date 20000000 2fffffff : Intel Corporation 440BX / ZX - 82443BX / ZX Host bridge 68000000-68000fff: Texas Instruments PCI1225 68001000-68001fff: Texas Instruments PCI1225 (nr. 2) e00000000-68000fff: PCI040ff0 #eff0: PCI0ff0 #eff0: #eff00000: PCI00ff0: ATI Technologies Inc 3D Rage LT Pro AGP-133 e6000000-e6000fff: ATI Technologies Inc 3D Rage LT Pro AGP-133 fffc0000-ffffffff: rezervat

Valorile pentru intervalele de adrese sunt afișate în notație hexazecimală. Pentru fiecare interval de adrese, este afișat proprietarul acestuia.

Înregistrarea accesului la memorie I/O este similară cu înregistrarea porturilor I/O și este construită pe același mecanism din nucleu.

Pentru a obține și elibera intervalul necesar de adrese de memorie I/O, șoferul trebuie să folosească următoarele apeluri:

Int check_mem_region (început lung nesemnat, len lung nesemnat); int request_mem_region (unsigned long start, unsigned long len, char * nume); int release_mem_region (început lung nesemnat, len lung nesemnat);

De obicei, driverul cunoaște intervalul de adrese de memorie I/O, astfel încât codul de alocare pentru această resursă poate fi redus în comparație cu exemplul de alocare a intervalului de porturi:

If (check_mem_region (mem_addr, mem_size)) (printk ("nume driver: memorie deja utilizată \ n"); return -EBUSY;) request_mem_region (mem_addr, mem_size, "drivername");

Alocarea resurselor în Linux 2.4

Mecanismul actual de alocare a resurselor a fost introdus în nucleul Linux 2.3.11 și oferă acces flexibil pentru gestionarea resurselor sistemului. Această secțiune descrie pe scurt acest mecanism. Cu toate acestea, funcțiile de bază de alocare a resurselor (cum ar fi request_region (), etc.) sunt încă implementate ca macrocomenzi și sunt utilizate pentru compatibilitatea cu versiunile anterioare ale nucleului. În cele mai multe cazuri, nu trebuie să știți nimic despre mecanismul de distribuție real, dar poate fi interesant atunci când construiți drivere mai complexe.

Sistemul de management al resurselor implementat în Linux poate gestiona resursele arbitrare într-o manieră ierarhică uniformă. Resursele globale ale sistemului (de exemplu, porturile I/O) pot fi subdivizate în subseturi - de exemplu, cele asociate cu un slot pe magistrala hardware. Anumiți drivere pot, de asemenea, să subdivizeze resursele capturate în funcție de structura lor logică, dacă se dorește.

Gama de resurse alocate este descrisă prin structura de resurse struct, care este declarată în fișierul antet :

Resursa Struct (const char * nume; început lung, sfârşit nesemnat; steaguri lungi nesemnate; resursă struct * părinte, * frate, * copil;);

Intervalul global de resurse (rădăcină) este creat în momentul pornirii. De exemplu, o structură de resurse care descrie porturile I/O este creată după cum urmează:

Structura resursă ioport_resource = ("PCI IO", 0x0000, IO_SPACE_LIMIT, IORESOURCE_IO);

Descrie o resursă numită PCI IO care acoperă intervalul de adrese de la zero la IO_SPACE_LIMIT. Valoarea acestei variabile depinde de platforma utilizată și poate fi 0xFFFF (spațiu de adrese de 16 biți, pentru arhitecturile x86, IA-64, Alpha, M68k și MIPS), 0xFFFFFFFF (spațiu de 32 de biți, pentru SPARC, PPC, SH) sau 0xFFFFFFFFFFFFFFFF (64 de biți, SPARC64).

Subdomenii ale acestei resurse pot fi create apelând allocate_resource (). De exemplu, în timpul inițializării magistralei PCI, o nouă resursă este creată pentru regiunea de adrese a acestei magistrale, care este atribuită unui dispozitiv fizic. Când codul de nucleu al managerului de magistrală PCI procesează alocările de porturi și memorie, creează o nouă resursă numai pentru acele regiuni și le alocă folosind apeluri ioport_resource () sau iomem_resource ().

Driverul poate solicita apoi un subset al unei resurse (de obicei parte a unei resurse globale) și îl poate marca ca ocupat. Resursa este capturată prin apelarea request_region (), care returnează fie un pointer către o nouă resursă struct care descrie resursa solicitată, fie NULL în cazul unei erori. Această structură face parte din arborele global de resurse. După cum sa menționat deja, după obținerea resursei, driverul nu are nevoie de valoarea acestui pointer.

Cititorul interesat se poate bucura să vadă detaliile acestei scheme de gestionare a resurselor în fișierul kernel / resource.c aflat în directorul surselor kernelului. Cu toate acestea, cunoștințele deja declarate vor fi suficiente pentru majoritatea dezvoltatorilor.

Mecanismul de stratificare al alocării resurselor aduce beneficii duble. Pe de o parte, oferă o reprezentare vizuală a structurilor de date ale nucleului. Să ne uităm din nou la exemplul fișierului / proc / ioports:

E800-e8ff: Adaptec AHA-2940U2 / W / 7890 e800-e8be: aic7xxx

Gama e800-e8ff este alocată unui adaptor Adaptec care sa identificat ca driver de magistrală PCI. Cea mai mare parte a acestei game a fost solicitată de driverul aic7xxx.

Un alt avantaj al unui astfel de management al resurselor este împărțirea spațiului de adrese în sub-benzi care reflectă interconectarea efectivă a echipamentului. Managerul de resurse nu poate aloca subdomenii de adrese suprapuse, ceea ce poate preveni instalarea unui driver care funcționează defectuos.

Configurare automată și manuală

Unii dintre parametrii solicitați de driver pot varia de la sistem la sistem. De exemplu, șoferul trebuie să știe despre adresele I/O valide și intervalele de memorie. Aceasta nu este o problemă pentru interfețele de magistrală bine organizate. Cu toate acestea, uneori trebuie să transmiteți parametri driverului pentru a-l ajuta să-și găsească propriul dispozitiv sau pentru a activa/dezactiva unele dintre funcțiile sale.

Acești parametri, care afectează funcționarea driverului, depind de dispozitiv. De exemplu, acesta ar putea fi numărul versiunii unui dispozitiv instalat. Desigur, aceste informații sunt necesare pentru ca șoferul să funcționeze corect cu dispozitivul. Definirea unor astfel de parametri (configurarea driverului) este suficientă
o sarcină dificilă efectuată atunci când driverul este inițializat.

De obicei, există două moduri de a obține valorile corecte pentru acest parametru - fie utilizatorul le definește în mod explicit, fie șoferul le determină independent, pe baza sondajului echipamentului. Deși detectarea automată a unui dispozitiv este, fără îndoială, cea mai bună soluție pentru configurarea unui driver,
configurația personalizată este mult mai ușor de implementat. Dezvoltatorul de drivere ar trebui să implementeze autoconfigurarea driverului ori de câte ori este posibil, dar, în același timp, ar trebui să ofere utilizatorului un mecanism de configurare manuală. Desigur, configurarea manuală ar trebui să aibă prioritate față de configurarea automată. În etapele inițiale de dezvoltare, de regulă, este implementat doar transferul manual al parametrilor către driver. Configurarea automată este adăugată mai târziu, dacă este posibil.

Mulți șoferi, printre parametrii lor de configurare, au parametri care controlează operațiunile șoferului. De exemplu, driverele de interfață Integrated Device Electronics (IDE) permit utilizatorului să controleze operațiunile DMA. Astfel, dacă driverul face o treabă bună de detectare automată a hardware-ului, poate doriți să oferiți utilizatorului controlul asupra funcționalității driverului.

Valorile parametrilor pot fi transmise în timpul încărcării modulului cu comenzile insmod sau modprobe. Recent, a devenit posibil să se citească valoarea parametrilor dintr-un fișier de configurare (de obicei /etc/modules.conf). Valorile întregi și șiruri pot fi transmise ca parametri. Astfel, dacă trebuie să transmiteți o valoare întreagă pentru parametrul skull_ival și o valoare șir pentru parametrul skull_sval, le puteți transmite în timpul încărcării modulului cu parametri suplimentari ai comenzii insmod:

Insmod skull skull_ival = 666 skull_sval = „fiara”

Cu toate acestea, înainte ca insmod să poată schimba valorile parametrilor modulului, modulul trebuie să pună la dispoziție acești parametri. Parametrii sunt declarați utilizând macrocomanda MODULE_PARM, care este definită în fișierul antet module.h. Macro-ul MODULE_PARM ia doi parametri: un nume de variabilă și un șir care îi definește tipul. Această definiție macro trebuie plasată în afara oricăror funcții și este de obicei situată la începutul fișierului după definirea variabilelor. Deci, cei doi parametri menționați mai sus pot fi declarați astfel:

Int skull_ival = 0; char * skull_sval; MODULE_PARM (craniu_ival, "i"); MODULE_PARM (craniu_sval, "s");

În prezent, sunt acceptate cinci tipuri de parametri de modul:

  • b - valoare de un octet;
  • h - (scurtă) valoare de doi octeți;
  • i - întreg;
  • l - întreg lung;
  • s - șir (char *);

În cazul parametrilor șir, un pointer (char *) trebuie declarat în modul. Comanda insmod alocă memorie pentru șirul transmis și o inițializează cu valoarea necesară. Folosind macro-ul MODULE_PARM, puteți inițializa matrice de parametri. În acest caz, întregul care precede litera de tip determină lungimea matricei. Când se specifică două numere întregi separate printr-o liniuță, acestea definesc numărul minim și maxim de valori care trebuie transmise. Pentru o înțelegere mai detaliată a funcționării acestei macrocomenzi, consultați fișierul antet .

De exemplu, să presupunem că matricea de parametri trebuie inițializată cu cel puțin două și cel puțin patru valori întregi. Apoi poate fi descris după cum urmează:

Int skull_array; MODULE_PARM (skull_array, "2-4i");

În plus, setul de instrumente al programatorului conține definiția macro MODULE_PARM_DESC, care vă permite să plasați comentarii asupra parametrilor modulului transferați. Aceste comentarii sunt salvate în fișierul obiect al modulului și pot fi vizualizate utilizând, de exemplu, utilitarul objdump sau folosind instrumente automate de administrare a sistemului. Iată un exemplu de utilizare a acestei definiții macro:

Int bază_port = 0x300; MODULE_PARM (port_bază, „i”); MODULE_PARM_DESC (base_port, „Portul de bază I/O (implicit 0x300)”);

Este de dorit ca toți parametrii modulului să aibă valori implicite. Modificarea acestor valori cu insmod ar trebui să fie necesară numai dacă este necesar. Modulul poate verifica setarea explicită a parametrilor verificând valorile curente ale acestora cu valorile implicite. Ulterior, puteți implementa un mecanism de autoconfigurare bazat pe următoarea diagramă. Dacă valorile parametrilor au valori implicite, atunci se realizează autoconfigurarea. În caz contrar, se folosesc valorile curente. Pentru ca această schemă să funcționeze, este necesar ca valorile implicite să nu se potrivească cu niciuna dintre configurațiile posibile ale sistemului din lumea reală. Apoi se poate presupune că astfel de valori nu pot fi setate de utilizator în configurarea manuală.

Următorul exemplu arată modul în care driverul craniu detectează automat spațiul de adrese de port pe un dispozitiv. În exemplul de mai sus, detectarea automată folosește navigarea pe mai multe dispozitive, în timp ce configurarea manuală restricționează driverul la un singur dispozitiv. Ați văzut deja funcția skull_detect mai devreme în secțiunea care descrie porturile I/O. Implementarea funcției skull_init_board () nu este afișată, așa cum este
efectuează inițializarea dependentă de hardware.

/ * * intervale de porturi: dispozitivul poate locui între * 0x280 și 0x300, în pași de 0x10. Folosește porturi 0x10. * / #define SKULL_PORT_FLOOR 0x280 #define SKULL_PORT_CEIL 0x300 #define SKULL_PORT_RANGE 0x010 / * * următoarea funcție efectuează autodetecția, cu excepția cazului în care o anumită valoare * a fost atribuită de către insmod pentru „skull_port_base” * / static int =skull0_port_base / * 0 forțează autodetecția * / MODULE_PARM (skull_port_base, "i"); MODULE_PARM_DESC (skull_port_base, „Port de bază I/O pentru craniu”); static int skull_find_hw (void) / * returnează numărul de dispozitive * / (/ * baza este fie valoarea timpului de încărcare, fie prima încercare * / int bază = skull_port_base? skull_port_base: SKULL_PORT_FLOOR; int rezultat = 0; / * bucla unu timp dacă este atribuită valoare; încercați-le pe toate dacă detectați automat * / do (dacă (skull_detect (bază, SKULL_PORT_RANGE) == 0) (skull_init_board (bază); rezultat ++;) bază + = SKULL_PORT_RANGE; / * pregătiți-vă pentru următoarea încercare * / ) în timp ce (skull_port_base == 0 && baza< SKULL_PORT_CEIL); return result; }

Dacă variabilele de configurare sunt folosite numai în interiorul driverului (adică, nu sunt publicate în tabelul cu simboluri ale nucleului), atunci programatorul poate simplifica ușor viața utilizatorului prin nefolosirea de prefixe în numele variabilelor (în cazul nostru, prefixul skull_) . Pentru utilizator, aceste prefixe înseamnă puțin, iar absența lor facilitează introducerea comenzilor de la tastatură.

Pentru a fi complet, vom oferi o descriere a încă trei macrocomenzi care vă permit să plasați câteva comentarii în fișierul obiect.

MODULE_AUTHOR (nume) Plasează un șir cu numele autorului în fișierul obiect. MODULE_DESCRIPTION (desc) Plasează o linie de descriere generală pentru un modul într-un fișier obiect. MODULE_SUPPORTED_DEVICE (dezvoltator) Plasează un șir care descrie dispozitivul suportat de modul. V
Linux: Ghidul complet Kolisnichenko Denis Nikolaevich

28.2. Compilarea unui modul

28.2. Compilarea unui modul

Vom compila fișierul module.c. Acest lucru necesită un compilator gcc instalat, fișiere antet și surse de kernel. Dacă ați citit cartea înainte de acest capitol, atunci ar trebui să aveți deja instalate pachetele:

1. cpp - preprocesor cpp;

2. binutils - un set de diverse utilități (cum ar fi, gprof, ld);

3.glibc-kerheaders - anteturi kernel;

4. glibc-devel - fișiere auxiliare pentru dezvoltarea aplicațiilor folosind biblioteca standard C;

5.gcc este compilatorul gcc.

Rămâne de instalat pachetul kernel-source - sursele nucleului Linux. În plus, trebuie să vă asigurați că nucleul dumneavoastră acceptă module care se pot încărca dinamic (secțiunea 20.3.2.3). Dacă opțiunea Activarea suportului pentru modulul încărcat este dezactivată, trebuie să-l activați, să salvați fișierul de configurare a nucleului și să recompilați nucleul.

Compilatorul gcc trebuie apelat cu multe opțiuni, așa că pentru a ne ușura munca, vom scrie un makefile (secțiunea 21.2):

Lista 28.5. Makefile pentru a construi modulul

PATH = / usr / include /usr/src/linux-2.4/include

MODFLAGS: = -O3 -Wall -DLINUX -D__KERNEL__ -I $ (CALEA)

module.o: module.c

$ (CC) $ (MODFLAGS) -c module.c

Opțiunile compilatorului înseamnă următoarele:

O3: va fi folosit al treilea nivel de optimizare (ce este acesta, veți afla în sistemul de ajutor gcc: om gcc);

Perete: activați toate avertismentele;

DLINUX: generarea codului pentru Linux;

I $ (PATH): definiți calea de căutare pentru fișierele antet. În mod implicit, compilatorul caută fișiere de antet în directorul / usr / include, dar este posibil să nu fie acolo. De exemplu, pentru o distribuție ALT Linux (kernel 2.4.21), fișierele de antet sunt localizate în directorul /usr/include/linux-2.4.21rel-std-up.

Plasați makefile în același director ca module.c și rulați make. După finalizarea acestuia, veți primi un fișier module.o, care va fi localizat în același director.

# insmod module.o

Veți vedea mesajul Modulul meu: Începe... Același mesaj va fi scris în fișierul jurnal / var / jurnal / mesaje.

Din cartea C++ de Hill Murray

1.1.2 Compilare De unde provin fluxul de ieșire cout și codul pentru operația de ieșire „“? Pentru a obține codul executabil, trebuie compilat un program scris în C++. În esență, procesul de compilare este același ca pentru C și cea mai mare parte a intrării

Din Ghidul utilizatorului Fedora 8 autorul

3.4.3. Compilare De regulă, codurile sursă ale programelor sunt distribuite sub forma unei arhive cu „extensia dublă” -.tar.gz. Este obișnuit să despachetați codul sursă în directorul /usr/src. Prin urmare, pentru a despacheta arhiva, trebuie să rulați următoarele comenzi: sucd / usr / srcgunzip archive.tar.gztar xvf

Din cartea Linux pentru utilizator autorul Kostromin Viktor Alekseevici

Din cele mai bune 200 de software Linux autorul Iaremciuk Serghei Akimovici

17.5.6. Compilarea modulelor Dacă ați configurat unele drivere ca module separate (ați ales opțiunea „m” în configurare când ați răspuns la unele întrebări), atunci trebuie să executați și comanda make modules, iar apoi și comanda make modules_install. În fișierul Documentație / modules.txt puteți

Din cartea Limbajul de programare C # 2005 și platforma .NET 2.0. autorul Troelsen Andrew

Compilarea programelor Chiar și după ce au apărut pachetele, care erau deja programe compilate, compilarea a rămas mult timp și, pentru unii, rămâne mijlocul principal de instalare. Notă Primele kituri precompilate au apărut în

Din cartea Asterisk™: The Future of Telephony, a doua ediție autorul Meggelen Jim Wan

Compilare condiționată Un alt pachet de directive de preprocesor (#if, #elif, #else, #endif) vă permite să compilați condiționat un bloc de cod pe baza simbolurilor predefinite. Cazul de utilizare clasic pentru aceste directive este identificarea blocurilor

Din cartea Linux Networking autorul Smith Roderick W.

Din cartea Limbajul de programare C pentru computerul personal autorul Bochkov S.O.

Compilarea libpri Bibliotecile libpri nu folosesc autoconf pentru a configura mediul de construcție sau selectorul de componente de construcție deoarece nu sunt necesare; astfel, instalarea este simplificată. libpri este folosit de diverși producători de hardware

Din cartea Linux: Ghidul complet autorul Kolisnichenko Denis Nikolaevici

Compilarea Asterisk După compilarea și instalarea pachetelor zaptel și libpri (dacă este necesar), puteți trece la instalarea Asterisk. Această secțiune acoperă o instalare standard și prezintă câteva argumente alternative care pot

Din cartea Linux Programming by Example autorul Robbins Arnold

Compilarea nucleului După ce ați configurat nucleul sistemului dumneavoastră rulând make xconfig sau cealaltă comandă de la începutul acestui capitol, trebuie să compilați nucleul și să instalați modulele acestuia. Pentru a face acest lucru, rulați următoarele comenzi: # make dep # make bzImage # make modules # make

Din cartea C Language - A Beginner's Guide de Prata Stefan

Compilarea condiționată Această secțiune descrie directivele care controlează compilarea condiționată. Aceste directive vă permit să excludeți orice părți ale fișierului sursă din procesul de compilare prin verificarea condițiilor (constante

Din cartea Linux prin ochii unui hacker autorul Flenov Mihail Evghenievici

20.5. Compilarea Kernel-ului 20.5.1. De ce să actualizezi nucleul? Linux crește mai rapid decât orice alt sistem de operare. Noi versiuni ale nucleului care implementează funcții noi apar în mod regulat. De exemplu, kitul de distribuție Fedora Core 4 abia a fost lansat pe nucleul 2.6.11, iar www.kernel.org are deja un

Din cartea Sistem de operare UNIX autorul Robachevsky Andrey M.

15.2. Compilarea pentru depanare Pentru a utiliza depanatorul sursă, executabilul care este depanat trebuie să fie compilat cu opțiunea de compilare -g. Această opțiune forțează compilatorul să injecteze identificatori suplimentari de depanare în codul obiectului; acesta este

Din cartea autorului

De ce să compilați? Cititorii BASIC se pot întreba de ce sunt atât de mulți pași pentru a finaliza un program. Se pare că acest mod de compilare necesită mai mult timp (și în unele cazuri chiar poate fi). Dar din moment ce în

Din cartea autorului

3.8.3. Compilarea nucleului Când instalăm dintr-un pachet RPM, obținem un nucleu modular, în care driverele de dispozitiv pot fi fie compilate într-o singură bucată cu nucleul, fie încărcate separat. Un astfel de nucleu funcționează mai lent, dar vă permite să actualizați driverele cu o simplă înlocuire

Din cartea autorului

Compilare Procedura de creare a majorității aplicațiilor este generală și este prezentată în Fig. 2.2. Orez. 2.2. Schema de compilare a programului Prima fază este etapa de compilare, când fișierele sursă ale programului, inclusiv fișierele antet, sunt procesate de compilator.