Från firmware till kernel: bootprocessen förklarad
Varje operativsystem börjar innan det verkligen är sig självt. CPU:n startar i en miljö som definieras av plattformen, firmware initialiserar tillräckligt med hårdvara för att ladda den första körbara filen, och den körbara filen förbereder maskinen för kernel. Först när den kedjan har gjort sitt arbete kan operativsystemet börja upprätthålla sina egna regler.
Det är lätt att behandla den tidiga vägen som ren VVS, men bootprocessen är en del av säkerhetsmodellen. För EriX är boot det första stället där opålitliga bytes blir betrodd exekvering, och det är också det första stället där maskinens auktoritet översätts till explicit struktur: verifierade images, minneskartor, moduldeskriptorer, entry-adresser, framebuffer-metadata, ACPI-pekare och till slut kernelobjekt.
Om den här vägen är slarvig börjar capability-systemet med en lögn. Det här inlägget går igenom EriX bootväg från firmware till kernel: vad UEFI ger, vad bootloadern måste göra, vad handoffen innehåller och varför kernel går in som en higher-half-exekverbar fil.
Boot börjar med firmware-auktoritet⌗
På nuvarande EriX-mål börjar bootvägen med UEFI på x86_64. UEFI är
firmware, och det körs före operativsystemet. Det tillhandahåller boottjänsterna
som låter en EFI-applikation läsa filer, allokera minne, inspektera
plattformens minneskarta, upptäcka konfigurationstabeller som ACPI, fråga
grafikutmatning via GOP och lämna boottjänsterna innan kernel tar över.
EriX bootloader byggs som en UEFI-applikation, så firmware laddar bootloadern
först och anropar dess UEFI-entrypoint. I implementationen är den entrypointen
efi_main, med UEFI:s x86_64-anropskonvention. Vid den här punkten styr EriX
inte maskinen ännu; det körs inne i en miljö som firmware tillhandahåller.
Bootloadern kan be UEFI läsa en fil, allokera minne, inspektera firmwaretabeller och förbereda sidtabeller, men all den auktoriteten är tillfällig. UEFI:s boottjänster är inte operativsystemet. De är byggställningar som bootloadern måste använda varsamt, sammanfatta som explicita fakta och sedan lämna bakom sig.
Bootloadern är betrodd kod⌗
Bootloadern körs innan kernel kan upprätthålla någonting, vilket placerar den i den betrodda beräkningsbasen. Om bootloadern laddar fel bytes, hoppar till fel adress, accepterar en manipulerad image, märker minne fel eller hittar på auktoritet som inte kom från verifierad input, börjar kernel från en komprometterad grund. För att hålla den risken granskningsbar håller EriX bootloaderns arbete smalt och explicit:
- lokalisera och ladda
boot.img - validera imagen innan den betros
- validera dynamic boot store
- relokera den dynamiska kerneln och dess nödvändiga tidiga objekt
- bygga en deterministisk handoff-struktur
- förbereda sidtabeller och en bootstrap-stack
- lämna UEFI:s boottjänster
- hoppa till kernel en gång
Detta är medvetet inte en generell bootmiljö. Det finns ingen bootmenypolicy i nuvarande scope, ingen fjärråterställningsväg och inget försök att fortsätta efter felformad bootkritisk input. Regeln är verifiera före exekvering: om boot imagen inte kan parsas, verifieras, laddas, mappas eller beskrivas konsekvent misslyckas bootförsöket innan kernel får kontrollen.
Ladda boot.img⌗
Den nuvarande UEFI-vägen letar efter den här filen på bootvolymen. Vägen är fast för den nuvarande profilen, vilket håller den tidiga mediahanteringen smal och undviker att göra boot till ett policydetekteringsproblem:
\ERIX\BOOT.IMG
Bootloadern öppnar bootvolymen genom UEFI:s filprotokoll, läser filen till
UEFI-allokerat minne och gör en billig första kontroll av containermagin
ERIXBOOT innan djupare parsning. Därefter tar lib-bootimg över: bootloadern
parsar imagen som en BootImage, verifierar dess signatur och hashvärden,
kontrollerar manifestets arkitektur och börjar först därefter extrahera
payloads.
Den separationen spelar roll eftersom bootmediet inte är betrott. Filen kan
saknas, vara trunkerad, vara felformad eller ha manipulerats, och bootloadern får
inte behandla en sektionsdeskriptor som sann bara för att den kom från disk. I
EriX är boot.img-parsern och verifieraren en del av den betrodda bootvägen,
och de måste avvisa dålig struktur, dåliga gränser, dåliga hashvärden, versioner
utan stöd och inkompatibla arkitekturdata innan någon laddad byte blir körbar.
Nästa inlägg går djupare in i boot.img-formatet och imageverifiering. För det
här inlägget är den viktiga punkten ordningen:
- läs bytes
- parsa struktur
- verifiera integritet
- kontrollera målkompatibilitet
- validera och relokera dynamiska bootartefakter
- bygg handoff-metadata
Exekvering kommer efter validering, inte före. Den ordningen är skillnaden mellan att behandla boot imagen som input och att behandla den som auktoritet.
Ladda kernel⌗
När boot.img har accepterats behöver bootloadern en kernel, och i EriX bootväg
laddas kernel från den signerade dynamic boot store. Bootloadern validerar den
dynamiska kerneln som ett ELF64 ET_DYN-objekt, kontrollerar EriX
dynamic-link-metadata, verifierar beroendenamn och objekthashar mot signerad
metadata, laddar nödvändiga delade objekt, applicerar godkända relokeringar,
tvingar W^X-begränsningar och förbereder en dynamisk bootkatalog åt kernel.
Det låter som mycket eftersom det är mycket, men säkerhetsformen förblir direkt och granskningsbar:
- data kommer från en verifierad boot image
- parsning av exekverbart format är explicit
- segmentintervall kontrolleras
- relokeringars sidoeffekter begränsas
- saknad eller inkonsekvent dynamisk metadata misslyckas före kernel-entry
Bootloadern finns för att validera och relokera den dynamiska kerneln, och sedan beskriva den verifierade tidiga objektgrafen för kernel. Den blir inte en generell dynamisk linker för det körande systemet; den rollen hör hemma senare, efter att kernel och modellen för user-space-tjänster finns.
Bevara bootmetadata⌗
Den dynamiska kerneln är inte det enda i boot imagen. Bootloadern bevarar också nödvändig icke-körbar bootmetadata för kernel och det tidiga user-space-systemet, medan körbara tjänster hör till den dynamiska objektgrafen. De körbara artefakterna beskrivs av den dynamiska katalogen som objekt, segment och beroendekanter härledda från den signerade storen och manifestet.
Andra nödvändiga sektioner är icke-körbara blobs. Exempel är bootkonfiguration, dynamic-link-metadatastores och konsolfontdata. De kopieras till mappat minne och beskrivs som skrivskyddade moduler utan entrypoint, eftersom en blob inte är körbar bara för att den förekommer i boot imagen.
Den distinktionen är viktig för capability-design. En bootkonfigurationspayload ska kunna läsas av det tidiga systemet, men den ska inte behandlas som kod. En fontblob kan behövas för framebuffer-kontinuitet, men den behöver inte exekveringsauktoritet. EriX bevarar distinktionen i handoffen:
- dynamiska objektdeskriptorer för körbara artefakter
- dynamiska segmentdeskriptorer för mappade körbara och dataområden
- dynamiska beroendedeskriptorer för tidiga objektrelationer
SECTION_TYPE_BOOT_CONFIGför bootpolicydata- moduldeskriptorer för icke-körbara bootblobs
Handoffen för skillnaden vidare så att kernel och rootd inte behöver gissa om
en bit bootdata är körbar auktoritet, skrivskyddad konfiguration eller vanlig
metadata.
Handoffen är kontraktet⌗
Bootloadern anropar inte ett kernel-API, eftersom det ännu inte finns någon
körande kernel. I stället bygger bootloadern en handoff-blob: ett versionerat
binärt kontrakt definierat av lib-handoff. I den nuvarande profilen från
bootloader till kernel börjar den med magin ERIXHK01, major/minor-versioner,
total storlek, arkitektur- och plattforms-ID, och därefter offsets och antal för
tabellerna som följer.
Handoffen kan innehålla flera klasser av data som kernel behöver innan den kan bygga sin egen runtime-vy av maskinen:
- normaliserade minneskarteposter
- deskriptorer för laddade moduler
- ACPI RSDP-pekare
- verifierat build ID och imagehash
- metadata för framebuffer-kontinuitet
- dynamiska objektdeskriptorer
- dynamiska segmentdeskriptorer
- dynamiska beroendekanter
Detta är den första strukturerade överföringen av auktoritet. Firmware gav bootloadern råa fakta och tillfälliga tjänster, och den verifierade boot imagen gav bootloadern signerad payloadmetadata. Bootloadern kombinerar detta till en deterministisk beskrivning av vad den laddade, var den placerade det och vad kernel kan lita på.
Handoffen är inte en vink eller “best effort”-metadata. Den är inputen som kernel börjar bygga sin egen bild av maskinen från, och därför bär den antal, entrystorlekar, offsets, hashvärden, typer, intervall och versionsfält. Det måste vara möjligt för kernel att avvisa den.
Minneskartor behöver normalisering⌗
Firmwares minneskartor är inte automatiskt formade för kernelpolicy. UEFI rapporterar regioner med firmware-minnestyper, medan bootloadern också känner till minnet den har allokerat för den dynamiska kerneln, tidiga objektmappningar, nödvändiga bootblobs, bootstrap-stack, handoff-sidor, framebuffer-mappningar och den ursprungliga boot imagen. De vyerna måste slås ihop innan kernel kan resonera om tillgängligt minne.
I EriX tar bootloadern en snapshot av UEFI-minneskartan, lägger till explicit bootägda regioner och normaliserar resultatet till icke-överlappande intervall med EriX-minnestyper som:
- användbart RAM
- reserverat minne
- ACPI reclaimable-minne
- ACPI NVS-minne
- MMIO/enhetsminne
- bootloaderägt minne
- boot-imageägt minne
Explicit bootägda regioner vinner över generiska firmwareklassificeringar. Det spelar roll eftersom kernel inte av misstag ska behandla minnet som håller boot imagen, handoff-bloben, dynamiska objektmappningar, nödvändiga bootblobs eller bootstrap-stacken som vanligt fritt RAM. Minne är auktoritet i EriX, så redan så här tidigt är systemet noga med vem som får återanvända vilka bytes.
Lämna UEFI bakom sig⌗
UEFI:s boottjänster är användbara, men tillfälliga. Innan bootloadern hoppar
till kernel anropar den ExitBootServices, vilket i praktiken är en enkelriktad
övergång. Efter en lyckad exit måste bootloadern behandla pekare till
boottjänster som ogiltiga; den kan inte fortsätta be firmware allokera minne
eller läsa filer efter att operativsystemet tar över.
Den övergången är känslig eftersom UEFI kräver att bootloadern lämnar med en aktuell minneskartnyckel. Om kartan ändras mellan snapshot och exit kan anropet misslyckas och loadern måste försöka igen med en färsk karta. EriX UEFI-adapter hanterar den retry-loopen i plattformslagret.
Den viktiga designpunkten är att kernel får kontroll efter att bootloadern har slutat använda firmwaretjänster. Kernel ska inte ärva ett halvöppet firmwareberoende; den ska ärva explicit data.
Bygga de första sidtabellerna⌗
Kernel får kontroll med paging redan aktivt. För den nuvarande x86_64
UEFI-profilen bygger bootloadern minimala sidtabeller med två sorters mappningar:
en identitetsmappad lågmemory-region för tidig bring-up och specifika
higher-half-mappningar för objekten som kernel kommer att använda direkt.
Den nuvarande implementationen identitetsmappar första 1 GiB med 2 MiB-sidor och identitetsmappar också APIC MMIO-fönster. Sedan mappar den higher-half- virtuella intervall för kernel, laddade dynamiska objekt, nödvändiga bootblobs, handoff-bloben, framebuffer och bootstrap-stack. Det räcker för att kernel ska starta i adressrymden den förväntar sig.
Sidtabellerna är inte det slutliga virtuella minnessystemet. De är en bro som låter kernel exekvera, validera handoffen, installera tidigt CPU-tillstånd och börja bygga den riktiga kernel- och user-space-miljön. Bron måste ändå vara korrekt: om kerneln entry-sida inte är mappad faultar maskinen direkt; om handoff-bloben är mappad på fel virtuell adress läser kernel nonsens; om stacken saknas misslyckas entry innan Rust-kod kan göra särskilt mycket.
Varför en higher-half-kernel?⌗
EriX går in i kernel i den övre halvan av den virtuella adressrymden. Det betyder att kernel körs på höga virtuella adresser i stället för att länkas och köras bara i det låga identitetsmappade intervallet. Det är en vanlig kerneldesign eftersom den ger kernel en stabil virtuell adressregion oberoende av var fysiskt minne allokerades.
Higher-half-layouten separerar också kerneln virtuella utrymme från vanliga user-space-intervall och låter kernel behålla sina egna mappningar när adressrymder ändras senare, medan user-space-mappningar kan variera per task. Adresslayouten upprätthåller inte hela säkerhetsmodellen på egen hand, men den stödjer gränsen genom att göra kernelminne skilt från vanligt processminne.
I EriX är bootloadern ansvarig för att göra den första higher-half-entryn
möjlig. Den laddar kernel enligt ELF-virtuella adresser, mappar dessa virtuella
adresser till allokerade fysiska sidor, skapar en bootstrap-stack i higher half,
mappar handoff-bloben på en känd higher-half-adress, laddar cr3 och hoppar
till kerneln entrypoint.
Vid hoppet är det nuvarande x86_64-kontraktet medvetet litet och explicit. Bootloadern tillhandahåller bara det tillstånd kernel behöver för att börja validera handoffen och installera sitt eget tidiga CPU-tillstånd:
- SysV ABI
rdiinnehåller handoff-pekarenrsppekar på bootstrap-stacken med förväntad alignment- paging är aktivt
- kontrollen återvänder inte
Detta ABI är litet avsiktligt. Ju mindre implicit tillstånd kerneln entry beror på, desto lättare är gränsen att granska.
Gå in i kernel⌗
På kernelsidan börjar entry innan hela kernel-runtime finns. Den dynamiska
kerneln exponerar erix_dynlink_entry, som går in i den tidiga kernelvägen med
handoff-pekaren, och det första arbetet är defensivt snarare än policytungt.
Kernel stänger av interrupts, initialiserar tidig seriell utmatning, kontrollerar
att handoff-pekaren inte är null, läser handoff-storleken från headern,
upprätthåller en maximal handoff-storlek och validerar sedan hela bloben genom
lib-handoff. Efter strukturell validering applicerar kernel sina egna
policykontroller:
- arkitekturen måste vara
x86_64 - plattformen måste vara UEFI
- build ID får inte vara tomt
- tabellerna i den dynamiska katalogen måste vara internt konsekventa
- dynamiska objektnamn, hashvärden, segment, beroenden och store-intervall måste valideras före användning
Bootloadern byggde redan handoffen, men kernel validerar den ändå. Betrodda komponenter får inte hoppa över kontrakt bara för att en annan betrodd komponent producerade datan. Poängen med en versionerad handoff är att båda sidor kan vara överens exakt om vad som överfördes.
Först därefter går kernel djupare in i tidig initialisering: GDT-installation, IDT-installation, syscall-vägssetup, valfri tidig konsolinitialisering och slutligen bootstrap-orkestrering för den första root-tasken. Kernel blir kernel gradvis, och det första den gör är att kontrollera marken under fötterna.
Boot är auktoritetsöversättning⌗
Det är lockande att beskriva boot som “ladda kernel och hoppa”. Det är tekniskt sant, men det missar operativsystemets designpoäng. För EriX är boot auktoritetsöversättning: firmware-auktoritet blir explicita bootfakta, boot image-bytes blir verifierade sektioner, ELF-filer blir mappade körbara intervall, blobs blir icke-körbara moduldeskriptorer, firmwares minneskartor blir normaliserade minnesregioner, dynamic-link-metadata blir en begränsad objektgraf och framebuffer-tillstånd blir kontinuitetsmetadata.
Allt detta blir en handoff-blob, och kernel tar emot bloben och avgör om den är acceptabel. Först då kan den börja förvandla maskinresurser till kernelobjekt, capabilities, adressrymder, endpoints och den första user-space-tasken. Det är därför bootprocessen hör hemma i en diskussion om capability-baserade operativsystem: capability-modellen börjar inte efter boot som ett tillägg; den är beroende av att boot inte smugglar in ambient auktoritet.
Bootloadern ska inte bara säga att den laddade några saker. Den ska säga exakt vad den laddade, var det finns, vad det är, hur det verifierades och vilka plattformfakta den observerade. Det är skillnaden mellan ett hopp och en handoff.
Vad EriX håller utanför bootloadern⌗
Bootloadern är kraftfull eftersom den körs tidigt, och just därför ska den förbli liten. EriX vill inte att bootloadern ska besluta runtime-policy: den ska inte bestämma vilken tjänst som äger minnespolicy, hur processer övervakas, hur drivrutiner hanteras eller hur filsystem komponeras.
De besluten hör till kernel och systemtjänster i user space. Bootloaderns arbete är smalare: validera bootartefakten, förbered den minimala exekveringsmiljön, beskriv vad den gjorde och överför kontrollen.
Den gränsen spelar roll för TCB-storlek. En bootloader med fler funktioner är inte automatiskt bättre, eftersom varje funktion i tidig boot är kod som körs innan kernel kan isolera den. Varje parser, fallback-väg, interaktivt läge och policyundantag ökar mängden betrott beteende som måste vara korrekt innan systemet startar.
Framåt⌗
Det här inlägget behandlade boot.img mest som en verifierad container. Nästa
steg är att öppna den containern och titta direkt på dess design.
Nästa inlägg förklarar EriX boot.img-format: varför systemet använder en
enhetlig image, hur sektioner är utlagda, vilken metadata som bärs med och hur
formatet stödjer reproducerbara, deterministiska bootartefakter.