Struktura modula kernela i metode njegove kompilacije. Karakteristike kompajliranja programa sa modularnom strukturom. Uvod i pozadina

Zašto sami kompajlirati kernel?
Možda je glavno pitanje koje se postavlja u vezi sa kompajliranjem kernela: “Zašto bih to radio?”
Mnogi ovo smatraju besmislenim gubljenjem vremena kako bi se pokazali kao pametan i napredan korisnik Linuxa. U stvari, kompajliranje kernela je veoma važna stvar. Recimo da ste kupili novi laptop i vaša web kamera ne radi. Vaši postupci? Gledate u tražilicu i tražite rješenje za problem po ovom pitanju. Često se može ispostaviti da vaša web kamera radi na više nova verzija nego tvoj. Ako ne znate koju verziju imate, unesite uname -r u terminal, kao rezultat ćete dobiti verziju kernela (na primjer, linux-2.6.31-10). Kompilacija kernela se također naširoko koristi za povećanje performansi: činjenica je da se po defaultu distribucije kernela kompiliraju „za svakoga“, zbog čega uključuje ogroman broj drajvera koji vam možda neće trebati. Dakle, ako dobro poznajete hardver koji koristite, možete onemogućiti nepotrebne drajvere u fazi konfiguracije. Takođe je moguće omogućiti podršku za više od 4 GB RAM-a bez promjene sistemske dubine bita. Dakle, ako još uvijek trebate imati vlastiti kernel, počnimo sa kompajliranjem!

Dobijanje izvornog koda kernela.
Prva stvar koju trebate učiniti je dobiti izvorni kod za potrebnu verziju kernela. Obično morate nabaviti najnoviju stabilnu verziju. Sve zvanične verzije kernela dostupne su na kernel.org. Ako već imate instaliran X server ( kućni računar), zatim možete otići na stranicu u svom omiljenom pretraživaču i preuzeti željenu verziju u tar.gz arhivi (gzip komprimiran). Ako radite u konzoli (na primjer, još niste instalirali X server ili konfigurirate server), možete koristiti tekstualni pretraživač (na primjer elinks). Možete koristiti i standardni wget menadžer preuzimanja:
wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.33.1.tar.gz
Ali imajte na umu da morate znati tačan broj verzije koji vam je potreban.

Raspakivanje arhive izvornog koda.
Nakon što primite arhivu izvornog koda, trebate je raspakirati u mapu. Ovo se može uraditi iz grafičke fajl menadžeri(delfin, nautilus, itd.) ili preko mc. Ili koristite tradicionalnu naredbu tar:
tar -zxvf put_do_arhive
Sada imate fasciklu sa izvornim kodom, idite na nju koristeći komandu cd kernel_source_directory(da navedete direktorijume u fascikli, koristite komandu ls).

Konfiguracija kernela.
Kada dođete do izvornog direktorija kernela, morate izvršiti "20-minutnu" konfiguraciju kernela. Njegov cilj je ostaviti samo potrebne drajvere i funkcije. Sve komande već moraju biti izvršene kao superkorisnik.

make config - konzolni mod konfiguratora.

napravi menuconfig - način rada konzole u obliku liste.

make xconfig - grafički način rada.

Nakon što izvršite potrebne promjene, sačuvajte postavke i izađite iz konfiguratora.

Kompilacija.
Došlo je vrijeme za završnu fazu montaže - kompilaciju. Ovo se radi sa dve komande:
napravi && napravi instalaciju
Prva komanda će kompajlirati sve datoteke u mašinski kod, a druga će instalirati novi kernel na vaš sistem.
Čekamo od 20 minuta do nekoliko sati (u zavisnosti od snage računara). Kernel je instaliran. Da bi se pojavio na listi grub(2), unesite (kao superkorisnik)
update-grub
Sada nakon ponovnog pokretanja, pritisnite "Escape" i vidjet ćete novi kernel na listi. Ako se kernel ne uključi, jednostavno se pokrenite sa starim kernelom i pažljivije ga konfigurirajte.

KernelCheck - kompajlira kernel bez odlaska na konzolu.
omogućava vam da izgradite kernel u potpuno grafičkom modu za Debian i distribucije koje se temelje na njemu. Nakon pokretanja, KernelCheck će ponuditi najnovije verzije kernela i zakrpe, a nakon vašeg pristanka, preuzeti izvorni kod i pokrenuti grafički konfigurator. Program će kompajlirati kernel u .deb pakete i instalirati ih. Sve što treba da uradite je da ponovo pokrenete sistem.

O: "Na osnovu prijevoda" Linux Device Driver 2. izdanje. Prevod: Knjažev Aleksej [email protected] Datum posljednje izmjene: 03.08.2004. Lokacija: http://lug.kmv.ru/index.php?page=knz_ldd2

A sada krenimo sa programiranjem! Ovo poglavlje pruža osnove o modulima i programiranju kernela.
Ovdje ćemo izgraditi i pokrenuti punopravni modul, čija struktura odgovara bilo kojem stvarnom modularnom drajveru.
Istovremeno ćemo se koncentrirati na glavne pozicije bez uzimanja u obzir specifičnosti stvarnih uređaja.

Svi dijelovi kernela kao što su funkcije, varijable, datoteke zaglavlja i makroi koji su ovdje spomenuti će biti
su detaljno opisani na kraju poglavlja.

Zdravo svijete!

U procesu upoznavanja sa originalnim materijalom koji su napisali Alessndro Rubini i Jonathan Corbet, primjer naveden kao Hello world mi se činio pomalo neuspjelim. Stoga želim čitatelju pružiti, po mom mišljenju, uspješniju verziju prvog modula. Nadam se da neće biti problema sa njegovom kompilacijom i instalacijom pod kernelom 2.4.x. Predloženi modul i način na koji je kompajliran omogućavaju da se koristi u kernelima koji podržavaju i ne podržavaju kontrolu verzija. Kasnije ćete se upoznati sa svim detaljima i terminologijom, pa sada otvorite vim i počnite raditi!

=================================================== === //fajl hello_knz.c #include #include <1>Zdravo, svijet\n"); vrati 0; ); void cleanup_module(void) ( printk("<1>Zbogom okrutni svijete\n"); ) MODULE_LICENSE(“GPL”); ================================== ==================

Za kompajliranje takvog modula možete koristiti sljedeći Makefile. Ne zaboravite staviti znak tabulatora prije reda koji počinje sa $(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 $@ $^ =========================================== =================== ====

Ovo koristi dvije karakteristike u poređenju s originalnim kodom Hello world koji su predložili Rubini & Corbet. Prvo, modul će imati istu verziju kao i verzija kernela. Ovo se postiže postavljanjem varijable PARAM u skriptu za kompilaciju. Drugo, modul će sada biti licenciran pod GPL (koristeći MODULE_LICENSE() makro). Ako to nije učinjeno, tada prilikom instaliranja modula u kernel možete vidjeti nešto poput sljedećeg upozorenja:

# insmod hello_knz.o Upozorenje: učitavanje hello_knz.o će pokvariti kernel: nema licence Pogledajte http://www.tux.org/lkml/#export-tainted za informacije o zaraženim modulima Modul hello_knz je učitan, sa upozorenjima

Hajde da sada objasnimo opcije kompilacije modula (makro definicije će biti objašnjene kasnije):

-Sa- sa ovom opcijom, gcc prevodilac će zaustaviti proces kompilacije datoteke odmah nakon kreiranja objektne datoteke, bez pokušaja kreiranja izvršne binarne datoteke.

-Zid- maksimalni nivo upozorenja kada gcc radi.

-D— definicije makro simbola. Isto kao i #define direktiva u kompajliranoj datoteci. Nema apsolutno nikakve razlike kako definisati makro simbole koji se koriste u ovom modulu, koristeći #define u izvornoj datoteci ili koristeći opciju -D za kompajler.

-Ja- dodatne staze pretraživanja za uključene datoteke. Obratite pažnju na upotrebu zamjene “uname -r”, koja će odrediti tačan naziv verzije kernela koja se trenutno koristi.

Sljedeći odjeljak pruža još jedan primjer modula. Takođe detaljno objašnjava kako da ga instalirate i skinete iz kernela.

Original Hello world!

Pogledajmo sada originalni kod za jednostavan modul "Hello, World" koji nudi Rubini & Corbet. Ovaj kod se može kompajlirati pod verzijama kernela 2.0 do 2.4. Ovaj primjer, kao i svi ostali predstavljeni u knjizi, dostupni su na O'Reilly FTP stranici (vidi Poglavlje 1).

//datoteka hello.c #define MODULE #include int init_module(void) ( printk("<1>Zdravo, svijet\n"); vrati 0; ) void cleanup_module(void) ( printk("<1>Zbogom okrutni svijete\n");)

Funkcija printk() definiran u Linux kernelu i radi kao standardna bibliotečka funkcija printf() na C jeziku. Kernelu je potrebna sopstvena, po mogućnosti mala, funkcija zaključivanja, sadržana direktno u kernelu, a ne u bibliotekama na nivou korisnika. Modul može pozvati funkciju printk() jer nakon učitavanja modula pomoću naredbe insmod Modul komunicira sa kernelom i ima pristup objavljenim (izvezenim) funkcijama i varijablama kernela.

String parametar “<1>” proslijeđen funkciji printk() je prioritet poruke. Originalni engleski izvori koriste termin loglevel, što znači nivo evidentiranja poruka. Ovdje ćemo koristiti termin prioritet umjesto originalnog “loglevel”. U ovom primjeru koristimo visoki prioritet za poruku koja ima nizak broj. Visoki prioritet poruke je namerno postavljen jer poruka sa podrazumevanim prioritetom možda neće biti prikazana u konzoli sa koje je modul instaliran. Smjer izlaza poruka kernela sa zadanim prioritetom ovisi o verziji pokrenutog kernela, verziji demona klogd, i vašu konfiguraciju. Detaljnije, rad s funkcijom printk() objasnićemo u poglavlju 4, Tehnike otklanjanja grešaka.

Modul možete testirati pomoću naredbe insmod da instalirate modul u kernel i komande rmmod da uklonite modul iz kernela. U nastavku ćemo pokazati kako se to može učiniti. U ovom slučaju, ulazna tačka init_module() se izvršava kada se modul instalira u kernel, a cleanup_module() se izvršava kada se ukloni iz kernela. Zapamtite da samo privilegirani korisnik može učitavati i isprazniti module.

Gornji primjer modula može se koristiti samo sa kernelom koji je izgrađen sa isključenom zastavicom “podrška verzije modula”. Nažalost, većina distribucija koristi kernele kontrolisane verzije (o tome se govori u odeljku "Kontrola verzija u modulima" poglavlja 11, "kmod i napredna modularizacija"). I iako starije verzije paketa modulils dozvoliti da se takvi moduli učitaju u jezgre kontrolirane verzije, što više nije moguće. Podsjetimo da paket modutils sadrži skup programa koji uključuje programe insmod i rmmod.

Zadatak: Odredite broj verzije i sastav modula paketa iz vaše distribucije.

Kada pokušate umetnuti takav modul u kernel koji podržava kontrolu verzija, možete vidjeti poruku o grešci sličnu sljedećoj:

# insmod hello.o hello.o: nepodudaranje verzije modula kernela hello.o je kompajliran za verziju kernela 2.4.20 dok je ovaj kernel verzija 2.4.20-9asp.

U katalogu misc-modules primjeri sa ftp.oreilly.com naći ćete originalni primjer programa hello.c, koji sadrži malo više redaka, i može se instalirati u jezgrima s kontroliranim verzijama i bez verzija. Međutim, toplo preporučujemo da izgradite vlastito jezgro bez podrške za kontrolu verzija. Istovremeno, preporučljivo je uzeti originalne izvore kernela na web stranici www.kernel.org

Ako ste novi u sastavljanju kernela, pokušajte pročitati članak koji je Alessandro Rubini (jedan od autora originalne knjige) objavio na http://www.linux.it/kerneldocs/kconf, koji bi vam trebao pomoći da savladate proces.

Pokrenite sljedeće naredbe u tekstualnoj konzoli da kompajlirate i testirate originalni primjer modula iznad.

Root# gcc -c hello.c root# insmod ./hello.o Zdravo, svijet root# rmmod zdravo Zbogom okrutni svijet root#

Ovisno o mehanizmu koji vaš sistem koristi za prosljeđivanje nizova poruka, smjer izlaza poruka koje šalje funkcija printk(), može se razlikovati. U datom primjeru kompajliranja i testiranja modula, poruke poslane iz funkcije printk() izlaze su na istu konzolu sa koje su date naredbe za instaliranje i pokretanje modula. Ovaj primjer je preuzet sa tekstualne konzole. Ako izvršite naredbe insmod I rmmod iz programa xterm, tada najvjerovatnije nećete vidjeti ništa na svom terminalu. Umjesto toga, poruka može završiti u jednom od sistemskih dnevnika, na primjer u /var/log/messages. Tačan naziv datoteke ovisi o distribuciji. Pogledajte vrijeme promjena u log datotekama. Mehanizam koji se koristi za prosljeđivanje poruka iz funkcije printk() opisan je u odjeljku "Kako se poruke evidentiraju" u poglavlju 4 "Tehnike"
otklanjanje grešaka".

Za pregled poruka modula u sistemskoj log datoteci /val/log/messages zgodno je koristiti sistemski uslužni program tail, koji podrazumevano prikazuje poslednjih 10 redova fajla koji mu je prosleđen. Zanimljiva opcija ovog uslužnog programa je opcija -f, koja pokreće uslužni program u načinu praćenja zadnjih redova datoteke, tj. Kada se novi redovi pojave u datoteci, oni će se automatski ispisati. Da biste zaustavili izvršavanje naredbe u ovom slučaju, morate pritisnuti Ctrl+C. Stoga, da vidite posljednjih deset redova datoteke sistemske evidencije, unesite sljedeće u komandnu liniju:

Root# rep /var/log/messages

Kao što vidite, pisanje modula nije tako teško kao što se čini. Najteži dio je razumjeti kako vaš uređaj radi i kako poboljšati performanse modula. Kako nastavljamo sa ovim poglavljem, naučit ćemo više o pisanju jednostavnih modula, ostavljajući specifičnosti uređaja za kasnija poglavlja.

Razlike između modula kernela i aplikacija

Aplikacija ima jednu ulaznu tačku, koja počinje da se izvršava odmah nakon postavljanja pokrenuta aplikacija u RAM-u računara. Ova ulazna tačka je opisana u C-u kao funkcija main(). Prekid funkcije main() znači ukidanje aplikacije. Modul ima nekoliko ulaznih tačaka koje se izvršavaju prilikom instaliranja i uklanjanja modula iz kernela, kao i prilikom obrade zahtjeva korisnika. Dakle, ulazna tačka init_module() se izvršava kada se modul učita u kernel. Cleanup_module() funkcija se izvršava kada se modul isprazni. U budućnosti ćemo se upoznati sa ostalim ulaznim tačkama u modul, koje se izvršavaju prilikom izvršavanja različitih zahteva prema modulu.

Mogućnost učitavanja i istovara modula su dva stuba mehanizma modularizacije. Mogu se procijeniti na različite načine. Za programera to znači, prije svega, smanjenje vremena razvoja, jer možete testirati funkcionalnost drajvera bez dugotrajnog procesa ponovnog pokretanja.

Kao programer, znate da aplikacija može pozvati funkciju koja nije deklarirana u aplikaciji. U fazama statičkog ili dinamičkog povezivanja određuju se adrese takvih funkcija iz odgovarajućih biblioteka. Funkcija printf() jedna od ovih funkcija koje se mogu pozvati koja je definirana u biblioteci libc. Modul je, s druge strane, povezan samo sa kernelom i može pozvati samo funkcije koje jezgro izvozi. Kod koji se izvršava u kernelu ne može koristiti vanjske biblioteke. Tako, na primjer, funkcija printk(), koji je korišten u primjeru hello.c, je analog dobro poznate funkcije printf(), dostupno u aplikacijama na nivou korisnika. Funkcija printk() nalazi se u jezgru i treba da bude što manji. Stoga, za razliku od printf(), ima vrlo ograničenu podršku za tipove podataka i, na primjer, uopće ne podržava brojeve s pomičnim zarezom.

Implementacije kernela 2.0 i 2.2 nisu podržavale specifikacije tipa L I Z. Oni su predstavljeni samo u verziji kernela 2.4.

Slika 2-1 prikazuje implementaciju mehanizma za pozivanje funkcija koje su ulazne tačke u modul. Takođe, ova slika prikazuje mehanizam interakcije instaliranog ili instaliranog modula sa kernelom.

Rice. 2-1. Komunikacija između modula i kernela

Jedna od karakteristika Unix/Linux operativnih sistema je nedostatak biblioteka koje se mogu povezati sa modulima kernela. Kao što već znate, moduli su, kada se učitaju, povezani u kernel, tako da sve funkcije van vašeg modula moraju biti deklarirane u datotekama zaglavlja kernela i prisutne u kernelu. Izvori modula nikad ne treba uključivati ​​obične datoteke zaglavlja iz biblioteka korisničkog prostora. U modulima kernela možete koristiti samo funkcije koje su zapravo dio kernela.

Cijelo sučelje kernela opisano je u datotekama zaglavlja koje se nalaze u direktorijima include/linux I include/asm unutar izvora kernela (obično se nalaze u /usr/src/linux-x.y.z(x.y.z je vaša verzija kernela)). Starije distribucije (na osnovu libc verzija 5 ili manje) koriste simboličke veze /usr/include/linux I /usr/include/asm u odgovarajuće direktorije u izvorima kernela. Ove simboličke veze omogućavaju, ako je potrebno, korištenje kernel sučelja u korisničkim aplikacijama.

Iako je interfejs biblioteka korisničkog prostora sada odvojen od interfejsa kernela, ponekad korisnički procesi moraju da koriste interfejse kernela. Međutim, mnoge reference u datotekama zaglavlja kernela odnose se samo na sam kernel i ne bi trebale biti dostupne korisničkim aplikacijama. Stoga su ove reklame zaštićene #ifdef __KERNEL__ blokova. Zbog toga se vaš drajver, kao i drugi kod kernela, mora kompajlirati sa deklarisanim makroom __KERNEL__.

Uloga pojedinačnih datoteka zaglavlja kernela će se raspravljati na odgovarajući način u cijeloj knjizi.

Programeri koji rade na velikim softverskim projektima (kao što je kernel) trebaju biti svjesni i izbjegavati ih "zagađenje prostora imena". Ovaj problem se javlja kada postoji veliki broj funkcija i globalnih varijabli čija imena nisu dovoljno ekspresivna (razlikuju se). Programer koji se kasnije mora baviti takvim aplikacijama primoran je da provede mnogo više vremena pamteći "rezervisana" imena i smišljajući jedinstvena imena za nove elemente. Kolizije imena (dvosmislenosti) mogu stvoriti širok raspon problema, u rasponu od grešaka pri učitavanju modula do nestabilnog ili neobjašnjivog ponašanja programa koji se može pojaviti kod korisnika koji koriste kernel ugrađen u drugačiju konfiguraciju.

Programeri si ne mogu priuštiti takve greške pri pisanju kernela, jer će čak i najmanji modul biti povezan s cijelim kernelom. Najbolje rješenje za izbjegavanje sudara imena je da prvo deklarirate svoje programske objekte kao statički, i, drugo, korištenje jedinstvenog, unutar sistema, prefiksa za imenovanje globalnih objekata. Dodatno, kao programer modula, možete kontrolisati opseg objekata u vašem kodu, kao što je opisano kasnije u odeljku "Tabela veza kernela".

Većina (ali ne sve) verzije naredbe insmod izvezite sve objekte modula koji nisu deklarisani kao statički, po defaultu, tj. osim ako modul ne definira posebne upute za ovu svrhu. Stoga je sasvim razumno deklarirati objekte modula koje ne namjeravate izvoziti kao statički.

Korištenje jedinstvenog prefiksa za lokalne objekte unutar modula može biti dobra praksa jer olakšava otklanjanje grešaka. Dok testirate svoj drajver, možda ćete morati da izvezete dodatne objekte u kernel. Korišćenjem jedinstvenog prefiksa za označavanje imena, ne rizikujete da unesete kolizije u imenski prostor kernela. Prefiksi koji se koriste u kernelu su, po konvenciji, mala slova, i mi ćemo se držati te konvencije.

Još jedna značajna razlika između kernela i korisničkih procesa je mehanizam za rukovanje greškama. Kernel kontroliše izvršavanje korisničkog procesa, tako da greška u korisničkom procesu rezultira porukom koja je bezopasna za sistem: greška segmentacije. U isto vrijeme, debugger se uvijek može koristiti za praćenje grešaka u izvornom kodu korisničke aplikacije. Greške koje se javljaju u kernelu su fatalne - ako ne za cijeli sistem, onda barem za trenutni proces. U odeljku “Otklanjanje grešaka u sistemu” poglavlja 4, “Tehnike otklanjanja grešaka”, pogledaćemo načine praćenja grešaka u kernelu.

Korisnički prostor i prostor kernela

Modul radi u tzv prostor kernela, dok aplikacije rade u . Ovaj koncept je osnova teorije operativnih sistema.

Jedna od glavnih namjena operativnog sistema je da korisniku i korisničkim programima obezbijedi računarske resurse, od kojih većinu predstavljaju eksterni uređaji. Operativni sistem mora ne samo da omogući pristup resursima, već i da kontroliše njihovu alokaciju i upotrebu, sprečavajući kolizije i neovlašćeni pristup. Pored ovoga, operativni sistem može kreirati nezavisne operacije za programe i zaštititi od neovlaštenog pristupa resursima. Rješavanje ovog ne-trivijalnog problema moguće je samo ako procesor štiti sistemske programe od korisničkih aplikacija.

Gotovo svaki savremeni procesor je u stanju da obezbedi takvo razdvajanje implementacijom različitih nivoa privilegija za izvršni kod (potrebna su najmanje dva nivoa). Na primjer, procesori arhitekture I32 imaju četiri nivoa privilegija od 0 do 3. Štaviše, nivo 0 ima najviše privilegije. Za takve procesore postoji klasa privilegovanih instrukcija koje se mogu izvršiti samo na privilegovanim nivoima. Unix sistemi koriste dva nivoa privilegija procesora. Ako procesor ima više od dva nivoa privilegija, koriste se najniži i najviši. Unix kernel radi dalje najviši nivo privilegije, osiguravajući kontrolu nad opremom i procesima korisnika.

Kada pričamo o prostor kernela I prostor za proces korisnika To ne znači samo različite nivoe privilegija za izvršni kod, već i različite adresne prostore.

Unix prenosi izvršenje iz korisničkog procesa u prostor kernela u dva slučaja. Prvo, kada korisnička aplikacija upućuje poziv kernelu (sistemski poziv), i drugo, dok servisira hardverske prekide. Kôd kernela koji se izvršava tokom sistemskog poziva izvodi se u kontekstu procesa, tj. radeći u ime pozivajućeg procesa, ima pristup podacima adresnog prostora procesa. S druge strane, kod koji se izvršava prilikom servisiranja hardverskog prekida je asinhroni u odnosu na proces i ne pripada nijednom posebnom procesu.

Svrha modula je proširenje funkcionalnosti kernela. Kod modula se izvršava u prostoru kernela. Tipično, modul obavlja oba prethodno navedena zadatka: neke funkcije modula se izvršavaju kao dio sistemskih poziva, a neke su odgovorne za upravljanje prekidima.

Paralelizacija u kernelu

Prilikom programiranja drajvera uređaja, za razliku od programiranja aplikacija, pitanje paralelizacije izvršnog koda je posebno akutno. Tipično, aplikacija radi uzastopno od početka do kraja bez brige o promjenama u svom okruženju. Kôd kernela mora raditi uz razumijevanje da mu se može pristupiti više puta u isto vrijeme.

Postoji mnogo razloga za paraleliziranje kernel koda. Linux obično ima mnogo pokrenutih procesa, a neki od njih mogu pokušati pristupiti kodu vašeg modula u isto vrijeme. Mnogi uređaji mogu uzrokovati hardverske prekide na procesoru. Obrađivači prekida se pozivaju asinhrono i mogu se pozvati dok vaš drajver izvršava drugi zahtjev. Neke softverske apstrakcije (kao što su tajmeri kernela, objašnjeni u poglavlju 6, „Tok vremena“) takođe rade asinhrono. Osim toga, Linux se može pokrenuti na sistemu sa simetričnim višeprocesorima (SMP), što znači da vaš kod drajvera može raditi paralelno na više procesora u isto vrijeme.

Iz ovih razloga, Linux kernel kod, uključujući kod drajvera, mora biti reentent, tj. mora biti u stanju raditi s više od jednog konteksta podataka u isto vrijeme. Strukture podataka moraju biti dizajnirane tako da prilagode paralelno izvršavanje više niti. Zauzvrat, kod kernela mora biti u stanju da rukuje više paralelnih tokova podataka bez njihovog oštećenja. Pisanje koda koji se može izvršavati paralelno i izbjegavanje situacija u kojima bi drugačija sekvenca izvršavanja dovela do nepoželjnog ponašanja sistema zahtijeva puno vremena, a možda i mnogo trikova. Svaki primjer drajvera u ovoj knjizi napisan je imajući na umu paralelno izvršavanje. Ako je potrebno, objasnit ćemo specifičnosti tehnike pisanja takvog koda.

Većina opšta greška Problem koji programeri prave je to što pretpostavljaju da istovremenost nije problem jer neki segmenti koda ne mogu ići u stanje mirovanja. Zaista, jezgro Linuxa nije stranicano, sa važnim izuzetkom rukovalaca prekida, koji ne mogu dobiti CPU dok se izvršava kritični kod kernela. U posljednje vrijeme, nemogućnost stranica je dovoljna da spriječi neželjenu paralelizaciju u većini slučajeva. Na SMP sistemima, međutim, nije potrebno preuzimanje koda zbog paralelnog računanja.

Ako vaš kod pretpostavlja da se neće učitati, onda neće raditi ispravno na SMP sistemima. Čak i ako nemate takav sistem, možda ga ima neko drugi koji koristi vaš kod. Takođe je moguće da će kernel u budućnosti koristiti mogućnost stranica, tako da će čak i jednoprocesorski sistemi morati da se nose sa konkurentnošću. Već postoje opcije za implementaciju takvih kernela. Dakle, razborit programer će napisati kod kernela uz pretpostavku da će on raditi na sistemu koji pokreće SMP.

Bilješka prevodilac: Izvinite, ali zadnja dva pasusa mi nisu jasna. Ovo može biti rezultat pogrešnog prijevoda. Stoga predstavljam originalni tekst.

Česta greška koju prave programeri drajvera je pretpostavka da istovremenost nije problem sve dok određeni segment koda
nije idite na spavanje (ili "blokirajte"). Istina je da jezgro Linuxa nije preventivno; sa važnim izuzetkom
servisiranje prekida, neće oduzeti procesor od koda kernela koji ne popušta svojevoljno. U prošlim vremenima, ovo nije preventivno
ponašanje je bilo dovoljno da spriječi neželjenu istovremenost većinu vremena. Na SMP sistemima, međutim, nije potrebno prvenstvo za izazivanje
istovremeno izvršenje.

Ako vaš kod pretpostavlja da neće biti preuzet, neće ispravno raditi na SMP sistemima. Čak i ako nemate takav sistem,
drugi koji pokreću vaš kod mogu ga imati. U budućnosti je također moguće da će kernel prijeći u preventivni način rada,
u kom trenutku će čak i jednoprocesorski sistemi morati da se bave konkurentnošću svuda (neke varijante kernela već implementiraju
to).

Informacije o trenutnom procesu

Iako se kod modula kernela ne izvršava sekvencijalno kao aplikacije, većina poziva kernelu se izvršava u odnosu na proces koji ga poziva. Kôd kernela može identificirati proces koji ga je pozvao pristupanjem globalnom pokazivaču koji ukazuje na strukturu struct task_struct, definisan za kernele verziju 2.4, u datoteci uključeno u . Pointer struja označava trenutno pokrenuti korisnički proces. Prilikom izvršavanja sistemskih poziva kao što je otvori() ili zatvori(), mora postojati proces koji ih je uzrokovao. Kôd kernela može, ako je potrebno, pozvati specifične informacije o procesu poziva putem pokazivača struja. Za primjere korištenja ovog pokazivača, pogledajte odjeljak “Kontrola pristupa datotekama uređaja” u poglavlju 5, “Poboljšane operacije drajvera char”.

Danas, indeks struja nije više globalna varijabla, kao u ranijim verzijama kernela. Programeri su optimizirali pristup strukturi koja opisuje trenutni proces premještanjem na stranicu steka. Možete pogledati trenutne detalje implementacije u datoteci . Kod koji vidite možda vam se ne čini jednostavnim. Imajte na umu da je Linux sistem usmjeren na SMP, a globalna varijabla jednostavno neće raditi kada imate posla sa više CPU-a. Detalji implementacije ostaju skriveni za druge podsisteme kernela, a drajver uređaja može pristupiti pokazivaču struja samo preko interfejsa .

Sa stanovišta modula, struja izgleda kao eksterna veza printk(). Modul se može koristiti struja gde god je potrebno. Na primjer, sljedeći dio koda ispisuje ID procesa (PID) i ime naredbe procesa koji je pozvao modul, dobijajući ih kroz odgovarajuća polja strukture struct task_struct:

Printk("Proces je \"%s\" (pid %i)\n", current->comm, current->pid);

Current->comm polje je ime komandne datoteke koja je pokrenula trenutni proces.

Kompajliranje i učitavanje modula

Ostatak ovog poglavlja posvećen je pisanju kompletnog, iako netipičnog, modula. One. Modul ne pripada nijednoj od klasa opisanih u odjeljku “Klase uređaja i modula” u poglavlju 1, “Uvod u upravljačke programe uređaja”. Primjer drajvera prikazan u ovom poglavlju će se zvati skull (Jednostavni program kernela za učitavanje lokaliteta). Modul scull možete koristiti kao predložak za pisanje vlastitog lokalnog koda.

Koristimo koncept “lokalnog koda” (lokalnog) da naglasimo promjene vašeg ličnog koda, u dobroj staroj Unix tradiciji (/usr/local).

Međutim, prije nego što popunimo funkcije init_module() i cleanup_module(), napisat ćemo Makefile skriptu koju će make koristiti za izgradnju objektnog koda modula.

Prije nego što pretprocesor može obraditi uključivanje bilo koje datoteke zaglavlja, __KERNEL__ makro simbol mora biti definiran s #define direktivom. Kao što je ranije spomenuto, kontekst specifičan za kernel može biti definiran u datotekama interfejsa kernela, vidljiv samo ako je simbol __KERNEL__ unaprijed definiran u prethodnoj obradi.

Drugi važan simbol definiran direktivom #define je simbol MODULE. Mora biti definirano prije omogućavanja interfejsa (isključujući one drajvere koji će biti kompajlirani sa kernelom). Drajveri sastavljeni u kernel neće biti opisani u ovoj knjizi, tako da će simbol MODULE biti prisutan u svim našim primjerima.

Ako gradite modul za sistem sa SMP-om, također morate definirati __SMP__ makro simbol prije nego što omogućite sučelje kernela. U verziji kernela 2.2, posebna stavka u konfiguraciji kernela uvela je izbor između jednoprocesorskog i višeprocesorskog sistema. Stoga će uključivanje sljedećih redova kao prvih redova vašeg modula rezultirati podrškom za više procesora.

#include #ifdef CONFIG_SMP # definirajte __SMP__ #endif

Programeri modula bi također trebali definirati -O optimizacijsku zastavicu za kompajler jer su mnoge funkcije deklarirane inline u datotekama zaglavlja kernela. Gcc kompajler ne izvodi inline proširenje funkcija osim ako nije omogućena optimizacija. Omogućavanje proširenja inline zamjena pomoću opcija -g i -O omogućit će vam kasnije otklanjanje grešaka koda koji koristi inline funkcije u debugeru. Pošto kernel u velikoj meri koristi inline funkcije, veoma je važno da se one pravilno prošire.

Imajte na umu, međutim, da je korištenje bilo koje optimizacije iznad -O2 razine rizično jer kompajler može proširiti funkcije koje nisu deklarirane inline. To može dovesti do problema jer... Neki kod funkcije očekuje da će pronaći standardni stog svog poziva. Umetnuta ekstenzija se podrazumijeva kao umetanje koda funkcije u točku njenog poziva umjesto odgovarajuće instrukcije za poziv funkcije. Shodno tome, u ovom slučaju, pošto ne postoji poziv funkcije, onda nema ni steka njenog poziva.

Možda ćete morati osigurati da za kompajliranje modula koristite isti kompajler koji je korišten za izgradnju kernela u koji će modul biti instaliran. Za detalje pogledajte originalni dokument iz datoteke Dokumentacija/Promjene nalazi se u izvornom direktoriju kernela. Razvoj kernela i kompajlera se obično sinhronizuje između razvojnih timova. Mogu postojati slučajevi kada ažuriranje jednog od ovih elemenata otkrije greške u drugom. Neki proizvođači distribucije isporučuju ultra-nove verzije kompajlera koje ne odgovaraju kernelu koji koriste. U ovom slučaju, oni obično pružaju poseban paket (često se naziva kgcc) sa kompajlerom posebno dizajniranim za
kompilacija kernela.

Konačno, kako biste spriječili neugodne greške, predlažemo da koristite opciju kompajliranja -Zid(sva upozorenja - sva upozorenja). Da biste zadovoljili sva ova upozorenja, možda ćete morati promijeniti svoj uobičajeni stil programiranja. Prilikom pisanja koda kernela, poželjno je koristiti stil kodiranja koji je predložio Linus Torvalds. Da, dokument Dokumentacija/Stil kodiranja, iz izvornog direktorija kernela, prilično je zanimljiv i preporučuje se svima onima koji su zainteresirani za programiranje na nivou kernela.

Preporučljivo je postaviti skup zastavica za kompilaciju modula, sa kojima smo se nedavno upoznali, u varijablu CFLAGS vaš Makefile. Za uslužni program make, ovo je posebna varijabla, čija će upotreba postati jasna iz sljedećeg opisa.

Pored zastavica u varijabli CFLAGS, možda će vam trebati cilj u vašem Makefileu koji kombinuje različite objektne datoteke. Takav cilj je neophodan samo kada je kod modula podijeljen u nekoliko izvornih datoteka, što općenito nije neuobičajeno. Objektni fajlovi se kombinuju sa komandom ld -r, što nije operacija povezivanja u općeprihvaćenom smislu, uprkos upotrebi povezivača ( ld). Rezultat izvršenja naredbe ld -r je još jedan objektni fajl koji kombinuje objektne kodove ulaznih datoteka linkera. Opcija -r znači " relocatable - premještanje“, tj. Premeštamo izlaznu datoteku naredbe u adresni prostor, jer još ne sadrži apsolutne adrese poziva funkcija.

Sljedeći primjer pokazuje minimalni Makefile potreban za kompajliranje modula koji se sastoji od dva izvorna fajla. Ako se vaš modul sastoji od jedne izvorne datoteke, onda iz datog primjera trebate ukloniti cilj koji sadrži naredbu ld -r.

# Put do vašeg izvornog direktorija kernela se može promijeniti ovdje, # ili ga možete proslijediti kao parametar kada pozivate “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 sve: skull.o skull.o: skull_init.o skull_clean.o $(LD) -r $^ -o $@ clean : rm -f * .o *~jezgro

Ako ste novi u tome kako make radi, možda ćete biti iznenađeni da ne postoje pravila za kompajliranje *.c datoteka u *.o objektne datoteke. Definisanje takvih pravila nije potrebno, jer uslužni program make, ako je potrebno, sam pretvara *.c datoteke u *.o datoteke koristeći zadani kompajler ili kompajler specificiran promjenljivom $(CC). U ovom slučaju, sadržaj varijable $(CFLAGS) koristi se za određivanje zastavica kompilacije.

Sljedeći korak nakon izgradnje modula je njegovo učitavanje u kernel. Već smo rekli da ćemo za ovo koristiti insmod uslužni program, koji povezuje sve nedefinirane simbole (pozive funkcija, itd.) modula sa tablicom simbola pokrenutog kernela. Međutim, za razliku od povezivača (na primjer, kao što je ld), on ne mijenja datoteku diska modula, već učitava objekt modula povezan s kernelom u RAM. Uslužni program insmod može prihvatiti neke opcije komandne linije. Detalje možete pogledati putem man insmod. Koristeći ove opcije možete, na primjer, dodijeliti određene cjelobrojne i string varijable u vašem modulu određenim vrijednostima prije povezivanja modula u kernel. Dakle, ako je modul ispravno dizajniran, može se konfigurirati prilikom pokretanja. Ova metoda konfiguriranja modula daje korisniku veću fleksibilnost od konfiguracije u vrijeme kompajliranja. Konfiguracija vremena pokretanja je objašnjena u odjeljku “Ručna i automatska konfiguracija” kasnije u ovom poglavlju.

Neke čitatelje će zanimati detalji o tome kako insmod uslužni program radi. Implementacija insmod-a se zasniva na nekoliko sistemskih poziva definisanih u kernel/module.c. Funkcija sys_create_module() dodjeljuje potrebnu količinu memorije u adresnom prostoru kernela za učitavanje modula. Ova memorija se dodeljuje pomoću funkcije vmalloc() (pogledajte odeljak „vmalloc i prijatelji“ u poglavlju 7, „Dobivanje memorije“). Sistemski poziv get_kernel_sysms() vraća tablicu simbola kernela, koja će se koristiti za određivanje stvarnih adresa objekata prilikom povezivanja. Funkcija sys_init_module() kopira objektni kod modula u adresni prostor kernela i poziva funkciju inicijalizacije modula.

Ako pogledate izvore koda kernela, naći ćete nazive sistemskih poziva koji počinju s sys_ prefiksom. Ovaj prefiks se koristi samo za sistemske pozive. Nijedna druga funkcija ga ne bi trebala koristiti. Imajte ovo na umu kada obrađujete izvore koda kernela pomoću uslužnog programa za pretraživanje grep.

Zavisnosti od verzije

Ako ne znate ništa više od onoga što je ovdje pokriveno, onda će najvjerovatnije moduli koje kreirate morati ponovo da se kompajliraju za svaku verziju kernela na koju su povezani. Svaki modul mora definirati simbol tzv __module_kernel_version, čija vrijednost
se upoređuje sa verzijom trenutnog kernela pomoću uslužnog programa insmod. Ovaj simbol se nalazi u odjeljku .modinfo ELF (Executable and Linking Format) datoteke. Ovo je detaljnije objašnjeno u poglavlju 11 “kmod i napredna modularizacija”. Imajte na umu da je ova metoda kontrole verzija primjenjiva samo na verzije kernela 2.2 i 2.4. U kernelu 2.0 to se radi na malo drugačiji način.

Kompajler će definirati ovaj simbol gdje god je uključena datoteka zaglavlja . Stoga, u prethodnom primjeru hello.c, nismo opisali ovaj simbol. To također znači da ako se vaš modul sastoji od mnogo izvornih datoteka, morate uključiti datoteku u vaš kod samo jednom. Izuzetak je slučaj kada se koristi definicija __NO_VERSION__, koju ćemo kasnije upoznati.

Ispod je definicija opisanog simbola iz datoteke module.h ekstrahovane iz koda kernela 2.4.25.

Static const char __module_kernel_versio/PRE__attribute__((section(".modinfo"))) = "kernel_version=" UTS_RELEASE;

Ako se modul ne uspije učitati zbog nepodudaranja verzije, možete pokušati učitati ovaj modul prosljeđivanjem insmod ključa u parametarsku liniju uslužnog programa -f(sila). Ova metoda učitavanja modula nije sigurna i nije uvijek uspješna. Prilično je teško objasniti razloge mogućih neuspjeha. Moguće je da se modul neće učitati jer se simboli ne mogu riješiti tokom povezivanja. U tom slučaju dobit ćete odgovarajuću poruku o grešci. Razlozi neuspjeha također mogu ležati u promjenama u radu ili strukturi kernela. U ovom slučaju, učitavanje modula može dovesti do ozbiljnih grešaka u radu, kao i do sistemske panike. Ovo poslednje bi trebalo da posluži kao dobar podsticaj za korišćenje sistema kontrole verzija. Nepodudaranja verzija mogu se elegantnije rješavati korištenjem kontrole verzija u kernelu. O tome ćemo detaljno govoriti u odjeljku “Kontrola verzija u modulima” u poglavlju 11 “kmod i napredna modularizacija”.

Ako želite kompajlirati svoj modul za određenu verziju kernela, morate uključiti datoteke zaglavlja za tu određenu verziju kernela. U gore opisanom primjeru Makefile, varijabla je korištena za određivanje direktorija za ove datoteke KERNELDIR. Takva prilagođena kompilacija nije neuobičajena kada su izvori kernela dostupni. Također, nije neuobičajeno da postoje različite verzije kernela u stablu direktorija. Svi primjeri modula u ovoj knjizi koriste varijablu KERNELDIR da naznači lokaciju izvornog direktorija za verziju kernela u koju bi sklopljeni modul trebao biti povezan. Možete koristiti sistemsku varijablu da odredite ovaj direktorij, ili možete proći njegovu lokaciju kroz opcije komandne linije da biste napravili.

Prilikom učitavanja modula, uslužni program insmod koristi vlastite staze za pretraživanje za objektne datoteke modula, gledajući kroz direktorije ovisne o verziji počevši od /lib/modules. I iako su starije verzije uslužnog programa uključivale trenutni direktorij u stazu pretraživanja, ovo ponašanje se sada smatra neprihvatljivim iz sigurnosnih razloga (isti problemi kao i korištenje sistemske varijable PUT). Dakle, ako želite da učitate modul iz trenutnog direktorijuma, možete ga navesti u stilu ./module.o. Ova indikacija položaja modula će raditi za bilo koju verziju insmod uslužnog programa.

Ponekad možete naići na interfejse kernela koji se razlikuju između 2.0.x i 2.4.x. U ovom slučaju, morat ćete pribjeći makrou koji određuje trenutnu verziju kernela. Ovaj makro se nalazi u datoteci zaglavlja . Naznačićemo slučajeve razlika u interfejsima kada ih koristite. To se može učiniti ili odmah uz opis, ili na kraju odjeljka, u posebnom dijelu posvećenom ovisnostima verzija. U nekim slučajevima, postavljanje detalja u poseban odjeljak će vam omogućiti da izbjegnete kompliciranje opisa kernela verzije 2.4.x koji je relevantan za ovu knjigu.

U datoteci zaglavlja linux/version.h Definirani su sljedeći makroi koji se odnose na određivanje verzije kernela.

UTS_RELEASE Makro koji se proširuje u niz koji opisuje trenutnu verziju kernela
izvorno stablo. Na primjer, makro se može proširiti na nešto poput ovoga:
linija: "2.3.48" . LINUX_VERSION_CODE Ovaj makro se proširuje u binarni prikaz verzije kernela, by
jedan bajt za svaki dio broja. Na primjer, binarni
reprezentacija za verziju 2.3.48 će biti 131888 (decimalna
reprezentacija za hex 0x020330). Moguće binarni
Reprezentacija će vam biti zgodnija od string reprezentacije. Obratite pažnju šta je
reprezentacija vam omogućava da opišete ne više od 256 opcija u svakoj
delovi broja. KERNEL_VERSION(glavni, mali, izdanje) Ova makro definicija vam omogućava da napravite “kernel_version_code”
od pojedinačnih elemenata koji čine verziju kernela. Na primjer,
sljedeći makro KERNEL_VERSION(2, 3, 48)
će se proširiti na 131888. Ova makro definicija je vrlo zgodna kada
poredeći trenutnu verziju kernela sa potrebnom. Bićemo opetovano
koristite ovu makro definiciju u cijeloj knjizi.

Evo sadržaja fajla: linux/version.h za kernel 2.4.25 (tekst zaglavlja je dat u potpunosti).

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

Datoteka zaglavlja version.h uključena je u datoteku module.h, tako da općenito ne morate eksplicitno uključiti version.h u kod modula. S druge strane, možete spriječiti da datoteka zaglavlja version.h bude uključena u module.h tako što ćete deklarirati makro __NO_VERSION__. Vi ćete koristiti __NO_VERSION__, na primjer u slučaju kada trebate omogućiti u nekoliko izvornih fajlova, koji će naknadno biti povezani u jedan modul. Najava __NO_VERSION__ prije uključivanja datoteke zaglavlja module.h sprječava
automatski opis niza __module_kernel_version ili njegov ekvivalent u izvornim datotekama. Ovo će vam možda trebati da zadovoljite pritužbe povezivača kada ld -r, kome se neće dopasti višestruki opisi simbola u tabelama veza. Tipično, ako je kod modula podijeljen na više izvornih datoteka, uključujući datoteku zaglavlja , zatim najava __NO_VERSION__ se radi u svim ovim datotekama osim u jednom. Na kraju knjige nalazi se primjer modula koji koristi __NO_VERSION__.

Većina zavisnosti verzije kernela može se rukovati upotrebom logike izgrađene na direktivama preprocesora koristeći makro definicije KERNEL_VERSION I LINUX_VERSION_CODE. Međutim, provjeravanje ovisnosti verzije može uvelike zakomplicirati čitljivost koda modula zbog heterogenih direktiva #ifdef. Stoga je možda najbolje rješenje postaviti provjeru zavisnosti u zasebnu datoteku zaglavlja. Zbog toga naš primjer uključuje datoteku zaglavlja sysdep.h, koristi se za smještaj svih makro definicija povezanih s provjerama ovisnosti o verziji.

Prva zavisnost verzije koju želimo da predstavimo je u ciljnoj deklaraciji" napravi instalaciju" naša skripta za kompilaciju drajvera. Kao što možete očekivati, instalacijski direktorij, koji se mijenja u skladu s verzijom kernela, odabran je na osnovu pregleda datoteke version.h. Evo isječka koda iz datoteke Pravila.make, koji koriste svi Makefiles kernela.

VERSIONFILE = $(INCLUDEDIR)/linux/version.h VREION = $(ljuska awk -F\" "/REL/ (ispis $$2)" $(VERSIONFILE)) INSTALLDIR = /lib/modules/$(VERSION)/misc

Imajte na umu da koristimo direktorij misc da instaliramo sve naše drajvere (deklaracija INSTALLDIR u primjeru Makefile iznad). Počevši od verzije kernela 2.4, ovaj direktorij je preporučeni direktorij za postavljanje prilagođenih drajvera. Dodatno, i stara i nova verzija paketa modutils sadrže misc direktorij u svojim stazama pretraživanja.

Koristeći gornju definiciju INSTALLDIR, cilj instalacije u Makefile-u može izgledati ovako:

Instaliraj: install -d $(INSTALLDIR) instalacija -c $(OBJS) $(INSTALLDIR)

Zavisnost od platforme

Svaka računarska platforma ima svoje karakteristike koje programeri kernela moraju uzeti u obzir da bi postigli najveće performanse.

Programeri kernela imaju mnogo više slobode u izboru i donošenju odluka od programera aplikacija. Upravo ta sloboda vam omogućava da optimizirate svoj kod, izvlačeći maksimum iz svake specifične platforme.

Kod modula se mora kompajlirati koristeći iste opcije kompajlera koje su korištene za kompajliranje kernela. Ovo se odnosi na oboje koji koriste iste obrasce korištenja registra procesora i izvode isti nivo optimizacije. File Pravila.make, koji se nalazi u korijenu izvornog stabla kernela, uključuje definicije specifične za platformu koje moraju biti uključene u sve Makefile kompilacije. Sve skripte za kompilaciju specifične za platformu nazivaju se Makefiles. platforma i sadrže vrijednosti varijabli za uslužni program make prema trenutnoj konfiguraciji kernela.

Još jedna zanimljiva karakteristika Makefile-a je njegova podrška za višeplatformsku ili jednostavno unakrsnu kompilaciju. Ovaj izraz se koristi kada trebate kompajlirati kod za drugu platformu. Na primjer, koristeći i86 platformu, kreirat ćete kod za M68000 platformu. Ako ćete unakrsno kompajlirati, onda ćete morati zamijeniti svoje alate za kompilaciju ( gcc, ld itd.) sa drugim skupom odgovarajućih alata
(Na primjer, m68k-linux-gcc, m68k-linux-ld). Prefiks koji se koristi može biti specificiran ili pomoću varijable $(CROSS_COMPILE) Makefile, opcijom komandne linije za pomoćni program make ili varijablom sistemskog okruženja.

SPARC arhitektura je poseban slučaj s kojim se mora postupati u skladu s tim u Makefileu. Korisnički programi koji rade na platformi SPARC64 (SPARC V9) su binarni programi, obično dizajnirani za platformu SPARC32 (SPARC V8). Stoga, default kompajler na SPARC64 platformi (gcc) generira objektni kod za SPARC32. S druge strane, kernel dizajniran da radi na SPARC V9 mora sadržavati objektni kod za SPARC V9, pa je čak i tada potreban unakrsni kompajler. Sve GNU/Linux distribucije dizajnirane za SPARC64 uključuju odgovarajući unakrsni kompajler, koji mora biti odabran u Makefile-u za skriptu za kompilaciju kernela.

I iako je potpuna lista ovisnosti o verziji i platformi malo složenija nego što je ovdje opisano, sasvim je dovoljno za unakrsnu kompilaciju. Za više informacija, možete pogledati skripte za kompilaciju Makefile i izvorne datoteke kernela.

Karakteristike kernela 2.6

Vrijeme ne miruje. I sada smo svjedoci pojave nove generacije kernela 2.6. Nažalost, original ove knjige ne pokriva novo jezgro, pa će prevodilac uzeti slobodu da dopuni prevod novim saznanjima.

Možete koristiti integrirana razvojna okruženja kao što je TimeSysov TimeStorm, koji će ispravno generirati kostur i skriptu za kompilaciju za vaš modul ovisno o potrebnoj verziji kernela. Ako ćete sve ovo sami pisati, onda će vam trebati neke dodatne informacije o glavnim razlikama koje uvodi novi kernel.

Jedna od karakteristika kernela 2.6 je potreba za korištenjem makronaredbi module_init() i module_exit() za eksplicitnu registraciju imena inicijalizacijskih i završnih funkcija.

MODULE_LISENCE() makro, uveden u kernelu 2.4, i dalje je potreban ako ne želite da vidite odgovarajuća upozorenja prilikom učitavanja modula. Možete odabrati sljedeće licencne nizove koji će se prenijeti na makro: “GPL”, “GPL v2”, “GPL i dodatna prava”, “Dual BSD/GPL” (izbor između BSD ili GPL licenci), “Dual MPL/GPL " (izbor između Mozilla ili GPL licenci) i
"Vlasnički".

Značajnije za novo jezgro je nova šema kompilacije modula, koja podrazumijeva ne samo promjene u kodu samog modula već i u Makefile skripti za njegovu kompilaciju.

Dakle, definicija makro simbola MODULE više nije potrebna ni u kodu modula ni u Makefileu. Ako je potrebno, nova shema kompilacije sama će odrediti ovaj makrosimbol. Također, nećete morati eksplicitno definirati makrosimbole __KERNEL__, ili novije poput KBUILD_BASENAME i KBUILD_MODNAME.

Također, ne biste trebali specificirati nivo optimizacije pri kompilaciji (-O2 ili drugi), jer vaš modul će biti preveden s cijelim skupom zastavica, uključujući zastavice za optimizaciju, s kojima su kompajlirani svi ostali moduli vašeg kernela - uslužni program make automatski koristi cijeli potreban skup zastavica.

Iz ovih razloga, Makefile za kompajliranje modula za kernel 2.6 je mnogo jednostavniji. Dakle, za hello.c modul Makefile će izgledati ovako:

Obj-m:= hello.o

Međutim, da biste kompajlirali modul, trebat će vam pristup pisanju izvornom stablu kernela, gdje će se kreirati privremene datoteke i direktoriji. Stoga bi naredba za kompajliranje modula za kernel 2.6, specificirana iz trenutnog direktorija koji sadrži izvorni kod modula, trebala izgledati ovako:

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

Dakle, imamo izvor modula hello-2.6.c, za kompilaciju u kernelu 2.6:

//hello-2.6.c #include #include #include MODULE_LICENSE("GPL"); static int __init my_init(void) ( printk("Hello world\n"); return 0; ); static void __exit my_cleanup(void) ( printk("Zbogom\n"); ); module_init(my_init); module_exit(my_cleanup);

U skladu s tim, imamo Makefile:

Obj-m:= hello-2.6.o

Pozivamo uslužni program make da obradi naš Makefile sa sljedećim parametrima:

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

Normalan proces kompilacije će proizvesti sljedeći standardni izlaz:

Make: Unesite direktorij `/usr/src/linux-2.6.3" *** Upozorenje: Nadjačavanje SUBDIRS-a na komandnoj liniji može uzrokovati *** nedosljednosti make: `arch/i386/kernel/asm-offsets.s" ne zahtijeva ažuriranje. CHK include/asm-i386/asm_offsets.h CC [M] /home/knz/j.kernel/3/hello-2.6.o Izgradnja modula, faza 2. /usr/src/linux-2.6.3/scripts/Makefile .modpost:17: *** Uh-oh, imate zastarele unose modula. Petljali ste sa SUBDIRS-om, /usr/src/linux-2.6.3/scripts/Makefile.modpost:18: nemojte se žaliti ako nešto krene po zlu. MODPOST CC /home/knz/j.kernel/3/hello-2.6.mod.o LD [M] /home/knz/j.kernel/3/hello-2.6.ko make: Izlaz iz direktorija `/usr/src / linux-2.6.3"

Konačni rezultat kompilacije će biti datoteka modula hello-2.6.ko koja se može instalirati u kernel.

Imajte na umu da u kernelu 2.6 datoteke modula imaju sufiks sa .ko umjesto .o kao u kernelu 2.4.

Tablica simbola kernela

Već smo govorili o tome kako insmod uslužni program koristi tabelu javnih simbola kernela kada povezuje modul sa kernelom. Ova tabela sadrži adrese globalnih objekata kernela - funkcije i varijable - koje su potrebne za implementaciju modularnih opcija drajvera. Javna tablica simbola kernela može se pročitati u tekstualnom obliku iz /proc/ksyms datoteke, pod uvjetom da vaše kernel podržava /proc sistem datoteka.

U kernelu 2.6, /proc/ksyms je preimenovan u /proc/modules.

Kada se modul učita, simboli koje je modul izvezao postaju dio tabele simbola kernela i možete ih vidjeti u /proc/ksyms.

Novi moduli mogu koristiti simbole koje izveze vaš modul. Na primjer, msdos modul se oslanja na znakove koje izvozi fat modul, a svaki USB uređaj koji se koristi u načinu čitanja koristi znakove iz usbcore i ulaznih modula. Ovaj odnos, ostvaren sekvencijalnim učitavanjem modula, naziva se stek modula.

Stack modula je zgodan za korištenje pri kreiranju složenih projekata modula. Ova apstrakcija je korisna za odvajanje koda drajvera uređaja na hardverski zavisne i hardverski nezavisne delove. Na primjer, set drajvera video-za-linux sastoji se od osnovnog modula koji izvozi simbole za drajver niskog nivoa koji uzima u obzir specifičnosti hardvera koji se koristi. Prema vašoj konfiguraciji, učitavate glavni video modul i modul specifičan za vaš hardver. Na isti način implementirana je podrška za paralelne portove i široku klasu povezanih uređaja, poput USB uređaja. Sistemski stog paralelnih portova prikazan je na Sl. 2-2. Strelice pokazuju interakciju između modula i programskog interfejsa kernela. Interakcija se može vršiti i na nivou funkcija i na nivou struktura podataka kojima upravljaju funkcije.

Slika 2-2. Paralelni port modula stog

Kada koristite stack module, zgodno je koristiti uslužni program modprobe. Funkcionalnost uslužnog programa modprobe je na mnogo načina slična uslužnom programu insmod, ali prilikom učitavanja modula, on provjerava njegove osnovne ovisnosti i, ako je potrebno, učitava potrebne module dok se potrebni stek modula ne popuni. Dakle, jedna naredba modprobe može rezultirati višestrukim pozivima insmod komandi. Moglo bi se reći da je komanda modprobe inteligentni omot oko insmoda. Modprobe umjesto insmod možete koristiti svuda, osim kada učitavate svoje module iz trenutnog direktorija, jer modprobe gleda samo određene direktorije modula i neće moći zadovoljiti moguće ovisnosti.

Podjela modula na dijelove pomaže u smanjenju vremena razvoja pojednostavljujući definiciju problema. Ovo je slično razdvajanju između implementacionog mehanizma i kontrolne politike, o čemu se govori u poglavlju 1, „Uvod u upravljačke programe uređaja“.

Obično modul implementira svoju funkcionalnost bez potrebe za izvozom simbola. Morat ćete izvesti simbole ako drugi moduli mogu imati koristi od toga. Možda ćete morati da uključite posebnu direktivu da sprečite izvoz nestatičkih znakova, jer Većina implementacija modutil-a sve ih izvozi po defaultu.

Datoteke zaglavlja jezgra Linuxa nude zgodan način za kontrolu vidljivosti vaših simbola, čime se sprječava zagađenje prostora imena tablice simbola kernela. Mehanizam opisan u ovom poglavlju radi u kernelima počevši od verzije 2.1.18. Kernel 2.0 imao je potpuno drugačiji kontrolni mehanizam
vidljivost simbola, što će biti opisano na kraju poglavlja.

Ako vaš modul uopće ne treba izvoziti simbole, možete eksplicitno postaviti sljedeći makro poziv u izvorni fajl modula:

EXPORT_NO_SYMBOLS;

Ovaj poziv makroa, definiran u datoteci linux/module.h, proširuje se u asembler direktivu i može se specificirati bilo gdje u modulu. Međutim, kada kreirate kod koji je prenosiv na različite kernele, potrebno je ovaj poziv makroa smjestiti u funkciju inicijalizacije modula (init_module), jer će verzija ovog makroa koju smo definirali u našoj sysdep.h datoteci za starije verzije kernela raditi samo ovdje.

S druge strane, ako trebate izvesti neke od simbola iz vašeg modula, onda morate koristiti makro simbol
EXPORT_SYMTAB. Ovaj makro simbol mora biti definiran prije uključivanjem datoteke zaglavlja module.h. To je uobičajena praksa
definiranje ovog makro karaktera putem zastavice -D u Makefile.

Ako je makro simbol EXPORT_SYMTAB definiran, tada se pojedinačni simboli mogu izvesti pomoću para makroa:

EXPORT_SYMBOL(ime); EXPORT_SYMBOL_NOVERS(ime);

Bilo koji od ova dva makroa će dati simbol učiniti dostupnim izvan modula. Razlika je u tome što je makro EXPORT_SYMBOL_NOVERS izvozi simbol bez informacija o verziji (pogledajte Poglavlje 11 “kmod i napredna modularizacija”). Za više detalja
provjerite datoteku zaglavlja , iako je navedeno sasvim dovoljno za praktičnu upotrebu
macros.

Inicijalizacija i dovršavanje modula

Kao što je spomenuto, funkcija init_module() registruje funkcionalne komponente modula sa kernelom. Nakon takve registracije, aplikacija koja koristi modul će imati pristup ulaznim tačkama modula preko interfejsa obezbeđenog od strane kernela.

Moduli mogu registrovati mnogo različitih komponenti, koje, kada su registrovane, predstavljaju nazive funkcija modula. Pokazivač na strukturu podataka koja sadrži pokazivače na funkcije koje implementiraju predloženu funkcionalnost prosljeđuje se funkciji registracije kernela.

U Poglavlju 1, „Uvod u upravljačke programe uređaja“, pomenuta je klasifikacija glavnih tipova uređaja. Možete registrovati ne samo tipove uređaja koji se tamo spominju, već i sve druge, čak i softverske apstrakcije, kao što su, na primjer, /proc fajlovi, itd. Sve što može raditi u kernelu preko interfejsa za programiranje drajvera može se registrovati kao drajver .

Ako želite da saznate više o tipovima drajvera koji se registruju koristeći vaše kernel kao primer, možete implementirati pretragu za podnizom EXPORT_SYMBOL u izvorima kernela i pronaći ulazne tačke koje nude različiti drajveri. Po pravilu, registracijske funkcije koriste prefiks u svom nazivu registar_,
tako da je još jedan mogući način da ih pronađete je traženje podniza registar_ u /proc/ksyms datoteci pomoću uslužnog programa grep. Kao što je već spomenuto, u kernelu 2.6.x datoteka /proc/ksyms je zamijenjena sa /proc/modules.

Rukovanje greškom u init_module

Ako dođe do bilo kakve greške prilikom inicijalizacije modula, morate poništiti inicijalizaciju koja je već završena prije zaustavljanja učitavanja modula. Do greške može doći, na primjer, zbog nedovoljne memorije u sistemu prilikom dodjele struktura podataka. Nažalost, to se može dogoditi i dobar kod bi trebao biti u stanju da se nosi sa takvim situacijama.

Sve što je bilo registrirano ili dodijeljeno prije nego što je došlo do greške u funkciji inicijalizacije init_module() mora biti poništeno ili oslobođeno, jer Linux kernel ne prati greške inicijalizacije i ne poništava posuđivanje i dodjelu resursa kodom modula. Ako niste vratili ili niste mogli vratiti završenu registraciju, kernel će ostati u nestabilnom stanju, a kada se modul ponovo učita
nećete moći ponoviti registraciju već registrovanih elemenata, niti ćete moći poništiti prethodno izvršenu registraciju, jer u novoj instanci funkcije init_module() nećete imati ispravnu vrijednost adresa registriranih funkcija. Vraćanje sistema u prethodno stanje će zahtijevati korištenje raznih složenih trikova, a to se često radi jednostavnim ponovnim pokretanjem sistema.

Implementaciju vraćanja prethodnog stanja sistema kada dođe do grešaka pri inicijalizaciji modula najbolje je implementirati pomoću goto operatora. Obično se ovaj operater tretira izuzetno negativno, pa čak i s mržnjom, ali u ovoj situaciji se ispostavlja da je vrlo koristan. Stoga se u kernelu naredba goto često koristi za rukovanje greškama inicijalizacije modula.

Sljedeći jednostavan kod, koristeći funkcije lažne registracije i odjave kao primjer, demonstrira ovaj način rukovanja greškama.

Int init_module(void) ( int err; /* registracija uzima pokazivač i ime */ err = register_this(ptr1, "skull"); if (err) ide na fail_this; err = register_that(ptr2, "skull"); ako (err) goto fail_that; err = register_those(ptr3, "lubanja"); if (err) goto fail_those; vratiti 0; /* uspjeh */ fail_those: unregister_that(ptr2, "skull"); fail_that: unregister_this(ptr1, " lubanja"); fail_this: vrati grešku; /* širi grešku */ )

Ovaj primjer pokušava registrirati tri komponente modula. Naredba goto se koristi kada dođe do greške pri registraciji i uzrokuje da se registrirane komponente odjave prije zaustavljanja učitavanja modula.

Još jedan primjer korištenja goto naredbe za lakše čitanje koda je trik “pamti” uspješne registracije modula i pozivanje cleanup_module() za prosljeđivanje ove informacije kada dođe do greške. Cleanup_module() funkcija je dizajnirana za vraćanje dovršenih operacija inicijalizacije i automatski se poziva kada se modul isprazni. Vrijednost koju vraća funkcija init_module() mora biti
predstavljaju kod greške inicijalizacije modula. U Linux kernelu, kod greške je negativan broj iz skupa definicija napravljenih u datoteci zaglavlja . Uključite ovu datoteku zaglavlja u svoj modul kako biste koristili simboličku mnemoniku za rezervirane kodove grešaka kao što su -ENODEV, -ENOMEM, itd. Upotreba takve mnemotehnike smatra se dobrim stilom programiranja. Međutim, treba napomenuti da neke verzije uslužnih programa iz paketa modutils ne obrađuju ispravno vraćene kodove grešaka i prikazuju poruku "Uređaj je zauzet"
kao odgovor na čitavu grupu grešaka potpuno drugačije prirode koje vraća funkcija init_modules(). U najnovijim verzijama paketa ovo
Dosadna greška je ispravljena.

Kôd funkcije cleanup_module() za gornji slučaj mogao bi, na primjer, biti ovakav:

Void cleanup_module(void) (unregister_those(ptr3, "skull"); unregister_that(ptr2, "skull"); unregister_this(ptr1, "skull"); return; )

Ako je vaš kod za inicijalizaciju i završetak složeniji nego što je ovdje opisano, tada korištenje goto naredbe može rezultirati teško čitljivim programskim tekstom jer se kod završetka mora ponoviti u funkciji init_module() koristeći više oznaka za prijelazne prijelaze. Iz tog razloga, pametniji trik je korištenje poziva funkcije cleanup_module() u funkciji init_module(), prenoseći informacije o opsegu uspješne inicijalizacije kada dođe do greške pri učitavanju modula.

Ispod je primjer kako napisati funkcije init_module() i cleanup_module(). Ovaj primjer koristi globalno definirane pokazivače koji nose informacije o opsegu uspješne inicijalizacije.

Strukturirajte nešto *item1; strukturirati nešto drugo *item2; int stuff_ok; void cleanup_module(void) ( if (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 (argumenti); item2 = allocate_thing2(arguments2); if (!item2 || !item2) ide na neuspjeh; err = register_stuff(item1, item2); if (!err) stuff_ok = 1; inače ide neuspješno; vraća 0; /* uspjeh */ neuspjeh: cleanup_module(); return err; )

Ovisno o složenosti inicijalizacijskih operacija vašeg modula, možda ćete htjeti koristiti jednu od ovdje navedenih metoda za kontrolu grešaka pri inicijalizaciji modula.

Brojač upotrebe modula

Sistem sadrži brojač upotrebe za svaki modul kako bi se utvrdilo da li se modul može bezbedno istovariti. Sistemu su potrebne ove informacije jer se modul ne može isprazniti ako ga neko ili nešto zauzima - ne možete ukloniti drajver sistema datoteka ako je taj sistem datoteka montiran, ili ne možete ukloniti modul karakternog uređaja ako neki proces koristi ovaj uređaj. inače,
ovo može dovesti do pada sistema - greške segmentacije ili panike kernela.

U modernim kernelima, sistem vam može pružiti automatski brojač korištenja modula koristeći mehanizam koji ćemo pogledati u sljedećem poglavlju. Bez obzira na verziju kernela, možete koristiti ručnu kontrolu ovog brojača. Dakle, kod koji bi se trebao koristiti u starijim verzijama kernela trebao bi koristiti model obračuna korištenja modula izgrađen na sljedeća tri makroa:

MOD_INC_USE_COUNT Povećava brojač upotrebe trenutnog modula MOD_DEC_USE_COUNT Smanjuje brojač upotrebe trenutnog modula MOD_IN_USE Vraća true ako je brojač upotrebe ovog modula nula

Ovi makroi su definisani u , i manipulišu posebnom internom strukturom podataka kojoj direktan pristup nije poželjan. Činjenica je da se interna struktura i način upravljanja ovim podacima može mijenjati od verzije do verzije, dok vanjski interfejs za korištenje ovih makroa ostaje nepromijenjen.

Imajte na umu da ne morate provjeravati MOD_IN_USE u kodu funkcije cleanup_module(), jer se ova provjera izvodi automatski prije nego se cleanup_module() pozove u sistemskom pozivu sys_delete_module(), koji je definiran u kernel/module.c.

Ispravno upravljanje brojačem upotrebe modula je kritično za stabilnost sistema. Zapamtite da kernel može odlučiti da automatski isprazni neiskorišteni modul u bilo kojem trenutku. Česta greška u programiranju modula je nepravilna kontrola ovog brojača. Na primjer, kao odgovor na određeni zahtjev, kod modula izvodi neke radnje i, kada se obrada završi, povećava brojač korištenja modula. One. takav programer pretpostavlja da je ovaj brojač namenjen za prikupljanje statistike korišćenja modula, dok je, u stvari, on, u stvari, brojač za trenutnu zauzetost modula, tj. prati broj procesa koji koriste kod modula u ovom trenutku. Stoga, kada obrađujete zahtjev za modul, morate pozvati MOD_INC_USE_COUNT prije izvođenja bilo kakvih radnji, i MOD_DEC_USE_COUNT nakon što su završeni.

Mogu postojati situacije u kojima, iz očiglednih razloga, nećete moći isprazniti modul ako izgubite kontrolu nad njegovim brojačem upotrebe. Ova situacija se često dešava u fazi razvoja modula. Na primjer, proces se može prekinuti dok pokušava dereferencirati NULL pokazivač, i nećete moći isprazniti takav modul dok ne vratite njegov brojač upotrebe na nulu. Jedno od mogućih rješenja ovog problema u fazi otklanjanja grešaka modula je potpuno napuštanje kontrole brojača korištenja modula redefiniranjem MOD_INC_USE_COUNT I MOD_DEC_USE_COUNT u prazan kod. Drugo rješenje je kreiranje ioctl() poziva koji prisiljava brojač korištenja modula na nulu. Ovo ćemo pokriti u odeljku “Korišćenje ioctl argumenta” u poglavlju 5, “Poboljšane operacije drajvera znakova”. Naravno, u drajveru spremnom za upotrebu, takve lažne manipulacije sa brojačem treba isključiti, međutim, u fazi otklanjanja grešaka, one štede vrijeme programera i sasvim su prihvatljive.

Pronaći ćete trenutni brojač upotrebe sistema za svaki modul u trećem polju svakog unosa u /proc/modules datoteci. Ova datoteka sadrži informacije o trenutno učitanim modulima - jedan red po modulu. Prvo polje reda sadrži naziv modula, drugo polje je veličina koju modul zauzima u memoriji, a treće polje je trenutna vrijednost brojača korištenja. Ove informacije, u formatiranom obliku,
može se dobiti pozivom uslužnog programa lsmod. Ispod je primjer datoteke /proc/modules:

Parport_pc 7604 1 (autoclean) lp 4800 0 (nekorišten) parport 8084 1 zaključan 33256 1 (autoclean) sunrpc 56612 1 (autoclean) ds 6252 1 i82365 22304 1 core 0pcs 80m2

Ovdje vidimo nekoliko modula učitanih u sistem. U polju zastavice (zadnje polje reda), gomila zavisnosti modula je prikazana u uglastim zagradama. Između ostalog, možete primijetiti da moduli paralelnog porta komuniciraju kroz stek modula, kao što je prikazano na Sl. 2-2. (autoclean) zastavica označava module koje kontroliše kmod ili kerneld. Ovo će biti pokriveno u poglavlju 11 “kmod i napredna modularizacija”). Oznaka (neiskorišteno) znači da modul trenutno nije u upotrebi. U kernelu 2.0, polje veličine je prikazivalo informacije ne u bajtovima, već u stranicama, što je za većinu platformi veličine 4 kB.

Istovar modula

Za istovar modula koristite uslužni program rmmod. Učitavanje modula je jednostavniji zadatak od njegovog učitavanja, što uključuje njegovo dinamičko povezivanje sa kernelom. Kada se modul isprazni, izvršava se sistemski poziv delete_module(), koji ili poziva funkciju cleanup_module() neučitanog modula ako je njegov broj korištenja nula, ili završava s greškom.

Kao što je već spomenuto, funkcija cleanup_module() vraća unatrag inicijalizacijske operacije izvršene prilikom učitavanja modula funkcijom cleanup_module(). Također, izvezeni simboli modula se automatski brišu.

Eksplicitno definiranje funkcija završetka i inicijalizacije

Kao što je već spomenuto, prilikom učitavanja modula kernel poziva funkciju init_module(), a kada se učitava poziva cleanup_module(). Međutim, u modernim verzijama kernela ove funkcije često imaju različita imena. Počevši od kernela 2.3.23, postalo je moguće eksplicitno definirati ime za funkciju učitavanja i izbacivanja modula. Danas je ovo eksplicitno imenovanje ovih funkcija preporučeni stil programiranja.

Dajemo primjer. Ako želite deklarirati funkciju my_init() kao funkciju inicijalizacije vašeg modula, a funkciju my_cleanup() kao završnu funkciju, umjesto init_module() i cleanup_module(), respektivno, tada ćete morati dodati sljedeće dvije makronaredbe u tekst modula (obično se ubacuju na kraju
izvorni fajl koda modula):

Modul_init(my_init); module_exit(my_cleanup);

Imajte na umu da ćete za korištenje ovih makroa morati uključiti datoteku zaglavlja u svoj modul .

Pogodnost korištenja ovog stila je da svaka funkcija inicijalizacije i završetka modula u kernelu može imati svoje jedinstveno ime, što uvelike pomaže u otklanjanju grešaka. Štaviše, upotreba ovih funkcija pojednostavljuje otklanjanje grešaka, bez obzira da li implementirate svoj kod drajvera kao modul ili ćete ga ugraditi direktno u kernel. Naravno, korištenje makronaredbi module_init i module_exit nije neophodno ako vaše funkcije inicijalizacije i završetka imaju rezervirana imena, tj. init_module() i cleanup_module() respektivno.

Ako pogledate 2.2 ili novije izvore kernela, možda ćete vidjeti malo drugačiji oblik opisa za funkcije inicijalizacije i završetka. Na primjer:

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

Upotreba atributa __u tomeće uzrokovati da se funkcija inicijalizacije isprazni iz memorije nakon što je inicijalizacija završena. Međutim, ovo radi samo za drajvere ugrađene u kernel i biće zanemareno za module. Takođe, za drajvere ugrađene u kernel, atribut __Izlazće uzrokovati da se cijela funkcija označena ovim atributom zanemari. Za module, ova zastavica će također biti zanemarena.

Korištenje atributa __u tome(I __initdata za opisivanje podataka) može smanjiti količinu memorije koju koristi kernel. Zastava __u tome Funkcija inicijalizacije modula neće donijeti ni korist ni štetu. Kontrola ovog tipa inicijalizacije još nije implementirana za module, iako bi to moglo biti moguće u budućnosti.

Rezimirajući

Dakle, kao rezultat prezentiranog materijala, možemo predstaviti sljedeću verziju modula “Zdravo svijet”:

Kod izvorne datoteke modula =============================================== = #include #include #include static int __init my_init_module (void) ( EXPORT_NO_SYMBOLS; printk("<1>Hello world\n"); return 0; ); static void __exit my_cleanup_module (void) ( printk("<1>Zbogom\n"); ); module_init(my_init_module); module_exit(my_cleanup_module); MODULE_LICENSE("GPL"); ========================= ===================== Makefile za kompajliranje modula ========================= =============== ==================== CFLAGS = -Zid -D__KERNEL__ -DMODULE -I/lib/moduli/ $(shell uname -r)/build/include hello.o: ==================================== ===========================

Imajte na umu da smo prilikom pisanja Makefile-a koristili konvenciju da GNU make uslužni program može nezavisno odrediti kako da generiše objektnu datoteku na osnovu varijable CFLAGS i kompajlera dostupnog na sistemu.

Upotreba resursa

Modul ne može završiti svoj zadatak bez korištenja sistemskih resursa kao što su memorija, I/O portovi, I/O memorija, prekidne linije i DMA kanali.

Kao programer, već biste trebali biti upoznati sa upravljanjem dinamičkom memorijom. Dinamičko upravljanje memorijom u kernelu nije bitno drugačije. Vaš program može dobiti memoriju pomoću funkcije kmalloc() i oslobodi je uz pomoć kfree(). Ove funkcije su vrlo slične funkcijama malloc() i free() koje su vam poznate, osim što se funkciji kmalloc() prosljeđuje dodatni argument - prioritet. Obično je prioritet GFP_KERNEL ili GFP_USER. GFP je akronim za „dobiti besplatnu stranicu“. Upravljanje dinamičkom memorijom u kernelu detaljno je obrađeno u Poglavlju 7, “Dobivanje memorije”.

Programer početnika može biti iznenađen potrebom da se eksplicitno dodijele I/O portovi, I/O memorija i prekidne linije. Tek tada modul kernela može lako pristupiti ovim resursima. Iako se sistemska memorija može dodijeliti bilo gdje, I/O memorija, portovi i prekidne linije igraju posebnu ulogu i dodjeljuju se drugačije. Na primjer, drajver treba da dodijeli određene portove, a ne
sve osim onih koje su mu potrebne za kontrolu uređaja. Ali vozač ne može koristiti ove resurse dok nije siguran da ih ne koristi neko drugi.

Područje memorije koju posjeduje periferni uređaj obično se naziva I/O memorija, kako bi se razlikovala od sistemske RAM-a (RAM), koja se jednostavno naziva memorija.

Portovi i I/O memorija

Rad tipičnog drajvera se uglavnom sastoji od portova za čitanje i pisanje i I/O memorije. Portovi i I/O memorija su ujedinjeni zajedničkim imenom - I/O region (ili oblast).

Nažalost, ne može svaka arhitektura sabirnice jasno definirati I/O regiju koja pripada svakom uređaju, i moguće je da će vozač morati pogoditi lokaciju regije kojoj pripada, ili čak pokušati izvršiti operacije čitanja/pisanja na mogućoj adresi prostori. Ovaj problem je posebno
odnosi se na ISA magistralu, koja se još uvijek koristi za instaliranje jednostavnih uređaja u personalni računar i vrlo je popularna u industrijskom svijetu u implementaciji PC/104 (pogledajte odjeljak „PC/104 i PC/104+“ u poglavlju 15 “Pregled perifernih autobusa”).

Koja god magistrala se koristi za povezivanje hardverskog uređaja, upravljačkom programu uređaja mora biti zagarantovan ekskluzivni pristup njegovom I/O regionu kako bi se spriječile kolizije između drajvera. Ako modul, pristupajući svom uređaju, upiše na uređaj koji mu ne pripada, to može dovesti do fatalnih posljedica.

Linux programeri implementirali su mehanizam za traženje/otpuštanje I/O regiona prvenstveno kako bi spriječili kolizije između različitih uređaja. Ovaj mehanizam se dugo koristi za I/O portove i nedavno je generaliziran na upravljanje resursima općenito. Imajte na umu da ovaj mehanizam predstavlja softversku apstrakciju i da se ne proteže na hardverske mogućnosti. Na primjer, neovlašteni pristup I/O portovima na razini hardvera ne uzrokuje nikakvu grešku sličnu „grešci segmentacije“, budući da hardver ne dodjeljuje i ne autorizira svoje resurse.

Informacije o registrovanim resursima dostupne su u tekstualnom obliku u datotekama /proc/ioports i /proc/iomem. Ove informacije su uvedene u Linux od kernela 2.3. Podsjećamo, ova knjiga se prvenstveno fokusira na 2.4 kernel, a napomene o kompatibilnosti će biti predstavljene na kraju poglavlja.

Luke

Slijedi tipičan sadržaj /proc/ioports datoteke:

0000-001f: dma1 0020-003f: pic1 0040-005f: tajmer 0060-006f: tastatura 0080-008f: dma stranica reg 00a0-00bf: pic2 00c0-00df: 00c0-00df0:f ide-07:f ide-01 1 01f0-01f7 : ide0 02f8-02ff: serijski(set) 0300-031f: NE2000 0376-0376: ide1 03c0-03df: vga+ 03f6-03f6: ide0 03f8-03ff: serijski(set) 10001 Intel 0370 1001 PI 470-1 -1003 : acpi 1004-1005: acpi 1008-100b: acpi 100c-100f: acpi 1100-110f: Intel Corporation 82371AB PIIX4 IDE 1300-131f: pcnet_cs 1400-100f: acpi 1100-110f: Intel Corporation 82371AB PIIX4 IDE 1300-131f: pcnet_cs 1400:0 IX Corporation 1400-1400-14 18ff: PCI CardBus #02 1c00- 1cff: PCI CardBus #04 5800-581f: Intel Corporation 82371AB PIIX4 USB d000-dfff: PCI sabirnica #01 d000-d0ff: ATI Technologies Inc 3D Rage LT Pro AGP-133

Svaki red ove datoteke prikazuje heksadecimalno raspon portova povezanih s upravljačkim programom ili vlasnikom uređaja. U ranijim verzijama kernela, datoteka je imala isti format, osim što hijerarhija portova nije bila prikazana.

Datoteka se može koristiti za izbjegavanje sudara portova prilikom dodavanja novog uređaja u sistem. Ovo je posebno zgodno kada se instalirana oprema ručno konfiguriše prebacivanjem kratkospojnika. U ovom slučaju, korisnik može lako pregledati listu korištenih portova i odabrati slobodan domet za uređaj koji će se instalirati. I iako većina modernih uređaja uopće ne koristi ručne skakače, oni se još uvijek koriste u proizvodnji manjih komponenti.

Ono što je još važnije je da /proc/ioports datoteka ima programski dostupnu strukturu podataka koja je povezana s njom. Stoga, kada se upravljački program uređaja inicijalizira, on može znati zauzeti opseg I/O portova. To znači da ako je potrebno skenirati portove u potrazi za novim uređajem, drajver može izbjeći situaciju pisanja na portove koje zauzimaju drugi uređaji.

Poznato je da je skeniranje ISA magistrale rizičan zadatak. Stoga neki drajveri distribuirani sa službenim Linux kernelom izbjegavaju takvo skeniranje prilikom učitavanja modula. Na taj način izbjegavaju rizik od oštećenja sistema koji radi pisanjem na portove koje koristi druga oprema. Na sreću, moderne arhitekture autobusa su imune na ove probleme.

Softversko sučelje koje se koristi za pristup I/O registrima sastoji se od sljedeće tri funkcije:

Int check_region(unsigned long start, unsigned long len); struct resurs *request_region(nepotpisani dugi početak, nepotpisani dugi len, char *ime); void release_region(nepotpisani dugi početak, nepotpisani dugi len);

Funkcija check_region() može se pozvati da provjeri da li je određeni raspon portova zauzet. Vraća negativan kod greške (kao što je -EBUSY ili -EINVAL) ako je odgovor negativan.

Funkcija request_region() vrši dodjelu datog raspona adresa, vraćajući, ako je uspješan, pokazivač koji nije nulti. Vozač ne mora pohraniti ili koristiti vraćeni pokazivač. Sve što trebate učiniti je provjeriti NULL. Kod koji bi trebao raditi samo sa jezgrom 2.4 (ili novijim) ne mora uopće pozivati ​​funkciju check_region(). Nema sumnje u prednost ovog načina distribucije, jer
nepoznato je šta se može dogoditi između poziva check_region() i request_region(). Ako želite da zadržite kompatibilnost sa starijim verzijama kernela, tada je potrebno pozvati check_region() prije request_region().

Funkcija release_region() mora biti pozvan kada drajver oslobodi prethodno korištene portove.

Stvarnu vrijednost pokazivača koju vraća request_region() koristi samo podsistem za dodjelu resursa koji radi u kernelu.

Ove tri funkcije su zapravo makroi definirani u .

Ispod je primjer sekvence poziva koja se koristi za registraciju portova. Primjer je uzet iz koda vozača za obuku lobanje. (Kôd funkcije skull_probe_hw() nije prikazan ovdje jer sadrži hardverski ovisan kod.)

#include #include static int skull_detect(unsigned int port, unsigned int range) ( int err; if ((err = check_region(port,range))< 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; }

Ovaj primjer prvo provjerava dostupnost potrebnog raspona portova. Ako portovi nisu dostupni, onda pristup opremi nije moguć.
Stvarna lokacija portova uređaja može se razjasniti skeniranjem. Funkcija request_region() ne bi trebala, u ovom primjeru,
završiće neuspehom. Kernel ne može učitati više od jednog modula istovremeno, tako da neće doći do kolizije korištenja porta
mora.

Svi I/O portovi koje je dodijelio drajver moraju se naknadno osloboditi. Naš drajver lubanje to radi u funkciji cleanup_module():

Statički void skull_release (nepotpisani int port, nepotpisani int raspon) (regija_release(port,opseg); )

Mehanizam zahtjeva/otpuštanja resursa je sličan mehanizmu registracije/deregistracije modula i savršeno je implementiran na osnovu gore opisane sheme korištenja goto operatora.

Memorija

Informacije o I/O memoriji dostupne su preko /proc/iomem datoteke. Ispod je tipičan primjer takve datoteke za personalni računar:

00000000-0009fbff: Sistemska RAM 0009fc00-0009ffff: rezervirana 000a0000-000bffff: Video RAM područje 000c0000-000c7fff: Video ROM 000f0000-000ff0 System 000ff0000ff00 0 0100000-0022c557: Šifra kernela 0022c558-0024455f: Podaci kernela 20000000 - 2ffffffff: Intel Corporation 440BX/ZX - 82443BX/ZX Host bridge 68000000-68000fff: Texas Instruments PCI1225 68001000-68001fff: Texas Instruments PCI1225 (#02) Buffe #030400000000 # 000-e7 ffffff: PCI sabirnica #01 e4000000 -e4ffffff : ATI Technologies Inc 3D Rage LT Pro AGP-133 e6000000-e6000fff: ATI Technologies Inc 3D Rage LT Pro AGP-133 fffc0000-ffffffff: rezervirano

Vrijednosti raspona adresa su prikazane u heksadecimalnoj notaciji. Za svaki raspon ari je prikazan njegov vlasnik.

Registrovanje pristupa I/O memoriji slično je registraciji I/O portova i izgrađeno je na istom mehanizmu u kernelu.

Da bi dobio i oslobodio potreban opseg I/O memorijskih adresa, drajver mora koristiti sljedeće pozive:

Int check_mem_region(nepotpisani dugi početak, nepotpisani dugi len); int request_mem_region(nepotpisani dugi početak, nepotpisani dugi len, char *name); int release_mem_region (nepotpisani dugi početak, nepotpisani dugi len);

Tipično, drajver poznaje opseg I/O memorijskih adresa, tako da se kod za dodeljivanje ovog resursa može smanjiti u poređenju sa primerom za dodelu opsega portova:

If (check_mem_region(mem_addr, mem_size)) ( printk("drivername: memorija je već u upotrebi\n"); return -EBUSY; ) request_mem_region(mem_addr, mem_size, "drivername");

Alokacija resursa u Linuxu 2.4

Trenutni mehanizam alokacije resursa uveden je u Linux kernel 2.3.11 i pruža fleksibilan pristup upravljanju sistemskim resursima. Ovaj odjeljak ukratko opisuje ovaj mehanizam. Međutim, osnovne funkcije dodjele resursa (kao što je request_region(), itd.) se i dalje implementiraju kao makroi i koriste se za kompatibilnost unatrag s ranijim verzijama kernela. U većini slučajeva ne morate znati ništa o stvarnom mehanizmu distribucije, ali može biti zanimljiv kada kreirate složenije drajvere.

Sistem upravljanja resursima implementiran u Linux može upravljati proizvoljnim resursima na jedinstven hijerarhijski način. Globalni sistemski resursi (na primjer, I/O portovi) mogu se podijeliti na podskupove - na primjer, one koji se odnose na određeni slot hardverske magistrale. Određeni upravljački programi također mogu opciono podijeliti uhvaćene resurse na osnovu njihove logičke strukture.

Opseg dodijeljenih resursa je opisan kroz strukturu resursne strukture, koja je deklarirana u zaglavlju :

Strukturni resurs ( const char *ime; nepotpisani dugi početak, kraj; nepotpisane duge zastavice; struct resurs *roditelj, *sestra, *dijete; );

Globalni (root) raspon resursa se kreira u vrijeme pokretanja. Na primjer, struktura resursa koja opisuje I/O portove kreira se na sljedeći način:

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

Ovdje je opisan resurs nazvan PCI IO, koji pokriva raspon adresa od nule do IO_SPACE_LIMIT. Vrijednost ove varijable ovisi o korištenoj platformi i može biti jednaka 0xFFFF (16-bitni adresni prostor, za x86, IA-64, Alpha, M68k i MIPS arhitekture), 0xFFFFFFFF (32-bitni adresni prostor, za SPARC, PPC , SH) ili 0xFFFFFFFFFFFFFFFFFF (64-bitni, SPARC64).

Podopsezi ovog resursa mogu se kreirati korištenjem poziva allocate_resource(). Na primjer, tokom inicijalizacije PCI magistrale, kreira se novi resurs za područje adrese ove magistrale i dodjeljuje se fizičkom uređaju. Kada kod PCI kernela obrađuje dodjele portova i memorije, stvara novi resurs samo za te regije i dodjeljuje ih koristeći pozive ioport_resource() ili iomem_resource().

Upravljački program tada može zatražiti podskup resursa (obično dio globalnog resursa) i označiti ga kao zauzet. Pribavljanje resursa se postiže pozivom request_region(), koji vraća ili pokazivač na novu strukturu strukture resursa koja opisuje traženi resurs ili NULL u slučaju greške. Ova struktura je dio globalnog stabla resursa. Kao što je već spomenuto, nakon dobijanja resursa, upravljačkom programu neće biti potrebna vrijednost ovog pokazivača.

Zainteresovani čitalac može uživati ​​u gledanju detalja ove šeme upravljanja resursima u kernel/resource.c datoteci koja se nalazi u direktorijumu izvora kernela. Međutim, za većinu programera će znanje koje je već predstavljeno biti dovoljno.

Slojeviti mehanizam alokacije resursa donosi dvostruku korist. S jedne strane, daje vizualni prikaz struktura podataka kernela. Pogledajmo ponovo primjer datoteke /proc/ioports:

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

Opseg e800-e8ff je dodijeljen Adaptec adapteru, koji je sebe označio kao drajver na PCI magistrali. Većinu ovog opsega je zahtijevao drajver aic7xxx.

Još jedna prednost ovog upravljanja resursima je podjela adresnog prostora na podopsege koji odražavaju stvarnu međusobnu povezanost opreme. Upravitelj resursa ne može dodijeliti preklapajuće podopsege adresa, što može spriječiti instalaciju neispravnog drajvera.

Automatska i ručna konfiguracija

Neki parametri koje zahtijeva upravljački program mogu se razlikovati od sistema do sistema. Na primjer, drajver mora biti svjestan važećih I/O adresa i memorijskih raspona. Za dobro organizovane interfejse magistrale to nije problem. Međutim, ponekad ćete morati da prosledite parametre drajveru da biste mu pomogli da pronađe sopstveni uređaj, ili da omogućite/onemogućite neke od njegovih funkcija.

Ove postavke koje utiču na rad drajvera razlikuju se u zavisnosti od uređaja. Na primjer, ovo može biti broj verzije instaliranog uređaja. Naravno, takve informacije su neophodne da bi vozač ispravno radio sa uređajem. Definiranje takvih parametara (konfiguracija drajvera) je prilično jednostavno
lukav zadatak koji se izvodi kada se drajver inicijalizira.

Obično postoje dva načina za dobivanje ispravnih vrijednosti ovog parametra - ili ih korisnik eksplicitno definira, ili ih vozač određuje samostalno, na osnovu anketiranja opreme. Iako je automatsko otkrivanje nesumnjivo najbolje rješenje za konfiguraciju drajvera,
prilagođenu konfiguraciju je mnogo lakše implementirati. Programer drajvera treba da implementira autokonfiguraciju drajvera gde god je to moguće, ali u isto vreme treba da obezbedi korisniku mehanizam za ručnu konfiguraciju. Naravno, ručna konfiguracija bi trebala imati veći prioritet od automatske konfiguracije. U početnim fazama razvoja obično se implementira samo ručni prijenos parametara do drajvera. Automatska konfiguracija, ako je moguće, dodaje se kasnije.

Mnogi upravljački programi, među svojim konfiguracijskim parametrima, imaju parametre koji kontroliraju rad drajvera. Na primjer, upravljački programi interfejsa Integrated Device Electronics (IDE) omogućavaju korisniku da kontroliše DMA operacije. Dakle, ako vaš drajver radi dobar posao u automatskom otkrivanju hardvera, možda biste željeli dati korisniku kontrolu nad funkcionalnošću drajvera.

Vrijednosti parametara se mogu proslijediti tokom učitavanja modula pomoću naredbi insmod ili modprobe. Nedavno je postalo moguće pročitati vrijednost parametara iz konfiguracijske datoteke (obično /etc/modules.conf). Cjelobrojne i string vrijednosti mogu se proslijediti kao parametri. Dakle, ako trebate proslijediti cjelobrojnu vrijednost za parametar skull_ival i vrijednost niza za parametar skull_sval, možete ih proslijediti tijekom učitavanja modula s dodatnim parametrima naredbi insmod:

Insmod skull skull_ival=666 skull_sval="zvijer"

Međutim, prije nego naredba insmod može promijeniti vrijednosti parametara modula, modul mora učiniti te parametre dostupnima. Parametri se deklariraju korištenjem MODULE_PARM makro definicije, koja je definirana u datoteci zaglavlja module.h. Makro MODULE_PARM uzima dva parametra: ime varijable i niz koji definira njen tip. Ova makro definicija mora biti smještena izvan bilo koje funkcije i obično se nalazi na početku datoteke nakon što su varijable definirane. Dakle, dva gore navedena parametra mogu se deklarirati na sljedeći način:

Int skull_ival=0; char *skull_sval; MODULE_PARM(lubanje_ival, "i"); MODULE_PARM(sval_lubanje, "s");

Trenutno je podržano pet tipova parametara modula:

  • b - jednobajtna vrijednost;
  • h - (kratka) dvobajtna vrijednost;
  • i - cijeli broj;
  • l - dugi cijeli broj;
  • s - string (char *);

U slučaju parametara niza, pokazivač (char *) mora biti deklariran u modulu. Naredba insmod dodjeljuje memoriju za proslijeđen niz i inicijalizira ga potrebnom vrijednošću. Koristeći MODULE_PARM makronaredbu, možete inicijalizirati nizove parametara. U ovom slučaju, cijeli broj koji prethodi karakteru tipa određuje dužinu niza. Kada se specificiraju dva cijela broja, odvojena crticom, oni određuju minimalni i maksimalni broj vrijednosti koje treba prenijeti. Za detaljnije razumijevanje kako ovaj makro funkcionira, pogledajte datoteku zaglavlja .

Na primjer, pretpostavimo da niz parametara mora biti inicijaliziran s najmanje dvije i najmanje četiri cjelobrojne vrijednosti. Tada se to može opisati na sljedeći način:

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

Pored toga, programerski alat ima makro definiciju MODULE_PARM_DESC, koja vam omogućava da stavite komentare na parametre modula koji se prosleđuju. Ovi komentari su pohranjeni u objektnoj datoteci modula i mogu se pregledati korištenjem, na primjer, uslužnog programa objdump, ili korištenjem automatiziranih alata za administraciju sistema. Evo primjera korištenja ove makro definicije:

Int base_port = 0x300; MODULE_PARM(bazni_port, "i"); MODULE_PARM_DESC (base_port, "Bazni I/O port (podrazumevano 0x300)");

Poželjno je da svi parametri modula imaju zadane vrijednosti. Promjena ovih vrijednosti pomoću insmoda bi trebala biti potrebna samo ako je potrebno. Modul može provjeriti eksplicitnu postavku parametara provjeravanjem njihovih trenutnih vrijednosti sa zadanim vrijednostima. Nakon toga, možete implementirati mehanizam auto-konfiguracije na osnovu sljedećeg dijagrama. Ako vrijednosti parametara imaju zadane vrijednosti, tada se vrši automatska konfiguracija. U suprotnom, koriste se trenutne vrijednosti. Da bi ova šema funkcionirala, potrebno je da zadane vrijednosti ne odgovaraju nijednoj od mogućih konfiguracija sistema u stvarnom svijetu. Tada bi se pretpostavilo da takve vrijednosti ne može postaviti korisnik u ručnoj konfiguraciji.

Sljedeći primjer pokazuje kako upravljački program lubanje automatski otkriva adresni prostor portova uređaja. U gornjem primjeru, automatsko otkrivanje gleda na više uređaja, dok ručna konfiguracija ograničava upravljački program na jedan uređaj. Već ste ranije upoznali funkciju skull_detect u odjeljku koji opisuje I/O portove. Implementacija skull_init_board() nije prikazana jer je
Izvodi inicijalizaciju ovisno o hardveru.

/* * opseg portova: uređaj može biti između * 0x280 i 0x300, u koracima od 0x10. Koristi 0x10 portove. */ #define SKULL_PORT_FLOOR 0x280 #define SKULL_PORT_CEIL 0x300 #define SKULL_PORT_RANGE 0x010 /* * sljedeća funkcija vrši autodetekciju, osim ako insmod nije dodijelio određenu * vrijednost za "skull_port_port_base" */skull_port_base; /* 0 prisiljava autodetekciju */ MODULE_PARM (baza_porta lubanje, "i"); MODULE_PARM_DESC(skull_port_base, "Bazni I/O port za lobanju"); static int skull_find_hw(void) /* vraća broj uređaja */ ( /* baza je ili vrijednost vremena učitavanja ili prva proba */ int base = skull_port_base ? skull_port_base: SKULL_PORT_FLOOR; int rezultat = 0; /* petlja jedan vrijeme ako je vrijednost dodijeljena; pokušajte ih sve ako se automatski otkriva */ uradi ( if (skull_detect(base, SKULL_PORT_RANGE) == 0) ( skull_init_board(base); result++; ) base += SKULL_PORT_RANGE; /* pripremite se za sljedeću probu */ ) dok (base_port_lubanje == 0 && baza< SKULL_PORT_CEIL); return result; }

Ako se konfiguracijske varijable koriste samo unutar drajvera (tj. nisu objavljene u tablici simbola kernela), tada programer može malo olakšati život korisniku tako što ne koristi prefikse u nazivima varijabli (u našem slučaju, prefiks skull_) . Ovi prefiksi malo znače korisniku, a njihovo odsustvo pojednostavljuje kucanje komande sa tastature.

Radi potpunosti, daćemo opis još tri makro definicije koje vam omogućavaju da postavite neke komentare u objektnu datoteku.

MODULE_AUTHOR (ime) Postavlja red sa imenom autora u objektnu datoteku. MODULE_DESCRIPTION(desc) Postavlja red sa opštim opisom modula u objektnu datoteku. MODULE_SUPPORTED_DEVICE(dev) Postavlja liniju koja opisuje uređaj koji modul podržava. Linux pruža moćan i opsežan API za aplikacije, ali ponekad to nije dovoljno. Za interakciju sa hardverom ili obavljanje operacija sa pristupom privilegovanim informacijama u sistemu, potreban je drajver kernela.

Modul jezgre Linuxa je kompajlirani binarni kod koji se ubacuje direktno u Linux kernel, radi u prstenu 0, unutrašnjem i najmanje bezbednom prstenu za izvršavanje instrukcija u x86–64 procesoru. Ovdje se kod izvršava potpuno bez ikakvih provjera, ali nevjerovatnom brzinom i sa pristupom svim sistemskim resursima.

Ne za obične smrtnike

Pisanje modula jezgra Linuxa nije za one sa slabim srcem. Promjenom kernela rizikujete gubitak podataka. Ne postoji standardna sigurnost u kodu kernela kao u redovnim Linux aplikacijama. Ako pogriješite, spustite cijeli sistem.

Ono što pogoršava situaciju je to što se problem ne mora nužno pojaviti odmah. Ako modul objesi sistem odmah nakon učitavanja, onda je ovo najbolji scenarij kvara. Što više koda postoji, to je veći rizik od beskonačnih petlji i curenja memorije. Ako ne budete pažljivi, problemi će se postepeno povećavati kako mašina radi. Na kraju, važne strukture podataka, pa čak i baferi mogu biti prepisani.

Tradicionalne paradigme razvoja aplikacija mogu se uglavnom zaboraviti. Pored učitavanja i izbacivanja modula, pisaćete kod koji reaguje na sistemske događaje umesto da sledi sekvencijalni obrazac. Kada radite sa kernelom, pišete API, a ne same aplikacije.

Takođe nemate pristup standardnoj biblioteci. Iako kernel pruža neke funkcije kao što su printk (koji je zamjena za printf) i kmalloc (koji radi slično malloc), uglavnom ste prepušteni hardveru. Osim toga, trebali biste potpuno očistiti za sobom nakon istovara modula. Ovdje nema odvoza smeća.

Potrebne komponente

Prije nego što počnete, trebali biste se uvjeriti da imate sve potrebne alate za posao. Ono što je najvažnije, potrebna vam je Linux mašina. Znam da je ovo neočekivano! Iako će svaka distribucija Linuxa poslužiti, ja koristim Ubuntu 16.04 LTS u ovom primjeru, tako da ćete možda morati malo izmijeniti komande instalacije ako koristite druge distribucije.

Drugo, potrebna vam je ili zasebna fizička mašina ili virtuelna mašina. Lično, više volim da radim na virtuelnoj mašini, ali birajte sami. Ne preporučujem korištenje vaše glavne mašine zbog gubitka podataka kada napravite grešku. Kažem “kada”, a ne “ako” jer ćete sigurno objesiti auto barem nekoliko puta tokom procesa. Vaše najnovije promjene koda mogu i dalje biti u međuspremniku za pisanje kada se kernel uspaniči, tako da su i vaši izvori možda oštećeni. Testiranje na virtuelnoj mašini eliminiše ove rizike.

Konačno, morate znati barem malo C. Vrijeme izvođenja C++-a je preveliko za kernel, tako da morate pisati u čistom, golom C-u. Poznavanje asemblerskog jezika je također korisno za interakciju sa hardverom.

Instaliranje razvojnog okruženja

Na Ubuntu morate pokrenuti:

Apt-get install build-essential linux-headers-`uname -r`
Instaliramo najvažnije razvojne alate i zaglavlja kernela potrebne za ovaj primjer.

Primeri ispod pretpostavljaju da radite kao običan korisnik, a ne kao root, ali da imate sudo privilegije. Sudo je neophodan za učitavanje modula kernela, ali želimo da radimo izvan root-a kad god je to moguće.

Počni

Počnimo pisati kod. Pripremimo naše okruženje:

Mkdir ~/src/lkm_example cd ~/src/lkm_example
Pokrenite svoj omiljeni editor (vim u mom slučaju) i kreirajte datoteku lkm_example.c sa sljedećim sadržajem:

#include #include #include MODULE_LICENSE(“GPL”); MODULE_AUTHOR(“Robert W. Oliver II”); MODULE_DESCRIPTION(“Jednostavan primjer Linux modula.”); MODULE_VERSION(“0.01”); static int __init lkm_example_init(void) ( printk(KERN_INFO “Zdravo, svijet!\n”); return 0; ) static void __exit lkm_example_exit(void) ( printk(KERN_INFO “Zbogom, svijete!\n”); )lkim_modul_ ); module_exit(lkm_example_exit);
Dizajnirali smo najjednostavniji mogući modul, pogledajmo pobliže njegove najvažnije dijelove:

  • include navodi datoteke zaglavlja potrebne za razvoj Linux kernela.
  • MODULE_LICENSE se može postaviti na različite vrijednosti u zavisnosti od licence modula. Da vidite kompletnu listu, pokrenite:

    Grep “MODULE_LICENSE” -B 27 /usr/src/linux-headers-`uname -r`/include/linux/module.h

  • Postavili smo init (učitavanje) i exit (unloading) kao statičke funkcije koje vraćaju cijele brojeve.
  • Obratite pažnju na upotrebu printk umjesto printf. Takođe, opcije za printk se razlikuju od printf. Na primjer, oznaka KERN_INFO za deklariranje prioriteta evidentiranja za određeni red je navedena bez zareza. Kernel obrađuje ove stvari unutar funkcije printk kako bi uštedio memoriju steka.
  • Na kraju datoteke možete pozvati module_init i module_exit i specificirati funkcije učitavanja i izbacivanja. Ovo omogućava proizvoljno imenovanje funkcija.
Međutim, još uvijek ne možemo kompajlirati ovu datoteku. Makefile potreban. Ovaj osnovni primjer će za sada biti dovoljan. Imajte na umu da je make vrlo izbirljiv u vezi sa razmacima i tabulatorima, pa obavezno koristite tabove umjesto razmaka gdje je to prikladno.

Obj-m += lkm_example.o sve: napravi -C /lib/modules/$(ljuska uname -r)/build M=$(PWD) moduli očisti: napravi -C /lib/modules/$(ljuska uname -r )/build M=$(PWD) čist
Ako pokrenemo make it bi trebao uspješno kompajlirati naš modul. Rezultat će biti datoteka lkm_example.ko. Ako dođe do bilo kakvih grešaka, provjerite da li su navodnici u izvornom kodu ispravno postavljeni, a ne slučajno u UTF-8 kodiranju.

Sada možete implementirati modul i testirati ga. Da bismo to uradili pokrećemo:

Sudo insmod lkm_example.ko
Ako je sve u redu, onda nećete ništa vidjeti. Funkcija printk ne daje izlaz u konzolu, već u dnevnik kernela. Za pregled morate pokrenuti:

Sudo dmesg
Trebali biste vidjeti red "Zdravo, svijet!" sa vremenskom oznakom na početku. To znači da je naš modul kernela učitan i uspješno upisan u dnevnik kernela. Također možemo provjeriti da li je modul još uvijek u memoriji:

lsmod | grep “lkm_example”
Da uklonite modul, pokrenite:

Sudo rmmod lkm_example
Ako ponovo pokrenete dmesg, vidjet ćete unos “Zbogom, svijete!” u dnevniku. Možete ponovo pokrenuti lsmod i provjeriti je li modul ispražnjen.

Kao što vidite, ovaj postupak testiranja je pomalo dosadan, ali se može automatizirati dodavanjem:

Test: sudo dmesg -C sudo insmod lkm_example.ko sudo rmmod lkm_example.ko dmesg
na kraju Makefile-a i zatim pokrenuti:

Napravite test
za testiranje modula i provjeru izlaza u dnevnik kernela bez potrebe za pokretanjem zasebnih naredbi.

Sada imamo potpuno funkcionalan, iako potpuno trivijalan, modul kernela!

Kopajmo malo dublje. Iako su moduli kernela sposobni za obavljanje svih vrsta zadataka, povezivanje s aplikacijama je jedan od najčešćih slučajeva upotrebe.

Budući da aplikacijama nije dozvoljeno da vide memoriju u prostoru kernela, moraju koristiti API za komunikaciju s njima. Iako tehnički postoji nekoliko načina da se to učini, najčešći je kreiranje datoteke uređaja.

Vjerovatno ste se ranije bavili datotekama uređaja. Naredbe u kojima se spominju /dev/zero, /dev/null i slično stupaju u interakciju sa "nula" i "null" uređajima, koji vraćaju očekivane vrijednosti.

U našem primjeru vraćamo “Hello, World”. Iako ovo nije posebno korisna funkcija za aplikacije, ipak pokazuje proces interakcije s aplikacijom putem datoteke uređaja.

Evo kompletne liste:

#include #include #include #include #include MODULE_LICENSE(“GPL”); MODULE_AUTHOR(“Robert W. Oliver II”); MODULE_DESCRIPTION(“Jednostavan primjer Linux modula.”); MODULE_VERSION(“0.01”); #define DEVICE_NAME “lkm_example” #define EXAMPLE_MSG “Zdravo, svijet!\n” #define MSG_BUFFER_LEN 15 /* Prototipovi za funkcije uređaja */ static int device_open(struct inode *, struct datoteka *); static int device_release(struct inode *, struct file *); statički ssize_t device_read(struct file *, char *, size_t, loff_t *); static ssize_t device_write(struct file *, const char *, size_t, loff_t *); statički int glavni_broj; static int device_open_count = 0; statički znak msg_buffer; statički znak *msg_ptr; /* Ova struktura ukazuje na sve funkcije uređaja */ static struct file_operations file_ops = ( .read = device_read, .write = device_write, .open = device_open, .release = device_release ); /* Kada proces čita sa našeg uređaja, ovo se poziva. */ static ssize_t device_read(struct file *flip, char *buffer, size_t len, loff_t *offset) ( int bytes_read = 0; /* Ako smo na kraju, vratimo se na početak */ if (*msg_ptr = = 0) ( msg_ptr = msg_buffer; ) /* Stavite podatke u bafer */ dok (len && *msg_ptr) ( /* Međuspremnik je u korisničkim podacima, a ne kernelu, tako da ne možete samo referencirati * pomoću pokazivača. funkcija put_user to rješava za nas */ put_user(*(msg_ptr++), buffer++); len--; bytes_read++; ) return bytes_read; ) /* Poziva se kada proces pokuša upisati na naš uređaj */ static ssize_t device_write(struct file * flip, const char *buffer, size_t len, loff_t *offset) ( /* Ovo je uređaj samo za čitanje */ printk(KERN_ALERT “Ova operacija nije podržana.\n”); return -EINVAL; ) /* Poziva se kada proces otvara naš uređaj */ static int device_open(struct inode *inode, struct fajl *file) ( /* Ako je uređaj otvoren, vrati zauzet */ if (device_open_count) ( return -EBUSY; ) device_open_count++; try_module_get(THIS_MODULE); return 0; ) /* Poziva se kada proces zatvori naš uređaj */ static int device_release(struct inode *inode, struct fajl *file) ( /* Smanjuje broj otvorenih i korištenih podataka. Bez toga, modul se ne bi ispraznio. */ device_open_count- -; module_put(THIS_MODULE); return 0; ) static int __init lkm_example_init(void) ( /* Popunite bafer sa našom porukom */ strncpy(msg_buffer, EXAMPLE_MSG, MSG_BUFFER_LEN); /* Postavite msg_ptr = msg_ptr na buffer_msg_ptr_ ; /* Pokušajte registrirati uređaj znakova */ major_num = register_chrdev(0, “lkm_example”, &file_ops); if (major_num< 0) { printk(KERN_ALERT “Could not register device: %d\n”, major_num); return major_num; } else { printk(KERN_INFO “lkm_example module loaded with device major number %d\n”, major_num); return 0; } } static void __exit lkm_example_exit(void) { /* Remember - we have to clean up after ourselves. Unregister the character device. */ unregister_chrdev(major_num, DEVICE_NAME); printk(KERN_INFO “Goodbye, World!\n”); } /* Register module functions */ module_init(lkm_example_init); module_exit(lkm_example_exit);

Testiranje poboljšanog primjera

Sada naš primjer radi više od samo ispisivanja poruke prilikom učitavanja i istovara, tako da će biti potrebna manje rigorozna procedura testiranja. Promenimo Makefile tako da učitava samo modul, bez njegovog rasterećenja.

Obj-m += lkm_example.o sve: napravi -C /lib/modules/$(ljuska uname -r)/build M=$(PWD) moduli očisti: napravi -C /lib/modules/$(ljuska uname -r )/build M=$(PWD) čist test: # Stavili smo - ispred komande rmmod da kažemo make-u da ignoriše # grešku u slučaju da modul nije učitan. -sudo rmmod lkm_example # Obriši evidenciju kernela bez echo sudo dmesg -C # Ubaci modul sudo insmod lkm_example.ko # Prikaži dnevnik kernela dmesg
Sada nakon pokretanja make testa vidjet ćete izlazni broj glavnog uređaja. U našem primjeru, automatski je dodijeljen od strane kernela. Međutim, ovaj broj je potreban za kreiranje novog uređaja.

Uzmite broj koji je generirao make test i upotrijebite ga za kreiranje datoteke uređaja tako da možemo komunicirati s našim kernel modulom iz korisničkog prostora.

Sudo mknod /dev/lkm_example sa MAJOR 0
(u ovom primjeru zamijenite MAJOR vrijednošću dobivenom iz make testa ili dmesg-a)

Opcija c u komandi mknod govori mknodu da trebamo kreirati datoteku uređaja karaktera.

Sada možemo dobiti sadržaj sa uređaja:

Cat /dev/lkm_example
ili čak preko naredbe dd:

Dd if=/dev/lkm_example of=test bs=14 count=100
Ovom fajlu možete pristupiti i iz aplikacija. To ne moraju biti kompajlirane aplikacije - čak i Python, Ruby i PHP skripte imaju pristup ovim podacima.

Kada završimo sa uređajem, uklanjamo ga i istovarujemo modul:

Sudo rm /dev/lkm_example sudo rmmod lkm_example

Zaključak

Nadam se da ste uživali u našim šalama u glavnom prostoru. Iako su prikazani primjeri primitivni, ove strukture se mogu koristiti za kreiranje vlastitih modula koji obavljaju vrlo složene zadatke.

Samo zapamtite da je u kernel prostoru sve vaša odgovornost. Nema podrške ili druge šanse za vaš kod. Ako radite projekat za klijenta, planirajte unaprijed dvostruko, ako ne i trostruko vrijeme za otklanjanje grešaka. Kôd kernela mora biti što je moguće savršeniji kako bi se osigurao integritet i pouzdanost sistema na kojima radi.

Neke karakteristike modularnog programiranja i opšte preporuke za konstruisanje potprograma modularne strukture.

Moduli se povezuju s glavnim programom redoslijedom kojim su deklarirani USES, a istim redoslijedom su inicijalizacijski blokovi modula koji su povezani s glavnim programom prije nego što program počne da se izvršava.

Redoslijed kojim se moduli izvršavaju može utjecati na pristup podacima biblioteke i rutinski pristup.

Na primjer, ako moduli s imenima M1, M2 sadrže isti tip A, varijablu B i potprogram C, tada će nakon povezivanja ovih USES modela, pozivi na A, B, C u ovoj PU biti ekvivalentni pozivima objekata na modul M2 .

Ali da bi se okarakterisala ispravnost poziva potrebnih objekata istog imena iz različitih povezanih modula, preporučljivo je prilikom pristupa modulu prvo navesti naziv modula, a zatim tačku naziv objekta: M1. A M1.B M1.C M2.B.

Očigledno, vrlo je lako podijeliti veliki program na dva dijela (PU), tj. glavni program + moduli.

Postavljanje svake PU u svoj memorijski segment iu svoj vlastiti disk fajl.

Sve deklaracije tipova, kao i one varijable koje treba da budu dostupne pojedinim PU (glavni program i budući moduli) treba staviti u poseban modul sa praznim izvršnim delom. Međutim, ne treba obraćati pažnju na činjenicu da neki PE (na primjer, modul) ne koristi sve ove deklaracije. Inicijacijski dio takvog modula može uključivati ​​naredbe koje povezuju varijable datoteke sa nestandardnim tekstualnim datotekama (ASSIGN) i pokreću ove datoteke, tj. označavajući pozive za prijenos podataka za njih (RESETIRANJE i REWRITE).

Prvu grupu drugih potprograma, na primjer, nekoliko kompaktnih funkcija treba smjestiti u modul 3 (zauzvrat), druge, na primjer, procedure opće namjene - u modul 4, itd.

Prilikom distribucije podprograma u module u složenom projektu posebnu pažnju treba obratiti na redosled i mesto njihovog pisanja.

TP okruženje sadrži alate koji kontroliraju različite načine kompajliranja modula.

Prevođenje Alt+F9 RUN Cntr+F9

Memorija odredišta

Ovi načini se razlikuju samo po načinu komunikacije i glavnom programu.

Način prevođenja

Prevodi glavni program ili modul koji je trenutno u aktivnom prozoru uređivača. Ako ovaj PU sadrži pristup nestandardnim korisničkim modulima, tada ovaj način zahtijeva prisustvo disk datoteka istog imena sa ekstenzijom ___.tpu za svaki takav plug-in modul.



Ako je Odredište pohranjeno u memoriji, onda ove datoteke ostaju samo u memoriji, a disk datoteka se ne kreira.

Međutim, mnogo je lakše kreirati tpu fajlove zajedno sa kompajlerom celog programa koristeći druge režime koji ne zahtevaju podešavanje Disk za opciju odredišta.

Make mode

Prilikom kompajliranja u ovom modu, prvo se provjerava sljedeće (prije kompajliranja glavnog programa) za svaki modul:

1) Postojanje disk tpu datoteke; ako ne postoji, onda se kreira automatski kompajliranjem izvornog koda modula, tj. njegov pas fajl

2) Korespondencija pronađene tpu datoteke sa izvornim tekstom modula, gdje su se mogle izvršiti izmjene; inače se tpu datoteka automatski ponovo kreira

3) Nepromjenjivost odjeljka interfejsa modula: ako se promijenio, tada se ponovo kompajliraju svi oni moduli (njihovi izvorni pas-fajlovi) u kojima je ovaj modul specificiran u USES klauzuli.

Ako nije bilo promjene u izvornim kodovima modula, kompajler stupa u interakciju s ovim tpu datotekama i koristi vrijeme kompilacije.

Build Mode

Za razliku od režima Make, on nužno zahteva prisustvo izvornih pas fajlova; kompilira (rekompajlira) svaki modul i time osigurava da se sve promjene u tekstovima pas datoteka uzimaju u obzir. Ovo povećava vrijeme kompilacije programa u cjelini.

Za razliku od načina prevođenja, načini Make i Build vam omogućavaju da započnete kompajliranje programa sa modularnom strukturom iz bilo koje pas datoteke (naziva se primarna datoteka), bez obzira na to koja je datoteka (ili dio programa) u aktivnoj prozor urednika. Da biste to učinili, u stavci kompajliranja odaberite opciju Primary file, pritisnite Enter i zapišite naziv primarne pas-datoteke, a zatim će kompilacija početi iz ove datoteke.

Ako primarni fajl nije specificiran na ovaj način, onda je kompilacija u načinima Make, Build i RUN moguća samo ako je glavni program u aktivnom prozoru uređivača.

Napomena: U budućnosti planiram koristiti T2 sistem za kompajliranje kernela i modula za Puppy. T2 je trenutno instaliran za kompajliranje kernela i brojnih dodatnih modula, ali ne verzija koja se trenutno koristi u Puppy. Namjeravam sinkronizirati u budućim verzijama Puppy-a, tako da će kernel kompajliran u T2 biti korišten u Puppy-u. Pogledajte http://www.puppyos.net/pfs/ za dodatne informacije u vezi Puppy i T2.

Puppy ima vrlo jednostavan način za korištenje C/C++ kompajlera dodavanjem jedne datoteke, devx_xxx.sfs, gdje je xxx broj verzije. Na primjer, Puppy 2.12 bi imao datoteku za razvoj usklađenosti pod nazivom devx_212.sfs. Kada radite u LiveCD modu, postavite datoteku devx_xxx.sfs na istu lokaciju kao i vaša datoteka ličnih postavki pup_save.3fs, koja se obično nalazi u /mnt/home/ direktoriju. Ovo se također odnosi na druge načine instalacije koji imaju datoteku pup_save.3fs. Ako je Puppy instaliran na hard disk sa potpunom instalacijom "Opcija 2", onda nema lične datoteke, pogledajte na Puppy web stranicama da se kompajliraju sa različitim opcijama konfiguracije, tako da moduli nisu kompatibilni. Ove verzije zahtijevaju samo jednu zakrpu za squashfs. Puppy 2.12 ima kernel 2.6.18.1 i ima tri ispravke; squashfs, zadani nivo dnevnika konzole i ispravka isključivanja.

Ove komande za zakrpanje kernela date su isključivo za vaše samoobrazovanje, pošto je zakrpljeni kernel već dostupan...

Prva stvar koju prvo treba da uradite je da preuzmete sam kernel. Nalazi se da biste pronašli vezu do odgovarajuće stranice za preuzimanje. Ovo bi trebao biti "drevni" izvor dostupan na kernel.org ili njegovim ogledalima.

Povežite se na Internet, preuzmite kernel u /usr/src folder. Zatim ga raspakujte:

cd / usr/ src tar -jxf linux-2.6.16.7.tar.bz2 tar -zxf linux-2.6.16.7.tar.gz

Trebalo bi da vidite ovu fasciklu: /usr/src/linux-2.6.16.7. Zatim morate biti sigurni da ova veza upućuje na kernel:

ln -sf linux-2.6.16.7 linux ln -sf linux-2.6.16.7 linux-2.6.9

Morate primijeniti sljedeće popravke tako da imate potpuno isti izvor koji se koristi prilikom kompajliranja kernela za Puppy. U suprotnom, dobićete poruke o grešci "neriješeni simboli" kada prevedete drajver, a zatim pokušate da ga koristite sa Puppy kernelom. Primjena popravka squashfs

Drugo, primijenite Squashfs zakrpu. Squashfs zakrpa dodaje podršku za Squashfs čineći sistem datoteka samo za čitanje.

Preuzmite zakrpu, squashfs2.1-patch-k2.6.9.gz, u folder /usr/src. Imajte na umu da je ova ispravka napravljena za verziju kernela 2.6.9, ali nastavlja da radi u verzijama 2.6.11.x sve dok postoji referenca na linux-2.6.9. Zatim primijenite popravak:

Cd/ usr/ src gunzip squashfs2.1-patch-k2.6.9.gz cd/ usr/ src/ linux-2.6.11.11

Napomena, p1 ima broj 1, a ne simbol l... (Sjajan vic - cca. prevod)

zakrpa --dry-run -p1< ../ squashfs2.1-patch patch -p1 < ../ squashfs2.1-patch

Spremni! Kernel je spreman za kompajliranje!

Prevođenje kernela

Morate nabaviti konfiguracijski fajl za kernel. Njegova kopija se nalazi u folderu /lib/modules.

Zatim slijedite ove korake:

Cd/usr/src/linux-2.6.18.1

Ako postoji .config datoteka, kopirajte je negdje privremeno ili je preimenujte.

očistiti učiniti mrproper

Kopirajte .config datoteku za Puppy u /usr/src/linux-2.6.18.1... Imat će različita imena u /lib/modules, pa preimenujte u .config... Sljedeći koraci čitaju .config datoteku i generiraju novi.

napravi menuconfig

...unesite sve promjene koje želite i sačuvajte ih. Sada ćete imati novu .config datoteku i trebali biste je negdje kopirati radi čuvanja. Vidi napomenu ispod

make bzImage

Sada ste kompajlirali kernel.

Odlično, Linux kernel ćete pronaći u /usr/src/linux-2.6.18.1/arch/i386/boot/bzImage

Kompajliranje modula

Sada idite u /lib/modules i ako već postoji fascikla pod nazivom 2.6.18.1, preimenujte fasciklu 2.6.18.1 u 2.6.18.1-old.

Sada instalirajte nove module:

Cd/ usr/ src/ linux-2.6.18.1 napravi module napravi module_install

...nakon ovoga trebali biste pronaći nove module instalirane u /lib/modules/2.6.18.1 folderu.

Imajte na umu da zadnji korak iznad pokreće program "depmod" i to može dati poruke o grešci o nedostajućim simbolima za neke od modula. Ne brinite o tome - jedan od programera je zeznuo i to znači da ne možemo koristiti taj modul.

Kako koristiti novi kernel i module

Bolje je ako imate instaliran Puppy Unleashed. Zatim se tarball proširuje i postoje 2 direktorija: "boot" i "kernel".

"Boot" sadrži strukturu datoteke i skriptu za kreiranje početnog virtualnog diska. Morat ćete tamo staviti neke module kernela.

Direktorij "kernels" ima direktorij kernels/2.6.18.1/ i morat ćete zamijeniti module svojim ažuriranim. Ne morate ga zamijeniti ako ste ponovo kompajlirali istu verziju kernela (2.6.18.1).

Imajte na umu da u kernels/2.6.18.1 postoji datoteka pod nazivom "System.map". Trebali biste ga preimenovati i zamijeniti novim iz /usr/src/linux-2.6.18.1. Pregledajte mapu kernels/2.6.18.1/ i trebali biste znati šta treba ažurirati.

Ako ste kompajlirali kernel u punoj Puppy instalaciji, možete se ponovo pokrenuti pomoću novog kernela. make modules_install je korak iznad za instaliranje novih modula u /lib/modules/2.6.18.1, ali morate instalirati i novi kernel. Pokrećem se sa Grubom i samo kopiram novi kernel u /boot direktorij (i preimenujem datoteku iz bzImage u vmlinuz).

Napomena u vezi konfiguracije menija. Koristim ga godinama, pa neke stvari uzmite zdravo za gotovo, međutim početnik bi mogao biti zbunjen ako želi da napusti program. U meniju najvišeg nivoa postoji meni za spremanje konfiguracije - zanemarite ga. Samo pritisnite tipku TAB (ili tipku sa strelicom nadesno) da označite “dugme” za izlaz i pritisnite tipku ENTER. Zatim ćete biti upitani da li želite da sačuvate novu konfiguraciju i vaš odgovor bi trebao biti Da.


Top