Ydinmoduulin rakenne ja sen käännösmenetelmät. Modulaarisella rakenteella olevan ohjelman laatimisen ominaisuudet. Johdanto ja tausta

Miksi kääntää ydin itse?
Ehkä pääkysymys, joka kysytään ytimen kääntämisestä, on: "Miksi minun pitäisi tehdä tämä?"
Monien mielestä tämä on turhaa ajanhukkaa näyttääkseen itsensä älykkäänä ja edistyneenä Linux-käyttäjänä. Itse asiassa ytimen kääntäminen on erittäin tärkeä asia. Oletetaan, että ostit uuden kannettavan tietokoneen, mutta verkkokamerasi ei toimi. Sinun tekosi? Tutkit hakukonetta ja etsit ratkaisua ongelmaan tässä asiassa. Melko usein voi käydä ilmi, että verkkokamerasi toimii enemmän uusi versio kuin sinun. Jos et tiedä mikä versio sinulla on, kirjoita terminaaliin uname -r, jolloin saat ytimen version (esim. linux-2.6.31-10). Ytimen kääntämistä käytetään myös laajalti suorituskyvyn lisäämiseen: tosiasia on, että oletusarvoisesti ytimen jakelut kääntävät "kaikkia varten", minkä vuoksi se sisältää valtavan määrän ohjaimia, joita et ehkä tarvitse. Joten jos tiedät käyttämäsi laitteiston hyvin, voit poistaa tarpeettomat ohjaimet käytöstä määritysvaiheessa. On myös mahdollista ottaa käyttöön yli 4 Gt RAM-muistin tuki muuttamatta järjestelmän bittisyvyyttä. Joten jos tarvitset vielä oman ytimen, aloitetaan kääntäminen!

Ytimen lähdekoodin hankkiminen.
Ensimmäinen asia, joka sinun on tehtävä, on hankkia vaaditun ydinversion lähdekoodi. Yleensä sinun on hankittava uusin vakaa versio. Kaikki viralliset ytimen versiot ovat saatavilla osoitteessa kernel.org. Jos sinulla on jo X-palvelin asennettuna ( kotitietokone), voit siirtyä sivustolle suosikkiselaimellasi ja ladata haluamasi version tar.gz-arkistosta (gzip-pakattu). Jos työskentelet konsolissa (esimerkiksi et ole vielä asentanut X-palvelinta tai konfiguroi palvelinta), voit käyttää tekstiselainta (esim. elinks). Voit myös käyttää tavallista lataushallintaa wget:
wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.33.1.tar.gz
Muista kuitenkin, että sinun on tiedettävä tarvitsemasi tarkka versionumero.

Lähdekoodiarkiston purkaminen.
Kun olet vastaanottanut lähdekoodiarkiston, sinun on purettava arkisto kansioon. Tämä voidaan tehdä graafisesti tiedostonhallinnasta(delfiini, nautilus jne.) tai mc:n kautta. Tai käytä perinteistä tar-komentoa:
tar -zxvf polku arkistoon
Nyt sinulla on kansio, jossa on lähdekoodi, siirry siihen komennolla cd:n ytimen_lähdehakemisto(Listaaksesi kansion hakemistot, käytä ls-komentoa).

Ytimen kokoonpano.
Kun olet siirtynyt ytimen lähdehakemistoon, sinun on suoritettava "20 minuutin" ytimen konfigurointi. Sen tavoitteena on jättää vain tarvittavat ajurit ja toiminnot. Kaikki komennot on jo suoritettava superkäyttäjänä.

make config - konfiguraattorin konsolitila.

tee menuconfig - konsolitila luettelon muodossa.

tee xconfig - graafinen tila.

Kun olet tehnyt tarvittavat muutokset, tallenna asetukset ja poistu konfiguraattorista.

Kokoelma.
On koittanut kokoonpanon viimeisen vaiheen - kokoamisen - aika. Tämä tehdään kahdella komennolla:
tee && tee asennus
Ensimmäinen komento kokoaa kaikki tiedostot konekoodiksi ja toinen asentaa uuden ytimen järjestelmääsi.
Odotamme 20 minuutista useisiin tunteihin (tietokoneen tehosta riippuen). Ydin on asennettu. Saat sen näkyviin grub(2)-luetteloon kirjoittamalla (pääkäyttäjänä)
update-grub
Nyt uudelleenkäynnistyksen jälkeen paina "Escape" ja näet uuden ytimen luettelossa. Jos ydin ei käynnisty, käynnistä yksinkertaisesti vanhalla ytimellä ja määritä se huolellisemmin.

KernelCheck - kääntää ytimen menemättä konsoliin.
voit rakentaa ytimen täysin graafisessa tilassa Debianille ja siihen perustuville jakeluille. Käynnistyksen jälkeen KernelCheck tarjoaa uusimmat ytimen versiot ja korjaukset, ja suostumuksesi jälkeen lataa lähdekoodi ja käynnistä graafinen konfiguraattori. Ohjelma kokoaa ytimen .deb-paketteihin ja asentaa ne. Sinun tarvitsee vain käynnistää laite uudelleen.

Tietoja: "Käännöksen perusteella" Linux Device Driver 2nd edition. Käännös: Knyazev Aleksei [sähköposti suojattu] Viimeisin muokkauspäivä: 08/03/2004 Sijainti: http://lug.kmv.ru/index.php?page=knz_ldd2

Aloitetaan nyt ohjelmointi! Tämä luku sisältää perustiedot moduuleista ja ytimen ohjelmoinnista.
Täällä rakennamme ja käynnistämme täysimittaisen moduulin, jonka rakenne vastaa mitä tahansa todellista modulaarista ajuria.
Samalla keskitymme pääasentoihin ottamatta huomioon todellisten laitteiden erityispiirteitä.

Kaikki tässä mainitut ytimen osat, kuten funktiot, muuttujat, otsikkotiedostot ja makrot ovat
kuvataan yksityiskohtaisesti luvun lopussa.

Hei maailma!

Tutustuessani Alessndro Rubinin & Jonathan Corbetin kirjoittamaan alkuperäiseen materiaaliin Hello world -esimerkki vaikutti minusta jokseenkin epäonnistuneelta. Siksi haluan tarjota lukijalle mielestäni onnistunemman version ensimmäisestä moduulista. Toivon, että sen kääntämisessä ja asennuksessa 2.4.x-ytimen alle ei tule ongelmia. Ehdotettu moduuli ja sen käännöstapa mahdollistavat sen käytön ytimissä, jotka tukevat ja eivät tue versionhallintaa. Tulet tutustumaan kaikkiin yksityiskohtiin ja terminologiaan myöhemmin, joten avaa nyt vim ja aloita työskentely!

==================================================== === //tiedosto hello_knz.c #include #sisältää <1>Hei maailma\n"); return 0; ); void cleanup_module(void) ( printk("<1>Hyvästi julma maailma\n"); ) MODULE_LICENSE("GPL"); ================================= ==================

Kääntääksesi tällaisen moduulin, voit käyttää seuraavaa Makefile-tiedostoa. Muista laittaa sarkainmerkki ennen riviä, joka alkaa $(CC) ... .

==================================================== === LIPUT = -c -Seinä -D__KERNEL__ -DMODULIPARAMI = -I/lib/moduulit/$(shell uname -r)/build/include hello_knz.o: hello_knz.c $(CC) $(FLAGS) $( PARAM) - o $@ $^ =========================================== =======================

Tämä käyttää kahta ominaisuutta verrattuna Rubinin & Corbetin alkuperäiseen Hello world -koodiin. Ensinnäkin moduulilla on sama versio kuin ydinversiolla. Tämä saavutetaan asettamalla PARAM-muuttuja käännösskriptissä. Toiseksi moduuli lisensoidaan nyt GPL-lisenssillä (käyttäen MODULE_LICENSE()-makroa). Jos tätä ei tehdä, moduulia ytimeen asennettaessa saatat nähdä seuraavanlaisen varoituksen:

# insmod hello_knz.o Varoitus: hello_knz.o:n lataaminen saastuttaa ytimen: ei lisenssiä Katso http://www.tux.org/lkml/#export-tainted lisätietoja saastuneista moduuleista Moduuli hello_knz ladattu varoituksineen

Selitämme nyt moduulien käännösvaihtoehdot (makromääritykset selitetään myöhemmin):

-Kanssa- tällä valinnalla gcc-kääntäjä pysäyttää tiedostojen käännösprosessin välittömästi objektitiedoston luomisen jälkeen yrittämättä luoda suoritettavaa binaaritiedostoa.

- Seinä- Suurin varoitustaso, kun gcc on käynnissä.

-D— makrosymbolien määritelmät. Sama kuin #define-direktiivi käännetyssä tiedostossa. Sillä ei ole mitään väliä, kuinka määritellään tässä moduulissa käytetyt makrosymbolit käyttämällä #define lähdetiedostossa tai käyttämällä kääntäjän valitsinta -D.

-Minä- lisähakupolut sisällyttäville tiedostoille. Huomaa "uname -r" -korvauksen käyttö, joka määrittää tällä hetkellä käytössä olevan ytimen version tarkan nimen.

Seuraavassa osassa on toinen esimerkki moduulista. Se selittää myös yksityiskohtaisesti, kuinka se asennetaan ja puretaan ytimestä.

Alkuperäinen Hello world!

Katsotaanpa nyt Rubini & Corbetin tarjoaman yksinkertaisen "Hello, World" -moduulin alkuperäistä koodia. Tämä koodi voidaan kääntää ytimen versioissa 2.0–2.4. Tämä esimerkki ja kaikki muut kirjassa esitetyt ovat saatavilla O'Reilly FTP -sivustolta (katso luku 1).

//tiedosto hello.c #define MODULE #include int init_module(void) ( printk("<1>Hei maailma\n"); return 0; ) void cleanup_module(void) ( printk("<1>Hyvästi julma maailma\n");)

Toiminto printk() määritelty Linux-ytimessä ja toimii tavallisena kirjastofunktiona printf() C-kielellä. Ydin tarvitsee oman, mieluiten pienen, päättelyfunktionsa, joka sisältyy suoraan ytimeen, ei käyttäjätason kirjastoihin. Moduuli voi kutsua funktiota printk() koska moduulin lataamisen jälkeen komennolla insmod Moduuli kommunikoi ytimen kanssa ja sillä on pääsy julkaistuihin (vietyihin) ytimen funktioihin ja muuttujiin.

Merkkijonoparametri "<1>printk()-funktiolle välitetty ” on viestin prioriteetti. Alkuperäisissä englanninkielisissä lähteissä käytetään termiä loglevel, joka tarkoittaa viestien kirjaamisen tasoa. Tässä käytämme termiä prioriteetti alkuperäisen "lokitason" sijaan. Tässä esimerkissä käytämme korkeaa prioriteettia viestille, jonka numero on pieni. Viestien korkea prioriteetti asetetaan tarkoituksella, koska oletusprioriteettia omaavaa viestiä ei ehkä näytetä konsolissa, josta moduuli asennettiin. Oletusprioriteetin ytimen viestien lähtösuunta riippuu käynnissä olevan ytimen versiosta, demonin versiosta klogd, ja kokoonpanosi. Tarkemmin, työskentely toiminnon kanssa printk() selitämme luvussa 4, Vianetsintätekniikat.

Voit testata moduulia komennolla insmod asentaaksesi moduulin ytimeen ja komentoihin rmmod poistaaksesi moduulin ytimestä. Alla näytämme, kuinka tämä voidaan tehdä. Tässä tapauksessa aloituspiste init_module() suoritetaan, kun moduuli asennetaan ytimeen, ja cleanup_module() suoritetaan, kun se poistetaan ytimestä. Muista, että vain etuoikeutettu käyttäjä voi ladata ja purkaa moduuleja.

Yllä olevaa moduuliesimerkkiä voidaan käyttää vain ytimen kanssa, joka on rakennettu niin, että "moduuliversion tuki" -lippu on poistettu käytöstä. Valitettavasti useimmat jakelut käyttävät versioohjattuja ytimiä (tätä käsitellään "Moduulien versionhallinta" -osiossa luvussa 11, "kmod and Advanced Modularization"). Ja vaikka paketin vanhemmat versiot modutils sallia tällaisten moduulien lataamisen versioohjattuihin ytimiin, mikä ei ole enää mahdollista. Muista, että modutils-paketti sisältää joukon ohjelmia, jotka sisältävät insmod- ja rmmod-ohjelmat.

Tehtävä: Määritä jakelusi modutils-paketin versionumero ja koostumus.

Kun yrität lisätä tällaisen moduulin versionhallintaa tukevaan ytimeen, saatat nähdä seuraavankaltaisen virhesanoman:

# insmod hello.o hello.o: kernel-moduulin versio ei täsmää hello.o käännettiin ytimen versiolle 2.4.20, kun taas tämä ydin on versiolle 2.4.20-9asp.

Luettelossa sekalaiset moduulit esimerkkejä osoitteesta ftp.oreilly.com löydät alkuperäisen esimerkkiohjelman hello.c, joka sisältää hieman enemmän rivejä ja joka voidaan asentaa sekä versioohjattuihin että versioimattomiin ytimiin. Suosittelemme kuitenkin vahvasti oman ytimen rakentamista ilman versionhallintatukea. Samalla on suositeltavaa ottaa alkuperäiset ytimen lähteet verkkosivulta www.kernel.org

Jos olet uusi ytimien kokoamisessa, yritä lukea artikkeli, jonka Alessandro Rubini (yksi alkuperäisen kirjan kirjoittajista) julkaisi osoitteessa http://www.linux.it/kerneldocs/kconf. Sen pitäisi auttaa sinua hallitsemaan prosessi.

Suorita seuraavat komennot tekstikonsolissa kääntääksesi ja testataksesi yllä olevaa alkuperäistä esimerkkimoduulia.

Root# gcc -c hello.c root# insmod ./hello.o Hei, maailmanjuuri# rmmod hei hyvästi julma maailma root#

Riippuen mekanismista, jota järjestelmäsi käyttää viestimerkkijonojen välittämiseen, funktion lähettämien viestien lähtösuunta printk(), voivat vaihdella. Yllä olevassa esimerkissä moduulin kääntämisestä ja testaamisesta printk()-funktiosta lähetetyt viestit tulostettiin samaan konsoliin, josta annettiin moduulien asennus- ja suorituskomennot. Tämä esimerkki on otettu tekstikonsolista. Jos suoritat komennot insmod Ja rmmod ohjelman alta xterm, silloin et todennäköisesti näe mitään terminaalissasi. Sen sijaan viesti voi päätyä johonkin järjestelmän lokeihin, esimerkiksi sisään /var/log/messages. Tiedoston tarkka nimi riippuu jakelusta. Katso lokitiedostojen muutosten ajankohta. Mekanismi, jota käytetään viestien välittämiseen printk()-funktiosta, on kuvattu "Kuinka viestit kirjautuvat" -osiossa luvussa 4 "Tekniikat"
virheenkorjaus".

Moduulien viestien katselu järjestelmän lokitiedostossa /val/log/messages on kätevää käyttää järjestelmän apuohjelma tail, joka näyttää oletusarvoisesti sille välitetyn tiedoston 10 viimeistä riviä. Tämän apuohjelman mielenkiintoinen vaihtoehto on vaihtoehto -f, joka ajaa apuohjelmaa tiedoston viimeisten rivien valvontatilassa, ts. Kun tiedostoon tulee uusia rivejä, ne tulostetaan automaattisesti. Jos haluat lopettaa komennon suorittamisen tässä tapauksessa, sinun on painettava Ctrl+C. Jos haluat tarkastella järjestelmälokitiedoston kymmenen viimeistä riviä, kirjoita komentoriville seuraava:

Root# tail /var/log/messages

Kuten näet, moduulin kirjoittaminen ei ole niin vaikeaa kuin miltä se saattaa näyttää. Vaikein osa on ymmärtää, miten laitteesi toimii ja kuinka parantaa moduulin suorituskykyä. Jatkaessamme tätä lukua, opimme lisää yksinkertaisten moduulien kirjoittamisesta, jättäen laitteen tiedot myöhempiä lukuja varten.

Erot ytimen moduulien ja sovellusten välillä

Sovelluksella on yksi sisääntulopiste, joka alkaa toimia heti sijoittamisen jälkeen käynnissä oleva sovellus tietokoneen RAM-muistissa. Tämä aloituskohta kuvataan C:ssä main()-funktiona. Main()-funktion lopettaminen tarkoittaa sovelluksen lopettamista. Moduulissa on useita sisääntulokohtia, jotka suoritetaan asennettaessa ja poistettaessa moduulia ytimestä sekä käsiteltäessä käyttäjän pyyntöjä. Näin ollen aloituspiste init_module() suoritetaan, kun moduuli ladataan ytimeen. Cleanup_module()-toiminto suoritetaan, kun moduuli puretaan. Jatkossa tutustumme muihin moduulin sisääntulokohtiin, jotka suoritetaan suoritettaessa erilaisia ​​pyyntöjä moduulille.

Mahdollisuus ladata ja purkaa moduuleja on modularisointimekanismin kaksi pilaria. Niitä voidaan arvioida eri tavoin. Kehittäjälle tämä tarkoittaa ennen kaikkea kehitysajan lyhentämistä, koska voit testata ohjaimen toimivuutta ilman pitkää uudelleenkäynnistysprosessia.

Ohjelmoijana tiedät, että sovellus voi kutsua funktiota, jota ei ole ilmoitettu sovelluksessa. Staattisen tai dynaamisen linkityksen vaiheissa määritetään tällaisten funktioiden osoitteet vastaavista kirjastoista. Toiminto printf() yksi näistä kutsuttavista funktioista, joka on määritetty kirjastossa libc. Toisaalta moduuli liittyy vain ytimeen ja voi kutsua vain ytimen viemiä toimintoja. Ytimessä suoritettu koodi ei voi käyttää ulkoisia kirjastoja. Siis esimerkiksi funktio printk(), jota käytettiin esimerkissä hei C, on hyvin tunnetun funktion analogi printf(), saatavilla käyttäjätason sovelluksissa. Toiminto printk() sijaitsee ytimessä ja sen tulee olla mahdollisimman pieni. Siksi, toisin kuin printf(), sillä on hyvin rajallinen tuki tietotyypeille, eikä se esimerkiksi tue liukulukuja ollenkaan.

2.0- ja 2.2-ytimen toteutukset eivät tukeneet tyyppimäärityksiä L Ja Z. Ne esiteltiin vain ytimen versiossa 2.4.

Kuvassa 2-1 on esitetty moduulin sisääntulopisteinä olevien funktioiden kutsumismekanismin toteutus. Tämä kuva näyttää myös asennetun tai asennetun moduulin vuorovaikutuksen mekanismin ytimen kanssa.

Riisi. 2-1. Tiedonsiirto moduulin ja ytimen välillä

Yksi Unix/Linux-käyttöjärjestelmien ominaisuuksista on ydinmoduuleihin linkitettävien kirjastojen puute. Kuten jo tiedät, moduulit, kun ne ladataan, linkitetään ytimeen, joten kaikki moduulin ulkopuoliset toiminnot on ilmoitettava ytimen otsikkotiedostoissa ja oltava ytimessä. Moduulien lähteet ei koskaan ei saa sisältää tavallisia otsikkotiedostoja käyttäjätilan kirjastoista. Ydinmoduuleissa voit käyttää vain toimintoja, jotka ovat itse asiassa osa ydintä.

Koko ytimen käyttöliittymä on kuvattu hakemistoissa sijaitsevissa otsikkotiedostoissa sisällyttää/linux Ja sisällyttää/asm ytimen lähteiden sisällä (yleensä sijaitsevat /usr/src/linux-x.y.z(x.y.z on ytimen versio)). Vanhemmat jakelut (perustuu libc versio 5 tai vanhempi) käytti symbolisia linkkejä /usr/include/linux Ja /usr/include/asm vastaaviin ytimen lähteiden hakemistoihin. Nämä symboliset linkit mahdollistavat tarvittaessa ytimen käyttöliittymien käytön käyttäjäsovelluksissa.

Vaikka käyttäjäavaruuskirjastojen käyttöliittymä on nyt erillään ytimen käyttöliittymästä, joskus käyttäjäprosessien on käytettävä ydinrajapintoja. Monet ytimen otsikkotiedostojen viittaukset viittaavat kuitenkin vain itse ytimeen, eivätkä ne saa olla käyttäjien käytettävissä. Siksi nämä mainokset ovat suojattuja #ifdef __KERNEL__ lohkot. Tästä syystä ohjaimesi, kuten muutkin ytimen koodit, on käännetty makron kanssa __YDIN__.

Yksittäisten ytimen otsikkotiedostojen roolia käsitellään tarvittaessa läpi kirjan.

Suurten ohjelmistoprojektien (kuten ydin) parissa työskentelevien kehittäjien tulee olla tietoisia ja välttää "nimitilan saastuminen". Tämä ongelma ilmenee, kun on olemassa suuri määrä funktioita ja globaaleja muuttujia, joiden nimet eivät ole tarpeeksi ilmeikäs (erotettavissa). Ohjelmoija, joka myöhemmin joutuu käsittelemään tällaisia ​​sovelluksia, joutuu käyttämään paljon enemmän aikaa "varattujen" nimien muistamiseen ja uusien elementtien ainutlaatuisten nimien keksimiseen. Nimien törmäykset (epäselvyydet) voivat aiheuttaa monenlaisia ​​ongelmia, jotka vaihtelevat virheistä moduulia ladattaessa epävakaaseen tai selittämättömään ohjelman toimintaan, jota voi esiintyä käyttäjille, jotka käyttävät eri kokoonpanoon rakennettua ydintä.

Kehittäjällä ei ole varaa tällaisiin virheisiin ytimen koodia kirjoittaessaan, koska pieninkin moduuli linkitetään koko ytimeen. Paras ratkaisu nimien törmäysten välttämiseksi on ilmoittaa ensin ohjelmaobjektit muodossa staattinen, ja toiseksi ainutlaatuisen etuliitteen käyttö järjestelmän sisällä globaalien objektien nimeämiseen. Lisäksi moduulien kehittäjänä voit hallita koodissasi olevien objektien laajuutta, kuten kuvataan myöhemmin "Ydinlinkkitaulukko" -osiossa.

Useimmat (mutta ei kaikki) komennon versiot insmod vie kaikki moduuliobjektit, joita ei ole ilmoitettu nimellä staattinen, oletuksena, ts. ellei moduuli määrittele tätä tarkoitusta varten erityisiä ohjeita. Siksi on melko järkevää ilmoittaa moduuliobjektit, joita et aio viedä staattinen.

Ainutlaatuisen etuliitteen käyttäminen paikallisille objekteille moduulin sisällä voi olla hyvä käytäntö, koska se helpottaa virheenkorjausta. Ohjaimen testaamisen aikana saatat joutua viemään lisäobjekteja ytimeen. Käyttämällä ainutlaatuista etuliitettä nimien määrittämiseen, et ole vaarassa aiheuttaa törmäyksiä ytimen nimiavaruuteen. Ytimessä käytetyt etuliitteet ovat sopimuksen mukaan pieniä kirjaimia, ja pysymme siinä.

Toinen merkittävä ero ytimen ja käyttäjän prosessien välillä on virheenkäsittelymekanismi. Ydin ohjaa käyttäjäprosessin suorittamista, joten virhe käyttäjäprosessissa johtaa järjestelmälle vaarattomaan sanomaan: segmentointivika. Samalla debuggerilla voidaan aina jäljittää virheitä käyttäjäsovelluksen lähdekoodissa. Ytimessä tapahtuvat virheet ovat kohtalokkaita - jos eivät koko järjestelmälle, niin ainakin nykyiselle prosessille. Luku 4, "Virheenkorjaustekniikat" -osiossa "Järjestelmän virheenkorjaus" tarkastellaan tapoja seurata ydinvirheitä.

Käyttäjätila ja ydintila

Moduuli toimii ns ytimen tila, kun sovellukset ovat käynnissä . Tämä käsite on käyttöjärjestelmäteorian perusta.

Yksi käyttöjärjestelmän päätarkoituksista on tarjota käyttäjälle ja käyttäjäohjelmille tietokoneresursseja, joista suurin osa on ulkoisten laitteiden edustamia. Käyttöjärjestelmän ei tule vain tarjota pääsyä resursseihin, vaan myös valvoa niiden allokointia ja käyttöä, jotta vältetään törmäykset ja luvaton pääsy. Tämän lisäksi, käyttöjärjestelmä voi luoda itsenäisiä toimintoja ohjelmille ja suojata resurssien luvattomalta käytöltä. Tämän ei-triviaalin ongelman ratkaiseminen on mahdollista vain, jos prosessori suojaa järjestelmäohjelmia käyttäjien sovelluksilta.

Melkein jokainen nykyaikainen prosessori pystyy tarjoamaan tällaisen erottelun toteuttamalla eritasoisia oikeuksia suorituskoodille (vähintään kaksi tasoa vaaditaan). Esimerkiksi I32-arkkitehtuuriprosessoreilla on neljä käyttöoikeustasoa 0–3. Lisäksi tasolla 0 on korkeimmat oikeudet. Tällaisille prosessoreille on luokka etuoikeutettuja ohjeita, jotka voidaan suorittaa vain etuoikeutetuilla tasoilla. Unix-järjestelmät käyttävät kahden tason suorittimen oikeuksia. Jos prosessorilla on enemmän kuin kaksi käyttöoikeustasoa, käytetään alinta ja korkeinta. Unix-ydin toimii korkein taso oikeudet, jotka varmistavat käyttäjän laitteiden ja prosessien hallinnan.

Kun puhumme ytimen tila Ja käyttäjän prosessitila Tämä ei tarkoita pelkästään suoritettavan koodin eri käyttöoikeustasoja, vaan myös erilaisia ​​osoiteavaruuksia.

Unix siirtää suorituksen käyttäjän prosessitilasta ydintilaan kahdessa tapauksessa. Ensinnäkin, kun käyttäjäsovellus soittaa ytimelle (järjestelmäkutsu), ja toiseksi laitteiston huollon aikana. Järjestelmäkutsun aikana suoritettu ydinkoodi suoritetaan prosessin yhteydessä, eli soittavan prosessin puolesta, sillä on pääsy prosessin osoiteavaruustietoihin. Toisaalta laitteistokeskeytystä huollettaessa suoritettava koodi on asynkroninen prosessin suhteen, eikä kuulu mihinkään erityiseen prosessiin.

Moduulien tarkoitus on laajentaa ytimen toimivuutta. Moduulin koodi suoritetaan ydintilassa. Tyypillisesti moduuli suorittaa molemmat aiemmin mainitut tehtävät: jotkut moduulin toiminnot suoritetaan osana järjestelmäkutsuja ja jotkut ovat vastuussa keskeytysten hallinnasta.

Rinnakkaisu ytimessä

Ohjelmoitaessa laiteajureita, toisin kuin ohjelmointiohjelmissa, suoritettavan koodin rinnakkaissuuntaus on erityisen akuutti. Tyypillisesti sovellus toimii peräkkäin alusta loppuun ilman huolta ympäristönsä muutoksista. Ytimen koodin on toimittava ymmärtäen, että sitä voidaan käyttää useita kertoja samanaikaisesti.

Ytimen koodin rinnastamiseen on monia syitä. Linuxissa on yleensä käynnissä useita prosesseja, ja jotkut niistä saattavat yrittää käyttää moduulikoodiasi samanaikaisesti. Monet laitteet voivat aiheuttaa laitteiston keskeytyksiä prosessorissa. Keskeytyskäsittelijöitä kutsutaan asynkronisesti ja niitä voidaan kutsua, kun ohjain suorittaa toista pyyntöä. Jotkin ohjelmiston abstraktit (kuten ytimen ajastimet, selitetty luvussa 6, "Ajan kulku") toimivat myös asynkronisesti. Lisäksi Linuxia voidaan käyttää järjestelmässä, jossa on symmetriset moniprosessorit (SMP), mikä tarkoittaa, että ohjainkoodisi voi toimia rinnakkain useissa prosessoreissa samanaikaisesti.

Näistä syistä Linux-ytimen koodin, mukaan lukien ajurikoodin, on oltava reentrant, ts. on kyettävä työskentelemään useamman kuin yhden datakontekstin kanssa samanaikaisesti. Tietorakenteet on suunniteltava siten, että ne mahdollistavat useiden säikeiden rinnakkaissuorittamisen. Ytimen koodin on puolestaan ​​kyettävä käsittelemään useita rinnakkaisia ​​tietovirtoja vahingoittamatta niitä. Rinnakkain suoritettavan koodin kirjoittaminen ja välttää tilanteet, joissa erilainen suoritusjärjestys johtaisi ei-toivottuun järjestelmän toimintaan, vaatii paljon aikaa ja ehkä paljon huijausta. Jokainen tämän kirjan ohjainesimerkki on kirjoitettu rinnakkaissuoritusta ajatellen. Tarvittaessa selitämme tällaisen koodin kirjoittamistekniikan yksityiskohdat.

Suurin osa yleinen virhe Ohjelmoijien aiheuttama ongelma on se, että he olettavat, että samanaikaisuus ei ole ongelma, koska jotkin koodisegmentit eivät voi mennä nukkumaan. Itse asiassa Linux-ydin on sivuttamaton, lukuun ottamatta keskeytysten käsittelijöitä, jotka eivät voi hankkia suoritinta kriittisen ydinkoodin suorittamisen aikana. Viime aikoina sivuttamattomuus on ollut riittävä estämään ei-toivottu rinnakkaistoiminto useimmissa tapauksissa. SMP-järjestelmissä koodin lataamista ei kuitenkaan vaadita rinnakkaisen laskennan vuoksi.

Jos koodisi olettaa, että sitä ei pureta, se ei toimi oikein SMP-järjestelmissä. Vaikka sinulla ei olisi tällaista järjestelmää, jollain muulla koodiasi käyttävällä voi olla sellainen. On myös mahdollista, että tulevaisuudessa ydin käyttää sivuttavuutta, joten jopa yhden prosessorin järjestelmät joutuvat käsittelemään samanaikaisuutta koko ajan. Tällaisten ytimien toteuttamiseen on jo vaihtoehtoja. Näin ollen järkevä ohjelmoija kirjoittaa ydinkoodin olettaen, että se toimii järjestelmässä, jossa on SMP.

Huomautus kääntäjä: Anteeksi, mutta kaksi viimeistä kappaletta eivät ole minulle selkeitä. Tämä voi johtua käännösvirheestä. Siksi esitän alkuperäisen tekstin.

Ajurien ohjelmoijien yleinen virhe on olettaa, että samanaikaisuus ei ole ongelma niin kauan kuin tietty koodisegmentti
ei mennä nukkumaan (tai "estää"). On totta, että Linux-ydin ei ole ennaltaehkäisevä; tärkeää poikkeusta lukuun ottamatta
huoltokeskeytyksiä, se ei vie prosessoria pois ytimen koodista, joka ei anna periksi vapaaehtoisesti. Menneinä aikoina tämä ei ennaltaehkäisevää
käyttäytyminen riitti estämään ei-toivotun samanaikaisuuden suurimman osan ajasta. SMP-järjestelmissä ennaltaehkäisyä ei kuitenkaan vaadita aiheuttamaan
samanaikainen toteutus.

Jos koodisi olettaa, että sitä ei ennaltaehkäistä, se ei toimi kunnolla SMP-järjestelmissä. Vaikka sinulla ei olisi tällaista järjestelmää,
muilla, jotka käyttävät koodiasi, voi olla sellainen. Tulevaisuudessa on myös mahdollista, että ydin siirtyy ennaltaehkäisevään toimintatilaan,
jolloin jopa yksiprosessorijärjestelmien on käsiteltävä samanaikaisuutta kaikkialla (jotkut ytimen muunnelmat ovat jo käytössä
se).

Tietoja nykyisestä prosessista

Vaikka ydinmoduulin koodia ei suoriteta peräkkäin kuten sovelluksia, useimmat ytimen kutsut suoritetaan suhteessa sitä kutsuvaan prosessiin. Ydinkoodi voi tunnistaa sitä kutsuneen prosessin käyttämällä yleistä osoitinta, joka osoittaa rakenteeseen struct tehtävä_rakenne, määritetty ytimien versiolle 2.4, tiedostossa mukana . Osoitin nykyinen osoittaa parhaillaan käynnissä olevan käyttäjäprosessin. Kun suoritetaan järjestelmäkutsuja, kuten avata() tai kiinni(), täytyy olla prosessi, joka aiheutti ne. Ydinkoodi voi tarvittaessa kutsua osoittimen avulla tiettyjä tietoja kutsuprosessista nykyinen. Esimerkkejä tämän osoittimen käytöstä on kohdassa "Laitetiedostojen käytön valvonta" luvussa 5, "Parannetut merkkiohjaintoiminnot".

Tänään indeksi nykyinen ei ole enää globaali muuttuja, kuten ytimen aiemmissa versioissa. Kehittäjät optimoivat pääsyn nykyistä prosessia kuvaavaan rakenteeseen siirtämällä sen pinosivulle. Voit katsoa nykyiset toteutustiedot tiedostosta . Siellä näkemäsi koodi ei ehkä vaikuta sinusta yksinkertaiselta. Muista, että Linux on SMP-keskeinen järjestelmä, ja globaali muuttuja ei yksinkertaisesti toimi, kun käsittelet useita suorittimia. Toteutustiedot jäävät piiloon muille ytimen alijärjestelmille, ja laiteohjain voi käyttää osoitinta nykyinen vain käyttöliittymän kautta .

Moduulin näkökulmasta nykyinen näyttää ulkoiselta linkiltä printk(). Moduuli voi käyttää nykyinen missä vain tarvitaan. Esimerkiksi seuraava koodinpätkä tulostaa moduulia kutsuneen prosessin prosessitunnuksen (PID) ja komennon nimen ja saa ne rakenteen vastaavien kenttien kautta. struct tehtävä_rakenne:

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

Current->comm-kenttä on sen komentotiedoston nimi, joka synnytti nykyisen prosessin.

Moduulien kokoaminen ja lataaminen

Tämän luvun loppuosa on omistettu täydellisen, vaikkakin epätyypillisen moduulin kirjoittamiselle. Nuo. Moduuli ei kuulu mihinkään luokkiin, jotka on kuvattu luvun 1, "Johdatus laiteajureihin" -osiossa "Laite- ja moduuliluokat". Tässä luvussa esitetyn esimerkkiohjaimen nimi on kallo (Simple Kernel Utility for Loading Localities). Voit käyttää scull-moduulia mallina oman paikallisen koodin kirjoittamiseen.

Käytämme käsitettä "paikallinen koodi" (paikallinen) korostaaksemme henkilökohtaisen koodisi muutoksia vanhan hyvän Unix-perinteen mukaisesti (/usr/local).

Ennen kuin täytämme init_module()- ja cleanup_module()-funktiot, kirjoitamme kuitenkin Makefile-komentosarjan, jota make käyttää moduulin objektikoodin rakentamiseen.

Ennen kuin esiprosessori voi käsitellä minkään otsikkotiedoston sisällyttämisen, makrosymboli __KERNEL__ on määritettävä #define-käskyllä. Kuten aiemmin mainittiin, ytimen käyttöliittymätiedostoissa voidaan määrittää ydinkohtainen konteksti, joka näkyy vain, jos __KERNEL__-symboli on ennalta määritetty esikäsittelyssä.

Toinen tärkeä #define-direktiivin määrittelemä symboli on MODULE-symboli. On määritettävä ennen käyttöliittymän käyttöönottoa (lukuun ottamatta niitä ajureita, jotka käännetään ytimen kanssa). Ytimeen koottuja ohjaimia ei kuvata tässä kirjassa, joten MODULE-symboli on läsnä kaikissa esimerkeissämme.

Jos rakennat moduulia järjestelmälle, jossa on SMP, sinun on myös määritettävä makrosymboli __SMP__ ennen ytimen liitäntöjen käyttöönottoa. Ytimen versiossa 2.2 erillinen ytimen kokoonpanon kohta esitteli valinnan yhden prosessorin ja moniprosessorisen järjestelmän välillä. Siksi seuraavien rivien sisällyttäminen moduulisi ensimmäisiksi riveiksi johtaa moniprosessoritukeen.

#sisältää #ifdef CONFIG_SMP # määritä __SMP__ #endif

Moduulien kehittäjien tulisi myös määrittää kääntäjälle -O-optimointilippu, koska monet funktiot on ilmoitettu ytimen otsikkotiedostoissa. Gcc-kääntäjä ei suorita funktioiden sisäistä laajennusta, ellei optimointi ole käytössä. Kun sallit tekstin sisäisten korvausten laajentamisen käyttämällä valintoja -g ja -O, voit myöhemmin suorittaa virheenkorjauskoodin, joka käyttää virheenkorjausohjelman sisäisiä toimintoja. Koska ydin käyttää laajasti sisäisiä toimintoja, on erittäin tärkeää, että ne laajennetaan oikein.

Huomaa kuitenkin, että optimoinnin käyttäminen -O2-tason yläpuolella on riskialtista, koska kääntäjä saattaa laajentaa toimintoja, joita ei ole ilmoitettu inline-tilassa. Tämä voi aiheuttaa ongelmia, koska... Jotkin funktiokoodit odottavat löytävänsä kutsunsa vakiopinon. Inline-laajennuksella tarkoitetaan toimintokoodin lisäämistä kutsukohtaan vastaavan toimintokutsukäskyn sijaan. Vastaavasti tässä tapauksessa, koska funktiokutsua ei ole, sen kutsusta ei ole pinoa.

Saatat joutua varmistamaan, että käytät samaa kääntäjää moduulien kääntämiseen, jota käytettiin ytimen rakentamiseen, johon moduuli asennetaan. Katso lisätietoja alkuperäisestä asiakirjasta tiedostosta Dokumentaatio/muutokset sijaitsee ytimen lähdehakemistossa. Ytimen ja kääntäjien kehitys on tyypillisesti synkronoitu kehitystiimien kesken. Joissakin tapauksissa yhden näistä elementeistä päivittäminen paljastaa virheitä toisessa. Jotkut jakeluvalmistajat toimittavat kääntäjästä erittäin uusia versioita, jotka eivät vastaa heidän käyttämänsä ydintä. Tässä tapauksessa ne tarjoavat yleensä erillisen paketin (kutsutaan usein kgcc) erityisesti suunnitellulla kääntäjällä
ytimen kokoelma.

Lopuksi, ikävien virheiden estämiseksi, suosittelemme, että käytät käännösvaihtoehtoa - Seinä(kaikki varoitukset - kaikki varoitukset). Kaikkien näiden varoitusten täyttämiseksi sinun on ehkä muutettava tavallista ohjelmointityyliäsi. Ydinkoodia kirjoitettaessa on suositeltavaa käyttää Linus Torvaldsin ehdottamaa koodaustyyliä. Kyllä, dokumentti Dokumentaatio/koodaustyyli, ytimen lähdehakemistosta, on varsin mielenkiintoinen ja suositellaan kaikille ydintason ohjelmoinnista kiinnostuneille.

On suositeltavaa sijoittaa muuttujaan joukko moduulien käännöslippuja, joihin olemme äskettäin tutustuneet LIPUT sinun Makefile. Make-apuohjelmalle tämä on erityinen muuttuja, jonka käyttö selviää seuraavasta kuvauksesta.

Muuttujan lippujen lisäksi LIPUT, saatat tarvita Makefile-kohteen, joka yhdistää erilaisia ​​objektitiedostoja. Tällainen tavoite on tarpeen vain silloin, kun moduulikoodi on jaettu useisiin lähdetiedostoihin, mikä ei yleensä ole harvinaista. Objektitiedostot yhdistetään komentoon ld -r, joka ei ole linkitysoperaatio yleisesti hyväksytyssä mielessä, vaikka linkkeri( ld). Komennon suorittamisen tulos ld -r on toinen objektitiedosto, joka yhdistää linkittäjän syöttötiedostojen objektikoodit. Vaihtoehto -r tarkoittaa " siirrettävä - siirrettävyys”, eli Siirrämme komennon tulostiedoston osoiteavaruuteen, koska se ei vielä sisällä funktiokutsujen absoluuttisia osoitteita.

Seuraava esimerkki näyttää vähimmäismäärän Makefile, joka tarvitaan kahdesta lähdetiedostosta koostuvan moduulin kääntämiseen. Jos moduulisi koostuu yhdestä lähdetiedostosta, sinun on poistettava komennon sisältävä kohde annetusta esimerkistä ld -r.

# Ytimen lähdehakemiston polku voidaan muuttaa täällä, # tai voit antaa sen parametrina kutsuttaessa "make" KERNELDIR = /usr/src/linux include $(KERNELDIR)/.config CFLAGS = -D__KERNEL__ -DMODULE - I$(KERNELDIR) /include \ -O -Wall ifdef CONFIG_SMP CFLAGS += -D__SMP__ -DSMP endif all: skull.o skull.o: skull_init.o skull_clean.o $(LD) -r $^ -o $@ puhdas : rm -f * .o *~ ydin

Jos olet uusi tekemisissä, saatat yllättyä, että *.c-tiedostojen kääntämiselle *.o-objektitiedostoiksi ei ole sääntöjä. Tällaisten sääntöjen määritteleminen ei ole välttämätöntä, koska make-apuohjelma muuntaa tarvittaessa itse *.c-tiedostot *.o-tiedostoiksi käyttämällä oletuskääntäjää tai muuttujan määrittämää kääntäjää $(CC). Tässä tapauksessa muuttujan sisältö $(CFLAGS) käytetään käännöslippujen määrittämiseen.

Seuraava vaihe moduulin rakentamisen jälkeen on ladata se ytimeen. Olemme jo sanoneet, että tähän käytetään insmod-apuohjelmaa, joka yhdistää kaikki moduulin määrittelemättömät symbolit (funktiokutsut jne.) käynnissä olevan ytimen symbolitaulukkoon. Toisin kuin linkittäjä (esim. ld), se ei kuitenkaan muuta moduulin levytiedostoa, vaan lataa ytimeen linkitetyn moduuliobjektin RAM-muistiin. Insmod-apuohjelma voi hyväksyä joitain komentorivivaihtoehtoja. Yksityiskohtia voi katsoa kautta mies insmod. Näiden valintojen avulla voit esimerkiksi määrittää moduulissasi tiettyjä kokonaisluku- ja merkkijonomuuttujia määritettyihin arvoihin ennen moduulin yhdistämistä ytimeen. Siten, jos moduuli on suunniteltu oikein, se voidaan konfiguroida käynnistyksen yhteydessä. Tämä moduulin konfigurointimenetelmä antaa käyttäjälle enemmän joustavuutta kuin konfigurointi käännöshetkellä. Käynnistysajan määritykset selitetään jäljempänä tässä luvussa "Manuaalinen ja automaattinen määritys" -osiossa.

Jotkut lukijat ovat kiinnostuneita insmod-apuohjelman toiminnan yksityiskohdista. Insmodin toteutus perustuu useisiin järjestelmäkutsuihin, jotka on määritelty tiedostossa kernel/module.c. Sys_create_module()-funktio varaa tarvittavan määrän muistia ytimen osoiteavaruudessa moduulin lataamista varten. Tämä muisti varataan vmalloc()-funktiolla (katso "vmalloc ja ystävät" -osio luvussa 7, "Muistin saaminen haltuun"). Järjestelmäkutsu get_kernel_sysms() palauttaa ytimen symbolitaulukon, jota käytetään määrittämään objektien todelliset osoitteet linkittäessä. Funktio sys_init_module() kopioi moduulin objektikoodin ytimen osoiteavaruuteen ja kutsuu moduulin alustusfunktiota.

Jos tarkastelet ytimen koodin lähteitä, löydät järjestelmäkutsujen nimet, jotka alkavat sys_-etuliitteellä. Tätä etuliitettä käytetään vain järjestelmäpuheluissa. Mikään muu toiminto ei saa käyttää sitä. Pidä tämä mielessä, kun käsittelet ytimen koodin lähteitä grep-hakuapuohjelmalla.

Versioriippuvuudet

Jos et tiedä muuta kuin mitä tässä on käsitelty, luomasi moduulit on todennäköisesti käännettävä uudelleen jokaiselle ytimen versiolle, johon ne on linkitetty. Jokaisen moduulin on määritettävä symboli nimeltä __module_kernel_version, jonka arvo
verrataan nykyisen ytimen versioon käyttämällä insmod-apuohjelmaa. Tämä symboli sijaitsee osiossa .modinfo ELF (Executable and Linking Format) -tiedostot. Tämä on selitetty tarkemmin luvussa 11 “kmod ja edistynyt modulointi”. Huomaa, että tämä versionhallintamenetelmä on sovellettavissa vain ytimen versioille 2.2 ja 2.4. 2.0-ytimessä tämä tehdään hieman eri tavalla.

Kääntäjä määrittelee tämän symbolin aina, kun otsikkotiedosto sisältyy . Siksi aiemmin annetussa hello.c-esimerkissä emme kuvanneet tätä symbolia. Tämä tarkoittaa myös, että jos moduulisi koostuu useista lähdetiedostoista, sinun on sisällytettävä tiedosto koodiisi vain kerran. Poikkeuksena on tapaus, kun käytetään määritelmää __EI_VERSION__, johon tapaamme myöhemmin.

Alla on kuvatun symbolin määritelmä tiedostosta module.h, joka on purettu 2.4.25-ytimen koodista.

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

Jos moduuli ei lataudu versiovirheen vuoksi, voit yrittää ladata tämän moduulin välittämällä insmod-avaimen apuohjelman parametririville -f(pakottaa). Tämä moduulin lataustapa ei ole turvallinen eikä aina onnistu. Mahdollisten epäonnistumisten syitä on melko vaikea selittää. On mahdollista, että moduuli ei lataudu, koska symbolit eivät ole selvitettävissä linkityksen aikana. Tässä tapauksessa saat asianmukaisen virheilmoituksen. Epäonnistumisen syyt voivat olla myös ytimen toiminnan tai rakenteen muutoksissa. Tässä tapauksessa moduulin lataaminen voi johtaa vakaviin ajonaikaisiin virheisiin sekä järjestelmäpaniikkiin. Jälkimmäisen pitäisi toimia hyvänä kannustimena käyttää versionhallintajärjestelmää. Versioiden yhteensopimattomuudet voidaan käsitellä tyylikkäämmin käyttämällä ytimen versionhallintaa. Puhumme tästä yksityiskohtaisesti "Moduuliversion hallinta" -osiossa luvussa 11 "kmod and Advanced Modularization".

Jos haluat kääntää moduulisi tiettyä ydinversiota varten, sinun on sisällytettävä otsikkotiedostot kyseiselle ytimen versiolle. Yllä kuvatussa Makefile-esimerkissä muuttujaa käytettiin määrittämään näiden tiedostojen hakemisto KERNELDIR. Tällainen mukautettu käännös ei ole harvinaista, kun ydinlähteitä on saatavilla. Ei myöskään ole harvinaista, että hakemistopuussa on erilaisia ​​versioita ytimestä. Kaikki tämän kirjan moduuliesimerkit käyttävät muuttujaa KERNELDIR osoittamaan sen ytimen version lähdehakemiston sijainnin, johon koottu moduuli on linkitetty. Voit määrittää tämän hakemiston järjestelmämuuttujan avulla tai välittää sen sijainnin komentorivivalintojen avulla.

Moduulia ladattaessa insmod-apuohjelma käyttää omia hakupolkujaan moduulin objektitiedostoille ja selaa versiokohtaisia ​​hakemistoja alkaen /lib/modules. Ja vaikka apuohjelman vanhemmat versiot sisälsivät nykyisen hakemiston hakupolulle, tätä toimintaa ei pidetä nyt turvallisuussyistä hyväksyttävänä (samat ongelmat kuin järjestelmämuuttujan käytössä PATH). Joten jos haluat ladata moduulin nykyisestä hakemistosta, voit määrittää sen tyyliin ./module.o. Tämä moduulin sijainnin ilmaisu toimii kaikissa insmod-apuohjelman versioissa.

Joskus saatat kohdata ytimen rajapintoja, jotka eroavat versioiden 2.0.x ja 2.4.x välillä. Tässä tapauksessa sinun on turvauduttava makroon, joka määrittää nykyisen ytimen version. Tämä makro sijaitsee otsikkotiedostossa . Ilmoitamme käyttöliittymien eroista tapauksia, kun niitä käytetään. Tämä voidaan tehdä joko heti kuvauksen yhteydessä tai osion lopussa erityisessä versioriippuvuuksille omistetussa osiossa. Joissakin tapauksissa yksityiskohtien sijoittaminen erilliseen osioon auttaa välttämään tämän kirjan kannalta merkityksellisen ytimen version 2.4.x kuvauksen monimutkaisuutta.

Otsikkotiedostossa linux/versio.h Seuraavat makrot on määritelty ytimen version määrittämiseen liittyen.

UTS_RELEASE Makro, joka laajenee nykyistä ytimen versiota kuvaavaksi merkkijonoksi
lähdepuu. Makro voi laajentua esimerkiksi näin:
linja: "2.3.48" . LINUX_VERSION_CODE Tämä makro laajenee ytimen version binääriesitykseen
yksi tavu kutakin numeron osaa kohti. Esimerkiksi binääri
version 2.3.48 esitys on 131888 (desimaali
Hex 0x020330:n esitys). Mahdollisesti binääri
Edustus on sinulle kätevämpi kuin merkkijonoesitys. Huomaa mikä on
esityksen avulla voit kuvata enintään 256 vaihtoehtoa kussakin
numeron osia. KERNEL_VERSION (suur, sivu, julkaisu) Tämän makromäärityksen avulla voit rakentaa "kernel_version_code"
yksittäisistä elementeistä, jotka muodostavat ytimen version. Esimerkiksi,
seuraava makro KERNEL_VERSION(2, 3, 48)
laajenee arvoon 131888. Tämä makron määritelmä on erittäin kätevä, kun
vertaamalla nykyistä ytimen versiota vaadittuun versioon. Tulemme toistuvasti
käytä tätä makromääritelmää koko kirjassa.

Tässä on tiedoston sisältö: linux/versio.h ytimelle 2.4.25 (otsikkotiedoston teksti annetaan kokonaisuudessaan).

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

version.h-otsikkotiedosto sisältyy module.h-tiedostoon, joten sinun ei yleensä tarvitse sisällyttää version.h-tiedostoa erikseen moduulikoodiisi. Toisaalta voit estää version.h-otsikkotiedoston sisällyttämisen module.h-tiedostoon ilmoittamalla makron __EI_VERSION__. Tulet käyttämään __EI_VERSION__, esimerkiksi siinä tapauksessa, että sinun on otettava käyttöön useisiin lähdetiedostoihin, jotka myöhemmin linkitetään yhdeksi moduuliksi. Ilmoitus __EI_VERSION__ ennen kuin sisällytät module.h-otsikkotiedoston
automaattinen merkkijonon kuvaus __module_kernel_version tai sitä vastaava lähdetiedostoissa. Saatat tarvita tätä tyydyttääksesi linkittäjän valitukset ld -r, jotka eivät pidä useista symbolien kuvauksista linkkitaulukoissa. Yleensä, jos moduulin koodi on jaettu useisiin lähdetiedostoihin, mukaan lukien otsikkotiedosto , sitten ilmoitus __EI_VERSION__ tehdään kaikissa näissä tiedostoissa yhtä lukuun ottamatta. Kirjan lopussa on esimerkki moduulista, joka käyttää __EI_VERSION__.

Useimpia ytimen versioiden riippuvuuksia voidaan käsitellä makromäärityksiä käyttävien esiprosessorin käskyjen pohjalta rakennetun logiikan avulla KERNEL_VERSION Ja LINUX_VERSION_CODE. Versioriippuvuuksien tarkistaminen voi kuitenkin monimutkaistaa moduulikoodin luettavuutta heterogeenisten ohjeiden vuoksi. #ifdef. Siksi ehkä paras ratkaisu on sijoittaa riippuvuustarkistus erilliseen otsikkotiedostoon. Tästä syystä esimerkkimme sisältää otsikkotiedoston sysdep.h, jota käytetään sisältämään kaikki versioriippuvuustarkistuksiin liittyvät makromääritykset.

Ensimmäinen versioriippuvuus, jonka haluamme edustaa, on kohdeilmoituksessa" tee asennus" ajurin käännösskriptimme. Kuten arvata saattaa, asennushakemisto, joka muuttuu käytetyn ydinversion mukaan, valitaan version.h-tiedoston katselun perusteella. Tässä on koodinpätkä tiedostosta Säännöt.make, jota kaikki ytimen Makefile-tiedostot käyttävät.

VERSIOFILE = $(INCLUDEDIR)/linux/version.h VREION = $(shell awk -F\" "/REL/ (tulosta $$2)" $(VERSIONFILE)) INSTALLDIR = /lib/moduulit/$(VERSION)/misc

Huomaa, että käytämme misc-hakemistoa asentaaksemme kaikki ajurit (INSTALLDIR-ilmoitus yllä olevassa Makefile-esimerkissä). Ytimen versiosta 2.4 alkaen tämä hakemisto on suositeltu hakemisto mukautettujen ohjainten sijoittamiseen. Lisäksi sekä vanhat että uudet modutils-paketin versiot sisältävät hakupoluissaan misc-hakemiston.

Yllä olevaa INSTALLDIR-määritelmää käyttäen Makefile-tiedoston asennuskohde saattaa näyttää tältä:

Asenna: asennus -d $(INSTALLDIR) asennus -c $(OBJS) $(INSTALLDIR)

Alustariippuvuus

Jokaisella tietokonealustalla on omat ominaisuutensa, jotka ytimen kehittäjien on otettava huomioon parhaan suorituskyvyn saavuttamiseksi.

Ytimen kehittäjillä on paljon enemmän vapautta valinnassa ja päätöksenteossa kuin sovellusten kehittäjillä. Juuri tämä vapaus antaa sinun optimoida koodisi ja saada kaikki irti jokaisesta tietystä alustasta.

Moduulin koodi on käännettävä käyttämällä samoja kääntäjän valintoja, joita käytettiin ytimen kääntämiseen. Tämä koskee sekä samojen prosessorirekisterien käyttötapoja että saman tason optimointia. Tiedosto Säännöt.make, joka sijaitsee ytimen lähdepuun juuressa, sisältää alustakohtaiset määritelmät, jotka on sisällytettävä kaikkiin Makefile-kokoelmaan. Kaikkia alustakohtaisia ​​käännösskriptejä kutsutaan Makefileiksi. alusta ja sisältävät muuttujien arvot make-apuohjelmalle nykyisen ytimen kokoonpanon mukaan.

Toinen Makefilen mielenkiintoinen ominaisuus on sen tuki useille alustoille tai yksinkertaisesti ristiin kääntämiselle. Tätä termiä käytetään, kun sinun on käännettävä koodia toiselle alustalle. Esimerkiksi käyttämällä i86-alustaa aiot luoda koodin M68000-alustalle. Jos aiot ristiin kääntää, sinun on vaihdettava käännöstyökalusi ( gcc, ld jne.) toisella vastaavilla työkaluilla
(Esimerkiksi, m68k-linux-gcc, m68k-linux-ld). Käytetty etuliite voidaan määrittää joko $(CROSS_COMPILE) Makefile-muuttujalla, make-apuohjelman komentorivin valinnalla tai järjestelmäympäristömuuttujalla.

SPARC-arkkitehtuuri on erikoistapaus, jota on käsiteltävä Makefile-sovelluksessa vastaavasti. SPARC64 (SPARC V9) -alustalla ajettavat käyttäjäohjelmat ovat binääritiedostoja, jotka on yleensä suunniteltu SPARC32 (SPARC V8) -alustalle. Siksi SPARC64-alustan oletuskääntäjä (gcc) luo kohdekoodin SPARC32:lle. Toisaalta SPARC V9:llä toimimaan suunnitellun ytimen tulee sisältää SPARC V9:n objektikoodi, joten silloinkin tarvitaan ristikääntäjä. Kaikki SPARC64:lle suunnitellut GNU/Linux-jakelut sisältävät sopivan ristiinkääntäjän, joka on valittava Makefile-tiedostosta ytimen käännösskriptiä varten.

Ja vaikka täydellinen luettelo versio- ja alustariippuvuuksista on hieman monimutkaisempi kuin tässä on kuvattu, se riittää ristiin kääntämiseen. Lisätietoja saat Makefile-kokoelman skripteistä ja ytimen lähdetiedostoista.

Ytimen ominaisuudet 2.6

Aika ei pysähdy. Ja nyt olemme todistamassa uuden sukupolven ytimen 2.6 syntymistä. Valitettavasti tämän kirjan alkuperäiskappale ei kata uutta ydintä, joten kääntäjä ottaa vapauden täydentää käännöstä uudella tiedolla.

Voit käyttää integroituja kehitysympäristöjä, kuten TimeSysin TimeStormia, jotka luovat oikein rungon ja käännösskriptin moduulillesi vaaditusta ydinversiosta riippuen. Jos aiot kirjoittaa kaiken tämän itse, tarvitset lisätietoja uuden ytimen tuomista tärkeimmistä eroista.

Yksi 2.6-ytimen ominaisuuksista on tarve käyttää module_init()- ja module_exit()-makroja rekisteröimään nimenomaisesti alustus- ja lopetusfunktioiden nimet.

2.4-ytimessä käyttöön otettua MODULE_LISENCE()-makroa tarvitaan edelleen, jos et halua nähdä vastaavia varoituksia moduulia ladattaessa. Voit valita seuraavat makroon siirrettävät lisenssimerkkijonot: “GPL”, “GPL v2”, “GPL ja lisäoikeudet”, “Dual BSD/GPL” (valinta BSD- tai GPL-lisenssien välillä), “Kaksois-MPL/GPL " (valinta Mozilla- tai GPL-lisenssien välillä) ja
"Omistaja".

Uudelle ytimelle tärkeämpää on uusi moduulien käännösmalli, joka sisältää paitsi itse moduulin koodin muuttamisen myös sen kääntämisen Makefile-skriptin.

Siten MODULE-makrosymbolin määrittelyä ei enää tarvita moduulikoodissa tai Makefile-tiedostossa. Tarvittaessa uusi käännösmalli itse määrittää tämän makrosymbolin. Sinun ei myöskään tarvitse erikseen määritellä __KERNEL__-makrosymboleja tai uudempia, kuten KBUILD_BASENAME ja KBUILD_MODNAME.

Älä myöskään määritä optimointitasoa käännösvaiheessa (-O2 tai muu), koska moduulisi käännetään koko joukolla lippuja, mukaan lukien optimointiliput, joilla kaikki muut ytimen moduulit käännetään - make-apuohjelma käyttää automaattisesti kaikkia tarvittavia lippuja.

Näistä syistä Makefile 2.6-ytimen moduulin kääntämiseen on paljon yksinkertaisempi. Joten hello.c-moduulin Makefile näyttää tältä:

Obj-m:= hei.o

Moduulin kääntämiseksi tarvitset kuitenkin kirjoitusoikeuden ytimen lähdepuuhun, jossa luodaan väliaikaiset tiedostot ja hakemistot. Siksi komennon kääntää moduuli 2.6-ytimelle, joka on määritetty nykyisestä moduulin lähdekoodin sisältävästä hakemistosta, pitäisi näyttää tältä:

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

Joten meillä on moduulin lähde hei-2.6.c, käännös ytimessä 2.6:

//hello-2.6.c #include #sisältää #sisältää MODULE_LICENSE("GPL"); static int __init my_init(void) ( printk("Hei maailma\n"); return 0; ); static void __exit my_cleanup(void) ( printk("Hyvästi\n"); ); module_init(my_init); module_exit(oma_siivous);

Näin ollen meillä on Makefile:

Obj-m:= hei-2.6.o

Kutsumme make-apuohjelmaa käsittelemään Makefilemme seuraavilla parametreilla:

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

Normaali käännösprosessi tuottaa seuraavan vakiotulosteen:

Merkki: Syötä hakemistoon `/usr/src/linux-2.6.3" *** Varoitus: SUBDIRSin ohittaminen komentorivillä voi aiheuttaa *** epäjohdonmukaisuuksia make: "arch/i386/kernel/asm-offsets.s" ei vaatii päivityksen. CHK include/asm-i386/asm_offsets.h CC [M] /home/knz/j.kernel/3/hello-2.6.o Moduulien rakentaminen, vaihe 2. /usr/src/linux-2.6.3/scripts/Makefile .modpost:17: *** Huh, sinulla on vanhentuneet moduulimerkinnät. Sekoitat SUBDIRSin kanssa, /usr/src/linux-2.6.3/scripts/Makefile.modpost:18: älä valittaa, jos jokin menee pieleen. MODPOST CC /home/knz/j.kernel/3/hello-2.6.mod.o LD [M] /home/knz/j.kernel/3/hello-2.6.ko make: Poistu hakemistosta `/usr/src / linux-2.6.3"

Käännöksen lopputuloksena on moduulitiedosto hello-2.6.ko, joka voidaan asentaa ytimeen.

Huomaa, että 2.6-ytimessä moduulitiedostojen pääte on .ko eikä .o, kuten 2.4-ytimessä.

Ytimen symbolitaulukko

Olemme jo puhuneet siitä, kuinka insmod-apuohjelma käyttää ytimen julkista symbolitaulukkoa linkittäessään moduulin ytimeen. Tämä taulukko sisältää globaalien ydinobjektien - funktioiden ja muuttujien - osoitteet, joita tarvitaan modulaaristen ohjainvaihtoehtojen toteuttamiseen. Ytimen julkinen symbolitaulukko voidaan lukea tekstimuodossa /proc/ksyms-tiedostosta, jos ytimesi tukee /proc-tiedostojärjestelmää.

Ytimessä 2.6 /proc/ksyms nimettiin uudelleen muotoon /proc/modules.

Kun moduuli ladataan, moduulin viemistä symboleista tulee osa ytimen symbolitaulukkoa, ja voit tarkastella niitä hakemistossa /proc/ksyms.

Uudet moduulit voivat käyttää moduulisi viemiä symboleja. Esimerkiksi msdos-moduuli luottaa rasvamoduulin viemiin merkkeihin, ja jokainen lukutilassa käytettävä USB-laite käyttää usbcore- ja syöttömoduulien merkkejä. Tätä suhdetta, joka toteutetaan moduulien peräkkäisellä lataamisella, kutsutaan moduulipinoksi.

Moduulipinoa on kätevä käyttää luotaessa monimutkaisia ​​moduuliprojekteja. Tämä abstraktio on hyödyllinen laiteohjainkoodin erottamisessa laitteistosta riippuvaisiin ja laitteistosta riippumattomiin osiin. Esimerkiksi video-for-linux-ohjainsarja koostuu ydinmoduulista, joka vie symboleja matalan tason ohjaimelle, joka ottaa huomioon käytettävän laitteiston erityispiirteet. Kokoonpanosi mukaan lataat päävideomoduulin ja laitteistokohtaisen moduulin. Samalla tavalla toteutetaan tuki rinnakkaisporteille ja laajalle joukolle kytkettyjä laitteita, kuten USB-laitteita. Rinnakkaisportin järjestelmäpino on esitetty kuvassa. 2-2. Nuolet osoittavat vuorovaikutuksen moduulien ja ytimen ohjelmointirajapinnan välillä. Vuorovaikutusta voidaan toteuttaa sekä toimintojen tasolla että toimintojen hallitsemien tietorakenteiden tasolla.

Kuva 2-2. Rinnakkaisporttimoduulipino

Pinomoduuleita käytettäessä on kätevää käyttää modprobe-apuohjelmaa. Modprobe-apuohjelman toiminnallisuus on monella tapaa samanlainen kuin insmod-apuohjelma, mutta moduulia ladattaessa se tarkistaa sen taustalla olevat riippuvuudet ja tarvittaessa lataa tarvittavat moduulit, kunnes tarvittava moduulipino on täytetty. Siten yksi modprobe-komento voi johtaa useisiin kutsuihin insmod-komennolle. Voidaan sanoa, että modprobe-komento on älykäs kääre insmodin ympärille. Voit käyttää modprobea insmodin sijaan kaikkialla, paitsi silloin, kun lataat omia moduuleja nykyisestä hakemistosta, koska modprobe tarkastelee vain tiettyjä moduulihakemistoja, eikä pysty tyydyttämään mahdollisia riippuvuuksia.

Moduulien jakaminen osiin auttaa vähentämään kehitysaikaa yksinkertaistamalla ongelman määrittelyä. Tämä on samanlainen kuin täytäntöönpanomekanismin ja ohjauspolitiikan erottaminen toisistaan, jota käsitellään luvussa 1, "Johdatus laiteajureihin".

Tyypillisesti moduuli toteuttaa toiminnallisuutensa tarvitsematta viedä symboleja ollenkaan. Sinun on vietävä symboleja, jos muut moduulit voivat hyötyä siitä. Saatat joutua sisällyttämään erityisohjeen estääksesi ei-staattisten merkkien viennin, koska Useimmat modutil-toteutukset vievät ne kaikki oletuksena.

Linux-ytimen otsikkotiedostot tarjoavat kätevän tavan hallita symbolien näkyvyyttä, mikä estää ytimen symbolitaulukon nimitilan saastumisen. Tässä luvussa kuvattu mekanismi toimii ytimissä versiosta 2.1.18 alkaen. Ytimessä 2.0 oli täysin erilainen ohjausmekanismi
symbolien näkyvyys, joka kuvataan luvun lopussa.

Jos moduulisi ei tarvitse viedä symboleita ollenkaan, voit nimenomaisesti sijoittaa seuraavan makrokutsun moduulin lähdetiedostoon:

EXPORT_NO_SYMBOLS;

Tämä linux/module.h-tiedostossa määritetty makrokutsu laajenee assembler-direktiiviksi ja voidaan määrittää missä tahansa moduulissa. Kuitenkin luotaessa koodia, joka on siirrettävä eri ytimiin, tämä makrokutsu on asetettava moduulin alustusfunktioon (init_module), koska tämän makron versio, jonka määritimme sysdep.h-tiedostossamme vanhemmille ydinversioille, toimii vain. tässä.

Toisaalta, jos sinun täytyy viedä joitain symboleja moduulistasi, sinun on käytettävä makrosymbolia
EXPORT_SYMTAB. Tämä makrosymboli on määritettävä ennen sisällyttämällä otsikkotiedoston module.h. Se on yleinen käytäntö
tämän makromerkin määrittäminen lipun avulla -D Makefilessä.

Jos makrosymboli EXPORT_SYMTAB määritelty, yksittäiset symbolit voidaan viedä makroparilla:

EXPORT_SYMBOL(nimi); EXPORT_SYMBOL_NOVERS(nimi);

Jompikumpi näistä kahdesta makrosta asettaa annetun symbolin saataville moduulin ulkopuolella. Erona on se, että makro EXPORT_SYMBOL_NOVERS vie symbolin ilman versiotietoja (katso luku 11 “kmod ja Advanced Modularization”). Lisätietoja
tarkista otsikkotiedosto , vaikka se, mitä on sanottu, on käytännössä riittävää
makroja.

Moduulien alustaminen ja viimeistely

Kuten mainittiin, init_module()-funktio rekisteröi moduulin toiminnalliset komponentit ytimeen. Rekisteröinnin jälkeen moduulia käyttävällä sovelluksella on pääsy moduulin sisääntulopisteisiin ytimen tarjoaman käyttöliittymän kautta.

Moduulit voivat rekisteröidä monia erilaisia ​​komponentteja, jotka rekisteröityessään ovat moduulitoimintojen nimiä. Osoitin tietorakenteeseen, joka sisältää osoittimia ehdotetun toiminnon toteuttaviin toimintoihin, välitetään ytimen rekisteröintifunktiolle.

Luvussa 1 "Johdatus laiteajureihin" mainittiin päälaitteiden luokitus. Voit rekisteröidä paitsi siellä mainittuja laitetyyppejä, myös mitä tahansa muita, jopa ohjelmistoabstraktioita, kuten esimerkiksi /proc-tiedostoja jne. Kaikki mikä voi toimia ytimessä ohjaimen ohjelmointirajapinnan kautta, voidaan rekisteröidä ajureiksi .

Jos haluat lisätietoja ajureiden tyypeistä, jotka rekisteröidään käyttämällä esimerkkinä ydintäsi, voit toteuttaa haun EXPORT_SYMBOL-alimerkkijonolla ytimen lähteistä ja löytää eri ohjaimien tarjoamat aloituskohdat. Rekisteröintitoiminnot käyttävät pääsääntöisesti etuliitettä nimessään register_,
joten toinen mahdollinen tapa löytää ne on etsiä alimerkkijonoa register_/proc/ksyms-tiedostossa grep-apuohjelmalla. Kuten jo mainittiin, 2.6.x-ytimessä /proc/ksyms-tiedosto korvattiin tiedostolla /proc/modules.

Virheen käsittelyssä init_module

Jos moduulia alustettaessa tapahtuu virhe, jo suoritettu alustus on kumottava ennen moduulin latautumisen pysäyttämistä. Virhe voi johtua esimerkiksi siitä, että järjestelmän muisti ei riitä tietorakenteiden allokoinnissa. Valitettavasti näin voi tapahtua, ja hyvän koodin pitäisi pystyä käsittelemään tällaisia ​​​​tilanteita.

Kaikki, mikä on rekisteröity tai varattu ennen virhettä init_module() alustusfunktiossa, on peruutettava tai vapautettava itse, koska Linux-ydin ei seuraa alustusvirheitä eikä peruuta lainaamista ja resurssien myöntämistä moduulikoodilla. Jos et peruuttanut tai et pystynyt palauttamaan valmiita rekisteröintiä, ydin pysyy epävakaassa tilassa ja kun moduuli ladataan uudelleen
et voi toistaa jo rekisteröityjen elementtien rekisteröintiä etkä voi peruuttaa aiemmin tehtyä rekisteröintiä, koska init_module()-funktion uudessa ilmentymässä sinulla ei ole rekisteröityjen funktioiden osoitteiden oikeaa arvoa. Järjestelmän palauttaminen aiempaan tilaan vaatii useiden monimutkaisten temppujen käyttöä, ja tämä tehdään usein yksinkertaisesti käynnistämällä järjestelmä uudelleen.

Järjestelmän edellisen tilan palauttaminen moduulin alustusvirheiden ilmetessä on parasta toteuttaa goto-operaattorilla. Yleensä tätä toimijaa kohdellaan erittäin negatiivisesti ja jopa vihaisesti, mutta juuri tässä tilanteessa hän osoittautuu erittäin hyödylliseksi. Siksi ytimessä goto-käskyä käytetään usein käsittelemään moduulin alustusvirheitä.

Seuraava yksinkertainen koodi, jossa käytetään esimerkkinä valerekisteröinti- ja rekisteröinnin peruutustoimintoja, osoittaa tämän tavan käsitellä virheitä.

Int init_module(void) ( int err; /* rekisteröinti vie osoittimen ja nimen */ err = register_this(ptr1, "kallo"); if (err) goto fail_this; err = register_that(ptr2, "kallo"); if (err) goto fail_that; err = register_those(ptr3, "kallo"); jos (err) goto fail_those; return 0; /* menestys */ fail_those: unregister_that(ptr2, "skull"); fail_that: unregister_this(ptr1, " kallo"); fail_this: return err; /* levittää virhettä */ )

Tämä esimerkki yrittää rekisteröidä kolme moduulikomponenttia. Goto-käskyä käytetään, kun tapahtuu rekisteröintivirhe, joka aiheuttaa rekisteröityjen komponenttien rekisteröinnin poistamisen ennen moduulin lataamisen pysäyttämistä.

Toinen esimerkki goto-käskyn käyttämisestä koodin lukemisen helpottamiseksi on "muistaminen" onnistuneista moduulirekisteröinneistä ja kutsu cleanup_module() välittämään nämä tiedot virheen sattuessa. Cleanup_module()-funktio on suunniteltu palauttamaan valmiit alustustoiminnot, ja sitä kutsutaan automaattisesti, kun moduuli puretaan. Init_module()-funktion palauttaman arvon on oltava
edustavat moduulin alustusvirhekoodia. Linux-ytimessä virhekoodi on negatiivinen luku otsikkotiedostossa tehdyistä määritelmistä . Sisällytä tämä otsikkotiedosto moduuliisi, jotta voit käyttää symbolisia muistomerkkejä varatuille virhekoodeille, kuten -ENODEV, -ENOMEM jne. Tällaisten muistien käyttöä pidetään hyvänä ohjelmointityylinä. On kuitenkin huomattava, että jotkin modutils-paketin apuohjelmien versiot eivät käsittele palautettuja virhekoodeja oikein ja näyttävät "Laite varattu" -viestin.
vastauksena koko joukkoon täysin erilaisia ​​virheitä, jotka funktio init_modules() palauttaa. Paketin uusimmissa versioissa tämä
Ärsyttävä bugi on korjattu.

Cleanup_module()-funktiokoodi yllä olevassa tapauksessa voisi olla esimerkiksi seuraava:

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

Jos alustus- ja lopetuskoodisi on monimutkaisempi kuin tässä kuvattu, goto-käskyn käyttö voi johtaa vaikeasti luettavaan ohjelmatekstiin, koska lopetuskoodi on toistettava init_module()-funktiossa käyttämällä useita tunnisteita goto-siirtymille. Tästä syystä fiksumpi temppu on kutsua cleanup_module()-funktiota init_module()-funktiossa, joka välittää tietoa onnistuneen alustuksen laajuudesta, kun moduulin latausvirhe tapahtuu.

Alla on esimerkki funktioiden init_module() ja cleanup_module() kirjoittamisesta. Tässä esimerkissä käytetään maailmanlaajuisesti määriteltyjä osoittimia, jotka sisältävät tietoa onnistuneen alustuksen laajuudesta.

Jäsennä jotain *item1; rakentaa jotain muuta *tuote2; int tavara_ok; void cleanup_module(void) ( if (tuote1) release_thing(tuote1); if (tuote2) release_thing2(tuote2); if (kamaa_ok) unregister_stuff(); return; ) int init_module(void) ( int err = -ENOMEM; item1 = allokoi_asia (argumentit); item2 = allokoi_asia2(argumentit2); if (!tuote2 || !tuote2) epäonnistui; err = register_stuff(tuote1, item2); if (!err) tavara_ok = 1; muuten epäonnistuu; palauttaa 0; /* menestys */ epäonnistui: cleanup_module(); return err; )

Moduulin alustustoimintojen monimutkaisuudesta riippuen saatat haluta hallita moduulin alustusvirheitä jollakin tässä luetelluista menetelmistä.

Moduulien käyttölaskuri

Järjestelmässä on jokaiselle moduulille käyttölaskuri, jonka avulla voidaan määrittää, voidaanko moduuli turvallisesti purkaa. Järjestelmä tarvitsee näitä tietoja, koska moduulia ei voi purkaa, jos joku tai joku on sen käytössä - et voi poistaa tiedostojärjestelmän ohjainta, jos tiedostojärjestelmä on asennettu, tai et voi purkaa merkkilaitemoduulia, jos jokin prosessi käyttää tätä laitetta. Muuten,
tämä voi johtaa järjestelmän kaatumiseen - segmentointivirheeseen tai ytimen paniikkiin.

Nykyaikaisissa ytimissä järjestelmä voi tarjota sinulle automaattisen moduulin käyttölaskurin käyttämällä mekanismia, jota tarkastellaan seuraavassa luvussa. Ytimen versiosta riippumatta voit käyttää tätä laskuria manuaalisesti. Siten koodissa, jota on tarkoitus käyttää ytimen vanhemmissa versioissa, tulisi käyttää moduulin käytön laskentamallia, joka on rakennettu seuraaville kolmelle makrolle:

MOD_INC_USE_COUNT Kasvata nykyisen moduulin käyttölaskuria MOD_DEC_USE_COUNT Vähentää nykyisen moduulin käyttölaskuria MOD_IN_USE Palauttaa tosi, jos tämän moduulin käyttölaskuri on nolla

Nämä makrot on määritelty kohdassa , ja ne käsittelevät erityistä sisäistä tietorakennetta, johon suora pääsy ei ole toivottavaa. Tosiasia on, että näiden tietojen sisäinen rakenne ja hallintatapa voivat muuttua versiosta toiseen, kun taas ulkoinen käyttöliittymä näiden makrojen käyttöä varten pysyy muuttumattomana.

Huomaa, että sinun ei tarvitse tarkistaa MOD_IN_USE cleanup_module()-funktiokoodissa, koska tämä tarkistus suoritetaan automaattisesti ennen kuin cleanup_module() kutsutaan sys_delete_module()-järjestelmäkutsussa, joka määritellään tiedostossa kernel/module.c.

Moduulien käyttölaskurin oikea hallinta on ratkaisevan tärkeää järjestelmän vakauden kannalta. Muista, että ydin voi päättää purkaa käyttämättömän moduulin automaattisesti milloin tahansa. Yleinen virhe moduulien ohjelmoinnissa on tämän laskurin väärä ohjaus. Esimerkiksi vastauksena tiettyyn pyyntöön moduulikoodi suorittaa joitain toimintoja ja kun käsittely on valmis, se lisää moduulin käyttölaskuria. Nuo. tällainen ohjelmoija olettaa, että tämä laskuri on tarkoitettu keräämään moduulin käyttötilastoja, kun taas itse asiassa se on itse asiassa moduulin nykyisen käyttöasteen laskuri, ts. pitää kirjaa moduulikoodia käyttävien prosessien määrästä tällä hetkellä. Siten, kun käsittelet pyyntöä moduulille, sinun on soitettava MOD_INC_USE_COUNT ennen minkään toiminnon suorittamista ja MOD_DEC_USE_COUNT niiden valmistuttua.

Saattaa olla tilanteita, joissa et ilmeisistä syistä voi purkaa moduulia, jos menetät sen käyttölaskurin hallinnan. Tämä tilanne esiintyy usein moduulin kehitysvaiheessa. Prosessi voi esimerkiksi keskeytyä yrittäessään poistaa viittausta NULL-osoittimeen, etkä voi purkaa tällaista moduulia ennen kuin palautat sen käyttölaskurin nollaan. Yksi mahdollisista ratkaisuista tähän ongelmaan moduulin virheenkorjausvaiheessa on luopua kokonaan moduulin käyttölaskurin ohjauksesta määrittämällä uudelleen MOD_INC_USE_COUNT Ja MOD_DEC_USE_COUNT tyhjään koodiin. Toinen ratkaisu on luoda ioctl()-kutsu, joka pakottaa moduulin käyttölaskurin nollaan. Käsittelemme tätä "ioctl-argumentin käyttäminen" -osiossa luvussa 5, "Parannetut merkkiohjaintoiminnot". Tietenkin käyttövalmiin ajureissa tällaiset petolliset manipulaatiot laskurin kanssa tulisi sulkea pois, mutta virheenkorjausvaiheessa ne säästävät kehittäjän aikaa ja ovat melko hyväksyttäviä.

Löydät kunkin moduulin nykyisen järjestelmän käyttölaskurin /proc/modules-tiedoston kunkin merkinnän kolmannesta kentästä. Tämä tiedosto sisältää tietoja ladatuista moduuleista - yksi rivi per moduuli. Rivin ensimmäinen kenttä sisältää moduulin nimen, toinen kenttä on moduulin muistissa oleva koko ja kolmas kenttä on käyttölaskurin nykyinen arvo. Nämä tiedot muotoillussa muodossa
saa soittamalla lsmod-apuohjelmaan. Alla on esimerkki /proc/modules-tiedostosta:

Parport_pc 7604 1 (automaattinen puhdistus) lp 4800 0 (käyttämätön) parport 8084 1 lockd 33256 1 (automaattinen puhdistus) sunrpc 56612 1 (automaattinen puhdistus) ds 6252 1 i82365 22304 1 41cm280

Tässä näemme useita järjestelmään ladattuja moduuleja. Lippukentässä (rivin viimeinen kenttä) moduuliriippuvuuksien pino näytetään hakasulkeissa. Muun muassa voit huomata, että rinnakkaisporttimoduulit kommunikoivat moduulipinon kautta, kuten kuvassa 1 on esitetty. 2-2. (Autoclean) -lippu merkitsee kmod- tai kerneld-ohjatut moduulit. Tätä käsitellään luvussa 11 “kmod ja edistynyt modulointi”). Lippu (käyttämätön) tarkoittaa, että moduuli ei ole tällä hetkellä käytössä. Ytimen 2.0:ssa kokokenttä ei näyttänyt tietoja tavuina, vaan sivuina, mikä useimmilla alustoilla on kooltaan 4 kt.

Moduulin purkaminen

Voit poistaa moduulin rmmod-apuohjelmalla. Moduulin purkaminen on yksinkertaisempi tehtävä kuin sen lataaminen, mikä edellyttää sen dynaamista yhdistämistä ytimeen. Kun moduuli puretaan, suoritetaan delete_module()-järjestelmäkutsu, joka joko kutsuu lataamattoman moduulin cleanup_module()-funktiota, jos sen käyttömäärä on nolla, tai päättyy virheeseen.

Kuten jo mainittiin, cleanup_module()-funktio peruuttaa alustustoiminnot, jotka suoritettiin ladattaessa moduuli cleanup_module()-funktiolla. Myös viedyt moduulisymbolit poistetaan automaattisesti.

Lopetus- ja alustustoimintojen selkeä määrittely

Kuten jo mainittiin, moduulia ladattaessa ydin kutsuu funktiota init_module() ja purkamisen yhteydessä cleanup_module(). Ytimen nykyaikaisissa versioissa näillä funktioilla on kuitenkin usein eri nimet. Ytimen versiosta 2.3.23 alkaen tuli mahdolliseksi määritellä nimenomaisesti nimi moduulin lataus- ja purkutoiminnolle. Nykyään tämä näiden toimintojen selkeä nimeäminen on suositeltu ohjelmointityyli.

Otetaan esimerkki. Jos haluat määrittää my_init()-funktion moduulisi alustusfunktioksi ja my_cleanup()-funktion lopulliseksi funktioksi init_module()- ja cleanup_module()-funktion sijaan, sinun on lisättävä seuraavat kaksi makrot moduulin tekstiin (yleensä ne lisätään loppuun
moduulikoodin lähdetiedosto):

Moduuli_init(my_init); module_exit(oma_siivous);

Huomaa, että käyttääksesi näitä makroja sinun on sisällytettävä otsikkotiedosto moduuliisi .

Tämän tyylin käyttömukavuus on, että jokaisella ytimen moduulin alustus- ja lopetustoiminnolla voi olla oma ainutlaatuinen nimi, mikä auttaa suuresti virheenkorjauksessa. Lisäksi näiden toimintojen käyttö yksinkertaistaa virheenkorjausta riippumatta siitä, toteutatko ajurikoodin moduulina vai aiotko upottaa sen suoraan ytimeen. Tietenkään module_init- ja module_exit-makrojen käyttäminen ei ole välttämätöntä, jos alustus- ja lopetusfunktioillasi on varatut nimet, ts. init_module() ja cleanup_module() vastaavasti.

Jos katsot 2.2:n tai uudemman ytimen lähteitä, saatat nähdä hieman erilaisen kuvauksen alustus- ja lopetusfunktioista. Esimerkiksi:

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

Attribuuttien käyttö __sen sisällä aiheuttaa alustustoiminnon purkamisen muistista, kun alustus on valmis. Tämä toimii kuitenkin vain ytimen sisäänrakennetuissa ohjaimissa, ja se jätetään huomiotta moduuleissa. Myös ytimeen sisäänrakennetuille ohjaimille attribuutti __poistu aiheuttaa sen, että koko tällä attribuutilla merkitty funktio ohitetaan. Moduuleissa tämä lippu jätetään myös huomiotta.

Attribuuttien käyttö __sen sisällä(Ja __initdata tietojen kuvaamiseen) voivat vähentää ytimen käyttämän muistin määrää. Lippu __sen sisällä Moduulin alustustoiminto ei tuota hyötyä eikä haittaa. Tämän tyyppisen alustuksen ohjausta ei ole vielä toteutettu moduuleille, vaikka se saattaa olla mahdollista tulevaisuudessa.

Yhteenveto

Joten esitetyn materiaalin tuloksena voimme esittää seuraavan version "Hello world" -moduulista:

Moduulin lähdetiedostokoodi =============================================== = #sisällytä #sisältää #sisältää static int __init my_init_module (void) ( EXPORT_NO_SYMBOLS; printk("<1>Hei maailma\n"); return 0; ); static void __exit my_cleanup_module (void) ( printk("<1>Hyvästi\n"); ); module_init(my_init_module); module_exit(my_cleanup_module); MODULE_LICENSE("GPL"); ======================== ===================== Makefile moduulin kääntämistä varten ========================= =============== ==================== CFLAGS = -Seinä -D__KERNEL__ -DMODULE -I/lib/moduulit/ $(shell uname -r)/build/include hello.o: ==================================== ===========================

Huomaa, että Makefilea kirjoittaessamme käytimme käytäntöä, jonka mukaan GNU make -apuohjelma voi itsenäisesti määrittää kuinka objektitiedosto luodaan CFLAGS-muuttujan ja järjestelmässä olevan kääntäjän perusteella.

Resurssien käyttö

Moduuli ei voi suorittaa tehtäväänsä käyttämättä järjestelmäresursseja, kuten muistia, I/O-portteja, I/O-muistia, keskeytyslinjoja ja DMA-kanavia.

Ohjelmoijana sinun pitäisi olla jo perehtynyt dynaamiseen muistinhallintaan. Dynaaminen muistinhallinta ytimessä ei ole pohjimmiltaan erilainen. Ohjelmasi voi saada muistia toiminnon avulla kmalloc() ja vapauttaa hänet avulla kfree(). Nämä funktiot ovat hyvin samankaltaisia ​​kuin tutut malloc()- ja free()-funktiot, paitsi että kmalloc()-funktiolle välitetään lisäargumentti - prioriteetti. Tyypillisesti prioriteetti on GFP_KERNEL tai GFP_USER. GFP on lyhenne sanoista "get free page". Ytimen dynaamisen muistin hallintaa käsitellään yksityiskohtaisesti luvussa 7, "Muistin saaminen haltuun".

Aloitteleva ohjainkehittäjä saattaa yllättyä tarpeesta allokoida I/O-portit, I/O-muisti ja keskeytyslinjat erikseen. Vain silloin ydinmoduuli voi käyttää näitä resursseja helposti. Vaikka järjestelmämuisti voidaan varata missä tahansa, I/O-muistilla, porteilla ja keskeytyslinjoilla on erityinen rooli, ja ne on varattu eri tavalla. Esimerkiksi kuljettajan on varattava tietyt portit, ei
kaikkea, paitsi ne, joita hän tarvitsee laitteen ohjaamiseen. Mutta kuljettaja ei voi käyttää näitä resursseja ennen kuin on varma, että joku muu ei käytä niitä.

Oheislaitteen omistamaa muistialuetta kutsutaan yleensä I/O-muistiksi, jotta se voidaan erottaa järjestelmän RAM-muistista (RAM), jota kutsutaan yksinkertaisesti muistiksi.

Portit ja I/O-muisti

Tyypillisen ajurin työ koostuu suurelta osin luku- ja kirjoitusporteista sekä I/O-muistista. Portteja ja I/O-muistia yhdistää yhteinen nimi - I/O-alue (tai alue).

Valitettavasti jokainen väyläarkkitehtuuri ei pysty selkeästi määrittelemään kullekin laitteelle kuuluvaa I/O-aluetta, ja on mahdollista, että kuljettajan on arvattava sen alueen sijainti, johon se kuuluu, tai jopa yritettävä luku-/kirjoitustoimintoja mahdolliselle osoitteelle. tilat. Tämä ongelma on erityisesti
viittaa ISA-väylään, jota käytetään edelleen yksinkertaisten laitteiden asentamiseen henkilökohtaisiin tietokoneisiin ja joka on erittäin suosittu teollisessa maailmassa PC/104:n toteutuksessa (katso luku "PC/104 ja PC/104+" luvussa 15). "Oheislaitteiden yleiskatsaus").

Riippumatta siitä, mitä väylää käytetään laitteistolaitteen yhdistämiseen, laiteohjaimelle on taattava yksinomainen pääsy I/O-alueeseensa, jotta vältetään ajureiden väliset törmäykset. Jos moduuli, joka käyttää omaa laitettaan, kirjoittaa laitteeseen, joka ei kuulu siihen, se voi johtaa kohtalokkaisiin seurauksiin.

Linux-kehittäjät ottivat käyttöön mekanismin I/O-alueiden pyytämiseksi/vapauttamiseksi ensisijaisesti eri laitteiden välisten törmäysten estämiseksi. Tätä mekanismia on käytetty pitkään I/O-porteissa, ja se on hiljattain yleistetty resurssien hallintaan yleensä. Huomaa, että tämä mekanismi edustaa ohjelmiston abstraktiota eikä ulotu laitteiston ominaisuuksiin. Esimerkiksi luvaton pääsy I/O-portteihin laitteistotasolla ei aiheuta "segmentointivirheen" kaltaista virhettä, koska laitteisto ei allokoi ja valtuuta resurssejaan.

Tietoja rekisteröidyistä resursseista on saatavilla tekstimuodossa tiedostoista /proc/ioports ja /proc/iomem. Tämä tieto on otettu käyttöön Linuxissa ytimen versiosta 2.3 lähtien. Muistutuksena, tämä kirja keskittyy ensisijaisesti 2.4-ytimeen, ja yhteensopivuushuomautuksia esitetään luvun lopussa.

Portit

Seuraava on /proc/ioports-tiedoston tyypillinen sisältö:

0000-001f: dma1 0020-003f: pic1 0040-005f: ajastin 0060-006f: näppäimistö 0080-008f: dma-sivu reg 00a0-00bf: pic2 00c0-00df01-7-fdpu : ide1 01f0-01f7 : ide0 02f8-02ff: sarja(sarja) 0300-031f: NE2000 0376-0376: ide1 03c0-03df: vga+ 03f6-03f6: ide0 03f8-03ff: sarja(sarja) 0PI3f3: Intel Corporation 18AB10 000 - 1003 : acpi 1004-1005: acpi 1008-100b: acpi 100c-100f: acpi 1100-110f: Intel Corporation 82371AB PIIX4 IDE 1300-131f: pcnet_cs Corporation 1400-131f: pcnet_cs Corporation 1400-141f: IX 141f28 141f:148 18ff: PCI CardBus #02 1c00- 1cff: PCI CardBus #04 5800-581f: Intel Corporation 82371AB PIIX4 USB d000-dfff: PCI-väylä #01 d000-d0ff: ATI Technologies Inc 3D Rage LT Pro AGP-133

Tämän tiedoston jokainen rivi näyttää heksadesimaalimuodossa ohjaimeen tai laitteen omistajaan liittyvien porttien alueen. Ytimen aiemmissa versioissa tiedostolla oli sama muoto, paitsi että porttihierarkiaa ei näkynyt.

Tiedostoa voidaan käyttää porttien törmäysten välttämiseen, kun järjestelmään lisätään uusi laite. Tämä on erityisen kätevää määritettäessä asennettuja laitteita manuaalisesti hyppyjohtimia vaihtamalla. Tässä tapauksessa käyttäjä voi helposti tarkastella käytettyjen porttien luetteloa ja valita asennettavalle laitteelle vapaan kantaman. Ja vaikka useimmat nykyaikaiset laitteet eivät käytä manuaalisia jumpperia ollenkaan, niitä käytetään silti pienimuotoisten komponenttien valmistuksessa.

Tärkeämpää on, että /proc/ioports-tiedostoon liittyy ohjelmallisesti käytettävissä oleva tietorakenne. Siksi laiteajurin alustuksen yhteydessä se voi tietää I/O-porttien varatun alueen. Tämä tarkoittaa, että jos on tarpeen skannata portteja uuden laitteen etsimiseksi, kuljettaja voi välttää tilanteen kirjoittamasta muiden laitteiden käyttämiin portteihin.

ISA-väylän skannaus tiedetään olevan riskialtis tehtävä. Siksi jotkin virallisen Linux-ytimen kanssa jaettavat ajurit välttävät tällaisen tarkistuksen moduulia ladattaessa. Näin he välttävät riskin vahingoittaa käynnissä olevaa järjestelmää kirjoittamalla muiden laitteiden käyttämiin portteihin. Onneksi nykyaikaiset linja-arkkitehtuurit ovat immuuneja näille ongelmille.

I/O-rekistereihin pääsyyn käytettävä ohjelmistoliittymä koostuu seuraavista kolmesta toiminnosta:

Int check_region(signed long start, unsigned long len); struct resurssi *request_region(signed long start, unsigned long len, char *name); void release_region(signed long start, unsigned long len);

Toiminto check_region() voidaan kutsua tarkistamaan, onko tietty porttialue varattu. Se palauttaa negatiivisen virhekoodin (kuten -EBUSY tai -EINVAL), jos vastaus on negatiivinen.

Toiminto request_region() suorittaa tietyn osoitealueen allokoinnin ja palauttaa onnistuessaan ei-nolla-osoittimen. Kuljettajan ei tarvitse tallentaa tai käyttää palautettua osoitinta. Sinun tarvitsee vain tarkistaa NULL. Koodin, jonka pitäisi toimia vain 2.4:n (tai uudemman) ytimen kanssa, ei tarvitse kutsua check_region()-funktiota ollenkaan. Tämän jakelumenetelmän eduista ei ole epäilystäkään, koska
ei tiedetä, mitä voi tapahtua check_region()- ja request_region()-kutsujen välillä. Jos haluat säilyttää yhteensopivuuden ytimen vanhempien versioiden kanssa, check_region()-kutsu ennen request_region():ta on välttämätöntä.

Toiminto release_region() täytyy kutsua, kun ohjain vapauttaa aiemmin käytetyt portit.

Pyynnön request_region() palauttaman osoittimen todellista arvoa käyttää vain ytimessä toimiva resurssien allokointialijärjestelmä.

Nämä kolme funktiota ovat itse asiassa makroja, jotka on määritelty kohdassa .

Alla on esimerkki puhelusekvenssistä, jota käytetään porttien rekisteröintiin. Esimerkki on otettu kalloharjoittelukuljettajan koodista. (Skull_probe_hw()-funktiokoodia ei näytetä tässä, koska se sisältää laitteistosta riippuvan koodin.)

#sisältää #sisältää static int skull_detect(signed 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; }

Tämä esimerkki tarkistaa ensin vaaditun porttialueen saatavuuden. Jos portit eivät ole käytettävissä, pääsy laitteisiin ei ole mahdollista.
Laiteporttien todellinen sijainti voidaan selvittää skannaamalla. Tässä esimerkissä request_region()-funktion ei pitäisi
päättyy epäonnistumiseen. Ydin ei voi ladata useampaa kuin yhtä moduulia kerrallaan, joten porttien käytön törmäyksiä ei tapahdu
on pakko.

Kaikki kuljettajan osoittamat I/O-portit on vapautettava myöhemmin. Pääkallo-ajurimme tekee tämän cleanup_module()-funktiossa:

Static void skull_release(signed int port, unsigned int range) ( release_region(port,range); )

Resurssipyyntö/vapautusmekanismi on samanlainen kuin moduulin rekisteröinti/poistomekanismi ja se on toteutettu täydellisesti edellä kuvatun goto-operaattorin käyttömallin perusteella.

Muisti

Tietoja I/O-muistista on saatavilla /proc/iomem-tiedoston kautta. Alla on tyypillinen esimerkki tällaisesta tiedostosta henkilökohtaiselle tietokoneelle:

00000000-0009fbff: Järjestelmän RAM 0009fc00-0009ffff: varattu 000a0000-000bffff: Video RAM-alue 000c0000-000c7fff: Video ROM 000f0000-000fff0ff: System3 ROM0ff0ff:000fff0 0 0100000-0022c557: Ytimen koodi 0022c558-0024455f: Ytimen tiedot 20000000 - 2fffffff: Intel Corporation 440BX/ZX - 82443BX/ZX isäntäsilta 68000000-68000fff: Texas Instruments PCI1225 68001000-68001fff: Texas Instruments PCI1225 (#2) väylä00ffff0003e0:00e 0-e7 ffffff: PCI-väylä #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: varattu

Osoitealueen arvot näytetään heksadesimaalimuodossa. Jokaisen aarialueen kohdalla näkyy sen omistaja.

I/O-muistin käyttöjen rekisteröinti on samanlaista kuin I/O-porttien rekisteröinti, ja se perustuu samaan mekanismiin ytimessä.

Tarvittavan I/O-muistiosoitealueen hankkimiseksi ja vapauttamiseksi kuljettajan on käytettävä seuraavia kutsuja:

Int check_mem_region(signed long start, unsigned long len); int request_mem_region(signed long start, unsigned long len, char *name); int release_mem_region(signed long start, unsigned long len);

Tyypillisesti ohjain tietää I/O-muistiosoitteiden alueen, joten tämän resurssin allokointikoodia voidaan vähentää verrattuna esimerkkiin porttialueen allokoinnista:

If (check_mem_region(mem_addr, mem_size)) ( printk("ohjaimen nimi: muisti jo käytössä\n"); return -EBUSY; ) request_mem_region(mem_addr, mem_size, "ajurinimi");

Resurssien allokointi Linuxissa 2.4

Nykyinen resurssien allokointimekanismi otettiin käyttöön Linux-ytimessä 2.3.11, ja se tarjoaa joustavan pääsyn järjestelmäresurssien hallintaan. Tässä osassa kuvataan lyhyesti tätä mekanismia. Perusresurssien allokointifunktiot (kuten request_region() jne.) toteutetaan kuitenkin edelleen makroina, ja niitä käytetään taaksepäin yhteensopivuuden takaamiseksi ytimen aiempien versioiden kanssa. Useimmissa tapauksissa sinun ei tarvitse tietää mitään varsinaisesta jakelumekanismista, mutta se voi olla mielenkiintoista luotaessa monimutkaisempia ohjaimia.

Linuxiin toteutettu resurssienhallintajärjestelmä voi hallita mielivaltaisia ​​resursseja yhtenäisellä hierarkkisella tavalla. Globaalit järjestelmäresurssit (esimerkiksi I/O-portit) voidaan jakaa osajoukkoihin - esimerkiksi niihin, jotka liittyvät tiettyyn laitteistoväyläpaikkaan. Tietyt ohjaimet voivat myös valinnaisesti jakaa siepatut resurssit loogisen rakenteensa perusteella.

Allokoitujen resurssien valikoima kuvataan struct-resurssirakenteen kautta, joka ilmoitetaan otsikkotiedostossa :

Rakenneresurssi ( const char *nimi; allekirjoittamaton pitkä alku, loppu; allekirjoittamattomat pitkät liput; rakenneresurssi *vanhempi, *sisarus, *lapsi; );

Globaali (juuri) resurssien valikoima luodaan käynnistyksen yhteydessä. Esimerkiksi I/O-portteja kuvaava resurssirakenne luodaan seuraavasti:

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

Tässä kuvataan PCI IO -niminen resurssi, joka kattaa osoitealueen nollasta IO_SPACE_LIMIT. Tämän muuttujan arvo riippuu käytetystä alustasta ja voi olla yhtä suuri kuin 0xFFFF (16-bittinen osoiteavaruus, x86-, IA-64-, Alpha-, M68k- ja MIPS-arkkitehtuureille), 0xFFFFFFFF (32-bittinen osoiteavaruus, SPARC, PPC , SH) tai 0xFFFFFFFFFFFFFFFF (64-bittinen, SPARC64).

Tämän resurssin alialueet voidaan luoda kutsumalla allocate_resource(). Esimerkiksi PCI-väylän alustuksen aikana uusi resurssi luodaan tämän väylän osoitealueelle ja osoitetaan fyysiselle laitteelle. Kun PCI-ydinkoodi käsittelee portti- ja muistimäärityksiä, se luo uuden resurssin vain niille alueille ja varaa ne kutsumalla ioport_resource() tai iomem_resource().

Kuljettaja voi sitten pyytää resurssin osajoukkoa (yleensä osa maailmanlaajuista resurssia) ja merkitä sen varatuksi. Resurssien hankinta suoritetaan kutsumalla request_region(), joka palauttaa joko osoittimen uuteen rakenneresurssirakenteeseen, joka kuvaa pyydettyä resurssia, tai NULL virheen sattuessa. Tämä rakenne on osa globaalia resurssipuuta. Kuten jo mainittiin, resurssin hankkimisen jälkeen ohjain ei tarvitse tämän osoittimen arvoa.

Kiinnostunut lukija voi nauttia tämän resurssinhallintajärjestelmän yksityiskohdista kernel/resource.c-tiedostosta, joka sijaitsee ytimen lähdehakemistossa. Useimmille kehittäjille kuitenkin jo esitetty tieto riittää.

Kerrostettu resurssien allokointimekanismi tuo kaksinkertaisen hyödyn. Toisaalta se antaa visuaalisen esityksen ytimen tietorakenteista. Katsotaanpa esimerkkitiedostoa /proc/ioports uudelleen:

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

e800-e8ff-sarja on varattu Adaptec-sovittimelle, joka nimesi itsensä PCI-väylän ohjaimeksi. Suurimman osan tästä alueesta pyysi aic7xxx-ohjain.

Toinen tämän resurssienhallinnan etu on osoiteavaruuden jakaminen alialueisiin, jotka kuvastavat laitteiden todellista yhteenliittämistä. Resurssinhallinta ei voi varata päällekkäisiä osoitealialueita, mikä voi estää viallisen ohjaimen asennuksen.

Automaattinen ja manuaalinen konfigurointi

Jotkut ohjaimen vaatimat parametrit voivat vaihdella järjestelmästä toiseen. Esimerkiksi kuljettajan on oltava tietoinen kelvollisista I/O-osoitteista ja muistialueista. Hyvin organisoiduissa väyläliitännöissä tämä ei ole ongelma. Joskus sinun on kuitenkin välitettävä parametreja ohjaimelle, jotta se löytää oman laitteensa, tai ottaa käyttöön tai poistaa käytöstä joitakin sen toimintoja.

Nämä ohjaimen toimintaan vaikuttavat asetukset vaihtelevat laitteittain. Tämä voi olla esimerkiksi asennetun laitteen versionumero. Tietenkin tällaiset tiedot ovat välttämättömiä, jotta kuljettaja toimii oikein laitteen kanssa. Tällaisten parametrien määrittäminen (ohjainkokoonpano) on melkoista
hankala tehtävä, joka suoritetaan, kun ohjain alustetaan.

Tyypillisesti on kaksi tapaa saada tämän parametrin oikeat arvot - joko käyttäjä määrittelee ne eksplisiittisesti tai kuljettaja määrittää ne itsenäisesti laitteen kyselyn perusteella. Vaikka automaattinen tunnistus on epäilemättä paras ratkaisu ajurin määrittämiseen,
mukautettu kokoonpano on paljon helpompi toteuttaa. Ohjaimen kehittäjän tulee ottaa käyttöön ajurien automaattinen määritys aina kun mahdollista, mutta samalla hänen tulee tarjota käyttäjälle manuaalinen konfigurointimekanismi. Tietenkin manuaalisella määrityksellä tulisi olla korkeampi prioriteetti kuin automaattisella määrityksellä. Kehityksen alkuvaiheessa toteutetaan yleensä vain manuaalinen parametrien siirto kuljettajalle. Automaattinen konfigurointi, jos mahdollista, lisätään myöhemmin.

Monilla ohjaimilla on konfigurointiparametriensa joukossa parametreja, jotka ohjaavat ohjaintoimintoja. Esimerkiksi Integrated Device Electronics (IDE) -liitäntäajurit antavat käyttäjälle mahdollisuuden ohjata DMA-toimintoja. Joten jos kuljettajasi tekee hyvää työtä laitteiston automaattisessa tunnistamisessa, sinun kannattaa antaa käyttäjälle mahdollisuus hallita ohjaimen toimintoja.

Parametriarvot voidaan välittää moduulin latauksen aikana insmod- tai modprobe-komennoilla. Viime aikoina on tullut mahdolliseksi lukea parametrien arvot asetustiedostosta (yleensä /etc/modules.conf). Kokonaisluku- ja merkkijonoarvot voidaan välittää parametreina. Jos siis haluat välittää kokonaislukuarvon skull_ival-parametrille ja merkkijonoarvon skull_sval-parametrille, voit välittää ne moduulin latauksen aikana lisäparametreilla insmod-komennolle:

Insmod skull skull_ival=666 skull_sval="peto"

Kuitenkin ennen kuin insmod-komento voi muuttaa moduulin parametrien arvoja, moduulin on asetettava nämä parametrit saataville. Parametrit ilmoitetaan käyttämällä MODULE_PARM-makromäärittelyä, joka on määritetty module.h-otsikkotiedostossa. MODULE_PARM-makro ottaa kaksi parametria: muuttujan nimen ja sen tyypin määrittävän merkkijonon. Tämä makromäärittely on sijoitettava kaikkien funktioiden ulkopuolelle, ja se sijaitsee yleensä tiedoston alussa muuttujien määrittämisen jälkeen. Joten kaksi edellä mainittua parametria voidaan ilmoittaa seuraavasti:

Int skull_ival=0; char *skull_sval; MODULE_PARM(kallo_ival, "i"); MODULE_PARM(kallo_svali, "s");

Tällä hetkellä tuettuja moduuliparametreja on viisi:

  • b - yhden tavun arvo;
  • h - (lyhyt) kaksitavuinen arvo;
  • i - kokonaisluku;
  • l - pitkä kokonaisluku;
  • s - merkkijono (merkki *);

Merkkijonoparametrien tapauksessa osoitin (char *) on ilmoitettava moduulissa. Insmod-komento varaa muistia syötetylle merkkijonolle ja alustaa sen vaaditulla arvolla. MODULE_PARM-makron avulla voit alustaa parametritaulukoita. Tässä tapauksessa tyyppimerkkiä edeltävä kokonaisluku määrittää taulukon pituuden. Kun määritetään kaksi kokonaislukua, jotka on erotettu viivalla, ne määrittävät lähetettävien arvojen vähimmäis- ja enimmäismäärän. Jos haluat tarkempia tietoja tämän makron toiminnasta, katso otsikkotiedosto .

Oletetaan esimerkiksi, että joukko parametreja on alustettava vähintään kahdella ja vähintään neljällä kokonaislukuarvolla. Sitten sitä voidaan kuvata seuraavasti:

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

Lisäksi ohjelmoijan työkalupakkissa on makromäärittely MODULE_PARM_DESC, jonka avulla voit kommentoida välitettäviä moduuliparametreja. Nämä kommentit tallennetaan moduuliobjektitiedostoon ja niitä voidaan tarkastella esimerkiksi objdump-apuohjelmalla tai automaattisten järjestelmänhallintatyökalujen avulla. Tässä on esimerkki tämän makromääritelmän käytöstä:

Int perusportti = 0x300; MODULE_PARM(kantaportti, "i"); MODULE_PARM_DESC (perusportti, "Perus-I/O-portti (oletus 0x300)");

On toivottavaa, että kaikilla moduuliparametreilla on oletusarvot. Näiden arvojen muuttaminen insmodilla tulisi vaatia vain tarvittaessa. Moduuli voi tarkistaa parametrien eksplisiittiset asetukset tarkistamalla niiden nykyiset arvot oletusarvoilla. Tämän jälkeen voit ottaa käyttöön automaattisen konfigurointimekanismin seuraavan kaavion perusteella. Jos parametriarvoilla on oletusarvot, automaattinen konfigurointi suoritetaan. Muussa tapauksessa käytetään nykyisiä arvoja. Jotta tämä kaavio toimisi, on välttämätöntä, että oletusarvot eivät vastaa mitään mahdollista reaalimaailman järjestelmäkokoonpanoa. Tällöin oletetaan, että käyttäjä ei voi asettaa tällaisia ​​arvoja manuaalisesti.

Seuraava esimerkki näyttää, kuinka pääkallo-ohjain tunnistaa automaattisesti laiteporttien osoiteavaruuden. Yllä olevassa esimerkissä automaattinen tunnistus tarkastelee useita laitteita, kun taas manuaalinen määritys rajoittaa ohjaimen yhteen laitteeseen. Olet jo tavannut skull_detect-funktion aiemmin I/O-portteja kuvaavassa osassa. Skull_init_board():n toteutusta ei näytetä, koska se
Suorittaa laitteistosta riippuvan alustuksen.

/* * porttialueet: laite voi sijaita välillä * 0x280 ja 0x300 0x10 askelin. Se käyttää 0x10 portteja. */ #define SKULL_PORT_FLOOR 0x280 #define SKULL_PORT_CEIL 0x300 #define SKULL_PORT_RANGE 0x010 /* * seuraava toiminto suorittaa automaattisen tunnistuksen, ellei insmod ole määrittänyt tiettyä * arvoa kohtaan "skull_port_base" */static_0 inbat /* 0 pakottaa automaattisen tunnistuksen */ MODULE_PARM (kallon_portin_pohja, "i"); MODULE_PARM_DESC(skull_port_base, "Skullin perus-I/O-portti"); static int skull_find_hw(void) /* palauttaa laitteiden lukumäärän */ ( /* base on joko latausajan arvo tai ensimmäinen kokeilu */ int base = skull_port_base ? skull_port_base: SKULL_PORT_FLOOR; int tulos = 0; /* silmukka yksi aika, jos arvo on määritetty; kokeile niitä kaikkia jos automaattinen havaitseminen */ do ( if (skull_detect(base, SKULL_PORT_RANGE) == 0) ( skull_init_board(base); result++; ) base += SKULL_PORT_RANGE; /* valmistaudu seuraavaan kokeiluun */ ) while (skull_port_base == 0 && kanta< SKULL_PORT_CEIL); return result; }

Jos konfiguraatiomuuttujia käytetään vain ajurin sisällä (eli niitä ei ole julkaistu ytimen symbolitaulukossa), ohjelmoija voi hieman helpottaa käyttäjän elämää olemalla käyttämättä muuttujien nimissä etuliitteitä (tässä tapauksessa skull_-etuliite). . Nämä etuliitteet merkitsevät käyttäjälle vähän, ja niiden puuttuminen yksinkertaistaa komennon kirjoittamista näppäimistöltä.

Täydellisyyden vuoksi annamme kuvauksen kolmesta muusta makromääritelmästä, joiden avulla voit lisätä kommentteja objektitiedostoon.

MODULE_AUTHOR (nimi) Sijoittaa objektitiedostoon rivin, jossa on tekijän nimi. MODULE_DESCRIPTION(desc) Sijoittaa objektitiedostoon rivin, jossa on moduulin yleinen kuvaus. MODULE_SUPPORTED_DEVICE (kehittäjä) Sijoittaa rivin, joka kuvaa moduulin tukemaa laitetta. Linux tarjoaa tehokkaan ja laajan API:n sovelluksille, mutta joskus se ei riitä. Ytimen ohjain tarvitaan, jotta voit olla vuorovaikutuksessa laitteiston kanssa tai suorittaa toimintoja, joilla on pääsy järjestelmän etuoikeutettuihin tietoihin.

Linux-ydinmoduuli on käännetty binäärikoodi, joka lisätään suoraan Linux-ytimeen ja joka toimii renkaassa 0, x86–64-prosessorin sisäisessä ja vähiten suojatussa käskyjen suoritusrenkaassa. Täällä koodi suoritetaan täysin ilman tarkistuksia, mutta uskomattomalla nopeudella ja pääsyn kaikkiin järjestelmäresursseihin.

Ei pelkille kuolevaisille

Linux-ydinmoduulin kirjoittaminen ei ole heikkohermoisille. Ydintä vaihtamalla voit menettää tietoja. Ytimen koodissa ei ole vakioturvallisuutta, kuten tavallisissa Linux-sovelluksissa. Jos teet virheen, sulje koko järjestelmä.

Tilannetta pahentaa se, että ongelma ei välttämättä ilmene heti. Jos moduuli ripustaa järjestelmän heti latauksen jälkeen, tämä on paras vikaskenaario. Mitä enemmän koodia on, sitä suurempi on loputtomien silmukoiden ja muistivuotojen riski. Jos et ole varovainen, ongelmat lisääntyvät vähitellen koneen toimiessa. Lopulta tärkeät tietorakenteet ja jopa puskurit voidaan ylikirjoittaa.

Perinteiset sovelluskehityksen paradigmat voidaan suurelta osin unohtaa. Moduulin lataamisen ja purkamisen lisäksi kirjoitat koodia, joka reagoi järjestelmän tapahtumiin peräkkäisen kaavan sijaan. Kun työskentelet ytimen kanssa, kirjoitat API, et itse sovelluksia.

Sinulla ei myöskään ole pääsyä vakiokirjastoon. Vaikka ydin tarjoaa joitain toimintoja, kuten printk (joka korvaa printf) ja kmalloc (joka toimii samalla tavalla kuin malloc), olet enimmäkseen jätetty laitteiston varaan. Lisäksi sinun tulee siivota itsesi kokonaan moduulin purkamisen jälkeen. Täällä ei ole jätehuoltoa.

Vaaditut komponentit

Ennen kuin aloitat, varmista, että sinulla on kaikki työhön tarvittavat työkalut. Mikä tärkeintä, tarvitset Linux-koneen. Tiedän, että tämä on odottamatonta! Vaikka mikä tahansa Linux-jakelu käy, käytän tässä esimerkissä Ubuntu 16.04 LTS:ää, joten saatat joutua muokkaamaan asennuskomentoja hieman, jos käytät muita jakeluja.

Toiseksi tarvitset joko erillisen fyysisen koneen tai virtuaalikoneen. Henkilökohtaisesti työskentelen mieluummin virtuaalikoneella, mutta tee valintasi. En suosittele pääkoneen käyttöä, koska tiedot menetetään virheen tekemisen yhteydessä. Sanon "milloin" enkä "jos", koska ripustat auton varmasti ainakin muutaman kerran prosessin aikana. Viimeisimmät koodimuutoksesi voivat edelleen olla kirjoituspuskurissa ytimen paniikkitilanteessa, joten myös lähteesi voivat olla vioittuneet. Virtuaalikoneessa testaus poistaa nämä riskit.

Lopuksi sinun on osattava ainakin vähän C:tä. C++-ajoaika on liian suuri ytimelle, joten sinun on kirjoitettava puhtaalla, paljaalla C:llä. Myös kokoonpanokielen taidosta on apua laitteiston kanssa vuorovaikutuksessa.

Kehitysympäristön asennus

Ubuntussa sinun on suoritettava:

Apt-get install build-essential linux-headers-`uname -r`
Asennamme tässä esimerkissä tarvittavat tärkeimmät kehitystyökalut ja ytimen otsikot.

Alla olevissa esimerkeissä oletetaan, että käytät tavallisena käyttäjänä pääkäyttäjän sijaan, mutta sinulla on sudo-oikeudet. Sudo vaaditaan ytimen moduulien lataamiseen, mutta haluamme työskennellä pääkäyttäjän ulkopuolella aina kun mahdollista.

Alkaa

Aloitetaan koodin kirjoittaminen. Valmistelemme ympäristöämme:

Mkdir ~/src/lkm_example cd ~/src/lkm_example
Käynnistä suosikkieditori (minun tapauksessani vim) ja luo tiedosto lkm_example.c, jossa on seuraava sisältö:

#sisältää #sisältää #sisältää MODULE_LICENSE("GPL"); MODULE_AUTHOR("Robert W. Oliver II"); MODULE_DESCRIPTION("Yksinkertainen esimerkki Linux-moduulista."); MODULE_VERSION("0.01"); static int __init lkm_example_init(void) ( printk(KERN_INFO "Hei, maailma!\n"); return 0; ) static void __exit lkm_example_exit(void) ( printk(KERN_INFO "Hyvästi, maailma!\n");() lkinitexample ); module_exit(lkm_example_exit);
Olemme suunnitelleet yksinkertaisimman mahdollisen moduulin, katsotaanpa tarkemmin sen tärkeimpiä osia:

  • include listaa Linux-ytimen kehittämiseen tarvittavat otsikkotiedostot.
  • MODULE_LICENSE voidaan asettaa erilaisiin arvoihin riippuen moduulin lisenssistä. Näet koko luettelon suorittamalla:

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

  • Asetamme init (lataus) ja exit (purku) staattisiksi funktioiksi, jotka palauttavat kokonaislukuja.
  • Huomaa printk:n käyttö printf:n sijaan. Myös printk:n asetukset poikkeavat printf:n valinnoista. Esimerkiksi KERN_INFO-lippu tietyn rivin kirjausprioriteetin ilmoittamiseksi määritetään ilman pilkkua. Ydin käsittelee nämä asiat printk-funktion sisällä pinomuistin säästämiseksi.
  • Tiedoston lopussa voit kutsua module_init ja module_exit ja määrittää lataus- ja purkutoiminnot. Tämä mahdollistaa funktioiden mielivaltaisen nimeämisen.
Emme kuitenkaan voi vielä kääntää tätä tiedostoa. Makefile tarvitaan. Tämä perusesimerkki riittää toistaiseksi. Huomaa, että merkki on erittäin nirso välilyöntien ja sarkainten suhteen, joten muista käyttää tarvittaessa sarkaimia välilyöntien sijaan.

Obj-m += lkm_example.o kaikki: tee -C /lib/modules/$(shell uname -r)/build M=$(PWD) moduulit puhtaiksi: tee -C /lib/modules/$(shell uname -r )/rakennus M=$(PWD) puhdas
Jos suoritamme sen, sen pitäisi kääntää moduulimme onnistuneesti. Tuloksena on tiedosto lkm_example.ko. Jos virheitä ilmenee, tarkista, että lähdekoodin lainausmerkit on asetettu oikein eivätkä vahingossa UTF-8-koodauksessa.

Nyt voit ottaa moduulin käyttöön ja testata sitä. Tätä varten suoritamme:

Sudo insmod lkm_example.ko
Jos kaikki on hyvin, et näe mitään. Printk-funktio ei anna tulosta konsoliin, vaan ytimen lokiin. Katsoaksesi sinun on suoritettava:

Sudo dmesg
Sinun pitäisi nähdä rivi "Hei, maailma!" aikaleimalla alussa. Tämä tarkoittaa, että ydinmoduulimme on ladattu ja kirjoitettu onnistuneesti ytimen lokiin. Voimme myös tarkistaa, että moduuli on edelleen muistissa:

lsmod | grep "lkm_example"
Voit poistaa moduulin suorittamalla:

Sudo rmmod lkm_example
Jos suoritat dmesg:n uudelleen, näet lokissa merkinnän "Goodbye, World!". Voit suorittaa lsmodin uudelleen ja varmistaa, että moduuli on purettu.

Kuten näette, tämä testausmenettely on hieman työläs, mutta se voidaan automatisoida lisäämällä:

Testi: sudo dmesg -C sudo insmod lkm_example.ko sudo rmmod lkm_example.ko dmesg
Makefile-tiedoston lopussa ja sen jälkeen käynnissä:

Tee testi
testata moduulia ja tarkistaa tulosteet ytimen lokiin ilman erillisten komentojen suorittamista.

Meillä on nyt täysin toimiva, vaikkakin täysin triviaali ydinmoduuli!

Kaivetaan vähän syvemmälle. Vaikka ydinmoduulit pystyvät suorittamaan kaikenlaisia ​​tehtäviä, käyttöliittymä sovellusten kanssa on yksi yleisimmistä käyttötapauksista.

Koska sovellukset eivät saa tarkastella muistia ydintilassa, niiden on käytettävä API:ta kommunikoidakseen niiden kanssa. Vaikka teknisesti on olemassa useita tapoja tehdä tämä, yleisin on laitetiedoston luominen.

Olet luultavasti käsitellyt laitetiedostoja aiemmin. Komennot, joissa mainitaan /dev/zero , /dev/null ja vastaavat, ovat vuorovaikutuksessa "nolla"- ja "null"-laitteiden kanssa, jotka palauttavat odotetut arvot.

Esimerkissämme palaamme "Hei, maailma". Vaikka tämä ei ole erityisen hyödyllinen ominaisuus sovelluksille, se näyttää silti prosessin vuorovaikutuksessa sovelluksen kanssa laitetiedoston kautta.

Tässä on koko listaus:

#sisältää #sisältää #sisältää #sisältää #sisältää MODULE_LICENSE("GPL"); MODULE_AUTHOR("Robert W. Oliver II"); MODULE_DESCRIPTION("Yksinkertainen esimerkki Linux-moduulista."); MODULE_VERSION("0.01"); #define DEVICE_NAME “lkm_example” #define EXAMPLE_MSG “Hei, maailma!\n” #define MSG_BUFFER_LEN 15 /* Laitetoimintojen prototyypit */ static int device_open(struct inode *, struct file *); static int laite_julkaisu(struct inode *, struct file *); static ssize_t device_read(struct file *, char *, size_t, loff_t *); static ssize_t device_write(struct file *, const char *, size_t, loff_t *); staattinen int päänumero; staattinen int laite_avoin_määrä = 0; staattinen merkki msg_buffer; staattinen merkki *msg_ptr; /* Tämä rakenne osoittaa kaikkiin laitetoimintoihin */ static struct file_operations file_ops = ( .read = laite_luku, .write = laite_kirjoitus, .open = laite_avaa, .release = laite_julkaisu ); /* Kun prosessi lukee laitteestamme, tätä kutsutaan. */ static ssize_t device_read(struct file *flip, char *puskuri, size_t len, loff_t *offset) ( int bytes_read = 0; /* Jos olemme lopussa, palaa takaisin alkuun */ if (*msg_ptr = = 0) ( msg_ptr = msg_buffer; ) /* Laita tiedot puskuriin */ while (len && *msg_ptr) ( /* Puskuri on käyttäjätiedoissa, ei ytimessä, joten et voi vain viitata *:ään osoittimella. funktio put_user hoitaa tämän puolestamme */ put_user(*(msg_ptr++), buffer++); len--; bytes_read++; ) return bytes_read; ) /* Kutsutaan, kun prosessi yrittää kirjoittaa laitteellemme */ static ssize_t device_write(struct file * flip, const char *puskuri, koko_t len, loff_t *offset) ( /* Tämä on vain luku -laite */ printk(KERN_ALERT "Tätä toimintoa ei tueta.\n"); return -EINVAL; ) /* Kutsutaan, kun prosessi avaa laitteemme */ static int device_open(struct inode *inode, struct file *file) ( /* Jos laite on auki, return varattu */ if (device_open_count) ( return -EBUSY; ) device_open_count++; try_module_get(THIS_MODULE); paluu 0; ) /* Kutsutaan, kun prosessi sulkee laitteemme */ static int device_release(struct inode *inode, struct file *file) ( /* Pienennä avointen laskuria ja käyttömäärää. Ilman tätä moduuli ei purkautuisi. */ device_open_count- -; module_put(THIS_MODULE); return 0; ) static int __init lkm_example_init(void) ( /* Täytä puskuri viestillämme */ strncpy(msg_buffer, EXAMPLE_MSG, MSG_BUFFER_LEN); /* Aseta msg_ptr = buffer */ptr msg. ; /* Yritä rekisteröidä merkkilaite */ major_num = register_chrdev(0, “lkm_example”, &file_ops); if (päänumero< 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);

Parannetun esimerkin testaus

Nyt esimerkkimme tekee enemmän kuin vain viestin tulostamisen lastauksen ja purkamisen yhteydessä, joten tarvitaan vähemmän tiukkaa testausmenettelyä. Muutetaan Makefile lataamaan vain moduuli, purkamatta sitä.

Obj-m += lkm_example.o kaikki: tee -C /lib/modules/$(shell uname -r)/build M=$(PWD) moduulit puhtaiksi: tee -C /lib/modules/$(shell uname -r )/build M=$(PWD) puhdas testi: # Laitamme rmmod-komennon eteen -merkin, jotta make jättää # virheen, jos moduulia ei ladata. -sudo rmmod lkm_example # Tyhjennä ytimen loki ilman kaikua sudo dmesg -C # Lisää moduuli sudo insmod lkm_example.ko # Näytä ytimen loki dmesg
Nyt kun olet suorittanut make testin, näet päälaitteen numeron tulosteena. Esimerkissämme ydin määrittää sen automaattisesti. Tämä numero tarvitaan kuitenkin uuden laitteen luomiseen.

Ota make testin luoma numero ja luo sen avulla laitetiedosto, jotta voimme kommunikoida ydinmoduulimme kanssa käyttäjätilasta.

Sudo mknod /dev/lkm_example ja MAJOR 0
(tässä esimerkissä korvaa MAJOR arvolla, joka on saatu make testistä tai dmesg:stä)

mknod-komennon c-vaihtoehto kertoo mknodille, että meidän on luotava merkkilaitetiedosto.

Nyt saamme sisältöä laitteelta:

Cat /dev/lkm_example
tai jopa dd-komennon kautta:

Dd if=/dev/lkm_example of=test bs=14 count=100
Voit käyttää tätä tiedostoa myös sovelluksista. Näiden ei tarvitse olla käännettyjä sovelluksia - jopa Python-, Ruby- ja PHP-skripteillä on pääsy näihin tietoihin.

Kun laite on valmis, poistamme sen ja puramme moduulin:

Sudo rm /dev/lkm_example sudo rmmod lkm_esimerkki

Johtopäätös

Toivottavasti pidit kepposistamme ydinavaruudessa. Vaikka esitetyt esimerkit ovat primitiivisiä, näitä rakenteita voidaan käyttää omien moduulien luomiseen, jotka suorittavat erittäin monimutkaisia ​​tehtäviä.

Muista vain, että ydintilassa kaikki on sinun vastuullasi. Koodillesi ei ole tukea tai toista mahdollisuutta. Jos teet projektin asiakkaalle, suunnittele kaksinkertainen, ellei kolminkertainen virheenkorjausaika. Ytimen koodin on oltava mahdollisimman täydellinen, jotta varmistetaan järjestelmien, joissa se toimii, eheys ja luotettavuus.

Joitakin modulaarisen ohjelmoinnin ominaisuuksia ja yleisiä suosituksia modulaarisen rakenteen aliohjelmien rakentamiseen.

Moduulit kytketään pääohjelmaan siinä järjestyksessä, jossa ne on ilmoitettu KÄYTTÖÖN, ja samassa järjestyksessä ovat pääohjelmaan kytkettyjen moduulien alustuslohkot ennen ohjelman suorittamista.

Moduulien suoritusjärjestys voi vaikuttaa kirjaston tietojen käyttöön ja rutiinikäyttöön.

Esimerkiksi, jos moduulit, joiden nimi on M1, M2 sisältävät saman tyypin A, muuttujan B ja aliohjelman C, näiden USES-mallien yhdistämisen jälkeen kutsut A:lle, B:lle, C:lle tässä PU:ssa vastaavat kutsuja objekteille moduuliin M2. .

Mutta eri yhdistetyistä moduuleista samannimiseen tarvittaviin objekteihin soitettujen kutsujen oikeellisuuden luonnehtimiseksi on suositeltavaa moduulia käsiteltäessä ilmoittaa ensin moduulin nimi ja sen jälkeen objektin nimi pisteellä: M1. A M1.B M1.C M2.B.

On selvää, että suuri ohjelma on erittäin helppoa jakaa kahteen osaan (PU), ts. pääohjelma + moduulit.

Jokaisen PU:n sijoittaminen omaan muistisegmenttiinsä ja omaan levytiedostoonsa.

Kaikki tyyppimääritykset sekä ne muuttujat, joiden tulisi olla yksittäisten PU:iden käytettävissä (pääohjelma ja tulevat moduulit), tulee sijoittaa erilliseen moduuliin, jossa on tyhjä suoritettava osa. Sinun ei kuitenkaan pidä kiinnittää huomiota siihen, että jokin PE (esim. moduuli) ei käytä kaikkia näitä ilmoituksia. Tällaisen moduulin aloitusosa voi sisältää käskyjä, jotka yhdistävät tiedostomuuttujat epästandardeihin tekstitiedostoihin (ASSIGN) ja käynnistävät nämä tiedostot, ts. ilmoittaa heille tiedonsiirtokutsut (RESET ja REWRITE).

Ensimmäinen ryhmä muita aliohjelmia, esimerkiksi useita kompakteja toimintoja tulisi sijoittaa moduuliin 3 (vuorollaan), muut, esimerkiksi yleiskäyttöiset toiminnot - moduuliin 4 jne.

Jaettaessa aliohjelmia moduuleiksi monimutkaisessa projektissa on kiinnitettävä erityistä huomiota niiden kirjoitusjärjestykseen ja -paikkaan.

TP-ympäristö sisältää työkaluja, jotka ohjaavat erilaisia ​​tapoja moduulien kääntämiseen.

Käännä Alt+F9 RUN Cntr+F9

Kohdemuisti

Nämä tilat eroavat toisistaan ​​vain viestintämenetelmän ja pääohjelman osalta.

Käännöstila

Kääntää pääohjelman tai moduulin, joka on tällä hetkellä aktiivisessa muokkausikkunassa. Jos tämä PU sisältää pääsyn epästandardeihin käyttäjämoduuleihin, tämä tila edellyttää samannimistä levytiedostoa, jonka laajennus on ___.tpu, jokaiselle tällaiselle laajennusmoduulille.



Jos kohde on tallennettu muistiin, nämä tiedostot jäävät vain muistiin, eikä levytiedostoa luoda.

On kuitenkin paljon helpompaa luoda tpu-tiedostoja yhdessä koko ohjelman kääntäjän kanssa käyttämällä muita tiloja, jotka eivät vaadi Disk-asetusta kohdeasetukseksi.

Tee tila

Käännettäessä tässä tilassa seuraava tarkistetaan ensin (ennen pääohjelman kääntämistä) jokaiselle moduulille:

1) levyn tpu-tiedoston olemassaolo; jos sitä ei ole, niin se luodaan automaattisesti kääntämällä moduulin lähdekoodi, ts. sen pas-tiedosto

2) Löydetyn tpu-tiedoston vastaavuus moduulin lähdetekstiin, johon olisi voitu tehdä muutoksia; muuten tpu-tiedosto luodaan automaattisesti uudelleen

3) Moduulin liitäntäosion muuttumattomuus: jos se on muuttunut, niin myös kaikki ne moduulit (niiden lähdepas-tiedostot), joissa tämä moduuli on määritelty USES-lauseessa, käännetään uudelleen.

Jos moduulien lähdekoodeissa ei tapahtunut muutoksia, kääntäjä on vuorovaikutuksessa näiden tpu-tiedostojen kanssa ja käyttää käännösaikaa.

Rakennustila

Toisin kuin Make-tila, se edellyttää välttämättä lähdepas-tiedostojen läsnäoloa; kääntää (kääntää uudelleen) jokaisen moduulin ja varmistaa siten, että kaikki muutokset pas-tiedostojen teksteissä otetaan huomioon. Tämä lisää koko ohjelman käännösaikaa.

Toisin kuin käännöstila, Make- ja Build-tiloissa voit aloittaa modulaarisen rakenteen omaavan ohjelman kääntämisen mistä tahansa tietystä pas-tiedostosta (tätä kutsutaan ensisijaiseksi tiedostoksi), riippumatta siitä mikä tiedosto (tai ohjelman osa) on aktiivisena. muokkausikkuna. Tätä varten valitse käännöskohdassa vaihtoehto Ensisijainen tiedosto, paina Enter ja kirjoita muistiin ensisijaisen pas-tiedoston nimi, jonka jälkeen käännös alkaa tästä tiedostosta.

Jos ensisijaista tiedostoa ei ole määritetty tällä tavalla, kääntäminen Make-, Build- ja RUN-tiloissa on mahdollista vain, jos pääohjelma on aktiivisessa muokkausikkunassa.

Huomaa: Tulevaisuudessa aion käyttää T2-järjestelmää Puppyn ytimen ja moduulien kääntämiseen. T2 on tällä hetkellä asennettuna kääntämään ydin ja lukuisia lisämoduuleja, mutta ei Puppyssa tällä hetkellä käytettyä versiota. Aion synkronoida Puppyn tulevissa versioissa, jotta T2:ssa käännetty ydin tulee käytettäväksi Puppyssa. Katso http://www.puppyos.net/pfs/ lisätietoa koiranpennusta ja T2:sta.

Puppylla on hyvin yksinkertainen tapa käyttää C/C++-kääntäjiä lisäämällä yksi tiedosto, devx_xxx.sfs, jossa xxx on versionumero. Esimerkiksi Puppy 2.12:ssa olisi vaanimeltä devx_212.sfs. Kun käytät LiveCD-tilassa, aseta devx_xxx.sfs-tiedosto samaan paikkaan kuin henkilökohtaiset asetustiedostosi pup_save.3fs, joka sijaitsee yleensä /mnt/home/-hakemistossa. Tämä koskee myös muita asennustiloja, joissa on pup_save.3fs-tiedosto. Jos Puppy on asennettu kiintolevylle, jossa on täysi "Vaihtoehto 2" -asennus, henkilökohtaista tiedostoa ei ole, katso Puppyn verkkosivuilta, jotka kootaan eri kokoonpanovaihtoehdoilla, joten moduulit eivät ole yhteensopivia. Nämä versiot vaativat vain yhden korjaustiedoston squashfille. Puppy 2.12:ssa on ydin 2.6.18.1 ja siinä on kolme korjausta; squashfs, oletuskonsolin lokitason ja sammutuksen korjaus.

Nämä ytimen korjauskomennot annetaan vain itseopiskeluasi varten, koska korjattu ydin on jo saatavilla...

Ensimmäinen asia, joka sinun tulee tehdä, on ladata itse ydin. Se sijaitsee linkin löytämiseksi sopivalle lataussivustolle. Tämän pitäisi olla "vanha" lähde, joka on saatavilla osoitteessa kernel.org tai sen peileissä.

Yhdistä Internetiin, lataa ydin /usr/src-kansioon. Pura se sitten pakkauksesta:

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

Sinun pitäisi nähdä tämä kansio: /usr/src/linux-2.6.16.7. Sitten sinun on varmistettava, että tämä linkki osoittaa ytimeen:

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

Sinun on otettava käyttöön seuraavat korjaukset, jotta sinulla on täsmälleen sama lähde, jota käytetään Puppyn ytimen kääntämiseen. Muussa tapauksessa saat "ratkaisemattomat symbolit" -virheilmoitukset, kun käännät ohjainta ja yrität sitten käyttää sitä Puppy-ytimen kanssa. Squashfs-korjauksen käyttäminen

Toiseksi kiinnitä Squashfs-laastari. Squashfs-korjaustiedosto lisää tuen Squashfsille, jolloin tiedostojärjestelmä on vain luku -muotoinen.

Lataa korjaustiedosto squashfs2.1-patch-k2.6.9.gz /usr/src-kansioon. Huomaa, että tämä korjaus tehtiin ytimen versiolle 2.6.9, mutta se toimii edelleen versiossa 2.6.11.x niin kauan kuin viittaus linux-2.6.9:ään on olemassa. Ota sitten korjaus käyttöön:

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

Huomaa, että p1:ssä on numero 1, ei symbolia l... (Hieno vitsi - noin käännös)

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

Valmis! Ydin on valmis käännettäväksi!

Ytimen kääntäminen

Sinun on hankittava ytimen asetustiedosto. Kopio siitä sijaitsee /lib/modules-kansiossa.

Toimi sitten seuraavasti:

Cd/usr/src/linux-2.6.18.1

Jos siellä on .config-tiedosto, kopioi se väliaikaisesti jonnekin tai nimeä se uudelleen.

tee puhdas tee mrproper

Kopioi Puppyn .config-tiedosto hakemistoon /usr/src/linux-2.6.18.1... Sillä on eri nimet tiedostossa /lib/modules, joten nimeä uudelleen muotoon .config... Seuraavat vaiheet lukevat .config-tiedoston ja luovat sen uusi.

tee menuconfig

...tee haluamasi muutokset ja tallenna ne. Sinulla on nyt uusi .config-tiedosto ja sinun tulee kopioida se jonnekin säilytystä varten. Katso huomautus alla

tee bzImage

Nyt olet kääntänyt ytimen.

Hienoa, löydät Linux-ytimen hakemistosta /usr/src/linux-2.6.18.1/arch/i386/boot/bzImage

Moduulien kokoaminen

Siirry nyt hakemistoon /lib/modules ja jos siellä on jo kansio nimeltä 2.6.18.1 , nimeä kansio 2.6.18.1 uudelleen muotoon 2.6.18.1-old .

Asenna nyt uudet moduulit:

Cd/ usr/ src/ linux-2.6.18.1 make modules make modules_install

...tämän jälkeen sinun pitäisi löytää uudet moduulit asennettuna /lib/modules/2.6.18.1-kansiosta.

Huomaa, että yllä oleva viimeinen vaihe suorittaa "depmod"-ohjelman ja tämä voi antaa virheilmoituksia joidenkin moduulien puuttuvista symboleista. Älä huoli siitä - yksi kehittäjistä meni sekaisin, ja se tarkoittaa, että emme voi käyttää kyseistä moduulia.

Kuinka käyttää uutta ydintä ja moduuleja

On parempi, jos sinulla on Puppy Unleashed asennettuna. Sitten tarball laajenee ja siellä on 2 hakemistoa: "boot" ja "kernels".

"Boot" sisältää tiedostorakenteen ja komentosarjan alkuperäisen virtuaalilevyn luomiseksi. Sinun täytyy laittaa siihen joitain ydinmoduuleja.

"Kernels"-hakemistossa on hakemisto kernels/2.6.18.1/ , ja sinun on korvattava moduulit päivitetyillä. Sinun ei tarvitse vaihtaa sitä, jos käänsit uudelleen saman ytimen version (2.6.18.1).

Huomaa, että kernels/2.6.18.1:ssä on tiedosto nimeltä "System.map". Nimeä se uudelleen ja korvaa se uudella hakemistosta /usr/src/linux-2.6.18.1. Selaa kernels/2.6.18.1/-kansiota ja sinun pitäisi tietää, mitä on päivitettävä.

Jos käänsit ytimen täydessä Puppy-asennuksessa, voit käynnistää uudelleen käyttämällä uutta ydintä. make modules_install on yllä oleva vaihe uusien moduulien asentamiseksi kansioon /lib/modules/2.6.18.1 , mutta sinun on asennettava myös uusi ydin. Käynnistän Grubilla ja kopioin vain uuden ytimen /boot-hakemistoon (ja nimeän tiedoston uudelleen nimellä bzImage vmlinuziksi).

Huomautus koskien menuconfig. Olen käyttänyt sitä iät ja ajat, joten pidä joitakin asioita itsestäänselvyytenä, mutta aloittelija voi kuitenkin hämmentyä halutessaan lopettaa ohjelman. Ylätason valikossa on valikko asetusten tallentamista varten - jätä se huomiotta. Paina TAB-näppäintä (tai oikeaa nuolinäppäintä) korostaaksesi Exit-painikkeen ja paina ENTER-näppäintä. Sitten sinulta kysytään, haluatko tallentaa uuden kokoonpanon, ja vastauksesi pitäisi olla Kyllä.


Yläosa