Beiträge anzeigen

Diese Sektion erlaubt es dir alle Beiträge dieses Mitglieds zu sehen. Beachte, dass du nur solche Beiträge sehen kannst, zu denen du auch Zugriffsrechte hast.


Nachrichten - Jonathan

Seiten: [1] 2
1
Einen halben Kilometer Draht und unzählige Lochrasterplatinen später: Das Projekt ist soweit abgeschlossen und vollständig lauffähig!

Kleiner Überblick über die Hardware:
  • Z80-CPU (CMOS) mit 4MHz
  • 1MB RAM (banked), maximal 4MB, 16kB-Pages
  • 16kB Boot-ROM für den Kernel
  • POST
  • Programmierbarer Interruptcontroller, programmierbarer Timer
  • Steckplätze für 4 Erweiterungsmodule (erweiterbar auf 252 Plätze...)
  • ROM-Modul als Speicherort für ein ROFS (32kB)
  • RS232-Interface
  • VGA-Ausgang (80x60 Textmodus, Hardwarescrolling)
  • PS/2-Tastaturcontroller (umfunktionierter x86-KBC)

Und die Software:
  • Modularer monolithischer Eigenbau-Kernel (C/Z80-ASM, Toolchain: SDCC)
  • POSIX-ähnliches VFS
  • Nachladen von Treibern als Kernelmodule (zur Laufzeit)
  • Mounten von Treibern/Geräten im VFS
  • Bus-Scan und automatisches Laden der benötigten Treiber
  • Laden und Ausführen von Executables vom VFS
  • Präemptives Multitasking
  • Minimalistische Shell (cd, ls, more, exit, Programme ausführen)
  • BASIC-Interpreter/-Editor
  • Ein paar weitere kleine Anwendungsprogramme

Ein beschreibbares Dateisystem wäre aber auch noch ganz nett... Und vielleicht ein paar mehr nützliche Programme, etc - da werde ich garantiert noch weiter dran basteln  :-D

Vielleicht wäre es auch mal Zeit für eine Wiki-Seite, wer weiß?


Viele Grüße
Jonathan
2
Inzwischen ist das VFS so ziemlich fertig, das Ding lädt seine Treiber ohne zu meckern und auch ein bisschen Multitasking hab ich schon. Nur yield() wäre noch ganz nett.

Man kann sogar schon mit einer kleinen Shell sprechen und Dinge auf dem Dateisystem tun:


(Ja, ich weiß, "print" tut auf Unix eigentlich was anderes. Aber ein vollständiges "cat" wollte ich nicht, Redirection kann meine Shell noch nicht und ich brauchte irgendwie halt nen Namen.)

Läuft zwar alles noch über die serielle Schnittstelle, aber mit einem Tastatur- und Bildschirmtreiber wird sich das schnell ändern.


Viele Grüße
Jonathan
3
Mal den Thread entstauben...

Inzwischen wurde mehrmals der ganze Kernel über den Haufen geworfen und neu gemacht. Wieso "Everything is a file" so praktisch ist und wieso ein gutes VFS so wichtig ist, habe ich inzwischen auch gelernt.  :-D

Deshalb gibt's jetzt anstatt dem schwachsinnigen Mikrokernel einen modularen Monolithen mit anständigem VFS.




Viele Grüße
Jonathan
4
Und weiter geht's...

Houston, wir haben einen Interruptcontroller:
https://www.dropbox.com/s/n59lf1t7f79w4fj/IMG_20140817_173718.jpg

Besteht aus 6 Chips (ATMega8515, 2x 74HCT563, 74HCT74, 74HCT32) und besitzt folgende Features:
- 8 Interrupts, pro angeschlossenem Gerät einer
- Priorisierung (fest vorgegeben)
- 300kHz max. Interruptfrequenz
- Integrierter programmierbarer Timer (40Hz bis 40kHz, höchste Interruptpriorität)
- Software Reset / Interrupt disable
- Config-Register zum Steuern des Interruptcontrollers / Timers

Und jetzt bastel ich mir als nächstes die serielle Schnittstelle ans System  8-)


Gruß
Jonathan
5
Klar, kann ich gern tun:

Inzwischen ist die Grafikkarte fertig und voll funktionsfähig. Sie unterstützt folgende Features:
- 640x480 VGA-Auflösung mit 2 Farben (S/W)
- 80x60 Textmodus (80 Spalten, 60 Zeilen)
- Codepage 437 aus einem ROM, später auch umschaltbare Codepages (bis zu 32 CPs)
- 8x8 Pixel pro Zeichen, DOS-Font
- 32kB VRAM
- 80x256 Textpuffer, unterstützt Hardware-Scrolling (es ist ein beliebiger 60 Textzeilen langer Bereich auswählbar, der dann auf den Bildschirm ausgegeben wird)
- Random Access, die CPU muss nicht während bestimmter Zeitpunkte (z.B. Sync) auf den VRAM zugreifen, sondern kann jederzeit unbeschränkt auf dem VRAM rumschreiben, ohne Bildstörungen zu verursachen. Dabei wird kein Dual-Port-RAM verwendet.


Die Karte selbst arbeitet intern recht kompliziert, und vorallem auf einem ziemlich schnellen Timing. Das gesamte Timing orientiert sich rund um einen Zyklus von 8 Pixeln, also einem Textzeichen.

Der Bus, über den die CPU die Karte (und auch alle anderen Hardware-Karten) ansteuern kann, ist bei der Grafikkarte an einer asynchronen Register-Logik angeschlossen. Diese besteht zur Zeit aus drei Registern: Dem Hardware-ID-Register (0x01 identifiziert die Standard-Grafikkarte), dem Scrolling-Positions-Register (hier kann man die Nummer der ersten Zeile reinschreiben, die auf dem Display dargestellt werden soll, um das Hardware-Scrolling zu steuern), und dem Zeilenauswahlregister für Schreibzugriffe (hier kann man eine Zeile im VRAM auswählen, welche zum Schreiben eingeblendet werden soll). Außerdem besitzt die Karte in der oberen Hälfte ihres Adressbereichs (die obersten 128 Bytes) etwas Logik, um dort Teile des VRAMs einzublenden. Dadurch kann die CPU auf eine durch ein Register ausgewählte Zeile direkt schreiben. Ein eventueller Schreibzugriff auf den VRAM wird von einem Puffer aufgenommen und kann dann von der Karte im richtigen Moment (wenn der VRAM gerade zum Schreiben verfügbar ist) ausgeführt werden.

Die VGA-Ausgabelogik der Karte nutzt Bus-Multiplexing. Hier werden zunächst durch zwei Zählerbausteine die Pixel der Zeile gezählt, und basierend darauf die Nummer des aktuell in der Zeile auszugebenden Zeichens berechnet. Nach 640 Pixeln wird die Schwarzschulter- und HSync-Logik aktiviert, und nach 800 Pixeln der Zähler zurückgesetzt und ein Signal zum Weiterschalten der Zeile erzeugt. Das Zählen der Zeilen wird von einem Mikrocontroller übernommen, der auch gleich das Hardware-Scrolling übernimmt, indem beim Start des Bildes der Wert im Scrolling-Register als Startwert des Zählers genommen wird. Der Controller generiert außerdem die vertikalen Schwarzschultern und das VSync-Signal.

Ein weiterer Mikrocontroller (ATTiny24 auf Pixeltakt 25,175MHz) generiert die Steuersignale für den internen 8-Pixel-Zyklus der Karte. Jedes Pixel dauert knapp 40ns. Während des ersten Pixels werden Bus-Treiber aktiviert, welche die Daten und die Adresse des VRAM-Schreib-Puffers an den VRAM anlegen. Sind gültige Daten vorhanden (das Data-Present-Flipflop gesetzt), wird dem VRAM beim zweiten Pixel ein Schreib-Befehl gegeben und die Daten geschrieben. Beim dritten Pixel wird das Data-Present-Flipflop wieder zurückgesetzt. Sobald das vierte Pixel beginnt, schaltet der Controller die Bustreiber für die interne Adresse an und die anderen Bustreiber wieder aus. Dadurch werden Zeilen- und Spaltennummer des aktuellen Zeichens als Adresse auf den VRAM gegeben. Dem VRAM wird ein Read-Befehl gegeben und der ASCII-Code des Zeichens ausgelesen. Dieser ASCII-Code wird noch im selben Zeitpunkt dann auf das Schriftart-ROM gegeben. Während dem 5. und 6. Pixel wartet die Karte darauf, dass das langsame Schriftart-ROM die Pixeldaten ausliest. Genau am Ende des 7. Pixels werden dann schließlich die neuen Pixeldaten (1 Byte, also 8 Pixel) in ein Schieberegister geladen und der Zugriff auf das VRAM beendet. Während das Schieberegister dann die 8 Pixel ausgibt, beginnt die Karte schonmal damit, im nächsten Zyklus die nächsten 8 Pixel zu laden.

Die Karte wurde mit 74HC-Gattern aufgebaut, also mit Logikgattern ohne besonders hohe Geschwindigkeit.


Und zum Abschluss noch ein paar Fotos von der funktionierenden Karte:

https://www.dropbox.com/s/9ou4emzv0mjy3g7/IMG_20140706_193439.jpg
https://www.dropbox.com/s/zick2o9gw7760hi/IMG_20140706_193255.jpg
https://www.dropbox.com/s/l4gy1vm6cdgneo0/IMG_20140707_220735.jpg
https://www.dropbox.com/s/kq521cfxrwchfqa/IMG_20140708_185714.jpg

1. Bild: Fertige Karte (Bestückungsseite)
2. Bild: Fertige Karte, eingesteckt in einen Slot des Eigenbau-Systems
3. Bild: CPU schreibt Testdaten in den VRAM, wird erfolgreich ausgegeben
4. Bild: Die holprigen Anfänge eines BIOS...


Gruß
Jonathan
6
So, also, äh, *räusper*

Alle die, die gedacht haben, hier wär eh tot, muss ich wohl leider enttäuschen: Is' nich :P

Geht nämlich schon die ganze Zeit über weiter, diesmal mit komplett neuer Hardware... Das alte Ding wurde langsam echt nicht mehr brauchbar, die Schaltung funktionierte nach einigen Änderungen schlicht nicht mehr, und in dem Drahtverhau kann man keine Fehler suchen. Deshalb gibt's jetzt eine vollständig modulare zweite Version des Computers, bei der auch die CPU, das BIOS, die MMU usw. auf Steckkarten leicht austauschbar sind.

Die Hauptplatine an sich funktioniert schon mal (inklusive CPU-, MMU- und BIOS-Modul), und aus gegebenem Anlass poste ich mal ein Bild von der Grafikkarte, die gerade zum ersten mal ihren nicht initialisierten VRAM erfolgreich auf einen Monitor geworfen hat:


Die Karte kann 80x60 Text darstellen und nutzt die VGA-Auflösung 640x480 bei zwei Farben. Auf dem Character-ROM befindet sich Codepage 437, später wird die auch noch umschaltbar sein. Der VRAM fasst 256 Textzeilen zu je 80 Zeichen, außerdem kann die Karte Hardware-Scrolling, sodass man einen 60 Zeilen langen beliebig wählbaren Ausschnitt aus dem VRAM auf dem Monitor darstellen kann. Das dürfte wohl für Konsolen-Ausgabe ziemlich nützlich sein.


Gruß
Jonathan
7
Und weiter geht's: Da mich die Festplatte im Moment absolut nicht gern hat, und ich noch keine Ahnung habe, wieso das Ding das tut, geh ich solange erstmal die mangelnde Geschwindigkeit des Boards an: s/55ns-SRAM/10ns-SRAM/g. Ich konnte bei Kessler Electronic zwei Stück (sonst kaum zu bekommende) 512kB-10ns-SRAMs ergattern, die ich jetzt anstatt der momentan langsamsten Komponente (55ns-RAM) in mein Board einbaue. Vielleicht kann ich dann ja sogar wieder auf 32MHz hochtakten. Schön wär's. Lediglich das SOJ-SMD-Gehäuse der RAMs ist etwas blöd, aber auch nicht unlötbar.

EDIT: Falls es jemanden interessiert, bei meinem RAM handelt es sich um IS61C5128AL-10KLI.


Gruß
Jonathan
8
Genau, ich nutze SDCC. Selfhosting ist noch nicht geplant, könnte aber (viel) später noch realisiert werden - wenn ich einen Assembler und einen Compiler für das System umschreiben bzw. neu schreiben kann, und man das System halbwegs komfortabel bedienen kann. Momentan funktioniert dafür aber noch viel zu wenig.


Gruß
Jonathan
9
Lang ist's her, weiter geht's.

Message- und Treibersystem haben nun auch noch ihren Platz im knappen Kernelspeicher gefunden und funktionieren. Die Kommunikation zwischen Prozessen läuft über Messages an PID:Interface-Adressen. Jeder Prozess hat dabei bis zu 256 Interfaces, die er z.B. als einen Treiber registrieren kann. Somit kann ein einzelner Prozess nach außen hin mehrere Funktionen zur Verfügung stellen, was wegen meiner Hardware nötig ist.

Inzwischen habe ich mir - anders als geplant - einen C-Compiler für den Z80 gesucht und zum Laufen gebracht. Jetzt mache ich zuerst mal die IDE-Karte fertig und programmiere dann einen ersten kleinen Festplattentreiber.


Gruß
Jonathan
10
Offtopic / Re: Logisim CPU
« am: 10. August 2013, 14:54 »
Ich würde dir stark empfehlen, deine CPU zunächst auf einem ATMega1284P o.ä. in Software nachzubilden, damit du ein ungefähres Gefühl dafür bekommst, was wie funktioniert, und was nicht. Direkt in Hardware anzufangen ist vielleicht doch ein wenig Overkill. Der µC kann dann ja genau die selben Signale ausgeben, wie es deine spätere richtige CPU tun wird. Höchstwahrscheinlich musst du dabei auf Bus Multiplexing zurückgreifen, aber das ist ja alles mit ein paar Latches zu schaffen.

Außerdem würde ich Interrupts wie einen ganz normalen CALL ablaufen lassen. Im Z80 ist das sogar ein "echter" CALL: Kommt ein INT rein, wird zwar im nächsten M1-Zyklus die Instruktion vom Speicher gelesen, jedoch intern durch einen CALL überschrieben und die externen Daten ignoriert. Dieser CALL pusht dann eine um seine Länge verringerte Rücksprungadresse auf den Stack und springt in die entsprechende ISR. Das ist erheblich einfacher und funktioneller als eine Variante, wo die CPU weiß, in welchem Typ Interrupt-Handler sie gerade steckt usw... Dann hat der Programmierer die volle Kontrolle darüber, wie sich die CPU verhalten soll - er kann z.B. Nested Interrupts auf Wunsch zulassen und muss sich keine Gedanken darüber machen, dass so etwas zu unkontrolliertem Verhalten führen kann. Stell dir vor, ein Interrupthandler würde vom Task Switcher (jemand könnte ihn am "Notfall-Interrupt" anschließen) unterbrochen und weggeswitcht werden - kein anderer Task könnte jetzt noch Interrupts nutzen.


Gruß
Jonathan
11
Offtopic / Re: Logisim CPU
« am: 25. July 2013, 23:15 »
Du solltest außerdem Interrupts zuerst latchen, bevor du sie ausführst, damit kein Int verloren geht, der während einem anderen gleichrangigen Int auftritt.


Gruß
Jonathan
12
So, geb ich doch mal wieder ne Rückmeldung von mir.

Inzwischen hab ich mal alle Datenstrukturen, die der Kernel so braucht, testweise angelegt, um den Speicherverbrauch grob zu bestimmen. Wie schon gesagt, ich hab ja nur 16kB Platz.


Etwa 2,5kB hab ich noch Platz, da der Stack ja auch in den selben Speicher muss. Da rein müssen nur noch Funktionen für's Messagesystem und für's Treibersystem passen - wird in Assembler wohl gut hinhauen. Schwein gehabt.  :mrgreen:

Läuft dann auf folgendes hinaus:
- Puffer von 8 Messages je Prozess
- Speicherpool für eine gesamte Pufferung von bis zu 255 Messages mit je 12 Bytes Daten
- Bis zu 255 registrierbare Treiber


Gruß
Jonathan
13
Nee, das kann sich nicht berühren - das Kabel ist an der Stelle komplett mit Lötzinn durchtränkt, das ist stocksteif. Berühren kann sich deshalb nichts. Und danke für den Respekt ;)


Es funktioniert übrigens immer mehr:


Auf dem Screenshot sieht man folgendes: Der Kernel hat bereits die Hardware initialisiert (sogut das ohne Treiber eben geht *hust*) und startet testweise einen zweiten Prozess. Dann begibt sich der Kernel Task in eine Endlosschleife, wo er per RST 20h immer die CPU zurück ans Betriebssystem abgibt. Die Zeit, die der abgebende Prozess noch übrig hat, bekommt dann der nächste Prozess, der Zeit braucht, zugewiesen - in dem Fall der Test-Prozess. Dieser gibt dann einfach in einer Schleife mit Wartezeit immer wieder die ihm verbleibenden Zeitscheiben aus - 1, 2, 1, 2... Funktioniert also. Die maximal verfügbare Zeit wird übrigens auf 4 Zeitscheiben begrenzt, damit nicht ein Prozess alles blockiert, nachdem das System eine Zeit lang im Leerlauf lief.


Gruß
Jonathan
14
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:
15
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-)
16
Lowlevel-Coding / Re: Was genau ist ein Sprung?
« am: 25. June 2013, 17:31 »
Ein mov ..., %eip ist nicht möglich - weil bei einem jmp noch zusätzliche Logik dabei ist, die ein Leeren der Pipeline und eine Synchronisation macht. Ansonsten würde es seeeehr tolle Effekte bei einem Sprung geben, wie z.B. sowas, dass einige der Befehle hinter dem Sprung noch ausgeführt werden (oder sogar nur Teile davon) und dann erst die neuen Befehle vom neuen eip kommen - verursacht durch Out of Order Execution und Pipelining.

Ein Sprung ist deshalb das Neuladen des IP gekoppelt mit einer Initialisierung der CPU auf die neue Adresse.


Gruß
Jonathan
17
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
18
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
19
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
20
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
Seiten: [1] 2

Einloggen