Ich bin gerade sehr neugierig. Ich bin ein Python-Programmierer, und diese Frage hat mich nur verwirrt: Sie schreiben ein Betriebssystem. Wie machst du das? Es muss irgendwie laufen, und so ist in einem anderen Betriebssystem?
Wie kann eine Anwendung ohne Betriebssystem ausgeführt werden? Wie können Sie den Computer anweisen, C auszuführen und diese Befehle auf dem Bildschirm auszuführen, wenn kein Betriebssystem zum Ausführen vorhanden ist?
Hat es mit einem UNIX-Kernel zu tun? Wenn ja, was ist ein Unix-Kernel oder ein Kernel im Allgemeinen?
Ich bin mir sicher, dass Betriebssysteme komplizierter sind, aber wie funktioniert das?
kernel
operating-systems
Thor Correia
quelle
quelle
Antworten:
Es gibt viele Websites, die den Startvorgang durchlaufen (z. B. Wie Computer hochfahren ). Kurz gesagt, es handelt sich um einen mehrstufigen Prozess, der das System immer wieder ein wenig aufbaut, bis es schließlich die Betriebssystemprozesse starten kann.
Es beginnt mit der Firmware auf dem Motherboard, die versucht, die CPU zum Laufen zu bringen. Anschließend wird das BIOS geladen, das wie ein Mini-Betriebssystem aussieht, das die andere Hardware zum Laufen bringt. Sobald dies erledigt ist, sucht es nach einem Startgerät (Festplatte, CD usw.) und findet, sobald es gefunden ist, den MBR (Master Boot Record), lädt ihn in den Speicher und führt ihn aus. Es ist dieses kleine Stück Code, das dann weiß, wie man das Betriebssystem initialisiert und startet (oder andere Bootloader, da die Dinge komplizierter geworden sind). An diesem Punkt würden Dinge wie der Kernel geladen und laufen.
Es ist ziemlich unglaublich, dass es überhaupt funktioniert!
quelle
Ein "Bare-Metal" -Betriebssystem läuft in nichts. Es führt den vollständigen Befehlssatz auf der physischen Maschine aus und hat Zugriff auf den gesamten physischen Speicher, alle Geräteregister und alle privilegierten Befehle, einschließlich derer, die die Hardware zur Unterstützung des virtuellen Speichers steuern.
(Wenn das Betriebssystem auf einer virtuellen Maschine ausgeführt wird , kann sie denken . Es in der gleichen Situation wie oben Der Unterschied besteht darin , dass bestimmte Dinge emuliert werden oder auf andere Weise durch den Hypervisor behandelt, dh dem Niveau , das die virtuellen Maschinen läuft .)
Obwohl das Betriebssystem möglicherweise in (zum Beispiel) C implementiert ist, stehen ihm nicht alle normalen C-Bibliotheken zur Verfügung. Insbesondere wird es nicht die normalen "stdio" -Bibliotheken geben. Stattdessen wird beispielsweise ein Plattengerätetreiber implementiert, der das Lesen und Schreiben von Plattenblöcken ermöglicht. Es implementiert ein Dateisystem über der Plattenblockschicht und darüber hinaus die Systemaufrufe, die die Laufzeitbibliotheken einer Benutzeranwendung zum Erstellen, Lesen und Schreiben von Dateien usw. ausführen.
Es muss sich um eine spezielle Art von Anwendung handeln (z. B. ein Betriebssystem), die weiß, wie man direkt mit der E / A-Hardware usw. interagiert.
Das tust du nicht.
Die Anwendung (die aus Gründen der Argumentation in C geschrieben wurde) wird kompiliert und auf einem anderen Computer verknüpft, um ein systemeigenes Codebild zu erhalten. Dann wird das Image auf die Festplatte an einer Stelle geschrieben, an der es vom BIOS gefunden werden kann. Das BIOS lädt das Image in den Speicher und führt eine Anweisung aus, um zum Einstiegspunkt der Anwendung zu springen.
Es gibt (normalerweise) kein "Ausführen von C und Ausführen von Befehlen" in der Anwendung, es sei denn, es handelt sich um ein vollständiges Betriebssystem. In diesem Fall liegt es in der Verantwortung des Betriebssystems, die gesamte dafür erforderliche Infrastruktur zu implementieren. Keine Magie. Einfach viel Code.
Die Antwort von Bill bezieht sich auf das Bootstrapping. Hierbei handelt es sich um den Vorgang, bei dem Sie von einem ausgeschalteten Computer zu einem Computer wechseln, auf dem das normale Betriebssystem ausgeführt wird. Es ist jedoch zu beachten, dass das BIOS nach Abschluss seiner Aufgaben (normalerweise) die vollständige Kontrolle über die Hardware an das Hauptbetriebssystem übergibt und keine weitere Rolle spielt - bis zum nächsten Neustart des Systems. Das Hauptbetriebssystem läuft sicherlich nicht "innerhalb" des BIOS im herkömmlichen Sinne.
Ja tut es.
Der UNIX-Kernel ist der Kern des UNIX-Betriebssystems. Es ist der Teil von UNIX, der alle oben beschriebenen "Bare-Metal" -Stoffe ausführt.
Die Idee eines "Kernels" ist, dass Sie versuchen, die Systemsoftware in Kernmaterial (das physischen Gerätezugriff, den gesamten Speicher usw. erfordert) und Nicht-Kernmaterial zu unterteilen. Der Kernel besteht aus dem Kernmaterial.
In Wirklichkeit ist die Unterscheidung zwischen Kernel / Core und Nicht-Kernel / Nicht-Core komplizierter. Und es gab eine Menge Debatten darüber, was wirklich in einen Kernel gehört und was nicht. (Schlagen Sie zum Beispiel den Micro-Kernel nach.)
quelle
The idea of a "kernel" is that you try to separate the system software into core stuff
Einfach zu merken, wenn man feststellt, dass der Begriffkernel
aus dem Deutschen stammtKern
, was Kern / Kern bedeutet.Am Anfang war kein Strom in der CPU.
Und der Mann sagte "lass es Strom geben", und die CPU fing an, von einer gegebenen Adresse im Speicher zu lesen und den Befehl auszuführen, der dort vorhanden war. Dann die nächste und so weiter bis zum Ende der Macht.
Das war der Stiefel. Ihre Aufgabe bestand darin, eine weitere Software zu laden, um auf die Umgebung zuzugreifen, in der sich die Hauptsoftware befand, und sie zu laden.
Schließlich wurden Sie auf einem freundlichen Bildschirm aufgefordert, sich anzumelden.
quelle
0x7C00
für jedex86
kompatible Architektur und hat zunächst vom BIOS gefüllt werden , die in der Regel lädt den ersten Sektor , was auch immer startfähiges Gerät es vorzieht ... Nizza Antwort aber: -7Es tut mir leid, dass ich zu spät komme, aber ich werde es so beschreiben:
Das Motherboard wird mit Strom versorgt.
Zeitschaltkreise starten und stabilisieren sich bei Bedarf allein aufgrund ihrer elektrischen Eigenschaften. Einige neuere Geräte verwenden möglicherweise einen sehr eingeschränkten Mikroprozessor oder Sequenzer.
- jkerian am 25. Oktober um 5:20 Uhr
Die CPU und der RAM werden mit Strom versorgt.
Die CPU lädt (basierend auf ihrer internen Verdrahtung) Daten aus dem BIOS. Auf einigen Computern wird das BIOS möglicherweise in den Arbeitsspeicher gespiegelt und von dort ausgeführt, aber dies ist selten IIRC.
-Micheal Steil, 17 Fehler, die Microsoft im Xbox-Sicherheitssystem gemacht hat ( Archiv )
Das BIOS ruft Hardware-Ports und -Adressen auf, die vom Motherboard für Festplatten- und andere Hardware-E / A-Vorgänge verwendet werden, und dreht Festplatten hoch, damit unter anderem der Rest des Arbeitsspeichers funktioniert.
Der BIOS-Code (über die in der Hardware gespeicherten CMOS-Einstellungen) verwendet IDE- oder SATA-Befehle auf niedriger Ebene, um den Startsektor jeder Festplatte in einer vom CMOS festgelegten Reihenfolge zu lesen, oder ein Benutzer überschreibt diese mit einem Menü.
Die erste Platte mit einem Bootsektor erhält ihren Bootsektor ausgeführt. Dieser Bootsektor ist eine Assembly, die Anweisungen zum Laden weiterer Daten von der Festplatte, zum Laden größerer
NTLDR
, späterer PhasenGRUB
usw. enthält.Schließlich wird der OS-Maschinencode vom Bootloader direkt oder indirekt über Chainloading ausgeführt, indem ein Bootsektor von einem alternativen oder versetzten Ort geladen wird.
Sie bekommen dann eine freundliche Kernel-Panik, einen erstickten Pinguin oder Ihre Festplatte kommt aufgrund eines Kopfcrashs zum Stillstand. =) Im alternativen Szenario richtet Ihr Kernel Prozesstabellen, speicherinterne Strukturen und Bereitstellungsdatenträger ein, lädt Treiber, Module und eine GUI oder eine Reihe von Diensten (falls auf einem Server). Dann werden Programme ausgeführt, wenn ihre Header gelesen werden, und ihre Assemblierung wird in den Speicher gebracht und entsprechend zugeordnet.
quelle
Es gibt viele gute Antworten, aber ich wollte Folgendes hinzufügen: Sie haben erwähnt, dass Sie aus einem Python-Hintergrund stammen. Python ist eine nicht interpretierte (oder "interpiled" oder was auch immer, zumindest in typischen CPython-Anwendungsfällen) Sprache. Das bedeutet, dass Sie eine andere Software (den Python-Interpreter) haben, die sich den Quellcode ansieht und ihn auf irgendeine Weise ausführt. Dies ist ein gutes Modell und ermöglicht sehr schöne Hochsprachen, die von der eigentlichen Hardware gut abstrahiert sind. Nachteil ist, dass Sie immer zuerst diese Interpreter-Software benötigen.
Eine solche Interpretersoftware ist typischerweise in einer Sprache geschrieben, die zu Maschinencode kompiliert wird, beispielsweise C oder C ++. Maschinencode ist das, was die CPU verarbeiten kann. Was eine CPU tun kann, ist, einige Bytes aus dem Speicher zu lesen und abhängig von den Bytewerten eine bestimmte Operation zu starten. Eine Bytesequenz ist also ein Befehl zum Laden einiger Daten aus dem Speicher in ein Register, eine andere Sequenz zum Hinzufügen von zwei Werten, eine andere zum Speichern des Werts aus einem Register zurück in den Hauptspeicher und bald (ein Register ist ein spezieller Speicherbereich, der Teil ist) von der CPU, wo es am besten funktionieren kann), sind die meisten dieser Befehle auf dieser Ebene ziemlich niedrig. Der für diese Maschinencode-Anweisungen lesbare Code ist Assembler-Code. Dieser Maschinencode ist im Grunde das, was in .exe- oder.com-Dateien unter Windows oder in Linux / Unix-Binärdateien gespeichert ist.
Wenn ein Computer jetzt hochgefahren wird, ist er stumm, hat aber einige Kabel, die solche Maschinencodeanweisungen lesen. Auf einem PC ist dies normalerweise (derzeit) ein EEPROM-Chip auf dem Mainboard, der das BIOS (Basic Input Output System) enthält. Dieses System kann nicht viel, es kann den Zugriff auf Hardware usw. vereinfachen und dann eine Tastenoperation ausführen: Gehen Sie zu Booten und kopieren Sie die ersten Bytes (auch bekannt als Master Boot Record, MBR) in den Speicher und teilen Sie der CPU mit, dass "hier ist Ihr Programm". Die CPU behandelt diese Bytes dann als Maschinencode und führt sie aus. Typischerweise ist dies ein Betriebssystem-Lader, der den Kernel mit einigen Parametern lädt und dann die Steuerung an diesen Kernel übergibt, der dann alle seine Treiber lädt, um auf die gesamte Hardware zuzugreifen, ein Desktop- oder Shell-Programm oder was auch immer zu laden und dem Benutzer zu erlauben, sich anzumelden und benutze das System.
quelle
Sie fragen "Wie kann eine Anwendung ohne Betriebssystem ausgeführt werden". Die einfache Antwort lautet "Ein Betriebssystem ist keine Anwendung". Ein Betriebssystem kann zwar mit denselben Tools wie eine Anwendung erstellt werden und besteht aus demselben Rohmaterial, es ist jedoch nicht dasselbe. Ein Betriebssystem muss nicht die gleichen Regeln wie eine Anwendung einhalten.
OTOH, Sie können sich die tatsächliche Hardware und Firmware als das "Betriebssystem" vorstellen, in dem die "Anwendung" des Betriebssystems ausgeführt wird. Die Hardware ist ein sehr einfaches Betriebssystem - sie kann Anweisungen ausführen, die in Maschinencode geschrieben sind, und sie weiß, dass sie beim Start eine ganz bestimmte Speicheradresse für ihre erste Anweisung prüfen sollte. Es startet also und führt sofort die erste Anweisung aus, gefolgt von der zweiten und so weiter.
Das Betriebssystem ist also einfach Maschinencode, der an einem bekannten Ort vorhanden ist und der direkt mit der Hardware interagieren kann.
quelle
Die Beantwortung Ihrer Frage setzt voraus, dass Sie wissen, wie der native Code (für die CPU) aussieht und wie er von der CPU interpretiert wird.
Normalerweise basiert der gesamte Kompilierungsprozess auf der Übersetzung von Dingen, die Sie in C, Pascal oder sogar Python (unter Verwendung von pypy) und C # schreiben, in Dinge, die die CPU versteht, dh einfache Anweisungen wie "etwas unter [Speicheradresse] speichern", "Zahlen hinzufügen, die unter eax-Registern gespeichert sind und ebx "," call function foo "," vergleiche eax mit 10 ". Diese Anweisungen werden nacheinander ausgeführt und bewirken Dinge, die Sie mit Ihrem Code tun wollten.
Denken Sie jetzt darüber nach: Sie brauchen kein Betriebssystem, um diesen nativen Code auszuführen! Sie müssen diesen Code nur in den Speicher laden und der CPU mitteilen, dass er vorhanden ist und ausgeführt werden soll. Seien Sie jedoch nicht zu sehr besorgt. Das ist der Job, um den sich das BIOS kümmern sollte - es lädt Ihren Code (nur einen und einen Sektor) direkt nach dem Start der CPU unter der physischen Adresse 0x7C00. Dann beginnt die CPU, diesen einen Sektor (512 B) Ihres Codes auszuführen. Und Sie können alles tun, was Sie sich vorstellen! Natürlich ohne jegliche Unterstützung durch das Betriebssystem. Das liegt daran, dass SIE das Betriebssystem sind. Cool was? Keine Standardbibliothek, kein Boost, kein Python, keine Programme, keine Treiber! Du musst alles selbst schreiben.
Und wie kommuniziert man mit Hardware? Nun, Sie haben zwei Möglichkeiten:
Jetzt fragst du, was Kernel ist. Kurz gesagt, Kernel ist alles, was Sie nicht direkt sehen und erleben. Es verwaltet zusammen mit den Treibern alles, angefangen von Ihrer Tastatur bis hin zu fast jeder Hardware in Ihrem PC. Sie kommunizieren damit über eine grafische Shell oder ein Terminal. Oder durch Funktionen in Ihrem Code, die jetzt zum Glück mit Unterstützung des Betriebssystems ausgeführt werden.
Zum besseren Verständnis kann ich Ihnen einen Rat geben: Versuchen Sie, Ihr eigenes Betriebssystem zu schreiben. Auch wenn es "Hallo Welt" auf den Bildschirm schreiben wird.
quelle
Es gibt einige Unterschiede für die Funktionsweise eines Betriebssystems, die extrem systemabhängig sind. Um nützlich zu sein, muss ein System beim Start ein vorhersehbares Verhalten aufweisen, z. B. "Ausführung an Adresse X starten". Bei Systemen, bei denen nichtflüchtiger Speicher (z. B. Flash-Speicher) im Programmbereich zugeordnet ist, ist dies relativ einfach, da Sie nur sicherstellen, dass Sie den Startcode an der richtigen Stelle im Programmbereich des Prozessors platzieren. Dies ist bei Mikrocontrollern sehr häufig. Einige Systeme müssen ihre Startprogramme von einem anderen Speicherort abrufen, bevor sie ausgeführt werden können. In diesen Systemen sind einige Operationen fest (oder fast fest) verkabelt. Es gibt einige Prozessoren, die ihren Startcode über i2c von einem anderen Chip abrufen.
Systeme, die die x86-Prozessorfamilie verwenden, verwenden normalerweise einen mehrstufigen Startprozess, der aufgrund seiner Evolution und Abwärtskompatibilität recht komplex ist. Das System führt eine Firmware (BIOS - Basic Input / Output System oder ähnliches) aus, die sich in einem nichtflüchtigen Speicher auf der Hauptplatine befindet. Manchmal wird ein Teil oder die gesamte Firmware in den Arbeitsspeicher kopiert (verlagert), um die Ausführung zu beschleunigen. Dieser Code wurde mit dem Wissen geschrieben, welche Hardware vorhanden und zum Booten verwendbar sein würde.
Die Start-Firmware wird normalerweise mit Annahmen darüber geschrieben, welche Hardware auf dem System vorhanden sein wird. Vor Jahren gab es auf einer 286-Maschine wahrscheinlich die Annahme, dass ein Diskettenlaufwerk-Controller an der E / A-Adresse X vorhanden ist und Sektor 0 an einen bestimmten Speicherort geladen wird, wenn ein bestimmter Befehlssatz (und der Code an Sektor 0) gegeben wird weiß, wie man die BIOS-eigenen Funktionen verwendet, um mehr Code zu laden, und schließlich wird genug Code geladen, um ein Betriebssystem zu sein. Bei einem Mikrocontroller kann davon ausgegangen werden, dass ein serieller Anschluss mit bestimmten Einstellungen vorhanden ist, bei denen darauf gewartet werden muss, dass Befehle (um eine komplexere Firmware zu aktualisieren) X-mal ausgeführt werden, bevor der Startvorgang fortgesetzt wird.
Der genaue Startvorgang eines bestimmten Systems ist für Sie nicht so wichtig wie das Wissen, dass er sich auf verschiedenen Systemen unterscheidet, aber auch, dass alle Dinge gemeinsam haben. Oft werden die E / A-Geräte innerhalb des Startcodes (Bootstrapping) abgefragt, wenn E / A-Vorgänge ausgeführt werden müssen, anstatt sich auf Interrupts zu verlassen. Dies liegt daran, dass Interrupts komplex sind, Stack-RAM verwenden (das möglicherweise noch nicht vollständig eingerichtet ist) und Sie sich keine Gedanken darüber machen müssen, andere Vorgänge zu blockieren, wenn Sie der einzige Vorgang sind.
Nach dem ersten Laden verhält sich der Betriebssystemkern (der Kernel ist der Hauptteil der meisten Betriebssysteme) zunächst ähnlich wie die Firmware. Es muss entweder mit Kenntnissen der vorhandenen Hardware programmiert werden oder vorhandene Hardware ermitteln, RAM als Stack-Space einrichten, verschiedene Tests durchführen, verschiedene Datenstrukturen einrichten, möglicherweise ein Dateisystem ermitteln und bereitstellen und dann möglicherweise ein Programm starten, das mehr ist wie die Programme, die Sie zum Schreiben verwenden (ein Programm, das auf einem vorhandenen Betriebssystem beruht).
Der OS-Code wird normalerweise in einer Mischung aus C und Assembly geschrieben. Der allererste Code für den OS-Kernel befindet sich wahrscheinlich immer in der Assembly und richtet beispielsweise den Stack ein, auf den sich der C-Code stützt, und ruft dann eine C-Funktion auf. Andere handgeschriebene Assemblierungen sind ebenfalls enthalten, da einige Operationen, die ein Betriebssystem ausführen muss, in C häufig nicht ausdrückbar sind (wie das Umschalten des Kontexts / das Austauschen von Stapeln). Oft müssen spezielle Flags an den C-Compiler übergeben werden, damit dieser sich nicht auf die von den meisten C-Programmen verwendeten Standardbibliotheken verlässt und nicht erwartet, dass eine vorhanden ist
int main(int argc, char *argv[])
im Programm. Außerdem müssen spezielle Linker-Optionen verwendet werden, die die meisten Anwendungsprogrammierer niemals verwenden. Dies kann dazu führen, dass das Kernel-Programm erwartet, an einer bestimmten Adresse geladen zu werden, oder es kann so eingerichtet werden, dass es an bestimmten Stellen externe Variablen gibt, obwohl diese Variablen in keinem C-Code deklariert wurden (dies ist nützlich für speicherabgebildete E / A oder andere spezielle Speicherplätze).Die gesamte Operation scheint zunächst magisch zu sein, aber nachdem Sie sie untersucht und Teile davon verstanden haben, wird die Magie nur zu einer Reihe von Programmen, für deren Implementierung viel mehr Planungs- und Systemkenntnisse erforderlich sind. Das Debuggen erfordert jedoch Magie.
quelle
Um zu verstehen, wie Betriebssysteme funktionieren, kann es hilfreich sein, sie in zwei Kategorien zu unterteilen: diejenigen, die auf Anfrage lediglich Dienste für Anwendungen bereitstellen, und diejenigen, die Hardwarefunktionen in der CPU verwenden, um zu verhindern, dass Anwendungen Dinge tun, die sie nicht tun sollten. MS-DOS war vom ehemaligen Stil; Alle Windows-Versionen seit 3.0 haben den letzteren Stil (zumindest wenn etwas leistungsfähigeres als ein 8086 ausgeführt wird).
Der ursprüngliche IBM PC, auf dem PC-DOS oder MS-DOS ausgeführt wird, wäre ein Beispiel für den früheren Stil von "OS" gewesen. Wenn eine Anwendung ein Zeichen auf dem Bildschirm anzeigen wollte, hätte es einige Möglichkeiten gegeben, dies zu tun. Es könnte die Routine aufrufen, die MS-DOS auffordert, sie an die "Standardausgabe" zu senden. Wenn dies der Fall wäre, würde MS-DOS prüfen, ob die Ausgabe umgeleitet wurde, und wenn dies nicht der Fall wäre, würde es eine Routine aufrufen, die im ROM gespeichert ist (in einer Sammlung von Routinen, die IBM als Basic Input / Output System bezeichnet), die ein Zeichen am anzeigt Cursor positionieren und Cursor bewegen ("Teletyp schreiben"). Diese BIOS-Routine würde dann ein Bytepaar irgendwo im Bereich von 0xB800: 0 bis 0xB800: 3999 speichern; Hardware auf dem Farbgrafikadapter ruft wiederholt Paare von Bytes in diesem Bereich ab. Verwenden Sie das erste Byte jedes Paares, um eine Zeichenform auszuwählen, und das zweite Byte, um Vordergrund- und Hintergrundfarben auszuwählen. Die Bytes werden abgerufen und in einer Reihenfolge zu roten, grünen und blauen Signalen verarbeitet, die eine lesbare Textanzeige ergibt.
Programme auf dem IBM PC können Text anzeigen, indem sie die DOS-Routine "Standardausgabe" oder die BIOS-Routine "Teletyp schreiben" verwenden oder ihn direkt im Anzeigespeicher speichern. Viele Programme, die viel Text anzeigen mussten, entschieden sich schnell für letzteres, da es buchstäblich hunderte Male so schnell sein konnte wie die DOS-Routinen. Dies lag nicht daran, dass die DOS- und BIOS-Routinen außergewöhnlich ineffizient waren. Sofern die Anzeige nicht ausgeblendet war, konnte sie nur zu bestimmten Zeiten beschrieben werden. Die BIOS-Routine zur Ausgabe eines Zeichens wurde so konzipiert, dass sie jederzeit aufgerufen werden kann. Jede Anforderung musste daher erneut gestartet werden, um auf den richtigen Zeitpunkt für die Ausführung einer Schreiboperation zu warten. Im Gegensatz dazu könnte sich Anwendungscode, der wusste, was zu tun ist, nach den verfügbaren Möglichkeiten zum Schreiben des Displays organisieren.
Ein wesentlicher Punkt hierbei ist, dass DOS und BIOS zwar die Ausgabe von Text auf dem Bildschirm ermöglichten, diese Fähigkeiten jedoch nicht besonders "magisch" waren. Eine Anwendung, die Text auf das Display schreiben wollte, konnte dies genauso effektiv tun, zumindest wenn die Display-Hardware wie erwartet funktionierte (wenn jemand einen Monochrome Display Adapter installiert hatte, der dem CGA ähnelte, aber über einen Zeichenspeicher verfügte) befindet sich bei 0xB000: 0000-0xB000: 3999), würde das BIOS dort automatisch Zeichen ausgeben; Eine Anwendung, die für die Arbeit mit dem MDA oder CGA programmiert wurde, könnte dies ebenfalls tun, aber eine Anwendung, die nur für den CGA programmiert wurde, wäre auf dem MDA völlig nutzlos.
Bei neueren Systemen sieht das etwas anders aus. Prozessoren haben verschiedene "Privileg" -Modi. Sie beginnen im privilegiertesten Modus, in dem Code alles tun darf, was er will. Sie können dann in einen eingeschränkten Modus wechseln, in dem nur ausgewählte Speicherbereiche oder E / A-Funktionen verfügbar sind. Code kann nicht direkt von einem eingeschränkten Modus zurück in den Berechtigungsmodus wechseln, aber der Prozessor hat Einstiegspunkte für den privilegierten Modus definiert, und Code für den eingeschränkten Modus kann den Prozessor auffordern, Code an einem dieser Einstiegspunkte im privilegierten Modus auszuführen. Darüber hinaus gibt es Einstiegspunkte im privilegierten Modus, die mit einer Reihe von Vorgängen verknüpft sind, die im eingeschränkten Modus verboten wären. Angenommen, jemand wollte mehrere MS-DOS-Anwendungen gleichzeitig ausführen, wobei jede einen eigenen Bildschirm hat. Wenn Anwendungen mit 0xB800: 0 direkt auf den Anzeigecontroller schreiben könnten, gäbe es keine Möglichkeit zu verhindern, dass eine Anwendung den Bildschirm einer anderen Anwendung überschreibt. Andererseits könnte ein Betriebssystem die Anwendung im eingeschränkten Modus ausführen und alle Zugriffe auf den Anzeigespeicher abfangen. Wenn sich herausstellte, dass eine Anwendung, die sich im "Hintergrund" befinden sollte, versuchte, 0xB800: 160 zu schreiben, konnte sie die Daten in einem Speicher ablegen, den sie als Hintergrundanwendungs-Bildschirmpuffer reserviert hatte. Wenn diese Anwendung später in den Vordergrund geschaltet wird, kann der Puffer auf den realen Bildschirm kopiert werden. Ein Betriebssystem könnte die Anwendung im eingeschränkten Modus ausführen und Zugriffe auf den Anzeigespeicher abfangen. Wenn sich herausstellte, dass eine Anwendung, die sich im "Hintergrund" befinden sollte, versuchte, 0xB800: 160 zu schreiben, konnte sie die Daten in einem Speicher ablegen, den sie als Hintergrundanwendungs-Bildschirmpuffer reserviert hatte. Wenn diese Anwendung später in den Vordergrund geschaltet wird, kann der Puffer auf den realen Bildschirm kopiert werden. Ein Betriebssystem könnte die Anwendung im eingeschränkten Modus ausführen und Zugriffe auf den Anzeigespeicher abfangen. Wenn sich herausstellte, dass eine Anwendung, die sich im "Hintergrund" befinden sollte, versuchte, 0xB800: 160 zu schreiben, konnte sie die Daten in einem Speicher ablegen, den sie als Hintergrundanwendungs-Bildschirmpuffer reserviert hatte. Wenn diese Anwendung später in den Vordergrund geschaltet wird, kann der Puffer auf den realen Bildschirm kopiert werden.
Die wichtigsten Dinge, die zu beachten sind, sind: (1) Obwohl es oft praktisch ist, über einen Standardsatz von Routinen zu verfügen, um verschiedene Standarddienste wie das Anzeigen von Text auszuführen, tun sie nichts, was eine Anwendung, die im "privilegierten Modus" ausgeführt wurde, nicht kann wenn es richtig programmiert wurde, um mit der Hardware umzugehen, die installiert wurde; (2) Obwohl die meisten Anwendungen, die heute ausgeführt werden, von ihrem Betriebssystem daran gehindert würden, solche E / A-Vorgänge direkt auszuführen, kann ein Programm, das im privilegierten Modus gestartet wird, alles tun, was es möchte, und beliebige Regeln für den eingeschränkten Modus einrichten Programme.
quelle
Wie Stephen C. sagte, geht es nicht nur um das Starten des Betriebssystems, sondern auch darum, wie es ausgeführt wird, mit der Hardware und der Software darüber interagiert.
Ich werde nur zu seiner Antwort hinzufügen, dass Sie vielleicht einen Blick auf "Die Elemente von Computersystemen" werfen möchten . Es ist ein Buch und einige Tools, die erklären, wie ein Computer, ein Betriebssystem und Compiler interagieren. Das Einzigartige daran ist, dass Sie mit diesen Tools Ihr eigenes Betriebssystem in einer simulierten Umgebung sehr schnell entwickeln können. Dabei werden die vielen Details ignoriert, die für eine echte Umgebung erforderlich sind, damit Sie die Konzepte verstehen können . Es macht einen tollen Job, wenn Sie den Wald anstelle der Bäume sehen.
Wenn Sie mehr über die Interaktion des Betriebssystems mit der Hardware erfahren möchten, lesen Sie Minix .
quelle
Ihre Anwendung wird in einem Betriebssystem ausgeführt. Dieses Betriebssystem bietet Dienste für Ihre Anwendung, z. B. das Öffnen einer Datei und das Schreiben von Bytes. Diese Dienste werden normalerweise über Systemaufrufe bereitgestellt.
Das Betriebssystem läuft in der Hardware. Die Hardware stellt Dienste für das Betriebssystem bereit, z. B. das Einstellen der Baudrate eines seriellen Anschlusses und das Schreiben von Bytes. Diese Dienste werden normalerweise über Speicherregister oder E / A-Ports bereitgestellt.
Um ein sehr vereinfachtes Beispiel zu geben, wie dies funktioniert:
Ihre Anwendung weist das Betriebssystem an, etwas in eine Datei zu schreiben. Das Betriebssystem bietet Ihrer Anwendung Konzepte wie Dateien und Verzeichnisse.
Auf der Hardware existieren diese Konzepte nicht. Die Hardware bietet Konzepte wie Festplatten, die in feste Blöcke von 512 Bytes unterteilt sind. Das Betriebssystem entscheidet, welche Blöcke für Ihre Datei verwendet werden sollen, und einige andere Blöcke für Metadaten wie Dateiname, Größe und Speicherort auf der Festplatte. Dann teilt es der Hardware mit: Schreiben Sie diese 512 Bytes in den Sektor mit dieser Nummer auf der Platte mit dieser Nummer; Schreiben Sie diese anderen 512 Bytes in den Sektor mit dieser anderen Nummer auf die Platte mit derselben Nummer. und so weiter.
Die Art und Weise, wie das Betriebssystem die Hardware dazu auffordert, ist sehr unterschiedlich. Eine der Funktionen eines Betriebssystems besteht darin, die Anwendungen vor diesen Unterschieden zu schützen. Für das Festplatten-Beispiel müsste das Betriebssystem auf einer Hardware-Art die Festplatten- und Sektornummer in einen E / A-Port schreiben und dann die Bytes nacheinander in einen separaten E / A-Port schreiben. Auf einer anderen Art von Hardware müsste das Betriebssystem die gesamten 512 Bytes eines Sektors in einen Speicherbereich kopieren, die Position dieses Speicherbereichs in einen speziellen Speicherbereich schreiben und die Platten- und Sektornummer in einen weiteren schreiben spezieller Speicherort.
Die heutige High-End-Hardware ist äußerst kompliziert. Die Handbücher mit allen Programmierdetails sind Türstopper mit Tausenden von Seiten. Das neueste Handbuch für Intel-CPUs umfasst beispielsweise sieben Bände mit insgesamt über 4000 Seiten - und das nur für die CPU. Die meisten anderen Komponenten stellen Speicherblöcke oder E / A-Ports zur Verfügung, die das Betriebssystem der CPU anweisen kann, Adressen in ihrem Adressraum zuzuordnen. Einige dieser Komponenten stellen noch mehr Dinge hinter ein paar E / A-Ports oder Speicheradressen zur Verfügung. Beispiel: Die Echtzeituhr (Real Time Clock, die Komponente, die die Zeit des Computers im ausgeschalteten Zustand festhält) stellt einige hundert Byte Speicher hinter zwei E / A-Anschlüssen zur Verfügung der ursprüngliche PC / AT. Dinge wie Festplatten haben ganze separate Prozessoren, mit denen das Betriebssystem über standardisierte Befehle spricht. GPUs sind noch komplizierter.
Mehrere Personen in den obigen Kommentaren schlugen den Arduino vor. Ich stimme ihnen zu, es ist viel einfacher zu verstehen - der ATmega328, der alles auf dem Arduino Uno macht, außer den USB-Anschluss als seriellen Anschluss freizulegen, hat ein Handbuch mit nur wenigen hundert Seiten. Auf dem Arduino laufen Sie direkt auf der Hardware, ohne dazwischen befindliches Betriebssystem. nur ein paar kleine Bibliotheksroutinen, die Sie nicht verwenden müssen, wenn Sie nicht möchten.
quelle
Lauffähige Beispiele
Technisch gesehen ist ein Programm, das ohne Betriebssystem läuft, ein Betriebssystem. Schauen wir uns also an, wie man ein paar winzige Hallo-Welt-Betriebssysteme erstellt und ausführt.
Der Code aller folgenden Beispiele ist auf diesem GitHub-Repo vorhanden .
Bootsektor
Auf x86 können Sie am einfachsten und niedrigsten einen Master- Bootsektor (MBR) erstellen , der eine Art Bootsektor darstellt , und diesen dann auf einer Festplatte installieren.
Hier erstellen wir eine mit einem einzigen
printf
Aufruf:Ergebnis:
Getestet unter Ubuntu 18.04, QEMU 2.11.1.
main.img
enthält folgendes:\364
in oktal ==0xf4
in hex: die Kodierung für einenhlt
Befehl, der die CPU auffordert, nicht mehr zu arbeiten.Deshalb wird unser Programm nichts tun: nur starten und stoppen.
Wir verwenden Oktal, da
\x
Hexadezimalzahlen von POSIX nicht angegeben werden.Wir könnten diese Kodierung leicht erhalten mit:
Die
0xf4
Kodierung ist aber natürlich auch im Intel-Handbuch dokumentiert.%509s
produzieren 509 Plätze. Muss die Datei bis zum Byte 510 ausfüllen.\125\252
in oktal ==0x55
gefolgt von0xaa
: magischen Bytes, die von der Hardware benötigt werden. Sie müssen die Bytes 511 und 512 sein.Ist dies nicht der Fall, wird dies von der Hardware nicht als startfähige Festplatte behandelt.
Beachten Sie, dass auch ohne etwas zu tun, einige Zeichen bereits auf dem Bildschirm gedruckt sind. Diese werden von der Firmware ausgedruckt und dienen zur Identifikation des Systems.
Laufen auf echter Hardware
Emulatoren machen Spaß, aber Hardware ist das einzig Wahre.
Beachten Sie jedoch, dass dies gefährlich ist und Sie Ihre Festplatte versehentlich löschen können: Tun Sie dies nur auf alten Computern, die keine kritischen Daten enthalten! Oder noch besser, Devboards wie der Raspberry Pi, siehe das ARM-Beispiel unten.
Für einen typischen Laptop müssen Sie Folgendes tun:
Brennen Sie das Image auf einen USB-Stick (zerstört Ihre Daten!):
Stecken Sie den USB an einen Computer
Mach es an
Sagen Sie ihm, er soll von USB booten.
Dies bedeutet, dass die Firmware USB vor der Festplatte auswählt.
Wenn dies nicht das Standardverhalten Ihres Computers ist, drücken Sie nach dem Einschalten die Eingabetaste, F12, ESC oder andere ungewöhnliche Tasten, bis Sie ein Startmenü erhalten, in dem Sie auswählen können, ob Sie vom USB-Stick starten möchten.
In diesen Menüs kann häufig die Suchreihenfolge konfiguriert werden.
Auf meinem alten Lenovo Thinkpad T430, UEFI BIOS 1.16, sehe ich beispielsweise Folgendes:
Hallo Welt
Nachdem wir ein minimales Programm erstellt haben, begeben wir uns in eine hallo Welt.
Die offensichtliche Frage ist: Wie mache ich IO? Ein paar Möglichkeiten:
serielle Schnittstelle . Dies ist ein sehr einfaches standardisiertes Protokoll, das Zeichen von einem Host-Terminal sendet und abruft.
Quelle .
Es ist leider nicht auf den meisten modernen Laptops ausgesetzt, aber es ist der übliche Weg für Entwicklungsboards, siehe die ARM-Beispiele unten.
Das ist wirklich schade, da solche Schnittstellen zum Beispiel für das Debuggen des Linux-Kernels sehr nützlich sind .
Verwenden Sie Debug-Funktionen von Chips. ARM nennt sie zum Beispiel Semihosting . Bei echter Hardware sind einige zusätzliche Hardware- und Softwareunterstützungen erforderlich, bei Emulatoren kann dies jedoch eine kostenlose, praktische Option sein. Beispiel .
Hier machen wir ein BIOS-Beispiel, da es auf x86 einfacher ist. Beachten Sie jedoch, dass dies nicht die robusteste Methode ist.
Netz
link.ld
Zusammenstellen und verknüpfen mit:
Ergebnis:
Getestet auf: Lenovo Thinkpad T430, UEFI BIOS 1.16. Die Festplatte wurde auf einem Ubuntu 18.04-Host erstellt.
Neben den Standardanweisungen für die Userland-Montage haben wir:
.code16
: Weist GAS an, 16-Bit-Code auszugebencli
: Software-Interrupts deaktivieren. Diese könnten dazu führen, dass der Prozessor nach dem erneut gestartet wirdhlt
int $0x10
: führt einen BIOS-Aufruf durch. Dies ist, was die Zeichen eins nach dem anderen druckt.Die wichtigen Link-Flags sind:
--oformat binary
: Gebe rohen binären Assembler-Code aus. Verwerfe ihn nicht in einer ELF-Datei, wie dies bei normalen Userland-Programmen der Fall ist.Verwenden Sie C anstelle von Assembly
Da C zu Assembly kompiliert wird und die Verwendung von C ohne die Standardbibliothek ziemlich einfach ist, benötigen Sie im Grunde nur Folgendes:
main
Folgendes festlegt :TODO: so ein x86 Beispiel auf GitHub verlinken. Hier ist ein ARM, den ich erstellt habe .
Es macht jedoch mehr Spaß, wenn Sie die Standardbibliothek verwenden möchten, da wir keinen Linux-Kernel haben, der einen Großteil der Funktionen der C-Standardbibliothek über POSIX implementiert .
Einige Möglichkeiten, ohne auf ein vollwertiges Betriebssystem wie Linux umzusteigen, sind:
Newlib
Ausführliches Beispiel unter: https://electronics.stackexchange.com/questions/223929/c-standard-libraries-on-bare-metal/223931
In Newlib müssen Sie die Syscalls selbst implementieren, aber Sie erhalten ein sehr minimales System, und es ist sehr einfach, sie zu implementieren.
Beispielsweise könnten Sie
printf
zu den UART- oder ARM-Systemen umleiten oderexit()
mit Semihosting implementieren .Embedded-Betriebssysteme wie FreeRTOS und Zephyr .
Mit solchen Betriebssystemen können Sie in der Regel die präventive Zeitplanung deaktivieren und so die vollständige Kontrolle über die Laufzeit des Programms behalten.
Sie können als eine Art vorimplementierte Newlib angesehen werden.
ARM
In ARM sind die allgemeinen Vorstellungen dieselben. Ich habe hochgeladen:
ein paar einfache QEMU-Baremetal-Beispiele hier auf GitHub . Das prompt.c-Beispiel nimmt Eingaben von Ihrem Host-Terminal entgegen und gibt sie über den simulierten UART zurück:
Siehe auch: https://stackoverflow.com/questions/38914019/how-to-make-bare-metal-arm-programs-and-run-them-on-qemu/50981397#50981397
Ein vollautomatisches Raspberry Pi-Blinker-Setup finden Sie unter: https://github.com/cirosantilli/raspberry-pi-bare-metal-blinker
Siehe auch: https://stackoverflow.com/questions/29837892/how-to-run-ac-program-with-no-os-on-the-raspberry-pi/40063032#40063032
Für den Raspberry Pi scheint https://github.com/dwelch67/raspberrypi das beliebteste Tutorial zu sein, das derzeit erhältlich ist.
Einige Unterschiede zu x86 sind:
Die Eingabe erfolgt durch direktes Schreiben an magische Adressen, es gibt keine
in
undout
Anweisungen.Dies wird als speicherabgebildete E / A bezeichnet .
Für echte Hardware wie den Raspberry Pi können Sie die Firmware (BIOS) selbst zum Festplatten-Image hinzufügen.
Das ist eine gute Sache, da es die Aktualisierung dieser Firmware transparenter macht.
Firmware
In Wahrheit ist Ihr Bootsektor nicht die erste Software, die auf der CPU des Systems ausgeführt wird.
Was tatsächlich zuerst ausgeführt wird, ist die sogenannte Firmware , bei der es sich um eine Software handelt:
Bekannte Firmwares sind:
Die Firmware macht Dinge wie:
Durchlaufen Sie jede Festplatte, jedes USB-Gerät, jedes Netzwerk usw., bis Sie etwas finden, das gebootet werden kann.
Wenn wir QEMU ausführen,
-hda
heißt das, dassmain.img
eine Festplatte an die Hardware angeschlossen ist, undhda
ist der erste, der ausprobiert wird, und er wird verwendet.Laden Sie die ersten 512 Bytes in die RAM-Speicheradresse
0x7c00
, platzieren Sie den RIP der CPU dort und lassen Sie ihn laufenZeigen Sie Dinge wie das Boot-Menü oder BIOS-Druckaufrufe auf dem Display an
Firmware bietet betriebssystemähnliche Funktionen, von denen die meisten Betriebssysteme abhängen. Beispielsweise wurde eine Python-Teilmenge für die Ausführung unter BIOS / UEFI portiert: https://www.youtube.com/watch?v=bYQ_lq5dcvM
Es kann argumentiert werden, dass Firmwares nicht von Betriebssystemen zu unterscheiden sind und dass Firmware die einzige "echte" Bare-Metal-Programmierung ist, die man durchführen kann.
Wie dieser CoreOS-Entwickler es ausdrückt :
Post-BIOS-Ausgangszustand
Wie viele Dinge in der Hardware ist die Standardisierung schwach, und eines der Dinge, auf die Sie sich nicht verlassen sollten, ist der Anfangszustand der Register, wenn Ihr Code nach dem BIOS ausgeführt wird.
Tun Sie sich selbst einen Gefallen und verwenden Sie einen Initialisierungscode wie den folgenden: https://stackoverflow.com/a/32509555/895245
Register mögen
%ds
und%es
haben wichtige Nebenwirkungen, daher sollten Sie sie auf Null setzen, auch wenn Sie sie nicht explizit verwenden.Beachten Sie, dass einige Emulatoren besser sind als echte Hardware und Ihnen einen schönen Anfangszustand geben. Wenn Sie dann auf echter Hardware laufen, geht alles kaputt.
GNU GRUB Multiboot
Bootsektoren sind einfach, aber nicht sehr praktisch:
Aus diesen Gründen hat GNU GRUB ein praktischeres Dateiformat namens Multiboot erstellt.
Minimales Arbeitsbeispiel: https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
Ich benutze es auch in meinem GitHub-Beispielrepo , um alle Beispiele problemlos auf echter Hardware ausführen zu können, ohne den USB-Stick millionenfach zu brennen. Auf QEMU sieht es so aus:
Wenn Sie Ihr Betriebssystem als Multiboot-Datei vorbereiten, kann GRUB es in einem regulären Dateisystem finden.
Dies ist, was die meisten Distributionen tun, indem sie OS-Images unterbringen
/boot
.Multiboot-Dateien sind im Grunde eine ELF-Datei mit einem speziellen Header. Sie werden von GRUB unter https://www.gnu.org/software/grub/manual/multiboot/multiboot.html angegeben
Sie können eine Multiboot-Datei mit in eine bootfähige Disk verwandeln
grub-mkrescue
.El Torito
Format, das auf CDs gebrannt werden kann: https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
Es ist auch möglich, ein Hybrid-Image zu erstellen, das entweder auf ISO oder USB funktioniert. Dies kann mit
grub-mkrescue
( Beispiel ) erfolgen und wird auch vom Linux-Kernel bei dermake isoimage
Verwendung durchgeführtisohybrid
.Ressourcen
quelle