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.


Themen - Jonathan

Seiten: [1]
1
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
Seiten: [1]

Einloggen