Autor Thema: Ein Drahthaufen und sein Betriebssystem... Fragen zum Aufbau des OS.  (Gelesen 22724 mal)

Jonathan

  • Beiträge: 22
    • Profil anzeigen
Gespeichert
Hallo Lowlevel-Community ;)

wie ihr seht, ist dies mein erster Post hier. Ich bin 16 Jahre alt und beschäftige mich bereits ca. 9 Jahre lang mit der Programmierung und seit ca. 3 Jahren mit Hardware-Design, was mich zu meinem neuesten Projekt brachte: Ein selbstgebautes Motherboard.

Das Board an sich ist schon ziemlich funktionsfähig. Es ist komplett auf Lochraster aufgebaut und wurde rund um eine alte Z80-CPU in CMOS-Ausführung gestrickt. Es beinhaltet momentan die CPU (getaktet auf 17 MHz), 1 MB RAM (bis zu 4 MB erweiterbar - wenn ich genug Geld für so viel schnellen SRAM hätte), ein rudimentäres BIOS, eine Bank-Switching-Unit (ziemlich das selbe wie Paging beim x86) um den Speicher auf die 64 kB Adressraum des Z80 abbilden zu können, einen I/O-Controller für ein PCI-ähnliches Steckkarteninterface und einen Taktgeber am nichtmaskierbaren Interrupt des Z80. Über die Bank-Switching-Unit werden die 64kB Adressraum des Z80 in 4 gleich große Banks zu je 16kB unterteilt, in die dann jeweils eine der 256 möglichen 16kB-Speicherseiten des großen RAMs eingeblendet werden können. Die unterste Bank ist fest mit Seite 0 des RAMs verbunden und dient als Kernel-Page. Das ist gut für Multitasking, da so jeder Prozess unabhängig von den anderen an der selben virtuellen Adresse mit separatem Speicher laufen kann, wie auch im x86-PC. Erweiterungskarten können über das I/O-Interface außerdem per DMA auf den Speicher zugreifen.

Das BIOS lädt beim Systemstart nun die unterste 16kB-Page (Kernel-Page) mit dem Inhalt eines ROMs und übergibt dann die Ausführung an die CPU. Das leider noch fest ins BIOS geflashte Betriebssystem der Kiste macht momentan folgendes: Es initialisiert zunächst die 3kB große Task Table (256 Tasks mit je 12 Byte in der TT) und trägt dann einen rudimentären Kernel Task in diese ein (erzeugt einen Stack und einen Eintrag in der TT). Die TT besteht für jeden Task aus Flags, Stackpointer, Creator PID, Paging-Konfiguration und Prozess-Basispage (für Exportfunktionen). 3 Bytes sind pro Eintrag noch frei.

Dann aktiviert das OS das Task Switching (Timer-Int am NMI) und begibt sich in eine Endlosschleife. Der NMI reißt die CPU dann aus der Schleife raus und gibt dem ersten (und einzigen) Task die Kontrolle über die CPU. Dieser Task sucht dann zunächst das I/O-Interface nach einer RS232-Karte ab und nutzt deren Port als Debug-Ausgabe. Dann prüft es, wie viel RAM vorhanden ist und vermerkt dies im Kernelspeicher. Schließlich listet es noch alle am I/O-Interface angeschlossenen Geräte auf.

Systemcalls in den Kernel funktionieren mit Calls an Adresse 0x0010 im Speicher, wobei man im 16-Bit-Register IY eine Funktionsnummer übergibt. Die restlichen Register können Parameter und Rückgabewerte der aufgerufenen Funktion enthalten. Multitasking funktioniert auch bereits, was ich mit dem klassischen "ABABAB"-Test geprüft habe. Auch die physikalische Speicherverwaltung (zuweisen und freigeben von Pages) funktioniert. Es existiert auch schon eine Funktion zum Erzeugen eines Prozesses aus Speicherseiten.


Was jetzt noch fehlt, ist eine gute Architektur für Interprozesskommunikation (am besten über RPCs oder Messages) und eine passende Treiberarchitektur. Das OS ist als Mikrokernel geplant. Darauf basierend wäre dann dringend ein Festplatten- und Dateisystemtreiber, sowie ein PS/2-Treiber und zum Schluss ein VGA-Treiber nötig. Die passenden Erweiterungskarten dazu möchten natürlich auch noch entworfen und gelötet werden, aber das ist kein großes Problem - es dauert nur relativ lange.


Für die RPCs habe ich mir folgenden Ansatz ausgedacht: Der aufrufende Prozess macht zunächst einen Call in eine Funktion "_KRNL_RPC_caller" in der Kernel-Page. Dabei übergibt er die Ziel-PID (8 Bit) und die Ziel-Funktionsnummer (16 Bit). Die restlichen Register können Parameter enthalten. Der Kernel pusht dann die aktuelle Speicherkonfiguration auf den Stack des aufrufenden Prozesses und stellt dann eine besondere Speicherkonfiguration her: Er verschiebt die Stack-Page des aufrufenden Prozesses auf Bank 3 und biegt den Stackpointer entsprechend um. Die Export-Page des aufgerufenen Prozesses wird dann auf Bank 1 gelegt. Bank 2 bleibt für beliebige Speicherzugriffe zur Verfügung. Dann macht der Kernel einen Call auf Adresse 16 von Bank 1, also auf 0x4010. Dort wartet ein ähnlicher Mechanismus wie in der Kernel-Page auf Aufrufe und ruft dann die passende Funktion zur übergebenen Funktionsnummer auf. Schließlich stellt der Kernel die ursprüngliche Speicherkonfiguration wieder her, biegt den Stackpointer zurück und kehrt in den aufrufenden Prozess zurück, in der Hoffnung dass durch das ganze Page-Schubsen nichts durch einander geraten ist. Basierend auf RPCs könnte man dann auch ein Message-System basteln.

Für die Treiber bin ich leider selbst noch ziemlich Ahnungslos. Das einzige, was mir bereits eingefallen ist, ist, dass jeder Treiber mehrere Schnittstellen registrieren können muss, da einzelne Geräte mehrere Funktionen haben können (z.B. eine RS232/PS2-Kombikarte). Dafür könnte man die Funktionsnummern für RPCs in zwei Teile unterteilen: Das High-Byte enthält die Schnittstellennummer und das Low-Byte enthält die Funktionsnummer innerhalb der Schnittstelle. Schnittstellen könnten dann einen Deskriptor erhalten, der angibt, was man bei jeder Schnittstelle vor sich hat. Beispielsweise könnte Schnittstelle 1 von Prozess 3 ein Massenspeichertreiber sein. Schnittstelle 0 von allen Prozessen könnte das Messagesystem sein. Wie ich die Treiber allerdings für andere Prozesse verfügbar machen soll, weiß ich leider noch nicht. Könnt ihr mir da vielleicht etwas vorschlagen?

Wie ist eure Meinung zu meinen IPC- und Treiber-Ansätzen? Was würdet ihr besser/anders machen?


Gruß
Jonathan


P.S.: Demnächst kann ich auch mal ein Foto von meinem Motherboard machen, aber bitte nicht vor dem zentimeterhohen Drahtverhau erschrecken.  :-D

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
Wow, das klingt mal ziemlich spannend. Ich bin beeindruckt, wie weit das Projekt schon fortgeschritten ist. Auch wenn ich von sowas keine Ahnung habe, würde ich mir sicherlich die Bilder anschauen, wenn du irgendwann mal welche machst. ;)

Eine Frage die ich hätte, ist, was du mit den Banks vor hast. Das Paging bei x86 bietet ja feineres Virtual Memory als deine Banks und darüber hinaus Schutzmechanismen, die du, wenn ich dich richtig verstanden habe, nicht hast. Wenn man Virtual Memory bei IPC/RPC verwendet, dann ja, um das Kopieren von Daten zu vermeiden. Das Verschieben von größeren Datenmengen passiert bei Treibern ja nur für Nutzdaten, und weil du ja DMA hast, dann kannst du die Treiber die physischen Adressen verwenden lassen, oder nicht?

Und bei IPC zwischen Prozessen, würde ich vorschlagen, dass du das auf einem Shared Memory Interface aufbaust. Das heißt die Prozesse einigen sich darauf eine physische Page zu teilen, und bei den RPC-Aufrufen werden dann Offsets (und Längen) innerhalb dieser Page übergeben. Der RPC-Aufruf selbst würde ja ein normaler Taskwechsel sein, bei dem der Kernel ganz normal den Prozesszustand sichert (natürlich so, dass der RPC-Aufruf auch Rückgabewerte an den Aufrufer zurückgeben kann.)
Dieser Text wird unter jedem Beitrag angezeigt.

streetrunner

  • Beiträge: 67
    • Profil anzeigen
Gespeichert
Mich würde auch interessieren wie Du DMA umgesetzt hast, denn da müssen sich ja die CPU und der/die DMA-Controller den Speicherzugriff teilen.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
Hallo,

Das Board an sich ist schon ziemlich funktionsfähig. Es ist komplett auf Lochraster aufgebaut und wurde rund um eine alte Z80-CPU in CMOS-Ausführung gestrickt.
Ach, du bist doch gemein. :-) Ich hab in meiner Bastelkiste einen Z80A gefunden und wollte im Verlauf des Sommers eine ähnliche Sache auf die Beine stellen (derzeitige Planung: 4 MHz, 256 KB DRAM geteilt in 64 Banks@4K und ein AVR für Taktgenerator, Bootstrap, Console und sonstige Hardware). Darauf sollte dann (mind.) CP/M 2.2 laufen. Was für einen Z80 hast du eigentlich benutzt?

Eine funktionierende Treiberschnittstelle gibt es mit dem CP/M BIOS. Damit kannst du zwei Character- (CON, AUX) und mehrere Blockdevices abbilden. Viel mehr Hardware dürftest du sowieso nicht haben, oder du erweiterst das BIOS. Der Vorteil ist, dass du CP/M laufen lassen oder sogar einigermaßen emulieren kannst (das schenkt dir Assembler und Compiler für C-artige Sprachen und Pascal). Allerdings liegt das BIOS "oben" im Adressraum und die Blockdevices sind auf je 32 MB beschränkt.

Die unterste Bank ist fest mit Seite 0 des RAMs verbunden und dient als Kernel-Page. Das ist gut für Multitasking, ...
Damit sind deine Anwendungen auf eine Startadresse von 0x4000 und maximal 48 KB Größe beschränkt. Overlay-Systeme werden hässlich, weil du insgesamt nur 3 Pages gleichzeitig für deine Anwendung halten kannst.

Besser wäre, du kopierst bei jedem Programmstart die Interrupt-Handler und einen minimalen Bankswitcher in die untersten Bytes und linkst deine Programme z.B. an 0x100. Dann muss der Kernel während der Programmausführung nicht gemappt sein. Damit sind bis zu 63 KB pro Anwendung direkt möglich (C-Compiler für CP/M machen das gerne). Du hast nur 64 Pages insgesamt im System (mit Swapping evtl. mehr), da möchtest du so wenig Pages wie möglich für Overhead verschwenden. Das heißt möglichst wenig Tasks und möglichst volle Pages. Da glänzen monolithische Kernel (und der Vorteil von Mikrokerneln mit der Sicherheit ist hinfällig, da du sowieso keine Speicherschutzmechanismen hast).

Darauf basierend wäre dann dringend ein Festplatten- und Dateisystemtreiber, sowie ein PS/2-Treiber und zum Schluss ein VGA-Treiber nötig.
Du könntest PS/2- und VGA-Treiber zu einem Konsolentreiber zusammenfassen und gemeinsam in eine Page stecken. Der Festplattentreiber müsste da eigentlich auch reinpassen. (Das wäre dann ein BIOS.)

Ein Syscall wäre einfach ein Sprung an eine bestimmte Adresse unterhalb von 0x100 (also ähnlich gelöst wie IRQ/NMI), wobei die Werte in den Registern übergeben werden können. Dieser Stub mappt BIOS und Kernel, die dann das tun, was getan werden muss und stellt danach den Task wieder her. Messages könnte man synchron implementieren, d.h. der sendende Task blockiert, bis der empfangende Task die Message verarbeitet hat.

Für die Treiber bin ich leider selbst noch ziemlich Ahnungslos. Das einzige, was mir bereits eingefallen ist, ist, dass jeder Treiber mehrere Schnittstellen registrieren können muss, da einzelne Geräte mehrere Funktionen haben können (z.B. eine RS232/PS2-Kombikarte).
Das ist Overkill. Du hast kein Hotplug-fähiges PCIe, an dem deine Hardware hängt. Das sind nur eine Handvoll I/O-Adressen, mit denen du ein paar Register ansprichst. Autokonfiguration wird von deiner Hardware nicht angeboten, also brauchst du die in der Software auch nicht vorsehen (im Gegenteil, manuelles Probing ist sogar gefährlich). Zudem ist es egal, auf welcher Karte sich deine Hardware befindet, da alle Geräte direkt am Bus hängen. (Das gilt vermutlich sogar für den RAM selbst.)

Was würdet ihr besser/anders machen?
Du hast da ein 8-Bit-System, ein Unikat, mit kräftiger Speicher- und Adressraumknappheit und statischer Konfiguration. Da brauchst du nicht mit den Waffen der mächtigen Hotplug-fähigen General-Purpose-Systeme drauf schießen. Schau dir mal ein altes Unix (System III oder so) an, wie die das gelöst hatten, aber das ist alles schon 16-Bit-Technik.

Für 8-Bit-Maschinen kenne ich nur:
- (ROM-)Monitore zum Programme laden und starten, kaum/wenig Funktionen (z.B. dem KC85 sein CAOS, Gameboy-BIOS)
- CP/M und Abkömmlinge (gibt es auch in Multitasking- und Multiuser), die als Treiberschnittstelle das oben beschriebene BIOS nutzen
- geniales Zeug:
--- UZI (Download), ein angeblich recht vollständiges unixoides System
--- UZI180 (Architekturbeschreibung), kann Bankswitching
--- Chromix (kommerzielles, für die hauseigenen Maschinen gebautes an Unix orientiertes System, aber kein Unixoid)
--- SymbOS (Webseite), ein an Windows orientiertes System, aber kein Windows :-D

Mich würde auch interessieren wie Du DMA umgesetzt hast, denn da müssen sich ja die CPU und der/die DMA-Controller den Speicherzugriff teilen.
Der Z80 hat keine Prefetch Queue, daher blockiert ein DMA-Zugriff immer die CPU. In meinem Design wird der AVR den Bus zwar übernehmen können und DMA ermöglichen, aber das werde ich nur für den Bootcode und I/O benutzen.

Gruß,
Svenska

Jonathan

  • Beiträge: 22
    • Profil anzeigen
Gespeichert
Puh... Naja, der Knackpunkt ist: Ich habe Hotplug-fähiges "PCIe"! Das kleine Systemchen besitzt einen Steckkartenbus für die gesamte Peripherie, wobei jedem Gerät 256 Bytes IO-Adressraum zugewiesen werden, deren Position vom Steckplatz abhängig ist. Auch das Paging kann ich Hardwarebedingt nicht ändern, da die Kiste ansonsten beim Speicherzugriff eine zu hohe Latenz durch den Bankswitcher (AVR mit Standardlogik) hätte. Auch bin ich hardwarebedingt auf 3 umschaltbare Pages beschränkt.

In den untersten 32 Byte des Geräte-Adresraums liegen Identifikationsdaten, über die der Kernel das Gerät erkennen und  einen dazu passenden Treiber laden kann. Diese Treiber laufen in jeweils einem eigenen Prozess und verwalten exakt ein Gerät. Dazu habe ich mir überlegt, dass jeder Prozess verschiedene Interfaces an verschiedenen Adressen zur Verfügung stellen kann, auf welche dann mit einer Standardmethode zugegriffen werden kann. Ich brauche lediglich noch eine Möglichkeit, diese Interfaces für andere Prozesse sichtbar zu machen. Dazu könnte man doch eigentlich eine Liste im Kernel benutzen, in der jedes Interface mit PID, Nummer und Typ aufgelistet wird, oder?

Die CPU ist eine Z84C0020PEC in CMOS. Mehr gibts dann, wenn ich aus der Schule zurück bin... ;)


Gruß
Jonathan

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
Willkommen, Jonathan!

Das klingt ja schon ziemlich cool, was du alles hast. Ich glaube, wir haben ein wirklich würdiges neues Mitglied gewonnen. ;)

Die unterste Bank ist fest mit Seite 0 des RAMs verbunden und dient als Kernel-Page. Das ist gut für Multitasking, da so jeder Prozess unabhängig von den anderen an der selben virtuellen Adresse mit separatem Speicher laufen kann, wie auch im x86-PC.
Wie Svenska schon sagte, ist dein (vor allem virtueller Speicher) ja doch sehr begrenzt und ich nehme an, volle 16k brauchst du nicht für deinen Mikrokernel. Hast du dir schonmal überlegt, ob du nicht vielleicht auch noch ein paar Treiber in die Page 0 legen willst? Speicherschutz hast du ja sowieso nicht, insofern brauchst du Kernel- und Usermode-Pages auch nicht strikt zu trennen. Dann hättest du Page 0 für Kernel und Treiber (die zufälligerweise auch bevorzugte RPC-Ziele sein werden, d.h. du kannst dir evtl. ein paar Bankswitches sparen, weil die Treiber sowieso immer da sind), und die restlichen drei Pages für Anwendungsprogramme.

Zitat
Es initialisiert zunächst die 3kB große Task Table (256 Tasks mit je 12 Byte in der TT) und trägt dann einen rudimentären Kernel Task in diese ein (erzeugt einen Stack und einen Eintrag in der TT). Die TT besteht für jeden Task aus Flags, Stackpointer, Creator PID, Paging-Konfiguration und Prozess-Basispage (für Exportfunktionen). 3 Bytes sind pro Eintrag noch frei.
Bist du dir sicher, dass der Kernel ein Task sein sollte? Im Moment sehe ich den großen Vorteil davon nicht.

Wenn jeder Task wenigstens eine Page braucht, dann kriegst du keine 256 Tasks in dein eines Megabyte und verschwendest wertvollen Speicher. Ich würde die Größe der Tabelle dynamisch halten, wenigstens beim Kernelstart Speicher für die mit dem verbauten Speicher maximal mögliche Taskzahl allozieren, oder besser vielleicht eine verkettete Liste von kleineren Tabellen mit 16 oder 32 Tasks.

Zitat
Auch die physikalische Speicherverwaltung (zuweisen und freigeben von Pages) funktioniert.
Bitte entschuldige die Erbsenzählerei, aber auf Deutsch heißt es "physische Speicherverwaltung", das hat nichts mit Physik zu tun. ;)

Zitat
Der Kernel pusht dann die aktuelle Speicherkonfiguration auf den Stack des aufrufenden Prozesses und stellt dann eine besondere Speicherkonfiguration her: Er verschiebt die Stack-Page des aufrufenden Prozesses auf Bank 3 und biegt den Stackpointer entsprechend um. Die Export-Page des aufgerufenen Prozesses wird dann auf Bank 1 gelegt. Bank 2 bleibt für beliebige Speicherzugriffe zur Verfügung.
Ich gehe davon aus, dass normale Code-, Export- und Stackpage in der Regel genau dieselbe Page sind, wenn das Programm mit 16k auskommt? Für Treiber einfacher Hardware dürfte das oft der Fall sein.

Zitat
Dann macht der Kernel einen Call auf Adresse 16 von Bank 1, also auf 0x4010. Dort wartet ein ähnlicher Mechanismus wie in der Kernel-Page auf Aufrufe und ruft dann die passende Funktion zur übergebenen Funktionsnummer auf. Schließlich stellt der Kernel die ursprüngliche Speicherkonfiguration wieder her, biegt den Stackpointer zurück und kehrt in den aufrufenden Prozess zurück, in der Hoffnung dass durch das ganze Page-Schubsen nichts durch einander geraten ist. Basierend auf RPCs könnte man dann auch ein Message-System basteln.
Was hast du vor zu machen, wenn ein Treiber gerade einen RPC verarbeitet und ein anderer Prozess auch einen RPC auf den Treiber machen will?

In tyndur habe ich die Erfahrung gemacht, dass beide Optionen Probleme mit sich bringen: Entweder man blockiert einfach und lässt den Aufrufer warten, bis der Treiber fertig ist. Irgendwie schafft man es immer, Rekursion reinzubringen (Treiber A benutzt eine Funktion von Treiber B und der benötigt wieder Treiber A - typisches Beispiel für A ist das VFS) und das ist dann eben ein Deadlock. Oder man unterbricht den RPC und behandelt den neuen zuerst, das macht die Bedingungen für einen Deadlock dann ein bisschen komplizierter und kann zu interessanten Effekten führen, weil Dinge in einer unerwarteten Reihenfolge bearbeitet werden (unser Tastaturtreiber hat mal die Tastendrücke rückwärts abgearbeitet, wenn man zu schnell tippt...).

Egal für welche IPC-Möglichkeit du dich genau entscheidest, sind das vermutlich die entscheidenden Fragen, die du dir stellen musst. Den Ringpuffer von Jidder mag ich zum Beispiel auch, aber das ist aus diesem Blickwinkel nicht viel anders als RPC zu blockieren, wenn du nicht anfangen willst, mehrere Ringpuffereinträge parallel abzuarbeiten.

Zitat
Für die Treiber bin ich leider selbst noch ziemlich Ahnungslos. Das einzige, was mir bereits eingefallen ist, ist, dass jeder Treiber mehrere Schnittstellen registrieren können muss, da einzelne Geräte mehrere Funktionen haben können (z.B. eine RS232/PS2-Kombikarte).
Bist du dir sicher, dass die Hardwareschnittstelle so eng gekoppelt ist, dass du dafür einen gemeinsamen Treiber braucht und nicht zwei getrennte Treiber laufen lassen kannst? Ein PCI-Gerät würde das sauber nach Funktionen trennen, so dass man das auf zwei Treiber aufteilen kann, und ein USB-Gerät hätte zwei Interfaces, für die dasselbe gilt.

Zitat
Wie ich die Treiber allerdings für andere Prozesse verfügbar machen soll, weiß ich leider noch nicht. Könnt ihr mir da vielleicht etwas vorschlagen?
Du brauchst irgendeine zentrale Stelle, die eine Zuordnung von Treiber (nach Namen oder einer fest definierten ID in der Art 1 = VFS, 2 = Tastatur, 3 = ...) zu PID vornimmt. Ich würde dafür den Kernel vorschlagen, so dass man über einen Syscall an die Information kommt.

Zitat
P.S.: Demnächst kann ich auch mal ein Foto von meinem Motherboard machen, aber bitte nicht vor dem zentimeterhohen Drahtverhau erschrecken.  :-D
Ja, bitte! Und der zentimeterhohe Drahtverhau macht die Sache sicher nur noch beeindruckender. ;)
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Jonathan

  • Beiträge: 22
    • Profil anzeigen
Gespeichert
Das klingt ja schon ziemlich cool, was du alles hast. Ich glaube, wir haben ein wirklich würdiges neues Mitglied gewonnen. ;)

Ähm, danke  :-o
(...Schleimer!!!  :-D )

Zitat
Wie Svenska schon sagte, ist dein (vor allem virtueller Speicher) ja doch sehr begrenzt und ich nehme an, volle 16k brauchst du nicht für deinen Mikrokernel. Hast du dir schonmal überlegt, ob du nicht vielleicht auch noch ein paar Treiber in die Page 0 legen willst? Speicherschutz hast du ja sowieso nicht, insofern brauchst du Kernel- und Usermode-Pages auch nicht strikt zu trennen. Dann hättest du Page 0 für Kernel und Treiber (die zufälligerweise auch bevorzugte RPC-Ziele sein werden, d.h. du kannst dir evtl. ein paar Bankswitches sparen, weil die Treiber sowieso immer da sind), und die restlichen drei Pages für Anwendungsprogramme.

Genau, außerdem kann sich eine Anwendung auch mehr Pages reservieren und nur die einblenden, die es braucht. Die Kernel-Page werde ich dann wahrscheinlich mit Allround-Funktionen wie Multiplikation, Division, Zahlenformatierung und solchem Kram vollpacken, damit die Anwendungen weniger mit sich rumschleppen müssen und die Kernel-Page nicht so brachliegt.

Wenn jeder Task wenigstens eine Page braucht, dann kriegst du keine 256 Tasks in dein eines Megabyte und verschwendest wertvollen Speicher. Ich würde die Größe der Tabelle dynamisch halten, wenigstens beim Kernelstart Speicher für die mit dem verbauten Speicher maximal mögliche Taskzahl allozieren, oder besser vielleicht eine verkettete Liste von kleineren Tabellen mit 16 oder 32 Tasks.

Das stimmt nicht ganz. Es können sich auch mehrere Tasks eine Page teilen, was dann Multithreading ergibt. Natürlich mit jeweils eigenem Stack, aber das ist ja kein Problem. Man kann auch z.B. die Kernelpage in alle Banks einblenden, womit man effektiv keine Page hat. Dann muss der Code halt in der Kernel-Page mit ausgeführt werden, wie es bereits beim Kernel Task passiert.

Zitat
Bitte entschuldige die Erbsenzählerei, aber auf Deutsch heißt es "physische Speicherverwaltung", das hat nichts mit Physik zu tun. ;)

Pffff ;)

Zitat
Ich gehe davon aus, dass normale Code-, Export- und Stackpage in der Regel genau dieselbe Page sind, wenn das Programm mit 16k auskommt? Für Treiber einfacher Hardware dürfte das oft der Fall sein.

Genau, dann wird diese Page einfach mehrmals eingeblendet. Kann man ja machen.

Zitat
Was hast du vor zu machen, wenn ein Treiber gerade einen RPC verarbeitet und ein anderer Prozess auch einen RPC auf den Treiber machen will?

Entweder ich blockiere per _KRNL_start_atomic (böööse in präemptivem Multitasking, ich weiß, brauchte ich jedoch an kritischen Stellen), ooooder ich arbeite mit Messages. Ich glaube, Messages wären hier die bessere Form der IPC.

Zitat
In tyndur habe ich die Erfahrung gemacht, dass beide Optionen Probleme mit sich bringen: Entweder man blockiert einfach und lässt den Aufrufer warten, bis der Treiber fertig ist. Irgendwie schafft man es immer, Rekursion reinzubringen (Treiber A benutzt eine Funktion von Treiber B und der benötigt wieder Treiber A - typisches Beispiel für A ist das VFS) und das ist dann eben ein Deadlock. Oder man unterbricht den RPC und behandelt den neuen zuerst, das macht die Bedingungen für einen Deadlock dann ein bisschen komplizierter und kann zu interessanten Effekten führen, weil Dinge in einer unerwarteten Reihenfolge bearbeitet werden (unser Tastaturtreiber hat mal die Tastendrücke rückwärts abgearbeitet, wenn man zu schnell tippt...).

Puh... Das hängt dann aber mehr mit der geschickten Programmierung der Treiber zusammen, als mit dem grundlegenden Konzept von IPC und Treiberschnittstellen. Vielleicht könnte man in den Treibern eine Art "Job Queuing" machen - eine Komponente schickt eine Message mit einem Job an einen Treiber und sobald diese an der Reihe ist, wird sie bearbeitet und eine weitere Message verschickt. Benötigt man dann innerhalb des Treibers ein Ergebnis eines anderen Treibers (z.B. Dateisystemtreiber fragt Festplattentreiber nach nem Datenblock), könnte man den Job zurück in die Warteschlange einreihen und dann fortsetzen, wenn der angefragte Treiber fertig ist. "Jobs" könnten einfache Messages in der ganz normalen Message Queue sein. Ich glaube, so mach ich das, wenn ihr nicht gerade gravierende Einwände dagegen habt.

Zitat
Egal für welche IPC-Möglichkeit du dich genau entscheidest, sind das vermutlich die entscheidenden Fragen, die du dir stellen musst. Den Ringpuffer von Jidder mag ich zum Beispiel auch, aber das ist aus diesem Blickwinkel nicht viel anders als RPC zu blockieren, wenn du nicht anfangen willst, mehrere Ringpuffereinträge parallel abzuarbeiten.

Man könnte es doch so machen: Eine Message im Puffer, die an der Reihe wäre, aber nicht abgearbeitet werden kann (z.B. warten auf anderen Treiber), wird einfach wieder ans Ende des Puffers geschrieben.

Zitat
Du brauchst irgendeine zentrale Stelle, die eine Zuordnung von Treiber (nach Namen oder einer fest definierten ID in der Art 1 = VFS, 2 = Tastatur, 3 = ...) zu PID vornimmt. Ich würde dafür den Kernel vorschlagen, so dass man über einen Syscall an die Information kommt.

Und da jeder Treiber mehrere Interfaces zur Verfügung stellen kann (könnte), würde ich dort am besten einfach die Interfaces auflisten. Dann steht in dieser Tabelle z.B.:

00: 02 01 05 <--- PID 2, Interface 1 ist Typ 5 (z.B. PS/2-Interface)
01: 02 02 04 <--- PID 2, Interface 2 ist Typ 4 (z.B. RS232-Interface)
02: 06 01 0F <--- PID 6, Interface 1 ist Typ 15 (z.B. Grafikbibliothek)
...

Somit würde die Trennung der einzelnen Gerätefunktionen in deren Treibern erfolgen und für jedes Gerät wäre exakt ein Treiber zuständig. Braucht man irgendeine Funktion, sucht man sich einfach das passende Interface raus. Auch Softwarebibliotheken (aka DLLs in Windows) könnte man so sehr einfach und vorallem Platzsparend realisieren, zusammen mit RPCs.

Zitat
Ja, bitte! Und der zentimeterhohe Drahtverhau macht die Sache sicher nur noch beeindruckender. ;)

Für Hirnverknotungen hafte ich nicht :P
http://s23.postimg.org/xscwc7957/PICT0160.jpg
http://s22.postimg.org/qcf25v469/PICT0161.jpg

Oberseite der Platine: Links mittig sitzt die Z80-CPU (Z84C0020PEC). Diese wird mit einem Takt von 17 MHz aus dem ATTiny24 darunter versorgt. Wahlweise kann man mit dem Jumper über dem 1 MHz Quarzoszillator auf 1 MHz Taktung von diesem oder auf 4 kHz Taktung aus dem BIOS umschalten. Das BIOS ist der Chip rechts neben der CPU (ist ein ATMega32), steht sogar drauf. Darüber befindet sich das 1 MB RAM-Modul, welches aus 2x 512 kB 50ns-SRAM zusammengesetzt ist. Links oben beim RAM befindet sich die Spannungsregelung (12V über das steckbare Modul, 5V über einen Linearbrater mit Kühlkörper). Am linken Rand der Platine befinden sich einige Logik-ICs zur Timing-Steuerung für I/O-Zugriffe, welche auch einen Timeout bei nicht reagierender Hardware beinhaltet. Auf der rechten, später angebauten Lochrasterplatine ist die Memory-Banking-Logik... Die ICs nummeriert von 0 bis 3 schalten bei einem Zugriff die Nummer der Page in der entsprechenden Bank auf den Bus durch (sind 74HCT244 Bustreiber), die 4 ICs dazwischen wählen den passenden Bustreiber aus (je nach Adressbereich eine andere Bank) und der große Chip dazwischen (ein ATMega8515) nimmt Bank-Switch-Anforderungen von der CPU entgegen und füttert die 74HCT244 mit passenden Page-Nummern. Der Eingang des mit 0 beschrifteten 74HCT244 ist einfach auf 0V geschaltet - ich hatte keinen Port mehr am ATMega8515 frei und brauchte sowieso eine Kernel-Page. Mit einem kleinen SRAM anstatt dieser Riesen-Logik hätte ich zwar kleinere Pages hinbekommen, jedoch ließ sich einfach kein schnelles SRAM (<15ns) auftreiben, wodurch ich leider bei 16kB-Pages bleiben musste. Unten am Motherboard sind dann die PCIe-artigen Steckplätze für die Peripherie. Momentan steckt dort nur meine glorreiche RS232-PS/2-Kombikarte drin. Je nach Adresse wird dort über den 74LS154 (4-zu-16-Dekoder) die IORQ-Leitung eines der maximal 15 Steckplätze aktiviert. Momentan sind nur 2 davon verdrahtet, was aber später nicht mehr ausreichen wird.


Na dann programmiere ich mal meine Messages.


Gruß
Jonathan

streetrunner

  • Beiträge: 67
    • Profil anzeigen
Gespeichert
Meinen Respekt dass das alles funktioniert. Das erinnert mich irgendwie an meinen letzten Besuch beim Italiener  :-D.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
Ähm, danke  :-o
(...Schleimer!!!  :-D )
Ich glaube nicht, dass ich es nötig hätte, mich bei dir einzuschleimen. Du kannst das ruhig als ehrliche Einschätzung ansehen. ;)

Zitat
Genau, außerdem kann sich eine Anwendung auch mehr Pages reservieren und nur die einblenden, die es braucht. Die Kernel-Page werde ich dann wahrscheinlich mit Allround-Funktionen wie Multiplikation, Division, Zahlenformatierung und solchem Kram vollpacken, damit die Anwendungen weniger mit sich rumschleppen müssen und die Kernel-Page nicht so brachliegt.
Also in anderen Worten Shared Libraries, allen voran die libc. Klingt prinzipiell nach einer guten Idee. Wobei Kernel + Treiber + libc dann in 16k vielleicht doch eng werden, dass musst du dann noch sehen.

Zitat
Das stimmt nicht ganz. Es können sich auch mehrere Tasks eine Page teilen, was dann Multithreading ergibt. Natürlich mit jeweils eigenem Stack, aber das ist ja kein Problem.
Hm, stimmt, an Multithreading hatte ich nicht gedacht. Dann könnten 256 Einträge auch mal zu wenig sein. Das spricht meines Erachtens sehr für eine verkettete Liste, damit man nicht willkürlich begrenzt und gleichzeitig nicht zu viel Speicher mit einer statischen Tabelle verschenkt, wenn es meistens eben doch nur wenige Tasks sind.

Zitat
Puh... Das hängt dann aber mehr mit der geschickten Programmierung der Treiber zusammen, als mit dem grundlegenden Konzept von IPC und Treiberschnittstellen.
Jein. Die IPC-Schnittstelle schreibt letztendlich vor, was die Treiber in dieser Hinsicht tun müssen, damit sie funktionieren. Die Art der IPC ist aus der Sicht des Treibers nur ein Mittel zum Zweck, nicht das Ziel, das der Treiber vorgeben würde. Der Treiber muss mit dem zurechtkommen, was das OS zur Verfügung stellt.

Zitat
Vielleicht könnte man in den Treibern eine Art "Job Queuing" machen - eine Komponente schickt eine Message mit einem Job an einen Treiber und sobald diese an der Reihe ist, wird sie bearbeitet und eine weitere Message verschickt. Benötigt man dann innerhalb des Treibers ein Ergebnis eines anderen Treibers (z.B. Dateisystemtreiber fragt Festplattentreiber nach nem Datenblock), könnte man den Job zurück in die Warteschlange einreihen und dann fortsetzen, wenn der angefragte Treiber fertig ist. "Jobs" könnten einfache Messages in der ganz normalen Message Queue sein. Ich glaube, so mach ich das, wenn ihr nicht gerade gravierende Einwände dagegen habt.
Kann dir die Queue mit Anfragen volllaufen, so dass die Antwort des Festplattentreibers am Ende nicht mehr reinpasst?

Zitat
Man könnte es doch so machen: Eine Message im Puffer, die an der Reihe wäre, aber nicht abgearbeitet werden kann (z.B. warten auf anderen Treiber), wird einfach wieder ans Ende des Puffers geschrieben.
Ich glaube, das Prinzip ist okay, aber ganz so einfach ist es im Detail nicht: Du musst noch einen gewissen Zustand halten, weil du die Anfrage ja halb abgearbeitet hast und beim nächsten Mal an der richtigen Stelle wieder fortsetzen musst. Dieser Zustand sollte wahrscheinlich nicht direkt im Shared Memory sein, sonst müsste der Aufrufer einen gültigen Anfangszustand kennen und mitgeben.

Zitat
Und da jeder Treiber mehrere Interfaces zur Verfügung stellen kann (könnte), würde ich dort am besten einfach die Interfaces auflisten. Dann steht in dieser Tabelle z.B.:

00: 02 01 05 <--- PID 2, Interface 1 ist Typ 5 (z.B. PS/2-Interface)
01: 02 02 04 <--- PID 2, Interface 2 ist Typ 4 (z.B. RS232-Interface)
02: 06 01 0F <--- PID 6, Interface 1 ist Typ 15 (z.B. Grafikbibliothek)
...

Somit würde die Trennung der einzelnen Gerätefunktionen in deren Treibern erfolgen und für jedes Gerät wäre exakt ein Treiber zuständig. Braucht man irgendeine Funktion, sucht man sich einfach das passende Interface raus. Auch Softwarebibliotheken (aka DLLs in Windows) könnte man so sehr einfach und vorallem Platzsparend realisieren, zusammen mit RPCs.
Ja, der Teil mit den Hardwaretreibern könnte funktionieren.

Das mit den Shared Libs wird ein bisschen schwieriger, sonst müsste das OS alle theoretisch möglichen Bibliotheken mit einer festen Schnittstelle vordefinieren, damit man diese Bibliothek implementieren kann. Wahrscheinlich würdest du da dann eher nur einen Typ "Bibliothek" wollen und ein paar vordefinierte RPC-IDs um Funktionen aufzulisten usw. (im Prinzip die Funktionalität von dlfcn.h). Wobei ich mir nicht sicher bin, ob es eine gute Idee ist, Shared Libraries über RPC abzuhandeln statt sie richtig zu linken...

Zitat
Für Hirnverknotungen hafte ich nicht :P
http://s23.postimg.org/xscwc7957/PICT0160.jpg
http://s22.postimg.org/qcf25v469/PICT0161.jpg
Awesome. :)

Und der Vergleich mit dem Italiener liegt wirklich nahe, ein Wunder, dass du da noch durchblickst. :-D
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
Hallo,

Naja, der Knackpunkt ist: Ich habe Hotplug-fähiges "PCIe"! Das kleine Systemchen besitzt einen Steckkartenbus für die gesamte Peripherie, wobei jedem Gerät 256 Bytes IO-Adressraum zugewiesen werden, deren Position vom Steckplatz abhängig ist. In den untersten 32 Byte des Geräte-Adresraums liegen Identifikationsdaten, über die der Kernel das Gerät erkennen und  einen dazu passenden Treiber laden kann.
Ich würde die Logik umdrehen. Alle Treiber sind im Kernel enthalten und werden beim Systemstart aktiviert; jeder Treiber schaut sich dann alle Geräte-IDs an und ignoriert Geräte, die er nicht kennt. Nach dem Prinzip funktioniert NetBSD.

Diese Treiber laufen in jeweils einem eigenen Prozess und verwalten exakt ein Gerät.
Das heißt, dass du für jeden Treiberzugriff das gesamte Banking mehrfach umschmeißen musst. Oder du lässt die Treiber als Threads im Kernel-Prozess (und der Kernel-Page) laufen, inklusive Daten und Stack...

Genau, außerdem kann sich eine Anwendung auch mehr Pages reservieren und nur die einblenden, die es braucht.
Overlay-Systeme sind verdammt unpraktisch, weil die in Hochsprachen schwierig bis fast unmöglich zu programmieren sind. Damit landest du zwangsweise bei Assembler.

Somit würde die Trennung der einzelnen Gerätefunktionen in deren Treibern erfolgen und für jedes Gerät wäre exakt ein Treiber zuständig.
Wenn du deine Hardware so strickst, dass jedes "Gerät" exakt 64 I/O-Adressen benutzt, dann würde jede Steckkarte maximal 4 Geräte bereitstellen und deine Treiber bräuchten den I/O-Adressraum nur in 64er-Schritten nach Geräten durchsuchen. In dem Moment sparst du dir eine komplette Abstraktionsebene.

Zitat
Ja, bitte! Und der zentimeterhohe Drahtverhau macht die Sache sicher nur noch beeindruckender. ;)
Für Hirnverknotungen hafte ich nicht :P
Beeindruckend! Schade, dass man die IC-Beschriftungen nicht lesen kann. Ich würde dir übrigens Kupferlackdraht empfehlen. :-D

Mit einem kleinen SRAM anstatt dieser Riesen-Logik hätte ich zwar kleinere Pages hinbekommen, jedoch ließ sich einfach kein schnelles SRAM (<15ns) auftreiben, wodurch ich leider bei 16kB-Pages bleiben musste.
Das wäre natürlich eine Idee... ich wollte die MMU mit 2x74189 und zwei Gattern diskret aufbauen.

Ansonsten: Respekt! :mrgreen:

Wobei ich mir nicht sicher bin, ob es eine gute Idee ist, Shared Libraries über RPC abzuhandeln statt sie richtig zu linken...
Naja, für Z80-Targets sind Shared Libraries sowieso eher selten. CP/M-basierte Compiler können das nicht und ob der SDCC das hinkriegt, weiß ich nicht.

Gruß,
Svenska

Jonathan

  • Beiträge: 22
    • Profil anzeigen
Gespeichert
Ich glaube nicht, dass ich es nötig hätte, mich bei dir einzuschleimen. Du kannst das ruhig als ehrliche Einschätzung ansehen. ;)

Ich weiß ;)

Zitat
Also in anderen Worten Shared Libraries, allen voran die libc. Klingt prinzipiell nach einer guten Idee. Wobei Kernel + Treiber + libc dann in 16k vielleicht doch eng werden, dass musst du dann noch sehen.

Jo, was nicht mehr passt, muss dann halt ausgelagert werden. Aber die Grundfunktionen bekomm ich sicher rein. Bisher programmiere ich das Teil ja noch komplett in Assembler.

Zitat
Hm, stimmt, an Multithreading hatte ich nicht gedacht. Dann könnten 256 Einträge auch mal zu wenig sein. Das spricht meines Erachtens sehr für eine verkettete Liste, damit man nicht willkürlich begrenzt und gleichzeitig nicht zu viel Speicher mit einer statischen Tabelle verschenkt, wenn es meistens eben doch nur wenige Tasks sind.

Ich denke mal, dass 256 Tasks ein guter Wert sind, da ich mehr wegen den 8-Bit-Registern sowieso nur mit argen Schwierigkeiten handhaben kann.

Zitat
Jein. Die IPC-Schnittstelle schreibt letztendlich vor, was die Treiber in dieser Hinsicht tun müssen, damit sie funktionieren. Die Art der IPC ist aus der Sicht des Treibers nur ein Mittel zum Zweck, nicht das Ziel, das der Treiber vorgeben würde. Der Treiber muss mit dem zurechtkommen, was das OS zur Verfügung stellt.

Ich denke, ich programmiere zunächst RPCs, worauf ich dann ein Messagesystem aufsetze. Prozess 1 ruft das Standard-Prozess-Interface 0 von Prozess 2 auf und lässt die Funktion "receiveMessage" mit seiner Message ausführen. Prozess 2 schreibt die Message in seinen Message-Puffer und arbeitet diese dann bei seiner nächsten Ausführung ab. Ist der Puffer voll, wird Prozess 1 blockiert.

Zitat
Kann dir die Queue mit Anfragen volllaufen, so dass die Antwort des Festplattentreibers am Ende nicht mehr reinpasst?

Siehe oben.

Zitat
Ich glaube, das Prinzip ist okay, aber ganz so einfach ist es im Detail nicht: Du musst noch einen gewissen Zustand halten, weil du die Anfrage ja halb abgearbeitet hast und beim nächsten Mal an der richtigen Stelle wieder fortsetzen musst. Dieser Zustand sollte wahrscheinlich nicht direkt im Shared Memory sein, sonst müsste der Aufrufer einen gültigen Anfangszustand kennen und mitgeben.

Für die Zwischenstände könnte ich Extra-Messages erstellen. Arbeite Message-Typ 1 ab, verschicke Message-Typ 2... Arbeite Typ 2 ab, verschicke 3... Und so weiter. Funktioniert so in einem anderen Projekt schon ziemlich gut.

Zitat
Ja, der Teil mit den Hardwaretreibern könnte funktionieren.

Na dann...!  :-D

Zitat
Das mit den Shared Libs wird ein bisschen schwieriger, sonst müsste das OS alle theoretisch möglichen Bibliotheken mit einer festen Schnittstelle vordefinieren, damit man diese Bibliothek implementieren kann. Wahrscheinlich würdest du da dann eher nur einen Typ "Bibliothek" wollen und ein paar vordefinierte RPC-IDs um Funktionen aufzulisten usw. (im Prinzip die Funktionalität von dlfcn.h). Wobei ich mir nicht sicher bin, ob es eine gute Idee ist, Shared Libraries über RPC abzuhandeln statt sie richtig zu linken...

Da hast du recht. Dann würde ich es so machen, dass man einer Funktion im Library-Interface einen Funktionsstring (z.B. "LIBGR_DRAW_RECT") übergibt und dann die gewünschte Funktion ausgeführt wird, falls sie vorhanden ist. Andernfalls gibt's nen Error.

Zitat
Awesome. :)

Und der Vergleich mit dem Italiener liegt wirklich nahe, ein Wunder, dass du da noch durchblickst. :-D

:D


Hallo,

Ich würde die Logik umdrehen. Alle Treiber sind im Kernel enthalten und werden beim Systemstart aktiviert; jeder Treiber schaut sich dann alle Geräte-IDs an und ignoriert Geräte, die er nicht kennt. Nach dem Prinzip funktioniert NetBSD.

Hm... Einen Makrokernel wollte ich nicht wirklich, da meine Hardware sich auch ändern kann - ist ja alles modular. Speicher ist auch so eine Sache - ich habe bloß 16kB für meinen Kernel. Ich denke, ich bleibe bei dem Ansatz mit Messages zwischen Treiber-Interfaces.

Zitat
Das heißt, dass du für jeden Treiberzugriff das gesamte Banking mehrfach umschmeißen musst. Oder du lässt die Treiber als Threads im Kernel-Prozess (und der Kernel-Page) laufen, inklusive Daten und Stack...

Das Bank-Switching ist relativ schnell, dadurch verliere ich kaum "Performance" (dieses Wort und ein 17MHz-System passen irgendwie nicht so recht zusammen).

Zitat
Overlay-Systeme sind verdammt unpraktisch, weil die in Hochsprachen schwierig bis fast unmöglich zu programmieren sind. Damit landest du zwangsweise bei Assembler.

Das stimmt. Da ich leider durch meine Hardware nichts daran ändern kann, muss ich mir, wenn der Kernel mal läuft, sowieso einen kleinen Hochsprachen-Compiler für den ganzen Quatsch schreiben. Dieser kann das ganze Bank-Switching dann auch gleich transparent für den User machen.

Zitat
Wenn du deine Hardware so strickst, dass jedes "Gerät" exakt 64 I/O-Adressen benutzt, dann würde jede Steckkarte maximal 4 Geräte bereitstellen und deine Treiber bräuchten den I/O-Adressraum nur in 64er-Schritten nach Geräten durchsuchen. In dem Moment sparst du dir eine komplette Abstraktionsebene.

Momentan müssen sie das sogar nur in 256er-Schritten ;)
Die Adressen sind alle 256Byte-aligned. Jedes Gerät hat in den ersten 32 Bytes seines Adressraums einen Deskriptor, über den der Kernel dann beim Booten die zu ladenden Treiber bestimmt. Diesen Treibern wird dann jeweils ein Gerät zugewiesen.

Zitat
Beeindruckend! Schade, dass man die IC-Beschriftungen nicht lesen kann. Ich würde dir übrigens Kupferlackdraht empfehlen. :-D

Danke! :-D
Ja, wenn das Ding mal läuft, werde ich wahrscheinlich auch mal einen Schaltplan zur Verfügung stellen. ;)

Zitat
Das wäre natürlich eine Idee... ich wollte die MMU mit 2x74189 und zwei Gattern diskret aufbauen.

Sind die 74189 nicht auch SRAM? Oder beziehst du dich auf mein Gemurkse mit den 74244?

Zitat
Ansonsten: Respekt! :mrgreen:

Danke! :D

Zitat
Naja, für Z80-Targets sind Shared Libraries sowieso eher selten. CP/M-basierte Compiler können das nicht und ob der SDCC das hinkriegt, weiß ich nicht.

Ich muss mir sowieso für den ganzen Krempel früher oder später einen eigenen Compiler basteln. Da führt kein Weg dran vorbei. Und wenn, dann kann dieser auch gleich diese Art von Libraries unterstützen.

Gruß
Jonathan

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
Ich glaube nicht, dass ich es nötig hätte, mich bei dir einzuschleimen. Du kannst das ruhig als ehrliche Einschätzung ansehen. ;)
Ich weiß ;)
Gut. Dann müssen wir dich jetzt nur noch unter irgendeinem Vorwand ins IRC bugsieren, dass du uns nicht mehr entkommen kannst. ;)

Zitat
Ich denke mal, dass 256 Tasks ein guter Wert sind, da ich mehr wegen den 8-Bit-Registern sowieso nur mit argen Schwierigkeiten handhaben kann.
Okay, das ergibt Sinn. Wenn du am Ende irgendwo Speicher einsparen musst, kannst du auch immer noch auf ein dynamisches Modell wechseln. Ist ja nur Software.

Zitat
Ich denke, ich programmiere zunächst RPCs, worauf ich dann ein Messagesystem aufsetze. Prozess 1 ruft das Standard-Prozess-Interface 0 von Prozess 2 auf und lässt die Funktion "receiveMessage" mit seiner Message ausführen. Prozess 2 schreibt die Message in seinen Message-Puffer und arbeitet diese dann bei seiner nächsten Ausführung ab. Ist der Puffer voll, wird Prozess 1 blockiert.

Zitat
Kann dir die Queue mit Anfragen volllaufen, so dass die Antwort des Festplattentreibers am Ende nicht mehr reinpasst?

Siehe oben.
Sprich du kriegst Deadlocks? Nicht gut.

Willst du RPCs auch direkt verwenden oder nur als Infrastruktur für das Message Passing? Wenn letzteres, würde ich nämlich eher direkt Message Passing im Kernel implementieren.

Zitat
Für die Zwischenstände könnte ich Extra-Messages erstellen. Arbeite Message-Typ 1 ab, verschicke Message-Typ 2... Arbeite Typ 2 ab, verschicke 3... Und so weiter. Funktioniert so in einem anderen Projekt schon ziemlich gut.
Ist eine Möglichkeit. Der Code, um sowas zu implementieren, wird ziemlich hässlich (weil in unzählige Callbacks zerrissen), aber funktionieren wird es.

Zitat
Da hast du recht. Dann würde ich es so machen, dass man einer Funktion im Library-Interface einen Funktionsstring (z.B. "LIBGR_DRAW_RECT") übergibt und dann die gewünschte Funktion ausgeführt wird, falls sie vorhanden ist. Andernfalls gibt's nen Error.
Meinst du, die Auswirkungen auf die Perfomance sind vernachlässigbar, wenn du für jeden Bibliotheksaufruf einen RPC durchführen musst, danach einen Stringvergleich mit allen Funktionen der Bibliothek und dann erst kannst du die Implementierung aufrufen? Ich finde, das klingt relativ teuer, habe es aber natürlich nicht ausprobiert. (Also, doch, ich habe das mit den Stringvergleichen für RPC-Funktionen ausprobiert und das ganze RPC-System in tyndur ist schwerfällig und langsam. Aber Shared Libs habe ich damit nicht implementiert.)

Zitat
Das stimmt. Da ich leider durch meine Hardware nichts daran ändern kann, muss ich mir, wenn der Kernel mal läuft, sowieso einen kleinen Hochsprachen-Compiler für den ganzen Quatsch schreiben. Dieser kann das ganze Bank-Switching dann auch gleich transparent für den User machen.
Hehe, irgendwie wusste ich, dass das kommen würde. Ich hatte mir noch überlegt, ob ich Svenska gleich antworten soll, dass ich dir dann ohne weiteres auch einen Compiler zutraue, wenn wir schon dabei sind. Aber ich wollte dann den Schleimer-Verdacht doch nicht erhärten. ;)

Schreibst du absichtlich so abstrakt Hochsprachen- statt C-Compiler? Willst du auch noch eine eigene Sprache basteln?
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Jonathan

  • Beiträge: 22
    • Profil anzeigen
Gespeichert
Gut. Dann müssen wir dich jetzt nur noch unter irgendeinem Vorwand ins IRC bugsieren, dass du uns nicht mehr entkommen kannst. ;)

Ach, ihr habt ein IRC?! Da braucht ihr nichtmal einen Vorwand! :-D

Zitat
Okay, das ergibt Sinn. Wenn du am Ende irgendwo Speicher einsparen musst, kannst du auch immer noch auf ein dynamisches Modell wechseln. Ist ja nur Software.

Jo, läuft ja auch schon, der Quatsch.

Zitat
Sprich du kriegst Deadlocks? Nicht gut.

Ja, das muss ich vermeiden...

Zitat
Willst du RPCs auch direkt verwenden oder nur als Infrastruktur für das Message Passing? Wenn letzteres, würde ich nämlich eher direkt Message Passing im Kernel implementieren.

Auf die Idee bin ich auch schon gekommen - ich denke, ich mache die Messages aus Performance- und Wartungsgründen doch lieber ins Kernel. Wie würdest du das Abfragen von Messages effizient machen? Immer alle Messages zu durchsuchen ist ja Grütze. Verkettete Listen sind aufgrund von mangelnder dynamischer Speicherverwaltung allerdings auch Grütze. Und eine solche lasse ich mir nicht ins Kernel, nicht auf einem Z80. Hmmmm...

Zitat
Ist eine Möglichkeit. Der Code, um sowas zu implementieren, wird ziemlich hässlich (weil in unzählige Callbacks zerrissen), aber funktionieren wird es.

Nunja, mit etwas gescheiter Formatierung im Code wird es wohl erträglich.

Zitat
Meinst du, die Auswirkungen auf die Perfomance sind vernachlässigbar, wenn du für jeden Bibliotheksaufruf einen RPC durchführen musst, danach einen Stringvergleich mit allen Funktionen der Bibliothek und dann erst kannst du die Implementierung aufrufen? Ich finde, das klingt relativ teuer, habe es aber natürlich nicht ausprobiert. (Also, doch, ich habe das mit den Stringvergleichen für RPC-Funktionen ausprobiert und das ganze RPC-System in tyndur ist schwerfällig und langsam. Aber Shared Libs habe ich damit nicht implementiert.)

Püüüh... Ja, da muss ich mir was überlegen. Wie wär's damit: Nur einmal den Namen der Bibliothek überprüfen, und die Funktionen dann mittels Nummern ansprechen. So wie bereits im Kernel. Müsste gut gehen, oder?

Zitat
Hehe, irgendwie wusste ich, dass das kommen würde. Ich hatte mir noch überlegt, ob ich Svenska gleich antworten soll, dass ich dir dann ohne weiteres auch einen Compiler zutraue, wenn wir schon dabei sind. Aber ich wollte dann den Schleimer-Verdacht doch nicht erhärten. ;)

Schreibst du absichtlich so abstrakt Hochsprachen- statt C-Compiler? Willst du auch noch eine eigene Sprache basteln?

Nein, das ist dann vieleicht schon "etwas" zu viel. Ich will an die Programmiersprache XProfan anlehnen, in der ich den Compiler dann auch schreiben werde (ich besitze ja die Vollversion dieser Sprache).


Gruß
Jonathan

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
Auf die Idee bin ich auch schon gekommen - ich denke, ich mache die Messages aus Performance- und Wartungsgründen doch lieber ins Kernel. Wie würdest du das Abfragen von Messages effizient machen? Immer alle Messages zu durchsuchen ist ja Grütze. Verkettete Listen sind aufgrund von mangelnder dynamischer Speicherverwaltung allerdings auch Grütze. Und eine solche lasse ich mir nicht ins Kernel, nicht auf einem Z80. Hmmmm...
Du musst ja nicht unbedingt die Daten im Kernel haben, sondern nur den Mechanismus, damit man ohne RPC auskommt, wenn man eine Nachricht schicken will.

Ich bin mir nicht sicher, an was für ein Modell du gedacht hast, wenn du von Nachrichten durchsuchen redest, deswegen kann ich im Moment nicht wirklich auf die Frage antworten. Ich hätte an einen Ringpuffer pro Programm gedacht (dessen Adresse das Programm beim Kernel registriert) und außerdem noch ein Mechanismus, mit dem ein Prozess bei einer leeren Warteschlange auf Nachrichten warten kann, ohne pollen zu müssen. Die Nachricht aus dem Ringpuffer abholen kann das Programm dann wieder selbst, und ich denke, es wäre immer die erste Nachricht, so dass man nicht viel durchsuchen muss.

Aber vermutlich steckt da irgendwo ein Denkfehler drin, wie immer, wenn ich schnell irgendwas vorschlage, ohne gründlich darüber nachgedacht zu haben. ;)

Zitat
Püüüh... Ja, da muss ich mir was überlegen. Wie wär's damit: Nur einmal den Namen der Bibliothek überprüfen, und die Funktionen dann mittels Nummern ansprechen. So wie bereits im Kernel. Müsste gut gehen, oder?
Ja, das wäre das Minimum, das ich auf jeden Fall machen würde. Dann hast du immer noch den Taskswitch-Overhead für einen Bibliotheksaufruf, aber der Rest dürfte sich einigermaßen in Grenzen halten.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
Hallo,

Da hast du recht. Dann würde ich es so machen, dass man einer Funktion im Library-Interface einen Funktionsstring (z.B. "LIBGR_DRAW_RECT") übergibt und dann die gewünschte Funktion ausgeführt wird, falls sie vorhanden ist. Andernfalls gibt's nen Error.
Das heißt, du baust dir einen Syscall-Interpreter. Klingt teuer. Dann lieber eine Sprungtabelle an den Prozessanfang, die beim Programmstart ausgefüllt wird.

Hm... Einen Makrokernel wollte ich nicht wirklich, da meine Hardware sich auch ändern kann - ist ja alles modular.
Also nach hotplug-fähig sieht deine Konstruktion nicht gerade aus. :-D Insofern musst du nach dem Umbau sowieso neu starten. Die Anzahl verschiedener Geräte dürfte in der gesamten Architektur sowieso überschaubar bleiben, deswegen tun statisch eingebaute Treiber auch vom Platz her nicht wirklich weh.

Deine Kernel-Page ist im RAM, also könntest du theoretisch auch dynamisch modularisieren.

Das Bank-Switching ist relativ schnell, dadurch verliere ich kaum "Performance" (dieses Wort und ein 17MHz-System passen irgendwie nicht so recht zusammen).
Es ist aber nicht kostenlos. Je langsamer das System ist, umso eher fallen die Probleme auf. :-)

Zitat
Overlay-Systeme sind verdammt unpraktisch, weil die in Hochsprachen schwierig bis fast unmöglich zu programmieren sind. Damit landest du zwangsweise bei Assembler.
Das stimmt. Da ich leider durch meine Hardware nichts daran ändern kann, muss ich mir, wenn der Kernel mal läuft, sowieso einen kleinen Hochsprachen-Compiler für den ganzen Quatsch schreiben. Dieser kann das ganze Bank-Switching dann auch gleich transparent für den User machen.
Das geht nicht ohne ziemlichen Aufwand, sonst hätte man es früher(TM) gemacht. Compiler mögen getrennte Adressräume nicht besonders, deswegen hat man Segmentierung aufgegeben.

Zitat
Wenn du deine Hardware so strickst, dass jedes "Gerät" exakt 64 I/O-Adressen benutzt, dann würde jede Steckkarte maximal 4 Geräte bereitstellen und deine Treiber bräuchten den I/O-Adressraum nur in 64er-Schritten nach Geräten durchsuchen. In dem Moment sparst du dir eine komplette Abstraktionsebene.
Die Adressen sind alle 256Byte-aligned. Jedes Gerät hat in den ersten 32 Bytes seines Adressraums einen Deskriptor, über den der Kernel dann beim Booten die zu ladenden Treiber bestimmt. Diesen Treibern wird dann jeweils ein Gerät zugewiesen.
Du hast mich missverstanden, glaube ich. Wenn du die Steckplätze im 256er-Raster und die Geräte im 64er-Raster verlegst, dann kannst du dir die gesamte Interface-Abstraktion sparen. Deine Kombikarte in Slot 2 hätte dann ein PS/2-Gerät ab Adresse 0x200 und eine serielle Schnittstelle ab 0x240 und zwei Treiber, die nichts voneinander wissen müssen. Eine 2-Byte-ID in den Geräten reicht auch aus, damit der Kernel den passenden Treiber laden kann (vgl. ISA-PnP).

Zitat
Beeindruckend! Schade, dass man die IC-Beschriftungen nicht lesen kann. Ich würde dir übrigens Kupferlackdraht empfehlen. :-D
Ja, wenn das Ding mal läuft, werde ich wahrscheinlich auch mal einen Schaltplan zur Verfügung stellen. ;)
Wenn das mechanisch nicht zu empfindlich ist, kannst du Leiterplatten auch auf einen Flachbettscanner legen und abscannen. Die Tiefenschärfe ist meist ausreichend, damit das schöner wird als ein Foto.

Zitat
Das wäre natürlich eine Idee... ich wollte die MMU mit 2x74189 und zwei Gattern diskret aufbauen.
Sind die 74189 nicht auch SRAM? Oder beziehst du dich auf mein Gemurkse mit den 74244?
Entweder SRAM oder Flipflops, aber auf jeden Fall schnell. Schnelle 8K-SRAMs müsste ich auch noch rumliegen haben, aber die möchte ich nicht für die MMU verschwenden (99.8% ungenutzt).

Gut. Dann müssen wir dich jetzt nur noch unter irgendeinem Vorwand ins IRC bugsieren, dass du uns nicht mehr entkommen kannst. ;)
Ach, ihr habt ein IRC?! Da braucht ihr nichtmal einen Vorwand! :-D
Dann muss ich ja doch mal wieder vorbeischauen... das Log ist mir inzwischen zu, äh, niederländisch manchmal. ;-)

Verkettete Listen sind aufgrund von mangelnder dynamischer Speicherverwaltung allerdings auch Grütze. Und eine solche lasse ich mir nicht ins Kernel, nicht auf einem Z80.
Dynamische Speicherverwaltung kannst du auch in einem Array implementieren. Statt Zeigern hast du dann halt Indizes und die maximale Anzahl von Einträgen ist begrenzt.

Nein, das ist dann vieleicht schon "etwas" zu viel. Ich will an die Programmiersprache XProfan anlehnen, in der ich den Compiler dann auch schreiben werde (ich besitze ja die Vollversion dieser Sprache).
Profan gibt es noch? Mit Profan² wurde in unserer alten Regattastrecke die Technik (Zeitnahme, Anzeigetafel, ...) erledigt, unter WfW 3.11. Aber gut zu wissen, dass Selfhosting kein Thema ist.

Gruß,
Svenska
« Letzte Änderung: 17. June 2013, 18:36 von Svenska »

Jonathan

  • Beiträge: 22
    • Profil anzeigen
Gespeichert
Sooo... In den nächsten Tagen mach ich mich wohl mal daran, mein IPC- und Treibersystem zu implementieren, wenn ich Zeit finde. Eventuell komme ich mit etwas Glück auch an  etwas mehr RAM (2MB/4MB). Also nicht wundern, wenn ihr erstmal nichts mehr von mir hört ;)


Gruß
Jonathan

P.S.:Ja, (X)Profan lebt noch :D

Jonathan

  • Beiträge: 22
    • Profil anzeigen
Gespeichert
Weiter geht's: Das neue (leistungsstärkere) Netzteil ist fast fertig. Auf dem Steckbrett geht es schon, fehlt nur noch ne schöne Platine. Problem ist nämlich: Bei Anschluss einer Festplatte an mein jetziges Billignetzteil bricht jedes mal die Spannung so weit ein, dass die Platte nicht anläuft und nur vor sich hin tickert...

Danach wird's wohl erstmal ein BIOS und einen HDD-Bootloader geben. Natürlich nichts weltbewegendes, sondern einfach nur ein zweckmäßiger Minicode ala "bootfähige Steckkarten suchen und wenn vorhanden Bootloader in den RAM kopieren". Vllt noch ein kurzer RAM-Test.


Gruß
Jonathan

P.S.: Dank der Sommerferien hab ich jetzt auch richtig viel Zeit für das Ding.  8-)

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
Was willst du denn noch mehr, wenn das nicht weltbewegend genug ist? Wenn du so weit bist, kannst du ja wirklich mehr an Software als an Hardware denken. Nach meiner Definition läuft das unter ziemlich großer Erfolg. :)
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Jonathan

  • Beiträge: 22
    • Profil anzeigen
Gespeichert
Na also, das Netzteil ist fertig und funktioniert sogar. Aufgebaut ist es als zwei Step-Down-Converter mit Pulspaketmodulation, gesteuert durch einen LM2901, 74HC74 und 74HC08. Als Schalttransistoren habe ich zwei IRFZ44N mit je einer Bootstrapschaltung genommen. Den dicken 10.000µF-Kondensator im Vordergrund hab ich übrigens nur dem Einschaltstromstoß der Festplatte zu verdanken.

Closeup Netzteil:


Gesamtes Setup:


Jetzt ist nur die Frage: Zuerst das Messagesystem für IPC machen, oder einen Bootloader/BIOS schreiben?


Gruß
Jonathan

P.S.: @kevin: Danke für die Blumen  :-D  :roll:

streetrunner

  • Beiträge: 67
    • Profil anzeigen
Gespeichert
Sieht gut aus, allerdings macht mir Dein Anschluss zum Tranformator etwas Sorgen, da guckt noch etwas zu viel unisoliertes Kabel raus. Wenn jetzt das Kabel mal unglücklich abknickt, warum auch immer, gibt das schnell mal nen schönen Kurzen. Am besten nimmst Du Schrumpfschlauch, damit geht das isolieren am besten bei so was.

Und sonst abermals meinen Respekt für das was Du da auf die Beine stellst.

 

Einloggen