Come funzionano i sistemi operativi ... senza avere un sistema operativo su cui eseguire?


167

Sono davvero curioso adesso. Sono un programmatore Python, e questa domanda mi ha fatto impazzire: scrivi un sistema operativo. Come lo esegui? Deve essere eseguito in qualche modo, e in quel modo è all'interno di un altro sistema operativo?

Come può essere eseguita un'applicazione senza essere in un sistema operativo? Come si dice al computer di eseguire, dire, C ed eseguire questi comandi sullo schermo, se non ha un sistema operativo in cui eseguire?

Ha a che fare con un kernel UNIX? In tal caso, cos'è un kernel Unix o un kernel in generale?

Sono sicuro che i sistemi operativi siano più complicati di così, ma come funziona?


14
Sono abbastanza sicuro che sia ciò a cui serve un BIOS: è un sistema operativo davvero piccolo che avvia il sistema operativo più grande.
sevenseacat,

64
Un sistema operativo è conveniente , ma non è necessario uno per eseguire programmi su un computer.
Andres F.

10
È perfettamente possibile anche scrivere software non OS senza un SO. Molti interpreti di Forth tradizionalmente giravano senza un sistema operativo (o si potrebbe dire che fossero sistemi operativi). Non è nemmeno così difficile. Se conosci C, potresti divertirti a scrivere un programma del genere (un piccolo gioco, forse) come un esercizio di apprendimento.
Max

44
Questa confusione è uno dei costi dei meravigliosi sistemi di elaborazione altamente astratti che utilizziamo in questi giorni: le persone possono essere programmatori molto bravi e competenti e non conoscere nemmeno i fondamenti su come funziona il computer. Quanto in basso vuoi andare? Per i livelli molto bassi, ma ancora al di sopra della fisica, vedi Come sono stati programmati i primi microprocessori? su Electronics.SE.
dmckee,

2
La programmazione è stata effettuata prima dell'invenzione dell'attuale concetto di sistema operativo. Ovviamente qualcosa a quel livello è ciò che dà il via al sistema operativo. I sistemi operativi vengono avviati. Questo è di solito menzionato almeno in un programma di 4 anni CS ad un certo punto poiché la maggior parte richiede una teoria informatica del corso di sistemi operativi.
Rig

Risposte:


263

Ci sono molti siti Web che passano attraverso il processo di avvio (come Come avviare i computer ). In breve, è un processo in più fasi che continua a costruire il sistema un po 'alla volta fino a quando non può finalmente avviare i processi del sistema operativo.

Si inizia con il firmware sulla scheda madre che tenta di far funzionare la CPU. Quindi carica il BIOS che è come un mini sistema operativo che mette in funzione l'altro hardware. Una volta fatto, cerca un dispositivo di avvio (disco, CD, ecc.) E, una volta trovato, individua l'MBR (master boot record) e lo carica in memoria ed esegue. È questo piccolo pezzo di codice che poi sa come inizializzare e avviare il sistema operativo (o altri boot loader poiché le cose sono diventate più complicate). È a questo punto che cose come il kernel sarebbero state caricate e avrebbero iniziato a funzionare.

È abbastanza incredibile che funzioni affatto!


108
+1 per la frase finale.
un CVn

39
C'è una ragione per cui si chiama "boot"; il termine è abbreviato per "bootstrap", come in "tirarsi su dai bootstrap".
KeithS,

5
C'è stato un tempo in cui qualcuno ha dovuto digitare o attivare il codice bootstrap. A volte è stato un semplice salto alla prima istruzione di un programma in ROM. Altre volte era codice leggere da un dispositivo e passare alla prima istruzione di programma nei dati letti. Adesso le cose sono molto più semplici.
BillThor,


15
@BillThor: Dove per "molto più semplice", ovviamente, intendi "molto più complicato". Sono solo più semplici da usare .
Raphael Schweikert,

173

Un sistema operativo "bare metal" non funziona all'interno di nulla. Esegue il set di istruzioni completo sulla macchina fisica e ha accesso a tutta la memoria fisica, a tutti i registri dei dispositivi e a tutte le istruzioni privilegiate, comprese quelle che controllano l'hardware di supporto della memoria virtuale.

(Se il sistema operativo è in esecuzione su una macchina virtuale, è possibile che si trovi nella stessa situazione di cui sopra. La differenza è che alcune cose sono emulate o gestite in qualche modo dall'hypervisor; ovvero il livello che esegue le macchine virtuali .)

Ad ogni modo, mentre il sistema operativo potrebbe essere implementato in (ad esempio) C, non avrà tutte le normali librerie C disponibili. In particolare, non avrà le normali librerie "stdio". Piuttosto, implementerà (ad esempio) un driver di dispositivo disco che gli consente di leggere e scrivere blocchi disco. Implementerà un file system in cima al livello del blocco del disco, e in più implementerà le chiamate di sistema che le librerie di runtime di un'applicazione utente fanno (ad esempio) per creare, leggere e scrivere file ... e così via.

Come può essere eseguita un'applicazione senza essere in un sistema operativo?

Deve essere un tipo speciale di applicazione (ad esempio un sistema operativo) che sappia interagire direttamente con l'hardware I / O, ecc.

Come si dice al computer di eseguire, dire, C ed eseguire questi comandi sullo schermo, se non ha un sistema operativo in cui eseguire?

Non

L'applicazione (che era per motivi di argomenti scritti in C) è compilata e collegata su qualche altra macchina per dare un'immagine di codice nativa. Quindi l'immagine viene scritta sul disco rigido in un punto in cui il BIOS può trovarla. Il BIOS carica l'immagine in memoria ed esegue un'istruzione per passare al punto di ingresso dell'applicazione.

Non esiste (in genere) "esecuzione di C ed esecuzione di comandi" nell'applicazione a meno che non sia un sistema operativo completo. E in tal caso, è responsabilità del sistema operativo implementare tutte le infrastrutture necessarie per realizzarlo. Nessuna magia. Solo un sacco di codice.

La risposta di Bill riguarda il bootstrap che è il processo in cui si passa da una macchina spenta a una macchina in cui il normale sistema operativo è attivo e in esecuzione. Tuttavia, vale la pena notare che quando il BIOS completa le sue attività, (in genere) dà il controllo completo dell'hardware al sistema operativo principale e non svolge alcuna ulteriore parte - fino al successivo riavvio del sistema. Il sistema operativo principale non esegue certamente "all'interno" del BIOS in senso convenzionale.

Ha a che fare con un kernel UNIX? In tal caso, cos'è un kernel unix o un kernel in generale?

Sì lo fa.

Il kernel UNIX è il nucleo del sistema operativo UNIX. È la parte di UNIX che fa tutto il materiale "bare metal" sopra descritto.

L'idea di un "kernel" è che si tenta di separare il software di sistema in elementi core (che richiedono l'accesso al dispositivo fisico, tutta la memoria, ecc.) E elementi non core. Il kernel è costituito da elementi fondamentali.

In realtà, la distinzione tra kernel / core e non-kernel / non-core è più complicata di così. E ci sono stati molti dibattiti su ciò che appartiene veramente a un kernel e cosa no. (Cerca micro-kernel per esempio.)


6
Risposta fenomenale. Ti darei molti altri voti, se possibile.
weberc2,

7
Molte buone risposte qui, quindi aggiungerò questo come commento, dal momento che nessuno lo ha menzionato finora: una delle caratteristiche chiave di un sistema operativo è consentire a più applicazioni di eseguire "simultaneamente" dal punto di vista dell'utente. La capacità di pianificare i processi e di proteggere i processi dall'alterazione del comportamento reciproco, sono in genere funzioni che si trovano solo in un sistema operativo e non firmware o BIOS.
Sean Barbeau,

2
The idea of a "kernel" is that you try to separate the system software into core stuffFacile da ricordare osservando che il termine kernelderiva dal tedesco Kern, che significa nucleo / nucleo.
deed02392,

1
Adoro questa risposta qui perché menziona che è compilato e collegato codice binario in esecuzione non C.
Travis Pessetto

3
"Allontanarsi da ciò ha reso gli utenti di PC meno intelligenti." - Non meno intelligente ... meno esperto di computer. Si potrebbe anche dire che ha aumentato il numero di utenti di PC.
Stephen C,

62

All'inizio non c'era corrente nella CPU.

E l'Uomo ha detto "lascia che ci sia energia", e la CPU ha iniziato a leggere da un determinato indirizzo in memoria ed eseguire le istruzioni che erano presenti lì. Quindi il prossimo e così via fino alla fine del potere.

Questo è stato l'avvio. Il suo compito era caricare un altro software per ottenere l'accesso all'ambiente, dove si trovava il software principale, e caricarlo.

Infine, una schermata amichevole ti ha invitato ad accedere.


58
Questa risposta dovrebbe essere spostata su christianity.stackexchange.com
Coomie il

6
cos'è "un determinato indirizzo" da dove viene. Mi dispiace per aver giocato a Charles Darwin qui.
Midhat,

7
@Midhat - Il primo indirizzo che viene recuperato dalla CPU è cablato al suo interno. Di solito è 0.
mouviciel

17
... E il settimo giorno, l'Uomo si riposò con i suoi giochi
Luke canadese,

9
@mouviciel L'indirizzo in memoria è 0x7C00per qualsiasi x86architettura compatibile e deve prima essere riempito dal BIOS che di solito carica il primo settore di qualsiasi dispositivo di avvio che preferisce ... Bella risposta però: -7
Tobias Kienzler

29

Scusate il ritardo, ma lo descriverò come tale:

  • La scheda madre è alimentata.

  • I circuiti di temporizzazione si avviano e si stabilizzano se necessario, in base esclusivamente alle loro caratteristiche elettriche. Alcuni dispositivi più recenti potrebbero effettivamente utilizzare un microprocessore o un sequencer molto limitato.

    Va notato che molte cose come "i circuiti di temporizzazione iniziano e si stabilizzano se necessario" non si verificano più nell'hardware. Una quantità enorme di quel lavoro è in realtà un software estremamente specializzato in esecuzione su subprocessori / sequencer molto limitati.

    - jkerian alle 5:20 del 25 ottobre

  • L'alimentazione viene fornita alla CPU e alla RAM.

  • La CPU carica (in base al suo cablaggio interno) i dati dal BIOS. Su alcune macchine, il BIOS può essere sottoposto a mirroring su RAM e quindi eseguito da lì, ma è raro IIRC.

    Quando accese, le CPU compatibili x86 iniziano all'indirizzo 0xFFFFFFF0 nello spazio degli indirizzi ...

    -Micheal Steil, 17 errori Microsoft Made in Xbox Security System ( archivio )

  • Il BIOS effettua chiamate alle porte e agli indirizzi hardware utilizzati dalla scheda madre per l'IO del disco e altri dispositivi hardware e avvia i dischi, facendo funzionare il resto della RAM, tra le altre cose.

  • Il codice BIOS (tramite le impostazioni CMOS, memorizzato nell'hardware) utilizza comandi IDE o SATA di basso livello per leggere il settore di avvio di ciascun disco, in un ordine specificato dal CMOS o da un utente con un menu.

  • Il primo disco con un settore di avvio viene eseguito il relativo settore di avvio. Questo settore di avvio è Assembly che contiene istruzioni per caricare più dati dal disco, caricare NTLDRfasi più grandi , successive GRUB, ecc.

  • Infine, il codice macchina del sistema operativo viene eseguito dal bootloader, direttamente o indirettamente tramite il chainloading caricando un settore di avvio da una posizione alternativa o offset.

Quindi si ottiene un panico amico del kernel, un pinguino soffocato o il disco si ferma a causa di un crollo della testa. =) In uno scenario alternativo, il kernel imposta tabelle di processo, strutture in memoria e monta dischi, caricando driver, moduli e una GUI o un set di servizi (se su un server). Quindi, i programmi vengono eseguiti mentre vengono letti i loro header e il loro assembly viene portato in memoria e mappato di conseguenza.


2
Va notato che molte cose come "i circuiti di temporizzazione si avviano e si stabilizzano se necessario" non si verificano più nell'hardware. Una quantità enorme di quel lavoro è in realtà un software estremamente specializzato in esecuzione su subprocessori / sequencer molto limitati. - Un amichevole ingegnere del firmware di quartiere
jkerian,

@jkerian Ti dispiace di aver citato il tuo commento nel mio post?
ζ--

eh, per niente.
jkerian

Il BIOS non è un sistema operativo. BIOS è una scorciatoia per il sistema di input / output di base, ed è quello che fa il BIOS. Consente ai programmatori di utilizzare risorse di basso livello con driver forniti dal produttore. Quando il sistema operativo entra in modalità protetta (32 bit) o ​​lunga (64 bit), il BIOS non è più disponibile e il sistema operativo utilizza i propri driver che sostanzialmente sostituiscono la funzionalità fornita dal BIOS a livelli "inferiori". I moderni sistemi operativi, ad esempio Linux e Windows, utilizzano il BIOS solo per rilevare sezioni RAM utilizzabili e caricare il proprio caricatore più avanzato in grado di caricare i driver richiesti.
Hannes Karppila,

1
@HannesKarppila Aggiornato; ora ho circa quattro anni e non sono più attivo su questo sito.
ζ--

15

Ci sono molte buone risposte ma volevo aggiungere questo: hai detto che provieni da uno sfondo di Python. Python è un linguaggio non interpretato (o "interpolato" o quant'altro, almeno nei tipici casi d'uso di CPython). Ciò significa che hai qualche altro software (l'interprete Python) che guarda la fonte e la esegue in qualche modo. Questo è un modello eccellente e consente linguaggi di alto livello piuttosto belli ben astratti dall'hardware reale. Il rovescio della medaglia è che hai sempre bisogno di questo software per interpreti prima.

Tale software di interpretazione, in genere, è scritto in un linguaggio che viene compilato in codice macchina, ad esempio C o C ++. Il codice macchina è ciò che può gestire la CPU. Ciò che una CPU può fare è leggere alcuni byte dalla memoria e, a seconda dei valori dei byte, avviare un'operazione specifica. Quindi una sequenza di byte è un comando per caricare alcuni dati dalla memoria in un registro, un'altra sequenza per aggiungere due valori, un'altra per memorizzare il valore di un registro nella memoria principale e presto (un registro è un'area di memoria speciale che fa parte della CPU dove può funzionare meglio), la maggior parte di questi comandi sono piuttosto bassi a quel livello. Il testo leggibile per queste istruzioni sul codice macchina è il codice assembler. Questo codice macchina, fondamentalmente, è ciò che è archiviato nei file .exe o.com su Windows o all'interno dei binari Linux / Unix.

Ora se un computer viene avviato è stupido, ma ha alcuni cavi che leggeranno tali istruzioni sul codice della macchina. Su un PC questo di solito (attualmente) è un chip EEPROM sulla scheda madre contenente il BIOS (sistema di inputput di base di input), questo sistema non può fare molto, può facilitare l'accesso ad alcuni hardware ecc e quindi eseguire un'operazione chiave: vai al avvia e copia i primi pochi byte (ovvero il record di avvio principale, MBR) in memoria e poi comunica alla CPU "qui, c'è il tuo programma" che la CPU tratterà lì quei byte come codice macchina ed eseguirà. In genere si tratta di un caricatore del sistema operativo che carica il kernel con alcuni parametri e quindi passa il controllo a quel kernel, che quindi caricherà tutti i suoi driver per accedere a tutto l'hardware, caricare qualche programma desktop o shell o qualsiasi altra cosa e consentire all'utente di accedere e usa il sistema.


6
"interpiled"? Non ho mai sentito quel termine prima.
Bryan Oakley,

3
Quel termine è stato usato circa 5 anni fa per descrivere interpreti "moderni" che hanno una fase di compilazione distinta che è separata dall'esecuzione. Non ho idea se questo termine sia sopravvissuto ovunque ;-)
johannes il

1
"ninterpreted"? Non ho mai sentito quel termine prima.
Cole Johnson,

12

Si chiede "Come può essere eseguita un'applicazione senza essere in un sistema operativo". La risposta semplice è "un sistema operativo non è un'applicazione". Mentre un sistema operativo può essere creato con gli stessi strumenti di un'applicazione e realizzato con la stessa materia prima, non sono la stessa cosa. Un sistema operativo non deve giocare con le stesse regole di un'applicazione.

OTOH, puoi pensare all'hardware e al firmware attuali come al "sistema operativo" in cui viene eseguita la "applicazione" del sistema operativo. L'hardware è un sistema operativo molto semplice: sa come eseguire le istruzioni scritte nel codice macchina e sa che quando si avvia dovrebbe guardare un indirizzo di memoria molto specifico per la sua prima istruzione. Quindi, si avvia e quindi esegue immediatamente la prima istruzione, seguita dalla seconda e così via.

Quindi, il sistema operativo è semplicemente un codice macchina che esiste in una posizione nota e che può interagire direttamente con l'hardware.


1
+1 Penso che questa sia la risposta migliore. In termini di astrazioni penso che tu lo stia inchiodando ai giusti livelli.
Preet Sangha,

6

La risposta alla tua domanda richiede la conoscenza di come appare il codice nativo (per CPU) e come viene interpretato dalla CPU.

Di solito l'intero processo di compilazione si basa sulla traduzione di cose che scrivi in ​​C, Pascal o persino in Python (usando pypy) e C # in cose che la CPU capisce, vale a dire semplici istruzioni come "archivia qualcosa sotto [indirizzo di memoria]" ", aggiungi numeri memorizzati nei registri eax e ebx "," call function foo "," confronta eax con 10 ". Quelle istruzioni, eseguite una per una, fanno le cose che vorresti fare con il tuo codice.

Ora pensaci: non hai davvero bisogno di un sistema operativo per eseguire questo codice nativo! Tutto ciò che serve è caricare questo codice in memoria e dire alla CPU che è lì e vuoi che venga eseguito. Non preoccuparti troppo di ciò, però. Questo è il lavoro di cui il BIOS dovrebbe preoccuparsi: carica il tuo codice (solo uno e un settore), subito dopo l'avvio della CPU, all'indirizzo fisico 0x7C00. Quindi la CPU inizia a eseguire questo settore (512 B) del codice. E puoi fare tutto quello che immagini! Senza, ovviamente, nessun supporto dal sistema operativo. Questo perché TU sei il sistema operativo. Splendido, no? Nessuna libreria standard, nessuna spinta, nessun pitone, nessun programma, nessun driver! Devi scrivere tutto da solo.

E come si comunica con l'hardware? Bene, hai due scelte:

  1. Rimani all'interno della "Modalità reale" - Modalità di esecuzione della CPU con solo 1 MB di memoria (e anche meno), nessuna funzionalità CPU avanzata come estensioni della CPU, protezione della memoria, multitasking; Codice eseguibile a 16 bit, antica modalità di indirizzamento ... Ma con alcune routine fornite dal BIOS, tra cui output dello schermo semplice, supporto tastiera, I / O del disco e gestione dell'alimentazione. In una parola, sei tornato in tempi di MS-DOS e CPU a 16 bit.
  2. Vai in "Modalità protetta" con tutte le funzionalità della tua CPU, tutta la memoria che hai installato e così via. Ma in modalità protetta SEI completamente solo e devi fare TUTTO da solo (e comunichi con l'hardware usando le istruzioni "in" e "out" per inserire / emettere dati alle porte I / O e usare gli interrupt. / O). Devo dire che tutti i sistemi operativi a partire da Windows 95 e Linux hanno scelto questa opzione?

Ora stai chiedendo cos'è il kernel. In breve, il kernel è tutto ciò che non si vede e non si sperimenta direttamente. Gestisce, insieme ai driver, tutto, a partire dalla tastiera fino a quasi tutti i componenti hardware all'interno del PC. Si comunica con esso tramite shell grafica o terminale. O da funzioni all'interno del tuo codice, ora eseguite, per fortuna, con il supporto del sistema operativo.

Per una migliore comprensione posso darti un consiglio: prova a scrivere il tuo sistema operativo. Anche se scriverà "Hello world" sullo schermo.


3

Vi sono alcune differenze nel modo in cui opera un sistema operativo che dipendono in larga misura dal sistema. Per essere utile, un sistema deve avere un comportamento prevedibile all'avvio, ad esempio "inizia a eseguire all'indirizzo X". Per i sistemi che hanno una memoria non volatile (come la memoria Flash) mappata nel loro spazio del programma, questo è abbastanza facile poiché ti assicuri solo di mettere il codice di avvio nella giusta posizione all'interno dello spazio del programma del processore. Questo è estremamente comune per i microcontrollori. Alcuni sistemi devono recuperare i loro programmi di avvio da un'altra posizione prima di eseguirlo. Questi sistemi avranno alcune operazioni cablate (o quasi cablate) al loro interno. Ci sono alcuni processori che recuperano il loro codice di avvio tramite i2c da un altro chip,

I sistemi che utilizzano la famiglia di processori x86 utilizzano in genere un processo di avvio in più fasi che è abbastanza complesso a causa della sua evoluzione e dei problemi di compatibilità con le versioni precedenti. Il sistema esegue un firmware (chiamato BIOS - Basic Input / Output System, o simile) che si trova in una memoria non volatile sulla scheda madre. A volte parte o tutto questo firmware viene copiato (riposizionato) nella RAM per renderlo più veloce. Questo codice è stato scritto con la conoscenza di quale hardware sarebbe presente e utilizzabile per l'avvio.

Il firmware di avvio è generalmente scritto con presupposti su quale hardware sarà presente sul sistema. Anni fa su una macchina 286 probabilmente si presumeva che ci sarebbe un controller di unità floppy all'indirizzo X di I / O e che avrebbe caricato il settore 0 in una determinata posizione di memoria se fosse stato fornito un certo set di comandi (e il codice nel settore 0 sa come utilizzare le funzioni del BIOS per caricare più codice e alla fine viene caricato abbastanza codice per essere un sistema operativo). Su un microcontrollore si può supporre che esista una porta seriale con determinate impostazioni che dovrebbe attendere i comandi (per aggiornare il firmware più complesso) per un periodo di tempo X prima di continuare con il processo di avvio.

L'esatto processo di avvio di un determinato sistema non è importante per te come sapere che differisce su sistemi diversi, ma anche che hanno tutti cose in comune. Spesso all'interno del codice di avvio (bootstrap) quando è necessario eseguire l'I / O, i dispositivi I / O vengono sottoposti a polling anziché basarsi sugli interrupt. Questo perché gli interrupt sono complessi, utilizzare la RAM dello stack (che potrebbe non essere ancora completamente configurata) e non è necessario preoccuparsi di bloccare altre operazioni quando si è l'unica operazione.

Al primo caricamento, il kernel del sistema operativo (il kernel è la parte principale della maggior parte dei sistemi operativi) inizialmente agirà in modo molto simile al firmware. Dovrà essere programmato con conoscenza o scoperta dell'hardware presente, impostare un po 'di RAM come stack-space, fare vari test, impostare varie strutture di dati, possibilmente scoprire e montare un filesystem e quindi probabilmente avviare qualche programma che è più come i programmi a cui sei abituato a scrivere (un programma che si basa sulla presenza di un sistema operativo).

Il codice del sistema operativo è solitamente scritto in una miscela di C e assembly. Il primo codice per il kernel del sistema operativo è probabilmente sempre in assembly e fa cose come impostare lo stack, su cui si basa il codice C, e quindi chiama una funzione C. Ci sarà anche un altro assemblaggio scritto a mano perché alcune operazioni che un SO deve fare spesso non sono esprimibili in C (come il cambio di contesto / lo scambio di stack). Spesso i flag speciali devono essere passati al compilatore C per dirgli di non fare affidamento sulle librerie standard utilizzate dalla maggior parte dei programmi C e di non aspettarsi che ci sia unint main(int argc, char *argv[])nel programma. Inoltre, non devono essere utilizzate opzioni di linker speciali che la maggior parte dei programmatori di applicazioni non utilizza. Questi possono far sì che il programma del kernel si aspetti di essere caricato a un determinato indirizzo o impostare cose che sembrano avere variabili esterne in determinate posizioni anche se quelle variabili non sono mai state dichiarate in alcun codice C (questo è utile per l'I / O mappato in memoria o altre posizioni di memoria speciali).

L'intera operazione sembra inizialmente magica, ma dopo averla esaminata e compresa parti di essa, la magia diventa solo un insieme di programmi che richiedono molta più pianificazione e conoscenza del sistema da implementare. Il debug, tuttavia, richiede magia.


3

Per capire come funzionano i sistemi operativi, può essere utile suddividerli in due categorie: quelli che forniscono semplicemente servizi alle applicazioni su richiesta e quelli che utilizzano funzionalità hardware nella CPU per impedire alle applicazioni di fare cose che non dovrebbero. MS-DOS era del vecchio stile; tutte le versioni di Windows dalla 3.0 sono state il secondo stile (almeno quando si esegue qualcosa di più potente di un 8086).

Il PC IBM originale con PC-DOS o MS-DOS sarebbe stato un esempio del precedente stile di "OS". Se un'applicazione desiderasse visualizzare un personaggio sullo schermo, ci sarebbero stati alcuni modi per farlo. Potrebbe chiamare la routine che richiederebbe a MS-DOS di inviarlo a "output standard". In tal caso, MS-DOS verificherebbe se l'output veniva reindirizzato e, in caso contrario, chiamerebbe una routine memorizzata nella ROM (in una raccolta di routine IBM chiamata Basic Input / Output System) che visualizzerebbe un carattere nel posizione del cursore e spostare il cursore ("scrivere teletype"). Quella routine BIOS memorizzerebbe quindi una coppia di byte da qualche parte nell'intervallo da 0xB800: da 0 a 0xB800: 3999; l'hardware sulla scheda grafica a colori recupera ripetutamente coppie di byte all'interno di tale intervallo, utilizzando il primo byte di ciascuna coppia per selezionare una forma di carattere e il secondo per selezionare i colori di primo piano e di sfondo. I byte vengono recuperati ed elaborati in segnali rossi, verdi e blu, in una sequenza che produce una visualizzazione di testo leggibile.

I programmi sul PC IBM potrebbero visualizzare il testo usando la routine DOS "output standard", oppure usando la routine BIOS "write teletype", o memorizzandola direttamente per visualizzare la memoria. Molti programmi che necessitavano di visualizzare un sacco di testo hanno optato rapidamente per quest'ultimo approccio, dal momento che potrebbe essere letteralmente centinaia di volte più veloce dell'uso delle routine DOS. Questo non perché le routine DOS e BIOS erano eccezionalmente inefficienti; a meno che il display non fosse oscurato, potrebbe essere scritto solo in determinati momenti. La routine BIOS per l'output di un personaggio è stata progettata in modo da poter essere richiamata in qualsiasi momento; ogni richiesta doveva quindi ricominciare da capo aspettando il momento giusto per eseguire un'operazione di scrittura. Al contrario, il codice dell'applicazione che sapeva cosa doveva fare poteva organizzarsi attorno alle opportunità disponibili per scrivere il display.

Un punto chiave qui è che mentre il DOS e il BIOS hanno fornito un mezzo per inviare testo al display, non c'era nulla di particolarmente "magico" in tali abilità. Un'applicazione che voleva scrivere testo sul display poteva farlo altrettanto efficacemente, almeno se l'hardware del display funzionasse come previsto dall'applicazione (se qualcuno avesse installato un adattatore per display monocromatico, che era simile al CGA ma aveva la sua memoria di caratteri situato a 0xB000: 0000-0xB000: 3999), il BIOS emetterebbe automaticamente i caratteri lì; un'applicazione programmata per funzionare con MDA o CGA potrebbe fare altrettanto, ma un'applicazione programmata solo per CGA sarebbe totalmente inutile su MDA).

Sui sistemi più recenti, le cose sono leggermente diverse. I processori hanno varie modalità di "privilegio". Iniziano nella modalità più privilegiata, dove il codice è autorizzato a fare tutto ciò che vuole. Possono quindi passare a una modalità limitata, dove sono disponibili solo intervalli di memoria o strutture I / O selezionati. Il codice non può tornare direttamente da una modalità limitata alla modalità privilegio, ma il processore ha definito i punti di ingresso in modalità privilegiata e il codice in modalità limitata può chiedere al processore di avviare l'esecuzione del codice in uno di quei punti di entrata in modalità privilegiata. Inoltre, ci sono punti di accesso in modalità privilegiata associati a una serie di operazioni che sarebbero vietati in modalità limitata. Supponiamo, ad esempio, che qualcuno volesse eseguire contemporaneamente più applicazioni MS-DOS, ognuna con il proprio schermo. Se le applicazioni potessero scrivere direttamente sul controller del display su 0xB800: 0, non ci sarebbe modo di impedire a un'applicazione di sovrascrivere la schermata di un'altra applicazione. D'altra parte, un sistema operativo potrebbe eseguire l'applicazione in modalità riservata e intercettare qualsiasi accesso alla memoria del display; se avesse scoperto che un'applicazione che si supponeva fosse in "background" stava provando a scrivere 0xB800: 160, avrebbe potuto memorizzare i dati in qualche memoria che aveva messo da parte come buffer dello schermo dell'applicazione in background. Se l'applicazione viene successivamente spostata in primo piano, il buffer potrebbe essere copiato sullo schermo reale. un sistema operativo potrebbe eseguire l'applicazione in modalità riservata e intercettare qualsiasi accesso alla memoria del display; se avesse scoperto che un'applicazione che si supponeva fosse in "background" stava provando a scrivere 0xB800: 160, avrebbe potuto memorizzare i dati in qualche memoria che aveva messo da parte come buffer dello schermo dell'applicazione in background. Se l'applicazione viene successivamente spostata in primo piano, il buffer potrebbe essere copiato sullo schermo reale. un sistema operativo potrebbe eseguire l'applicazione in modalità riservata e intercettare qualsiasi accesso alla memoria del display; se avesse scoperto che un'applicazione che si supponeva fosse in "background" stava provando a scrivere 0xB800: 160, avrebbe potuto memorizzare i dati in qualche memoria che aveva messo da parte come buffer dello schermo dell'applicazione in background. Se l'applicazione viene successivamente spostata in primo piano, il buffer potrebbe essere copiato sullo schermo reale.

Le cose principali da notare sono (1) sebbene sia spesso utile disporre di un set standard di routine per eseguire vari servizi standard come la visualizzazione di testo, non fanno nulla che un'applicazione in esecuzione in "modalità privilegiata" non possa fare se è stato correttamente programmato per gestire l'hardware installato; (2) anche se la maggior parte delle applicazioni in esecuzione oggi sarebbe impedita dal loro sistema operativo di eseguire direttamente tale I / O, un programma che si avvia in modalità privilegiata può fare quello che vuole e può impostare qualunque regola desideri per la modalità limitata programmi.


2

Come ha detto Stephen C., non si tratta solo di avviare il sistema operativo, ma anche di come funziona, interagisce con l'hardware e con il software sopra di esso.

Aggiungerò solo alla sua risposta, che potresti voler dare un'occhiata a "Gli elementi dei sistemi informatici" . È un libro e alcuni strumenti che spiega come interagiscono un computer, un sistema operativo e dei compilatori. La cosa unica è che ti dà gli strumenti per sviluppare molto rapidamente il tuo sistema operativo in un ambiente simulato, ignorando i molti dettagli richiesti per uno reale, in modo che tu possa afferrare i concetti . Fa un ottimo lavoro nel farti vedere la foresta invece degli alberi.

Se vuoi approfondire il modo in cui il sistema operativo interagisce con l'hardware, dai un'occhiata a Minix .


1

Scrivi un sistema operativo. Deve essere eseguito in qualche modo, e in quel modo è all'interno di un altro sistema operativo?

L'applicazione è in esecuzione in un sistema operativo. Questo sistema operativo fornisce servizi all'applicazione, ad esempio l'apertura di un file e la scrittura di byte su di esso. Questi servizi sono generalmente forniti tramite chiamate di sistema.

Il sistema operativo è in esecuzione all'interno dell'hardware. L'hardware fornisce servizi al sistema operativo, cose come l'impostazione della velocità di trasmissione di una porta seriale e la scrittura di byte su di essa. Questi servizi sono generalmente forniti tramite registri mappati in memoria o porte I / O.


Per dare un esempio molto semplificato di come funziona:

La tua applicazione dice al sistema operativo di scrivere qualcosa in un file. Alla tua applicazione, il sistema operativo fornisce concetti come file e directory.

Sull'hardware, questi concetti non esistono. L'hardware fornisce concetti come i dischi divisi in blocchi fissi di 512 byte. Il sistema operativo decide quali blocchi utilizzare per il file e alcuni altri blocchi per metadati come il nome, la dimensione e la posizione del file sul disco. Quindi dice all'hardware: scrivere questi 512 byte nel settore con questo numero sul disco con quel numero; scrivere questi altri 512 byte nel settore con questo diverso numero sul disco con lo stesso numero; e così via.

Il modo in cui il sistema operativo dice all'hardware di farlo varia molto. Una delle funzioni di un sistema operativo è proteggere le applicazioni da queste differenze. Per l'esempio del disco, su un tipo di hardware, il sistema operativo dovrebbe scrivere il numero del disco e del settore su una porta I / O, quindi scrivere i byte uno alla volta su una porta I / O separata. Su un altro tipo di hardware, il sistema operativo dovrebbe copiare tutti i 512 byte di un settore in un'area di memoria, scrivere la posizione di quell'area di memoria in una posizione di memoria speciale e scrivere il disco e il numero di settore su ancora un altro posizione di memoria speciale.


L'hardware di fascia alta di oggi è estremamente complicato. I manuali che forniscono tutti i dettagli di programmazione sono fermaporta con migliaia di pagine; ad esempio, l'ultimo manuale della CPU Intel è composto da sette volumi, con un totale di oltre 4000 pagine, e questo è solo per la CPU. La maggior parte degli altri componenti espone blocchi di memoria o porte I / O, che il sistema operativo può dire alla CPU di mappare agli indirizzi all'interno del suo spazio di indirizzi. Molti di questi componenti espongono ancora più cose dietro alcune porte I / O o indirizzi di memoria; ad esempio, RTC (Real Time Clock, il componente che mantiene il tempo del computer mentre è spento) espone alcune centinaia di byte di memoria dietro una coppia di porte I / O, e questo è un componente molto semplice che risale al il PC / AT originale. Cose come i dischi rigidi hanno processori completamente separati, a cui il sistema operativo parla tramite comandi standardizzati. Le GPU sono ancora più complicate.

Diverse persone nei commenti sopra hanno suggerito l'Arduino. Sono d'accordo con loro, è molto più semplice da capire: l'ATmega328, che fa tutto su Arduino Uno tranne che esponendo il connettore USB come una porta seriale, ha un manuale con solo poche centinaia di pagine. Su Arduino, si esegue direttamente sull'hardware, senza alcun sistema operativo in mezzo; solo alcune piccole routine di libreria, che non è necessario utilizzare se non si desidera.


1

Esempi eseguibili

Tecnicamente, un programma che funziona senza un SO, è un SO. Vediamo quindi come creare ed eseguire alcuni minuscoli sistemi operativi Hello World.

Il codice di tutti gli esempi seguenti è presente in questo repository GitHub .

Settore di avvio

Su x86, la cosa più semplice e di livello più basso che puoi fare è creare un Master Boot Sector (MBR) , che è un tipo di settore di avvio , e quindi installarlo su un disco.

Qui ne creiamo uno con una sola printfchiamata:

printf '\364%509s\125\252' > main.img
sudo apt-get install qemu-system-x86
qemu-system-x86_64 -hda main.img

Risultato:

inserisci qui la descrizione dell'immagine

Testato su Ubuntu 18.04, QEMU 2.11.1.

main.img contiene quanto segue:

  • \364in ottale == 0xf4in esadecimale: la codifica per hltun'istruzione, che indica alla CPU di smettere di funzionare.

    Pertanto il nostro programma non farà nulla: solo avviare e arrestare.

    Usiamo ottale perché i \xnumeri esadecimali non sono specificati da POSIX.

    Potremmo ottenere facilmente questa codifica con:

    echo hlt > a.asm
    nasm -f bin a.asm
    hd a
    

    ma la 0xf4codifica è ovviamente documentata anche nel manuale Intel.

  • %509sprodurre 509 spazi. Necessario per compilare il file fino al byte 510.

  • \125\252in ottale == 0x55seguito da 0xaa: byte magici richiesti dall'hardware. Devono essere byte 511 e 512.

    Se non presente, l'hardware non lo tratterà come un disco di avvio.

Nota che anche senza fare nulla, alcuni personaggi sono già stampati sullo schermo. Quelli sono stampati dal firmware e servono per identificare il sistema.

Esegui su hardware reale

Gli emulatori sono divertenti, ma l'hardware è il vero affare.

Nota che questo è pericoloso e potresti cancellare il tuo disco per errore: fallo solo su macchine vecchie che non contengono dati critici! O ancora meglio, devboard come il Raspberry Pi, vedi l'esempio ARM di seguito.

Per un tipico laptop, devi fare qualcosa del tipo:

  • Masterizza l'immagine su una chiavetta USB (distruggerà i tuoi dati!):

    sudo dd if=main.img of=/dev/sdX
    
  • collegare l'USB su un computer

  • accendilo

  • digli di avviarsi dall'USB.

    Ciò significa che il firmware deve selezionare USB prima del disco rigido.

    Se questo non è il comportamento predefinito del tuo computer, continua a premere Invio, F12, ESC o altre chiavi così strane dopo l'accensione fino a quando non ottieni un menu di avvio in cui è possibile selezionare per l'avvio da USB.

    Spesso è possibile configurare l'ordine di ricerca in quei menu.

Ad esempio, sul mio vecchio Lenovo Thinkpad T430, UEFI BIOS 1.16, posso vedere:

Ciao mondo

Ora che abbiamo creato un programma minimo, spostiamoci in un mondo di ciao.

La domanda ovvia è: come fare IO? Alcune opzioni:

  • chiedere al firmware, ad esempio BIOS o UEFI, di fare se per noi
  • VGA: area di memoria speciale che viene stampata sullo schermo se scritta. Può essere utilizzato in modalità protetta.
  • scrivere un driver e parlare direttamente con l'hardware del display. Questo è il modo "corretto" per farlo: più potente, ma più complesso.
  • porta seriale . Questo è un protocollo standardizzato molto semplice che invia e recupera i caratteri da un terminale host.

    Fonte .

    Sfortunatamente non è esposto sulla maggior parte dei laptop moderni, ma è il modo comune di utilizzare schede di sviluppo, vedere gli esempi ARM di seguito.

    Questo è davvero un peccato, dal momento che tali interfacce sono davvero utili per il debug del kernel Linux, ad esempio .

  • utilizzare le funzionalità di debug dei chip. ARM chiama ad esempio il loro semihosting . Sull'hardware reale, richiede un supporto hardware e software aggiuntivo, ma sugli emulatori può essere un'opzione conveniente gratuita. Esempio .

Qui faremo un esempio di BIOS in quanto è più semplice su x86. Ma nota che non è il metodo più robusto.

main.S

.code16
    mov $msg, %si
    mov $0x0e, %ah
loop:
    lodsb
    or %al, %al
    jz halt
    int $0x10
    jmp loop
halt:
    hlt
msg:
    .asciz "hello world"

link.ld

SECTIONS
{
    . = 0x7c00;
    .text :
    {
        __start = .;
        *(.text)
        . = 0x1FE;
        SHORT(0xAA55)
    }
}

Assembla e collega con:

gcc -c -g -o main.o main.S
ld --oformat binary -o main.img -T linker.ld main.o

Risultato:

inserisci qui la descrizione dell'immagine

Testato su: Lenovo Thinkpad T430, UEFI BIOS 1.16. Disco generato su un host Ubuntu 18.04.

Oltre alle istruzioni standard di assemblaggio per l'utente, abbiamo:

  • .code16: dice a GAS di emettere codice a 16 bit

  • cli: disabilita gli interrupt software. Questi potrebbero far ripartire il processore dopo ilhlt

  • int $0x10: esegue una chiamata BIOS. Questo è ciò che stampa i personaggi uno per uno.

I flag di link importanti sono:

  • --oformat binary: genera codice assembly binario non elaborato, non deformarlo all'interno di un file ELF come nel caso dei normali eseguibili userland.

Usa C invece di assemblare

Poiché C viene compilato per l'assemblaggio, l'utilizzo di C senza la libreria standard è piuttosto semplice, in pratica è sufficiente:

  • uno script di linker per mettere le cose in memoria nel posto giusto
  • flag che indicano a GCC di non utilizzare la libreria standard
  • un piccolo punto di ingresso dell'assieme che imposta lo stato C richiesto per main, in particolare:
    • la pila
    • azzerare BSS

TODO: collega così alcuni esempi x86 su GitHub. Ecco un ARM che ho creato .

Le cose diventano più divertenti se si desidera utilizzare la libreria standard, poiché non abbiamo il kernel Linux, che implementa gran parte delle funzionalità della libreria C standard tramite POSIX .

Alcune possibilità, senza passare a un sistema operativo completo come Linux, includono:

  • newlib

    Esempio dettagliato su: https://electronics.stackexchange.com/questions/223929/c-standard-libraries-on-bare-metal/223931

    In Newlib, devi implementare tu stesso le syscalls, ma ottieni un sistema molto minimale ed è molto facile implementarle.

    Ad esempio, è possibile reindirizzare printfverso i sistemi UART o ARM o implementare exit()con semihosting .

  • sistemi operativi integrati come FreeRTOS e Zephyr .

    Tali sistemi operativi in ​​genere consentono di disattivare la pianificazione preventiva, offrendo quindi il pieno controllo sul tempo di esecuzione del programma.

    Possono essere visti come una sorta di Newlib pre-implementato.

BRACCIO

In ARM, le idee generali sono le stesse. Ho caricato:

Per il Raspberry Pi, https://github.com/dwelch67/raspberrypi sembra il tutorial più popolare disponibile oggi.

Alcune differenze rispetto a x86 includono:

  • L'IO viene fatto scrivendo direttamente agli indirizzi magici, non ci sono ine outistruzioni.

    Questo si chiama IO mappato in memoria .

  • per alcuni hardware reali, come Raspberry Pi, puoi aggiungere tu stesso il firmware (BIOS) all'immagine del disco.

    Questa è una buona cosa, poiché rende l'aggiornamento del firmware più trasparente.

firmware

In verità, il tuo settore di avvio non è il primo software che gira sulla CPU del sistema.

Ciò che effettivamente viene eseguito per primo è il cosiddetto firmware , che è un software:

  • prodotto dai produttori di hardware
  • tipicamente sorgente chiusa ma probabilmente basato su C.
  • memorizzato nella memoria di sola lettura e quindi più difficile / impossibile da modificare senza il consenso del fornitore.

I firmware ben noti includono:

  • BIOS : vecchio firmware x86 tutto presente. SeaBIOS è l'implementazione open source predefinita utilizzata da QEMU.
  • UEFI : successore del BIOS, meglio standardizzato, ma più capace e incredibilmente gonfio.
  • Coreboot : il nobile tentativo open source ad arco incrociato

Il firmware fa cose come:

  • passa su ogni disco rigido, USB, rete, ecc. fino a quando non trovi qualcosa di avviabile.

    Quando eseguiamo QEMU, -hdadice che main.imgè un disco rigido collegato all'hardware e

    hda è il primo a essere provato e viene utilizzato.

  • caricare i primi 512 byte nell'indirizzo di memoria RAM 0x7c00, inserire lì il RIP della CPU e lasciarlo funzionare

  • mostra cose come il menu di avvio o le chiamate di stampa del BIOS sul display

Il firmware offre funzionalità simili al sistema operativo da cui dipende la maggior parte dei sistemi operativi. Ad esempio, un sottoinsieme Python è stato portato per essere eseguito su BIOS / UEFI: https://www.youtube.com/watch?v=bYQ_lq5dcvM

Si può sostenere che i firmware sono indistinguibili dai sistemi operativi e che il firmware è l'unica "vera" programmazione in metallo nudo che si possa fare.

Come dice questo sviluppatore CoreOS :

La parte difficile

Quando si accende un PC, i chip che compongono il chipset (northbridge, southbridge e SuperIO) non sono ancora inizializzati correttamente. Anche se la ROM del BIOS è la più lontana possibile dalla CPU, ciò è accessibile dalla CPU, perché deve essere, altrimenti la CPU non avrebbe istruzioni da eseguire. Ciò non significa che la ROM del BIOS sia completamente mappata, di solito no. Ma quel tanto che basta è mappato per avviare il processo di avvio. Qualsiasi altro dispositivo, dimenticalo e basta.

Quando esegui Coreboot in QEMU, puoi sperimentare con i livelli più alti di Coreboot e con i payload, ma QEMU offre poche opportunità di sperimentare con il codice di avvio di basso livello. Per prima cosa, la RAM funziona fin dall'inizio.

Stato iniziale post BIOS

Come molte cose nell'hardware, la standardizzazione è debole e una delle cose su cui non dovresti fare affidamento è lo stato iniziale dei registri quando il codice inizia a funzionare dopo il BIOS.

Quindi fatevi un favore e utilizzate un codice di inizializzazione come il seguente: https://stackoverflow.com/a/32509555/895245

I registri piacciono %dse %eshanno effetti collaterali importanti, quindi dovresti azzerarli anche se non li stai usando esplicitamente.

Nota che alcuni emulatori sono più belli dell'hardware reale e ti danno un buono stato iniziale. Quindi, quando corri su hardware reale, tutto si rompe.

GNU GRUB Multiboot

I settori di avvio sono semplici, ma non sono molto convenienti:

  • puoi avere un solo SO per disco
  • il codice di caricamento deve essere veramente piccolo e contenere 512 byte. Questo potrebbe essere risolto con la chiamata int 0x13 BIOS .
  • devi fare molte startup da solo, come passare alla modalità protetta

È per questi motivi che GNU GRUB ha creato un formato file più conveniente chiamato multiboot.

Esempio di lavoro minimo: https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world

Lo uso anche sul mio repository di esempi GitHub per essere in grado di eseguire facilmente tutti gli esempi su hardware reale senza masterizzare l'USB un milione di volte. Su QEMU è simile al seguente:

inserisci qui la descrizione dell'immagine

Se prepari il tuo sistema operativo come file multiboot, GRUB è in grado di trovarlo all'interno di un normale filesystem.

Questo è ciò che fanno la maggior parte delle distro, mettendo sotto le immagini del sistema operativo /boot.

I file multiboot sono fondamentalmente un file ELF con un'intestazione speciale. Sono specificati da GRUB su: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html

È possibile trasformare un file multiboot in un disco di avvio con grub-mkrescue.

El Torito

Formato che può essere masterizzato su CD: https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29

È anche possibile produrre un'immagine ibrida che funziona su ISO o USB. Questo può essere fatto con grub-mkrescue( esempio ), ed è anche fatto dal kernel di Linux make isoimageusando isohybrid.

risorse

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.