Mikroytimet ja monoliittiset ytimet: kompromissit uudelleen tarkasteltuna
Harva käyttöjärjestelmäsuunnittelun keskustelu on jatkunut yhtä pitkään kuin keskustelu mikroytimistä ja monoliittisista ytimistä.
Pintatasolla ero näyttää yksinkertaiselta:
- monoliittiset ytimet pitävät suurimman osan käyttöjärjestelmäpalveluista ytimen sisällä
- mikroytimet siirtävät suurimman osan palveluista käyttäjätilaan
Käytännössä kompromissi on hienovaraisempi.
Todellinen kysymys ei ole, onko toinen rakenne aina nopeampi, siistimpi tai turvallisempi. Todellinen kysymys on, missä auktoriteetin, monimutkaisuuden, vikojen ja suorituskykykustannusten pitäisi sijaita.
Tämä kirjoitus tarkastelee tätä kompromissia uudelleen, selittää miksi monet vanhat mikroytimiä koskevat argumentit yksinkertaistettiin liikaa ja näyttää, miksi EriXin kaltaiset modernit järjestelmät tekevät mikroydinmallista jälleen käytännöllisen.
Keskustelun historiallinen muoto⌗
Varhaiset käyttöjärjestelmät rakennettiin tiukkojen laitteistorajoitteiden alla.
Muistia oli vähän. Suorittimet olivat hitaampia. Kontekstinvaihdot olivat kalliita. Välimuistit, TLB:t, moniprosessorijärjestelmät ja nopeat syscall-polut olivat paljon vähemmän kyvykkäitä kuin nykyään.
Näissä olosuhteissa monoliittiset ytimet olivat luonnollinen valinta.
Unix-tyyppiset järjestelmät sijoittivat tiedostojärjestelmät, laiteajurit, verkkopinot, prosessien hallinnan ja monet muut palvelut yhteen etuoikeutettuun ytimen osoiteavaruuteen. Tämä rakenne teki monista operaatioista halpoja:
- tiedostojärjestelmä pystyi kutsumaan lohkokerrosta suoraan
- verkkopino pystyi käyttämään ajurin rakenteita suoraan
- ytimen alijärjestelmät pystyivät jakamaan dataa ilman IPC:tä
Tulos oli tehokas ja pragmaattinen.
Se tarkoitti myös, että suuri määrä koodia suoritettiin täydellä ydinprivilegiolla.
Miksi mikroytimet syntyivät⌗
Mikroytimet lähtivät liikkeelle toisesta havainnosta:
Suurin osa käyttöjärjestelmäkoodista ei tarvitse täyttä koneauktoriteettia.
Tiedostojärjestelmän ei tarvitse muuttaa mielivaltaisia sivutauluja. Näppäimistöajurin ei tarvitse päästä käsiksi jokaiseen prosessiin. Verkkopinon ei tarvitse hallita ajastinta.
Mikroytimet pitävät ytimessä vain kaikkein perustavimmat mekanismit, yleensä:
- ajastus
- osoiteavaruuksien hallinta
- prosessienvälinen viestintä
- kyvykkyyksien tai kahvojen hallinta
- keskeytysten ja poikkeusten toimitus
Korkeamman tason palvelut suoritetaan tavallisina käyttäjätilan prosesseina.
Tämä antaa järjestelmälle vahvemman eristyksen. Ajurin kaatumisen ei tarvitse olla ytimen kaatuminen. Tiedostojärjestelmävirhe ei automaattisesti muutu mielivaltaiseksi ytimen muistin korruptioksi. Auktoriteettia voidaan jakaa tarkemmin.
Ajatus oli vakuuttava, mutta varhaiset toteutukset kamppailivat usein suorituskyvyn ja yhteensopivuuden kanssa.
Ensimmäinen suorituskykyongelma⌗
Klassinen kritiikki mikroytimiä kohtaan on, että ne ovat hitaita.
Tämä kritiikki ei syntynyt tyhjästä.
Jotkut varhaiset mikroydinjärjestelmät sijoittivat perinteiset käyttöjärjestelmäpalvelut monien erillisten käyttäjätilapalvelinten taakse ja yrittivät sen jälkeen säilyttää tutut Unix-rajapinnat niiden päällä. Yksinkertainen operaatio saattoi muuttua viestiketjuksi:
- sovellus tiedostopalvelimelle
- tiedostopalvelin muistinhallinnalle
- muistinhallinta sivuttajalle
- sivuttaja lohkopalvelulle
- lohkopalvelu ajurille
Jokainen vaihe saattoi sisältää kontekstinvaihdon, viestin validoinnin, ajastuspäätöksen ja joskus kopiointia.
Jos rajapinnat ovat liian rupattelevia, kustannus kertyy.
Virhe oli muuttaa tämä yleiseksi säännöksi:
Mikroytimet ovat hitaita.
Tarkempi sääntö on:
Huonosti suunnitellut IPC-polut ja liian rupattelevat palvelurajat ovat hitaita.
Tällä erolla on merkitystä.
Monoliittinen nopea polku⌗
Monoliittiset ytimet voivat olla erittäin nopeita, koska ne välttävät monia suojausrajoja.
Ytimen sisäinen tiedostojärjestelmä voi kutsua ytimen sisäistä lohkokerrosta tavallisella funktiokutsulla. Ajuri voi jakaa muistia suoraan toisen alijärjestelmän kanssa. Jokaista pyyntöä ei tarvitse sarjallistaa viestimuotoon.
Tämä on todellinen etu.
Mutta se ei ole ilmainen.
Monoliittinen nopea polku tuo usein mukanaan:
- enemmän etuoikeutettua koodia
- enemmän jaettua muuttuvaa tilaa
- enemmän ytimen sisäistä lukitusmonimutkaisuutta
- enemmän tapoja, joilla yksi alijärjestelmä voi korruptoida toisen
- suuremman luotetun laskentapohjan
Suorituskyky ei ole vain käskyjen laskemista. Siihen kuuluvat myös välimuistikäyttäytyminen, lukitusten kilpailu, vikojen rajaaminen, palautuminen ja oikeellisuuden ylläpitämisen kustannus ajan mittaan.
Monoliittinen ydin voi voittaa raa’an mikrobenchmarkin ja samalla tehdä eristyksestä ja auditoitavuudesta vaikeampaa.
Suorituskyyttimyytti: jokainen raja on kohtalokas⌗
Yleinen myytti on, että jokainen mikroydinraja on niin kallis, ettei rakenne voi kilpailla.
Tämä näkemys on vanhentunut.
Rajalla on kustannus, mutta modernit järjestelmät voivat tehdä kustannuksesta hallittavan:
- nopeat syscall- ja paluupolut
- paremmat ajastusheuristiikat
- jaetun muistin datapolut
- sivujen kartoitus massakopioinnin sijaan
- pyyntöjen eräajo
- asynkroninen tapahtumien toimitus
- huolellisesti suunnitellut IPC-ABI:t
Tärkeä suunnittelutavoite on pitää politiikka ytimen ulkopuolella pakottamatta jokaista datatavua kulkemaan ytimen kautta.
Ytimen pitäisi välittää auktoriteettia. Sen ei välttämättä pitäisi siirtää kaikkea dataa.
Suorituskyyttimyytti: IPC tarkoittaa kaiken kopioimista⌗
IPC kuvitellaan usein muotoon “kopioi koko tämä puskuri prosessilta A prosessille B”.
Se on vain yksi mahdollinen malli.
Mikroydin voi välittää pieniä ohjausviestejä samalla, kun se siirtää auktoriteettia jaettuun muistiin, frameihin, endpointteihin tai laiteobjekteihin. Kallis datapolku voi pysyä kartoitettuna, kun ydin vain validoi, kenellä on lupa käyttää sitä.
Tämä on keskeistä kyvykkyyspohjaisessa suunnittelussa.
Sen sijaan että suuria tietorakenteita kopioitaisiin etuoikeutetun alijärjestelmän kautta, prosessi voi vastaanottaa kyvykkyyden, joka valtuuttaa pääsyn tiettyyn objektiin tietyillä oikeuksilla.
Ydin vastaa edelleen siirron pakottamisesta. Sen ei tarvitse ymmärtää jokaista korkean tason protokollaa, joka rakennetaan tämän siirron päälle.
Suorituskyyttimyytti: käyttäjätilan ajurit eivät ole käytännöllisiä⌗
Käyttäjätilan ajureita käsitellään usein tutkimusideana.
Huoli on ymmärrettävä. Laitteistopääsy on herkkää, keskeytykset ovat aikakriittisiä ja ajurit sijaitsevat usein kuumilla poluilla.
Useimmat ajurit eivät kuitenkaan tarvitse täyttä ydinauktoriteettia.
Ajuri tarvitsee yleensä pääsyn:
- tiettyyn I/O-porttialueeseen
- tiettyyn MMIO-alueeseen
- tiettyyn keskeytyslinjaan
- tiettyyn DMA- tai puskurijärjestelyyn
Nämä ovat kapeampia auktoriteetin muotoja kuin “koko ydin”.
Jos ydin voi delegoida täsmälleen nämä resurssit, ajuri voi toimia ytimen ulkopuolella ja silti tehdä hyödyllistä työtä. Jos se epäonnistuu, järjestelmällä on mahdollisuus pysäyttää, käynnistää uudelleen tai korvata ajuri käsittelemättä vikaa ytimen muistin korruptiona.
Kompromissi on todellinen: käyttäjätilan ajurit tarvitsevat hyvän IPC:n, huolellisen keskeytysten toimituksen ja resurssien eksplisiittisen omistajuuden. Mutta malli ei ole lähtökohtaisesti epäkäytännöllinen.
Mitä EriX sijoittaa ytimeen⌗
EriX on suunniteltu kyvykkyysmikroytimeksi.
EriXin ydin on tarkoituksellisesti politiikkaminimaalinen. Sen arkkitehtuuridokumentit määrittelevät ytimen vastuiksi:
- bootloaderilta ytimelle tapahtuvan luovutuksen validointi
- ytimen perusobjektien ja kyvykkyyssemantiikan hallinta
- root-tehtävän luominen
- trap-, syscall- ja keskeytyssisääntulopisteiden tarjoaminen
Ydin ei nimenomaisesti vastaa:
- järjestelmäpolitiikasta
- prosessien orkestrointipolitiikasta
- korkean tason muistipolitiikasta
- palvelujen elinkaaripolitiikasta
Tämä on mikroydinraja käytännössä.
Ydin aloittaa koneauktoriteetilla, mutta sen on muunnettava tämä auktoriteetti eksplisiittisiksi ydinobjekteiksi ja kyvykkyysviitteiksi. Ambienttia auktoriteettia ei ole tarkoitus vuotaa käyttäjätilaan.
Mitä EriX siirtää ytimen ulkopuolelle⌗
EriX sijoittaa politiikkaa kantavan toiminnallisuuden käyttäjätilan palveluihin.
Esimerkiksi:
rootdon ensimmäinen politiikkaa kantava käyttäjätilan auktoriteettiprocdomistaa prosessien elinkaaren hallinnandevicedomistaa ajuripolitiikan ja ajurien käynnistyksen orkestroinninvfsdomistaa julkisen tiedostojärjestelmän nimiavaruuden- tiedostojärjestelmäpalveluntarjoajat kuten
ramfsd,e2fsdjafatdpysyvät yksityisinä backend-vertaisinavfsd:n takana
Tämä ei ole vain “siirretään koodi ytimen ulkopuolelle” esteettisenä valintana.
Jokainen palveluraja määrittää auktoriteettirajan.
rootd jakaa vähimmän etuoikeuden käynnistyskyvykkyyksiä. procd luo ja
käynnistää prosesseja vaiheistetun lapsiprosessin luonnin ja
asennusgranttien kautta. deviced ei muutu suoraan ytimeksi; se pyytää
procd:tä hallitsemaan ajuriprosesseja ja välittää vain kunkin roolin
tarvitseman ajuriauktoriteetin.
Tämä rakenne on monisanaisempi kuin monoliittisen ytimen kutsugraafi, mutta se tekee auktoriteetin kulun näkyväksi.
Kapea auktoriteetti laajan privilegion sijaan⌗
Yksi EriXin tärkeimmistä toteutusyksityiskohdista on siirtyminen pois laajasta root-endpointista normaalina runtime-ohjauspintana.
Nykyinen ydin tarjoaa kapeita kernel-control-endpoint-perheitä tiettyihin tehtäviin:
- ajan hallinta
- keskeytysten hallinta
- hotplug-tapahtumat
- PCI-konfiguraation luvut
- konsolin ja framebufferin käyttö
- COM1-I/O
- i8042-I/O
- muistin retyping
- VSpace-kartoitus
- sivuttajan vikojen ratkaisu
- prosessien hallinta
- ACPI-luvut
Runtime-dispatch määräytyy endpoint-objektin ja sen lajin perusteella, ei etuoikeutetun globaalin slot-numeron perusteella.
Tällä on merkitystä, koska tehtävä ei saa auktoriteettia vain tietämällä tavanomaisen slot-arvon. Sen täytyy oikeasti omistaa oikea kyvykkyys omassa paikallisessa kyvykkyysavaruudessaan.
Esimerkiksi drv-serial saa COM1-kohtaisen I/O-auktoriteetin. drv-i8042 saa
i8042-kohtaisen I/O-auktoriteetin. drv-acpi saa ACPI-lukuauktoriteetin.
probed saa PCI-konfiguraation lukuauktoriteetin.
Tämä on erilainen turvallisuusmuoto kuin kaikkien näiden operaatioiden sijoittaminen yhden laajan ydinkahvan taakse.
Laitemuisti eksplisiittisenä objektina⌗
EriX käsittelee myös laitemuistin auktoriteettia eksplisiittisenä ja tyypitettynä.
Ytimellä on erillinen CAP_TYPE_DEVICE_FRAME validoidulle laitemuistille.
Tallennuspolussa BAR-taustainen MMIO-frame voidaan johtaa deviced:lle, ja
deviced voi sen jälkeen asentaa vain tämän johdetun laiteframen ajurin
vaiheistettuun käynnistyspakettiin.
Tarkoitus ei ole, että laiteajureista tulisi yksinkertaisia.
Tarkoitus on, että MMIO-auktoriteettia ei sekoiteta tavallisiin RAM-frameihin eikä sitä paljasteta yleisenä “tee mitä tahansa laitemuistilla” -pakotienä.
Juuri tällaiset yksityiskohdat tekevät moderneista mikroytimistä käytännöllisiä: laitteistopääsy delegoidaan täsmällisenä objektina täsmällisillä oikeuksilla.
IPC ABI:na, ei sattumana⌗
Monoliittisessa ytimessä monet sisäiset rajapinnat ovat tavallisia funktiokutsuja.
Mikroytimessä IPC:stä tulee osa järjestelmän ABI:a. Se tekee siitä tärkeämmän, ei vähemmän tärkeää.
EriX käsittelee IPC:tä jaettuna sopimuksena:
- viestiotsakkeet ovat versioituja
- asettelut ovat kiinteitä
- jäsennys käyttää tarkistettua aritmetiikkaa
- virheelliset payloadit epäonnistuvat suljetusti
- kyvykkyyssiirrot ovat eksplisiittisiä
- siirtoja kantavat runtime-viestit vaativat
GRANT-oikeuden
Tämä on vastakohta sille, että IPC:tä käsiteltäisiin jälkikäteen lisättynä yksityiskohtana.
IPC:n kustannusta hallitaan osin toteutuksella, mutta myös rajapintasuunnittelun avulla. Huolellisesti suunniteltu ABI välttää tarpeettomia edestakaisia matkoja, pitää viestit rajattuina ja erottaa ohjauksen siirron datan liikkumisesta.
Miksi mikroytimet ovat jälleen käytännöllisiä⌗
Mikroytimet ovat nykyään käytännöllisempiä useasta syystä.
1. Laitteisto muuttui⌗
Suojausrajan suhteellinen kustannus on muuttunut.
Kontekstinvaihdot ja syscallit eivät vieläkään ole ilmaisia, mutta modernit suorittimet, muistijärjestelmät ja keskeytysmekanismit tekevät raakakustannuksesta vähemmän ratkaisevan kuin silloin, kun varhaisia mikroydinkokeiluja arvioitiin.
Samaan aikaan modernit järjestelmät ovat monimutkaisempia ja alttiimpia. Ytimen kompromettoinnin kustannus on kasvanut.
Eristys on nyt arvokkaampaa.
2. Ymmärrämme IPC:tä paremmin⌗
Aiemmista järjestelmistä opittu asia ei ole “vältä IPC:tä”.
Oppi on:
- vältä tarpeetonta IPC:tä
- vältä liian rupattelevia protokollia
- vältä suurten tietomäärien kopiointia, kun auktoriteetin siirto riittää
- suunnittele palvelurajat todellisen omistajuuden ympärille
Mikroytimet ovat käytännöllisiä, kun IPC:tä käsitellään ensimmäisen luokan suunnitteluongelmana.
3. Kyvykkyydet tekevät rajoista hyödyllisiä⌗
Koodin siirtäminen käyttäjätilaan on vain puolet tarinasta.
Jos jokainen käyttäjätilapalvelin saa edelleen laajan implisiittisen privilegion, järjestelmä on pitkälti luonut monoliitin uudelleen lisäkontekstinvaihdoilla.
Kyvykkyydet tekevät rajasta merkityksellisen.
EriXissä auktoriteetti esitetään tyypitettyinä kyvykkyyksinä eksplisiittisillä oikeuksilla. Palvelut validoivat vastaanottamansa kyvykkyydet. Käynnistyspaketit kuvaavat ilmoitetun auktoriteetin. Ydin- ja palvelukoodi välttää käsittelemästä kanonisia slot-numeroita ambienttina lupana.
Tämä tekee hajauttamisesta enemmän kuin modulaarisuutta. Se tekee hajauttamisesta osan turvallisuusmallia.
4. Kielet ja työkalut paranivat⌗
Modernit toteutuskielet ja työkalut muuttavat myös kompromissia.
Rust ei poista käyttöjärjestelmävirheitä, mutta se tekee monien muistiturvallisuusvirheiden vahingossa kirjoittamisesta vaikeampaa. Se tekee myös unsafe-rajat näkyviksi katselmoinnissa.
Mikroydinjärjestelmälle tämä on erityisen hyödyllistä. Ydin voi pysyä pienenä ja auditoitavana, samalla kun käyttäjätilan palvelut voidaan kirjoittaa vahvemmilla turvatakuilla kuin perinteiset C-painotteiset järjestelmäkomponentit.
EriX yhdistää tämän puhdastilalähestymistapaan ja kolmannen osapuolen cratejen välttämiseen, mikä pitää järjestelmän helpommin auditoitavana, vaikka toteutustyö kasvaa.
Jäljelle jäävät kustannukset⌗
Mikroytimillä on edelleen todellisia kustannuksia.
Ne vaativat:
- eksplisiittisempää käynnistyslogiikkaa
- huolellisesti versioituja IPC-sopimuksia
- vahvaa palveluvalvontaa
- enemmän huomiota eräajoon ja datan liikkumiseen
- jokaisen kyvykkyyden selkeää omistajuutta
- hyvää jäljitystä ja suorituskykymittausta
Ne myös siirtävät osan monimutkaisuudesta ytimen ulkopuolelle sen sijaan, että poistaisivat sen.
rootd, procd, deviced ja tiedostojärjestelmäpalvelut tarvitsevat edelleen
huolellista suunnittelua. Ne voivat olla ytimen ulkopuolella, mutta ne voivat
silti olla luotettuja komponentteja tietyissä järjestelmän osissa.
Ero on siinä, että niiden auktoriteetti voi olla ydinauktoriteettia kapeampi, ja niiden viat voidaan rajata tarkoituksellisemmin.
Kompromissi uudelleen tarkasteltuna⌗
Vanha kehystys oli usein:
- monoliittiset ytimet ovat nopeita
- mikroytimet ovat siistejä mutta hitaita
Tämä kehystys on liian yksinkertainen.
Parempi kehystys on:
- monoliittiset ytimet optimoivat suoraa ytimen sisäistä yhteistyötä
- mikroytimet optimoivat eksplisiittistä auktoriteettia ja vikojen eristystä
- kumpi tahansa malli voi olla nopea tai hidas toteutuksesta riippuen
- kumpi tahansa malli voi muuttua monimutkaiseksi, jos rajat valitaan huonosti
EriXissä mikroydinvalinta seuraa järjestelmän tavoitteista:
- minimaalinen luotettu laskentapohja
- eksplisiittinen auktoriteetti kyvykkyyksien kautta
- tiukka erottelu ytimen ja käyttäjätilan välillä
- auditoitavat palvelurajat
- deterministinen käynnistys ja vikakäyttäytyminen
Nämä tavoitteet eivät tee suorituskyvystä merkityksetöntä.
Ne määrittelevät, missä suorituskykytyön pitäisi tapahtua: nopea IPC, huolelliset palvelurajapinnat, jaetun muistin datapolut, kapeat endpoint-perheet ja eksplisiittinen kyvykkyyksien siirto.
Katse eteenpäin⌗
Mikroytimet eivät ole oikotie.
Ne vaativat enemmän etukäteistä suunnittelukuria kuin yksinkertainen ytimen sisäinen kutsugraafi. Ne pakottavat järjestelmän määrittelemään auktoriteetin, omistajuuden ja vikakäyttäytymisen aikaisin.
Juuri siksi ne ovat kiinnostavia.
EriX käyttää mikroydinmallia ei siksi, että se olisi muodikas, vaan koska se sopii arkkitehtuuriin: pieni ydin, kyvykkyyksillä välitetty auktoriteetti ja eksplisiittisten käyttäjätilapalvelujen toteuttama politiikka.
Seuraava kirjoitus tarkastelee ajatusta, joka motivoi suurta osaa tästä rakenteesta: luotettu laskentapohja.
Katsomme, mitä TCB todella sisältää, miksi sen koko vaikuttaa hyökkäyspintaan ja miten EriX pyrkii pitämään luotetun koodin pienenä siirtämällä politiikan eksplisiittisiin, kyvykkyyksien rajoittamiin käyttäjätilapalveluihin.