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:

  1. sovellus tiedostopalvelimelle
  2. tiedostopalvelin muistinhallinnalle
  3. muistinhallinta sivuttajalle
  4. sivuttaja lohkopalvelulle
  5. 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:

  • rootd on ensimmäinen politiikkaa kantava käyttäjätilan auktoriteetti
  • procd omistaa prosessien elinkaaren hallinnan
  • deviced omistaa ajuripolitiikan ja ajurien käynnistyksen orkestroinnin
  • vfsd omistaa julkisen tiedostojärjestelmän nimiavaruuden
  • tiedostojärjestelmäpalveluntarjoajat kuten ramfsd, e2fsd ja fatd pysyvät yksityisinä backend-vertaisina vfsd: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.