Was sind die Details zu Link-Dateien und Startcode, die man kennen muss, um ein Betriebssystem für ein uC zu schreiben? [geschlossen]

Ich weiß, dass einige Leute sagen werden, dass ich kein Betriebssystem schreiben muss, weil es viele Möglichkeiten gibt. Aber ich bin nicht daran interessiert, ein Betriebssystem zu verwenden, um ein bestimmtes Problem zu lösen. Ich möchte selbst einen schreiben, um zu lernen, wie man es macht.

Ich weiß viel über die Theorie von Betriebssystemen und ich weiß auch, wie man Mikrocontroller in Bare Metal programmiert. Aber ich weiß sehr wenig über die Konfigurationsdetails, die nicht direkt mit der Programmierung zusammenhängen. Ich spreche von Link-Befehlsdateien, Startcode usw.

Meine Frage ist: Was muss ich über diese "Konfigurations"-bezogenen Dinge wissen, um das Betriebssystem zum Laufen zu bringen? Bitte verlinken Sie, wenn möglich, einige externe Inhalte zu diesen Dingen.

Es ist absolut großartig, dass Sie ein solches Projekt übernehmen möchten. Genau so lernen und erweitern Ingenieure, ob es nun nur um die Erkundung von Plänen und Ideen geht oder bis hin zur fertigen Software. Es ist jedoch keine Frage des Elektronikdesigns, also sind Sie im falschen Forum. Aber ein geniales Projekt und Ziel :-)
@TonyM Danke für deinen Kommentar. Ich poste die Frage hier, weil dies das Stackexchange-Forum ist, das näher an eingebetteter Software, RTOS, eingebettetem Betriebssystem und Mikrocontrollern liegt. Wenn ich diese Frage auf Stackoverflow stelle, werden die Leute sagen, dass ich auch im falschen Forum bin. :D
Dies ist eine ziemlich heikle Frage, und sie kann als zu weit gefasst angesehen werden, um sie in einer einzigen Antwort zu beantworten. Ich würde empfehlen, sich die Dokumentation für freeRTOS zusammen mit den von ihnen bereitgestellten Beispielprojekten oder einem ähnlichen eingebetteten Betriebssystem anzusehen , um zu sehen, wie sie es tun.
@DanielGiesbrecht Ja! Ich schaue mir bereits freeRTOS und ChibiOS an. Danke!
Ein Betriebssystem ist nur ein Bare-Metal-Programm! Wenn Sie versuchen, ein Betriebssystem auf einem PC zu schreiben, müssen Sie durch alle möglichen Hürden springen, um Ihren Bare-Metal-Code zum Laufen zu bringen. Aber auf einem Mikrocontroller haben Sie diese Fähigkeit bereits von Natur aus, es gibt keine Reifen! Sie sollten nur genau die gleichen Linker-Skripte usw. benötigen, die Sie für jeden anderen Bare-Metal-Code benötigen würden (was hoffentlich keiner von ihnen ist).
Es mag seltsam sein, so darüber nachzudenken, aber Ihr Betriebssystem ist nur ein Programm, und "Programme, die auf Ihrem Betriebssystem ausgeführt werden" sind eigentlich nur Bibliotheken für Ihr Betriebssystemprogramm.
Sie können eigentlich ein Betriebssystem ohne Task-Wechsel schreiben – wenn alle Ihre Programme ereignisgesteuert sind und zeitnah von den Event-Handlern zurückkehren. Als Ersteller des Betriebssystems können Sie entscheiden, wie Ihre Programme strukturiert sind, daher könnte dies ein interessanter Ausgangspunkt sein. Aber Sie werden auf diese Weise keine bestehenden Programme auf Ihr Betriebssystem portieren. (Tatsächlich können Sie dieses Betriebssystem ohne eine einzige Montagelinie schreiben.)

Antworten (3)

Hier sind einige Dinge, die einem sofort in den Sinn kommen.

  1. Sie müssen alle Details darüber kennen, was passiert, wenn der Prozessor aus einem Power-On-Reset kommt. Es wird eine Reihe von Steuerregistern geben, und diese haben Standardwerte. Sie müssen diese wissen, kalt.
  2. Sie müssen alle Details der CPU-Architektur kennen. Alle Register, spezielle Modi, die sie verwenden usw. Diese Dinge geben Ihnen Hinweise darauf, wie Sie die Register zum Aufrufen und Zurückkehren von Funktionen usw. anordnen. Wenn Sie C verwenden oder mit C kompatibel sein möchten, müssen Sie um über die Entscheidungen zu lesen, die von verschiedenen C-Compiler-Anbietern getroffen wurden (nicht immer die gleichen Entscheidungen).
  3. Sie müssen alle Details zu verschiedenen funktionalen Einheiten und Bibliothekscode kennen, deren Einbindung Sie zulassen werden. Insbesondere dann, wenn Sie Vorkaufsrecht unterstützen. In einigen Fällen können Sie keine Vorrangigkeit zulassen, da es keine Möglichkeit gibt, den Status einiger laufender Operationen zu speichern (der MSP430-Multiplikator ist so), obwohl ein Intervall-Timer diesen Prozess unterbrechen kann. In einigen Fällen können Bibliotheken auch nicht unterbrochen werden, da sie einen statischen Zustand haben, der durch einen separaten Thread beschädigt würde, der auf denselben Code zugreift. Usw.
  4. Ihr Startcode muss mit der Power-On-Reset-Adresse für die MCU verknüpft werden. Seine Aufgabe ist es, die versprochene Initialisierung auszuführen. Dies kann das Tabellieren von Speicherbereichen, das Initialisieren von Speicher und alles andere sein, was in einem bekannten Zustand vor der Ausführung des ersten Threads/Prozesses erwartet wird. Wenn C beteiligt ist, bedeutet dies, alle initialisierten statischen Variablen mit ihren entsprechenden Werten zu initialisieren und alle anderen statischen Variablen mit ihrem semantischen Äquivalent von 0 zu initialisieren, was auch immer das sein mag.
  5. Sie müssen alle Möglichkeiten kennen, wie Ausnahmen auftreten können, und wie Sie beispielsweise zwischen einem Watchdog-Timer-Ereignis und einem Ereignis zum Zurücksetzen beim Einschalten unterscheiden können.
  6. Die Linker-Eingabedatei legt lediglich die Bereiche fest, in denen Code und initialisierte Daten platziert werden können, und wie groß sie sind. Es verwendet auch Namen, damit der Verknüpfungsprozess benannten Code oder benannte initialisierte Daten den entsprechenden Stellen zuweisen kann, wie in der Linker-Eingabedatei angegeben. Das ist nicht kompliziert. Aber die Angaben müssen stimmen. Wenn Sie einen C-Compiler verwenden, wird dieser C-Compiler wahrscheinlich alle möglichen Annahmen über die Namen von Abschnitten (Segmenten) treffen, und Sie müssen ihnen entweder gehorchen oder ein Tool schreiben, das den Objektcode vor dem Linken ändert.
  7. Möglicherweise müssen Sie sowieso ein spezielles Tool zum Verlinken schreiben. Dies kann passieren, wenn Sie mehrere C-Compiler unterstützen müssen, die unterschiedlich benannte Segmente in ihren Objektdateien erzeugen; oder eine Assembler-Ausgabe, die ebenfalls unterschiedliche Namen verwendet; usw. Manchmal gehen die routinemäßigen Prologe und Epiloge nicht einmal von denselben Annahmen aus, und Sie müssen diese Dinge möglicherweise vor dem Verlinken patchen. Möglicherweise müssen Sie sowieso ein Patch-Tool schreiben, um Adressen, die in den Objektdateien angegeben sind, vor dem Linken zu patchen oder zu "korrigieren". (Ich musste dies mit 6502/65816-Prozessorcode tun, der für die ROM-Dateiproduktion mit seltsamen Memory-Mappern bestimmt war, die unterschiedlich und auch nicht miteinander kompatibel sind.)

Und das ist nur eine sehr kurze Liste, die mir aus den Fingern fließt, ohne darüber nachzudenken. Ich bin mir sicher, dass ich diese Liste verdoppeln könnte, wenn ich weitere 5 Minuten aufwenden würde. (Zum Beispiel habe ich nicht einmal etwas darüber angesprochen, was ein Debugger in Bezug auf Informationen und / oder geänderten oder eingefügten Code zur Unterstützung seiner Operationen benötigen könnte. Ich habe auch nicht die Unterschiede erörtert, die zwischen Harvard- und von Neumann-Speichersystemen bestehen. Noch Ich habe das standardmäßige "Programmmodell" der Organisation besprochen [Code; Konstanten; Init-Daten; Uninit-Daten; Heap; Stack usw.])

Ich würde empfehlen, dies in langsamen Schritten anzugehen. Da Sie behaupten, sich bereits mit Bare-Metal-Programmierung auszukennen und sich auch mit Betriebssystemen auszukennen, möchte ich Ihnen einfach empfehlen, das allererste XINU-Buch von Douglas Comer zu lesen (es ist um 1984, hat ein rotes Cover und es gibt keinen Band 2 , usw.) Dann sehen Sie, ob Sie eine kooperative Vermittlung zusammenschustern könnenBetriebssystem. Das bedeutet KEINE VORBEUGUNG. Es bedeutet nur THREADS – keine vollständigen, separaten Prozesse – sondern Threads, die denselben Coderaum, dieselben Konstanten, statischen Daten und denselben Heap teilen; mit dem einzigen Unterschied, dass sie separate Stacks haben. Unterstützen Sie einen switch()-Aufruf, damit dieser kooperative Thread-Wechsel funktioniert. Es muss auch Hardware-Treiberereignisse unterstützen, ohne dabei versehentlich den Stack eines Threads zum Überlaufen zu bringen. Sie müssen sorgfältig entwerfen, wie Sie mit Hardwareereignissen und ihrem Treibercode umgehen. (Ich habe dies in Low-Level-Hardware-Antwortcode + High-Level-Thread-zugänglichen Code aufgeteilt, die durch Puffer getrennt sind, um die beiden voneinander zu entkoppeln, damit sie unabhängig voneinander arbeiten können und gleichzeitig die Hardware vollständig unterstützen.)

Wenn Sie den nächsten Schritt machen möchten, fügen Sie Nachrichten zwischen Threads hinzu. Verwenden Sie ein einfaches Wort – nur ein Wort – für jeden Thread und lassen Sie es von einem anderen Thread schreiben. Überschreiben Sie es tatsächlich. Machen Sie das nicht kompliziert. Sehen Sie, ob Sie einzelne Wortmeldungen zwischen den Prozessen erhalten können.

Fügen Sie dann eine Schlafwarteschlange hinzu und stellen Sie einen Timer bereit, der Threads aus der Schlafwarteschlange zurück in die Ausführungswarteschlange verschieben kann.

Fügen Sie dann Semaphor-Warteschlangen hinzu.

Wenn Sie so weit kommen, haben Sie viel gelernt.

Übrigens habe ich weniger als zwei Arbeitstage (Montag bis zum frühen Dienstagnachmittag) gebraucht, um alle oben genannten Funktionen zum Laufen zu bringen – von Grund auf neu und ohne eine einzige Codezeile aus früheren Projekten, also einfach so schnell tippen wie ich denken könnte - einschließlich einer vollständigen Mischung aus Assembler und C, um Hardwareereignisse und so weiter zu verarbeiten. Ich hatte Run/Sleep/Semaphor-Warteschlangen, Timer und kooperative Threads zusammen mit Ausnahmebehandlung pro Thread hinzugefügt ... in weniger als zwei Tagen.

Dies ist also KEINE besonders schwierige Aufgabe, die vor uns liegt.

Haben Sie es.

Es ist sicherlich eine schwierige Aufgabe, lassen Sie es uns nicht trivialisieren, und wird am Ende viel länger als zwei Tage dauern, aber es ist perfekt machbar, und das ist der Punkt, auf den Sie meiner Meinung nach ansprechen. Hervorragende detaillierte Antwort, positiv bewertet. Es ist großartig zu sehen, wie begeistert und zufrieden Sie den Berg erklimmen, der nach den Jahren in Ihren Worten genauso hell brennt :-) Ich schätze es immer noch, meinen ersten Versuch zu wagen, einen Z80-Preemptor auf einem 64180.
@TonyM Das XINU-Buch bietet die Grundlagen in so leicht verständlicher Form, wie ich es in einem Buch gesehen habe. Ja, es wird eine neue Person viel länger als zwei Tage dauern!! Meistens wollte ich nur darauf hinweisen, dass sie zunächst NICHT versuchen sollten, vorzubeugen. Halte es einfach. Erstellen Sie einfach separate Stacks, verwalten Sie ein kleines Array, stellen Sie eine sehr einfache switch()-Funktion bereit und haben Sie Spaß. Fügen Sie sleep hinzu, wenn Sie bereit sind, sich mit dem Starten und Verwenden eines Timers zu befassen (aber immer noch nicht vorgreifen ... verschieben Sie nur Threads.) Fügen Sie dann Nachrichten hinzu (einfach) und später nach einigen ausgefallenen Semaphor-Warteschlangen. Das ist ein langer Weg, nützlich zu sein, denke ich.
Ich stimme zu und es ist großartig, Ihre Erfahrung, Ermutigung und Begeisterung dabei zu sehen, sehr wertvoll für das OP. Ich hatte das übliche „Kauf dir was ein, Idiot“ von den „Nichts machen, nichts lernen, stolz sein“-Händlern erwartet, die sich an diesen Dingen beteiligen. Ihr "Probieren Sie es aus" ist bei weitem die richtige Einstellung, und Sie können heutzutage für so wenig Geld eine Party darauf machen.
Ich habe dies bereits in LabView und Javascript und Assmebler getan. LabView machte es einfach, Prioritätswarteschlangen und Aufgabenaktivierungen und -blockierungen zu stapeln (Executive Branch). Dann gibt es die Aufgabenteilung und Trigger. Halten Sie sich an die Regeln der Logik, und es wird gut funktionieren.
@TonyM Ich habe in einer kleinen Firma mit 1 Programmierer beraten. Sein Code war ein Rattennest und funktionierte außerdem nicht gut. Ich überzeugte ihn, zu versuchen, einen kooperativen Switcher zu schreiben (ich wurde nicht beauftragt, das für ihn zu tun). Also setzte ich mich hin und erklärte ihm, wie er das angehen könnte. Er hatte so etwas NIE zuvor getan. Ich glaube, nicht mehr als zwei Tage später rannte er in mein Büro. Er hatte es geschafft!! Und es funktionierte. Ich habe es mir angeschaut und war zufrieden! Die Software dieser Firma wurde bald darauf viel besser. Er hatte ASM-Erfahrung, also war es einfach zu unterrichten. Er war den Ideen einfach nicht ausgesetzt gewesen, das ist alles.

Die meisten Mikrocontroller sind dafür nicht wirklich geeignet. Sie wollen mehr von einem Allzweck-Mikroprozessor.

Mit einigen High-End-32-Bit-Mikros wie ARM und PIC32 können Sie möglicherweise ein vernünftiges Betriebssystem schreiben. Für ein allgemeines Betriebssystem benötigen Sie einen Prozessor, der Benutzercode so ausführen kann, dass der Benutzercode dem System keinen Schaden zufügen kann. Dies wird normalerweise durch einen privilegierten Modus und einen Benutzermodus erreicht. Die meisten Micros haben das nicht.

Ein weiteres Problem besteht darin, dass die meisten Mikros nur aus dem ROM heraus ausgeführt werden, nicht aus dem RAM. Das macht das Laden von beliebigem Benutzercode und das anschließende Ausführen schwierig oder unmöglich. Der gesamte RAM- und ROM-Speicherplatz ist normalerweise ebenfalls festgelegt und kann und wird nicht extern erweitert. Das verhindert kein Betriebssystem, macht aber die Anwendungen, die das System ausführen kann, ziemlich begrenzt. Die meisten Mikros haben keine MMUs, die reale auf logische Speicheradressen umwandeln können und beim Versuch, auf bestimmten Speicher zuzugreifen, Traps verursachen. Das macht das Paging schwierig oder unmöglich.

Schau dir mal den PIC32 an. Es kann vom RAM ausgeführt werden und verfügt mindestens über eine grundlegende MMU. Einige Varianten haben genug Speicher, um nützliche Programme im Benutzermodus zu ermöglichen.

Was die Details der Verknüpfung betrifft, müssen Sie wirklich die Handbücher für die Tools lesen. Es gibt keinen Ersatz dafür, zu verstehen, was der Linker tut und wie man ihn steuert. Sie müssen RTFM. Es gibt keine Abkürzung.

* RTFM = Lesen Sie das feine Handbuch
Olin Lathrop, meine Zielplattform ist das TI TM4C1294 Connected Launchpad (EK-TM4C1294XL), das auf ARM Cortex-M4F (32 Bit) basiert. Die Hardware bietet eine gute Unterstützung für ein Betriebssystem.

Ich vermute, dass es möglich sein könnte, ein Echtzeitbetriebssystem ohne spezielle Kenntnisse oder Anpassungen des Linker-Direktivenskripts und des C-Laufzeit-Startcodes zu implementieren.

Das Linkerdirektivenskript identifiziert Regionen und Abschnitte des Speichers. Wenn der Toolanbieter eine standardmäßige Linker-Skriptdatei bereitstellt, die für Ihre Bare-Metal-Anwendungen funktioniert, könnte sie auch für Ihre RTOS-basierte Anwendung funktionieren. Sie würden das Linkerskript anpassen, wenn Ihre Anwendung spezielle Speicheranforderungen hat. Beispiele umfassen eine Anwendung für ein kundenspezifisches Board mit externen Speichern, eine Anwendung mit spezialisierten Speicherabschnitten, die eine spezialisierte Initialisierung erfordern, und eine Anwendung, die in Verbindung mit einem Bootloader-Programm arbeitet. Ich kann mir keinen Grund vorstellen, warum die Verwendung eines RTOS Sie dazu zwingen würde , das Linker-Skript anzupassen. Das Wissen darüber, wie das Linker-Skript angepasst werden kann, ist wichtig für jede spezialisierte Anwendung, die dies erfordert, unabhängig davon, ob ein RTOS verwendet wird.

Der Startcode initialisiert die C-Laufzeitumgebung für Ihre Anwendung. Wenn der Standardstartcode des Toolanbieters für Ihre Bare-Metal-Anwendung gut genug ist, dann ist er möglicherweise auch für Ihre RTOS-basierte Anwendung gut genug. Der Startcode kopiert Initialisierungswerte vom ROM in den RAM und löscht nicht initialisierte Daten. (Für C++ ruft es die Konstruktoren für statisch zugewiesene Objekte auf.) Sie würden den Startcode anpassen, wenn Sie spezielle Speicherabschnitte haben, die eine spezielle Initialisierung erfordern. Sie können auch den Startcode anpassen, wenn Ihr Board über eine spezielle Hardwareinitialisierung verfügt, die vor der Initialisierung der Laufzeitumgebung (oder vor main()) durchgeführt werden sollte. Ich kann mir keinen Grund vorstellen, warum die Verwendung eines RTOS erzwingen würdeSie können den Startcode anpassen. Das RTOS kann von initialisiert werden main()(nachdem der Startcode vollständig ist). Das Wissen darüber, wie der Startcode angepasst werden kann, ist wichtig für jede spezialisierte Anwendung, die dies erfordert, unabhängig davon, ob ein RTOS verwendet wird.

Sie werden jedoch alles über die CPU-Register wissen wollen und wie Interrupts von der CPU gehandhabt werden. Sie müssen wissen, welche CPU-Register gespeichert/wiederhergestellt werden sollen, um den Kontext zu wechseln. Und Interrupts sind eine erstklassige Gelegenheit, wenn ein RTOS-Kontextwechsel auftritt.

Sehen Sie sich neben der FreeRTOS-Dokumentation auch die uC/OS-III-Bücher von Micrium an.