Autor Thema: Qemu-Bug und Ideen  (Gelesen 5355 mal)

Vogtinator

  • Beiträge: 24
    • Profil anzeigen
Gespeichert
« am: 03. October 2012, 21:44 »
Hallo, ich hab` da mal ein paar Fragen:

Ich habe jetzt einmal eine eigene physische Speicherverwaltung geschrieben, die auch soweit alles macht, wie ich es will,
nur die Initialisierung funktioniertr nicht, ich hatte immer einen Nullpointer zurückbekommen, es wird phys_free(0) aufgerufen.
Diesen Fehler konnte ich nun dadurch beseitigen, dass ich
if (mmap->type == 1 && mmap->base != 0)geschrieben habe, aber woran kann es liegen, dass der Bootloader von Qemu einen Bereich ab 0 als frei markiert?
Wirklich frei ist er nicht, denn Qemu schmiert ab, wenn ich nach 0 etwas schreibe (Nullpointer sind auch einfach böse :evil:)

Dann habe ich noch eine Frage bezgl. Modulen:
Würde es funktionieren, wenn am Anfang eines Modules (als flache Binary wahrscheinlich) eine Funktion ist, die einen Kernel-Interrupt aufruft,
die einen Pointer auf eine Tabelle mit Kernel-Funktionen zurückgibt (darunter auch eine, die das Modul aufruft, um sich selbst zu registrieren)?
Oder kann es sein, dass ich gerade das Modulsystem von Linux beschrieben habe?  :roll:

Eine Idee, wie man eine Speicher-/Heapverwaltung umsetzen könnte, habe ich auch noch:
Am Anfang ist der ganze Speicher ein Bereich, am Anfang jedes Bereiches liegt ein uint32_t (vllt. überdimensioniert),
der die Größe des Bereiches angibt und ob er frei/belegt ist.
Will man nun zuerst Speicher allozieren, so setzt man den Anfang des ersten freien Bereiches, der groß genug ist,
die Größe des belegten Platzes und dass er belegt ist, geschrieben und hinter diesen Bereich die Größe des restlichen freien Speichers.
Um einen Bereich wieder frei zu geben, markiert man diesen wieder als frei und verbindet ihn wenn möglich mit dem freien Speicher dahinter.
Damit die Performance nicht auf der Strecke bleibt, wäre ein Array(begrenzter Größe, also eher Cache) der/einiger freien Bereiche ratsam,
sonst müsste man im schlimmsten Fall alle Bereiche durchgehen, bis man am Ende merkt, dass überhaupt kein Speicher mehr frei ist.
Kann man das so umsetzen?
Dass ich bei meiner Methode eine hohe Fragmentierung erzeuge, weiß ich, auch dass ich pro Bereich 4 Byte zusätzlich brauche.
Wenn man die maximale Größe eines Bereiches auf 2^16 Bytes begrenzt und auch freien Speicher teilt, lässt sich das verbessern.
Alle malloc(), die ich bis jetzt gesehen habe, waren viel komplizierter, oder hat meine Methode einen großen Denkfehler, man weiß ja nie  :roll:?

Edit: Ich hab noch was vergessen:
Warum funktioniert es nicht, wenn ich (wie von der Multiboot-Spez. vorgeschlagen) zur nächsten mmap wechsle, indem ich mmap->size Bytes überspringe?
Bei den ersten beiden mmap´s stimmts noch mit sizeof(struct multiboot) überein, aber danach kommen nur noch komische Werte, einer reicht ja schon, um rauszukommen.
« Letzte Änderung: 03. October 2012, 21:50 von Vogtinator »

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 03. October 2012, 22:27 »
woran kann es liegen, dass der Bootloader von Qemu einen Bereich ab 0 als frei markiert?
Wirklich frei ist er nicht, denn Qemu schmiert ab, wenn ich nach 0 etwas schreibe (Nullpointer sind auch einfach böse :evil:)
Die Memory Map gibt den freien RAM an. Jedes gängige System sollte den Bereich ab 0 als frei markieren, nicht nur QEMU. Auf jedem System ist es außerdem möglich auf die Adresse 0 zuzugreifen. Dass es einen Absturz gibt, muss an etwas anderem liegen.

Eine Sache, die du auf jeden Fall beachten solltest ist, dass die Memory Map nicht angibt, welcher Speicher in Benutzung ist. Zum Beispiel können die Multiboot-Datenstrukturen (unter anderem genau die Memory Map) in dem Speicherblock, der bei 0 beginnt liegen. (Außerdem liegen in dem Bereich ein paar Sachen, die das BIOS benutzt, aber wenn du das BIOS nicht benutzt, dann sollte es keine Probleme geben.)

Ein weiterer Punkt, den auch beachten solltest, ist, dass die Adressen in der Memory Map 64-Bit groß sind. Falls du nur die unteren 32-Bit auswertest, könnte es durchaus zu Problemen kommen, wenn du ein System mit 4 GB oder mehr startest.

Dann habe ich noch eine Frage bezgl. Modulen:
Würde es funktionieren, wenn am Anfang eines Modules (als flache Binary wahrscheinlich) eine Funktion ist, die einen Kernel-Interrupt aufruft,
die einen Pointer auf eine Tabelle mit Kernel-Funktionen zurückgibt (darunter auch eine, die das Modul aufruft, um sich selbst zu registrieren)?
Oder kann es sein, dass ich gerade das Modulsystem von Linux beschrieben habe?  :roll:
Das ist möglich. Du solltest nur bedenken, dass es dazu erforderlich ist, dass das Modul im Kernel Mode laufen muss. Dann kann das durchaus den Linux Kernelmodulen sehr ähnlich sein. Wenn das Modul im User Mode läuft, solltest du nicht einfach Pointer aus dem Kernel in den User Space übergeben. Der User Space sollte (im Allgemeinen) mit dem Kernel nur über Systemaufrufe kommunizieren.

Eine Idee, wie man eine Speicher-/Heapverwaltung umsetzen könnte, habe ich auch noch:
Am Anfang ist der ganze Speicher ein Bereich, am Anfang jedes Bereiches liegt ein uint32_t (vllt. überdimensioniert),
der die Größe des Bereiches angibt und ob er frei/belegt ist.
Will man nun zuerst Speicher allozieren, so setzt man den Anfang des ersten freien Bereiches, der groß genug ist,
die Größe des belegten Platzes und dass er belegt ist, geschrieben und hinter diesen Bereich die Größe des restlichen freien Speichers.
Um einen Bereich wieder frei zu geben, markiert man diesen wieder als frei und verbindet ihn wenn möglich mit dem freien Speicher dahinter.
Damit die Performance nicht auf der Strecke bleibt, wäre ein Array(begrenzter Größe, also eher Cache) der/einiger freien Bereiche ratsam,
sonst müsste man im schlimmsten Fall alle Bereiche durchgehen, bis man am Ende merkt, dass überhaupt kein Speicher mehr frei ist.
Kann man das so umsetzen?
Ja, klingt nicht verkehrt.

Zitat
Dass ich bei meiner Methode eine hohe Fragmentierung erzeuge, weiß ich, auch dass ich pro Bereich 4 Byte zusätzlich brauche.
Wenn man die maximale Größe eines Bereiches auf 2^16 Bytes begrenzt und auch freien Speicher teilt, lässt sich das verbessern.
Alle malloc(), die ich bis jetzt gesehen habe, waren viel komplizierter, oder hat meine Methode einen großen Denkfehler, man weiß ja nie  :roll:?
Eine Größenbegrenzung würde ich nicht empfehlen. Die anderen mallocs sind aus Performancegründen komplizierter. Es geht natürlich einmal um Fragmentierung, als auch darum wie schnell man einen freien Block findet (und wie schnell man einen Block wieder freigeben kann.) Dein Cache ist ein guter Ansatz, um das Problem einfach zu lösen. Um die Fragmentierung würde ich mir erstmal keine Sorgen machen. Mein Vorschlag wäre, dass du deine Idee implementierst, und dann irgendwann, wenn du deine Speicherverwaltung als Performancebremse in Verdacht hast, dir komplexere Speicherverwaltungen anschaust. Das sollte so dann in etwa der Fall sein, wenn du einen Webbrowser oder so drauf laufen lässt. ;) Dann kannst du anfangen Messungen zu machen um festzustellen, wie lange Speicheranfragen dauern, wie groß die Fragmentierung tatsächlich ist, wie gut die Cacheausnutzung ist, etc. Bis dahin gilt: Je einfacher, desto besser.

Zitat
Edit: Ich hab noch was vergessen:
Warum funktioniert es nicht, wenn ich (wie von der Multiboot-Spez. vorgeschlagen) zur nächsten mmap wechsle, indem ich mmap->size Bytes überspringe?
Bei den ersten beiden mmap´s stimmts noch mit sizeof(struct multiboot) überein, aber danach kommen nur noch komische Werte, einer reicht ja schon, um rauszukommen.

Ich überspringe immer noch 4 zusätzliche Bytes (= sizeof(mmap->size)).
mmap = (multiboot_mmap_t*)((paddr_t)mmap + mmap->size + sizeof(mmap->size))Wobei multiboot_mmap_t so definiert ist:
typedef struct multiboot_mmap {
uint32_t size;
uint64_t base_addr;
uint64_t length;
uint32_t type;
} multiboot_mmap_t;
« Letzte Änderung: 03. October 2012, 22:31 von Jidder »
Dieser Text wird unter jedem Beitrag angezeigt.

Vogtinator

  • Beiträge: 24
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 03. October 2012, 22:55 »
Zitat
Die Memory Map gibt den freien RAM an. Jedes gängige System sollte den Bereich ab 0 als frei markieren, nicht nur QEMU. Auf jedem System ist es außerdem möglich auf die Adresse 0 zuzugreifen. Dass es einen Absturz gibt, muss an etwas anderem liegen.
Ok, da schaue ich nochmal.

Zitat
Ein weiterer Punkt, den auch beachten solltest, ist, dass die Adressen in der Memory Map 64-Bit groß sind. Falls du nur die unteren 32-Bit auswertest, könnte es durchaus zu Problemen kommen, wenn du ein System mit 4 GB oder mehr startest.
Wenn ich überprüfe, ob mmap->base plötzlich kleiner geworden ist und ich dann einfach abbreche, sollte das keine Probleme geben.

Zitat
Das ist möglich. Du solltest nur bedenken, dass es dazu erforderlich ist, dass das Modul im Kernel Mode laufen muss. Dann kann das durchaus den Linux Kernelmodulen sehr ähnlich sein. Wenn das Modul im User Mode läuft, solltest du nicht einfach Pointer aus dem Kernel in den User Space übergeben. Der User Space sollte (im Allgemeinen) mit dem Kernel nur über Systemaufrufe kommunizieren.
IO über Syscalls machen zu müssen klingt für mich nach purer Langsamkeit. Memory-Mapped-IO könnte noch funktionieren, aber bei VGA z.b. braucht man da schon mehr als 30 outw/outp.

Zitat
Das sollte so dann in etwa der Fall sein, wenn du einen Webbrowser oder so drauf laufen lässt.  Dann kannst du anfangen Messungen zu machen um festzustellen, wie lange Speicheranfragen dauern, wie groß die Fragmentierung tatsächlich ist, wie gut die Cacheausnutzung ist, etc. Bis dahin gilt: Je einfacher, desto besser.
Werd` ich machen.
Mein entgültiges Ziel (also bis ich ein besseres/einfacheres gefunden habe) ist es, Opera zum laufen zu kriegen.
Könnte sehr spaßig werden, der Quellcode ist schließlich nicht offen  :x

Zitat
Ich überspringe immer noch 4 zusätzliche Bytes (= sizeof(mmap->size)).
Warum funktioniert mmap++ (also mmap += sizeof(struct multiboot_mmap)), aber nicht mmap += 24 Bytes, obwohl sizeof(struct multiboot_mmap) = 24 ist?
Mit 28 Bytes scheint es ja laut dir zu funktionieren, warum auch immer..

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 03. October 2012, 23:22 »
Wenn ich überprüfe, ob mmap->base plötzlich kleiner geworden ist und ich dann einfach abbreche, sollte das keine Probleme geben.
Kannst du natürlich machen. Es könnte allerdings sein, dass dir niemand garantiert, dass die Speicherbereiche nach mmap->base sortiert sind. (Du kannst das natürlich auch nachschlagen und mich widerlegen ;)) Falls sie es mal nicht sein sollten, wirst du weniger Speicher nutzen können, als tatsächlich vorhanden ist. Wenn du das irgendwann besser lösen willst, solltest du prüfen, ob mmap->base > 4GB, und wenn ja den Eintrag verwerfen. Und außerdem prüfen, ob mmap->base + mmap->length > 4 GB ist, und wenn ja, dann statt mmap->length den Wert 4 GB - mmap->base nutzen. Das Ganze natürlich mit 64-Bit Adressen.

IO über Syscalls machen zu müssen klingt für mich nach purer Langsamkeit. Memory-Mapped-IO könnte noch funktionieren, aber bei VGA z.b. braucht man da schon mehr als 30 outw/outp.
Du kannst die I/O-Ports für User-Prozesse freigeben. Dazu dient die I/O-Bitmap in der TSS.

Das worauf ich mich bezog, war eher, dass es problematisch ist, wenn der Kernel dem Modul ein paar Pointer auf Funktionen zurückgibt, und der Userprozess dann diese Funktionen aufruft. Das Problem ist ja, dass der Userprozess im Ring 3 läuft, und deswegen nicht auf Speicherbereiche zugreifen kann, die für den Kernel (Ring 0) gemappt sind. Das heißt, du kriegst einen Page Fault, wenn der User Prozess eine Funktion aufruft, die versucht Kernel Speicher zu modifizieren. Wenn du versuchst, das zu umgehen, indem du den Kernelspeicher beschreibbar machst, dann versaust du (im Allgemeinen) das gesamte Konzept der Trennung von User Mode und Kernel Mode. Deswegen solltest du weiterhin privilgierte Operationen dem Kernel vorbehalten. Aber zum Beispiel I/O kannst du im User Mode machen. Die verzwickten Sachen (und da ist dann dein Konzept gefragt) sind eher: Wie kommt ein Prozess an Speicher und wie kommuniziert ein Prozess mit anderen Prozessen. Wenn du dir da was cleveres einfallen lässt, dann sollte die Performance brauchbar sein.

Warum funktioniert mmap++ (also mmap += sizeof(struct multiboot_mmap)), aber nicht mmap += 24 Bytes, obwohl sizeof(struct multiboot_mmap) = 24 ist?
Mit 28 Bytes scheint es ja laut dir zu funktionieren, warum auch immer..
mmap += 24 (oder mmap += 28) ist nicht dasselbe wie mmap++, wenn mmap so deklariert ist:
multiboot_mmap_t *mmap;
Der Unterschied ist, dass mmap++ auf das nächste Element zeigt. (Unter der Annahme, dass alle Elemente gleich groß sind. Das sind sie aber nicht, deswegen gibt es ja das size-Feld.) Das ist das selbe wie mmap += 1. Wenn du aber mmap += 24 machst, dann gehst du 24 Elemente weiter. Das heißt, du nimmst an, das du 24 Speicherbereiche hast. Das könnte auch die Ursache für den Absturz mit dem Nullpointer sein. Der 24ste Eintrag ist nämlich garantiert nicht mehr sinnvoll, weil es in der Regel deutlich weniger Bereiche gibt (3-8). Dadurch könnte letztendlich der Absturz entstanden sein, weil der Eintrag nur Unsinn enthalten hat (und nur zufällig auch mmap->base == 0).

Diese ganze Geschichte mit mmap++ vs. mmap += 28 ist auch der Grund, warum ich in meinem Code mmap auf paddr_t caste und danach wieder auf multiboot_mmap_t* zurückcaste. paddr_t ist bei mir unsigned int.
« Letzte Änderung: 03. October 2012, 23:42 von Jidder »
Dieser Text wird unter jedem Beitrag angezeigt.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #4 am: 04. October 2012, 11:24 »
IO über Syscalls machen zu müssen klingt für mich nach purer Langsamkeit.
Ich glaube, da hast du was falsch verstanden. I/O ist pure Langsamkeit. Für die Zeit, die du mit einem Portzugriff verbrätst, könntest du wohl einige Male vom Userspace in den Kernel und wieder zurückwechseln...

Zitat
Mein entgültiges Ziel (also bis ich ein besseres/einfacheres gefunden habe) ist es, Opera zum laufen zu kriegen.
Könnte sehr spaßig werden, der Quellcode ist schließlich nicht offen  :x
Öhm, und hast du auch einen Plan dafür, wie du das erreichst? Soll dein OS linuxkompatibel werden oder sowas? Sowas sind ja durchaus Ziele, die man von Anfang an berücksichtigen muss, wenn man sie wirklich umsetzen will.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Vogtinator

  • Beiträge: 24
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 04. October 2012, 18:01 »
Zitat
Falls sie es mal nicht sein sollten, wirst du weniger Speicher nutzen können, als tatsächlich vorhanden ist. Wenn du das irgendwann besser lösen willst, solltest du prüfen, ob mmap->base > 4GB, und wenn ja den Eintrag verwerfen. Und außerdem prüfen, ob mmap->base + mmap->length > 4 GB ist, und wenn ja, dann statt mmap->length den Wert 4 GB - mmap->base nutzen. Das Ganze natürlich mit 64-Bit Adressen.
Ja, das geht natürlich auch. Ich hatte gestern eh nur eine Art Notlösung zum Testen, und es war schon spät  :-D

Zitat
Das worauf ich mich bezog, war eher, dass es problematisch ist, wenn der Kernel dem Modul ein paar Pointer auf Funktionen zurückgibt, und der Userprozess dann diese Funktionen aufruft. Das Problem ist ja, dass der Userprozess im Ring 3 läuft, und deswegen nicht auf Speicherbereiche zugreifen kann, die für den Kernel (Ring 0) gemappt sind. Das heißt, du kriegst einen Page Fault, wenn der User Prozess eine Funktion aufruft, die versucht Kernel Speicher zu modifizieren. Wenn du versuchst, das zu umgehen, indem du den Kernelspeicher beschreibbar machst, dann versaust du (im Allgemeinen) das gesamte Konzept der Trennung von User Mode und Kernel Mode. Deswegen solltest du weiterhin privilgierte Operationen dem Kernel vorbehalten. Aber zum Beispiel I/O kannst du im User Mode machen. Die verzwickten Sachen (und da ist dann dein Konzept gefragt) sind eher: Wie kommt ein Prozess an Speicher und wie kommuniziert ein Prozess mit anderen Prozessen. Wenn du dir da was cleveres einfallen lässt, dann sollte die Performance brauchbar sein.
Mit IPC muss ich mich eh auseinandersetzen, wenn ich Multitasking im Userspace einigermaßen sinnvol benutzen möchte.
Die Pinzipien der Ringe habe ich schon verstanden, wusste aber noch nicht, dass man auch IO machen kann.

Zitat
Diese ganze Geschichte mit mmap++ vs. mmap += 28 ist auch der Grund, warum ich in meinem Code mmap auf paddr_t caste und danach wieder auf multiboot_mmap_t* zurückcaste. paddr_t ist bei mir unsigned int.
Exakt deshalb hatte ich auch
Zitat
Warum funktioniert mmap++ (also mmap += sizeof(struct multiboot_mmap)), aber nicht mmap += 24 Bytes, obwohl sizeof(struct multiboot_mmap) = 24 ist?
geschrieben. Sonst hätte ich auch einfach zu wenige mmaps statt einfach falschen Werten.

Zitat
Ich glaube, da hast du was falsch verstanden. I/O ist pure Langsamkeit. Für die Zeit, die du mit einem Portzugriff verbrätst, könntest du wohl einige Male vom Userspace in den Kernel und wieder zurückwechseln...
Also kann es für ein Modul praktisch von der Performance egal sein, in welchem Ring es läuft.
Aber ich denke, ein Modul im Userspace ist zwar sicherer, aber ersteinmal zu kompliziert und unnötig.
Vllt. schreib ich den Kernel auch ohne Module, ich nenn den einfach mal Gigakernel :-D

Zitat
Öhm, und hast du auch einen Plan dafür, wie du das erreichst? Soll dein OS linuxkompatibel werden oder sowas? Sowas sind ja durchaus Ziele, die man von Anfang an berücksichtigen muss, wenn man sie wirklich umsetzen will.
Mein Ziel hab ich extra so geplant, dass ich es nie erreiche, sonst bin ich ja irgendwann fertig und hab dann nix mehr zu tun  :-D
Am sinnvollsten wär es wohl, Qemu zu porten und darunter Linux zu starten.
Aber darüber mache ich mir noch keine Gedanken, ich schreib gerade nur meinen Kernel zum rumtesten, das was da gerade drin steht, will ich noch keinem echten PC antun  :roll:

 

Einloggen