Autor Thema: Speicherverwaltung: vmm_alloc - Richtige Umsetzung?  (Gelesen 12831 mal)

Tele

  • Beiträge: 13
    • Profil anzeigen
Gespeichert
« am: 26. November 2015, 14:47 »
Hallo Forum!

Ich beschäftige mich gerade mit der Paging-Implementierung und bin nun bei der Implementierung von vmm_alloc().
Ich halte mich an das hier im Wiki gestellte Tutorial, daher die Namensgleicheiten was Funktionen, Variablen, etc. angeht.
Hauptsächlich geht es mir um die Berechnung einer freien virtuellen Adresse.
Hierzu habe ich folgende Funktion get_free_page, welche mir für einen gegebenen context die erste nicht belegte virtuelle Adresse zurückliefert.
Mit dieser kann ich dann ein map_page() machen, mit vorher alloziierter physischer Page.
static uintptr_t get_free_page(struct vmm_context_t* context) {
uint32_t* page_table;
uint32_t page;
int i;
int j;

// loop page tables in context
for (i = 0; i < 1024; i++) {
page_table = context->pagedir[i];

// used page table
if (*page_table & VMM_USED) {

// loop pages
for (j = 0; j < 1024; j++) {
page = page_table[j];

// check if page is used
if (page == 0) {
return (i << 22) + ((j << 12));
}
}
}
}

return NULL;
}
Ist das soweit richtig oder ist mein Grundgedanke schon falsch?
Eine weitere Frage:
Nachdem ich mir eine physische Page alloziert habe und diese ausgebe, ist diese 0x1000. Das entspräche doch den ersten 4KB im RAM, oder?
Wenn dem so ist, funktioniert mein pmm-mapping auch nicht, denn da sollte zumindest Kernel + Multiboot + Videobuffer gemappt sein, also eine Adresse > 0x1000 rauskommen.
Oder liege ich hier auch falsch?
Ich hoffe jemand kann Licht in mein Dunkel bringen :-)

Viele Grüße,

Der Tele
I'm in love with /dev/null

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 26. November 2015, 15:05 »
Dein Ansatz ist im Prinzip richtig, der Code aber im Detail falsch. Insbesondere musst du dir klar werden, wie ein Page-Directory-Eintrag aussieht: Das ist in den höherwertigen Bits die physische (!) Adresse einer Page Table und in den unteren Bits Flags. Bevor du den Eintrag also als Pointer benutzen kannst, musst du die Flags herausfiltern, und falls du kein 1:1-Mapping für alle Page Tables hast, die Adresse in eine virtuelle Adresse übersetzen.

Du solltest dir auch bewusst sein, dass *page_table den ersten Eintrag in der PT zurückgibt. Ich weiß nicht, was VMM_USED genau ist, aber vermute, du wolltest stattdessen ein Flag im PDE prüfen - vermutlich das Present Bit, aber dann wäre VMM_PRESENT ein besserer Name.

Zu den physischen Adressen: Der Tutorialkernel ist an 1 MB gelinkt, der Videospeicher geht von 0xa0000 bis 0xbffff. Der erste physische Page kann also durchaus noch frei sein.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Tele

  • Beiträge: 13
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 26. November 2015, 15:17 »
Vielen Dank für deine Antwort!

Was meinst du mit 1:1-Mapping?
falls du kein 1:1-Mapping für alle Page Tables hast, die Adresse in eine virtuelle Adresse übersetzen.
Hab ich im Tutorial etwas überlesen?
Ja genau, mit VMM_USED ist das Present Bit gemeint.
Wenn ich die Flags herausfiltern möchte, reicht es, diese mit '&' zu clearen, damit der Pointer funktioniert?
Du solltest dir auch bewusst sein, dass *page_table den ersten Eintrag in der PT zurückgibt
Oh, die Dereferenzierung da macht auch wenig Sinn. Das wäre dann aber die erste Page der Page Table?

EDIT:

Ich habe mir alles nochmal durchgelesen und etwas gefixed. Mein get_free_page sieht jetzt so aus:
static uintptr_t get_free_page(struct vmm_context_t* context) {
uint32_t* page_table;
uint32_t page;
int i;
int j;

// loop page tables in context
for (i = 0; i < 1024; i++) {

// used page table
if (context->pagedir[i] & VMM_USED) {
page_table = (uint32_t *)(context->pagedir[i] & ~0xFFF);

// loop pages
for (j = 0; j < 1024; j++) {
page = page_table[j];

// check if page is used
if (page == 0) {
uintptr_t ret_addr = (i << 22) + ((j << 12));
if (ret_addr != NULL) {
return ret_addr;
}
}
}
}
}

return NULL;
}

In meiner vmm_init mappe ich Kernel und Videobuffer und alloziiere nach dem Setzen des cr0-Registers einen Int-Pointer.
Da ich nach dem Bootvorgang eine "5" ausgegeben bekomme, sollte dies ja heißen, dass Paging erfolgreich die Adresse übersetzt hat, oder?
Wenn ich mir die Adressen mal anschaue, sind dies im Moment 1:1 Übersetzungen, sollte dann also richtig sein?
void vmm_init(void) {
uint32_t cr0;
kernel_context = create_context();

// map kernel
map_page_range(kernel_context, KERNEL_START, KERNEL_START, KERNEL_END, 1);

// map video buffer
map_page_range(kernel_context, 0xb8000, 0xb8000, (0xb8000 + (2 * 25 * 80)), 1);


vmm_activate_memory_context(kernel_context);

// set bit 31 in register cr0 to enable paging
asm volatile("mov %%cr0, %0" : "=r" (cr0));
cr0 |= (1 << 31);
asm volatile("mov %0, %%cr0" : : "r" (cr0));

int* test = (int *)vmm_alloc(kernel_context, sizeof(int));
*test = 5;

printf("%d\n", *test);
}
« Letzte Änderung: 26. November 2015, 16:25 von Tele »
I'm in love with /dev/null

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 26. November 2015, 22:49 »
Du kannst im Qemu-Monitor mit "info tlb" und "info mem" nachschauen, wie deine Page Tables interpretiert werden. Das ist sehr hilfreich, um sowas schnell nachzuprüfen.

Später wird das Thema VMM eher eine Politikfrage. Wenn deine Routinen stehen, kannst du dich mal mit den Vor- und Nachteilen eines "higher-half kernels" befassen und damit, wie man am besten mit Pagefaults umgeht, bzw. wie man sie sinnvoll einsetzt.

Tele

  • Beiträge: 13
    • Profil anzeigen
Gespeichert
« Antwort #4 am: 27. November 2015, 09:17 »
Vielen Dank für die Tipps, das mit Qemu ist wirklich sehr hilfreich.
Ich habe auch gleich einen Fehler, zumindest passt mein Mapping noch nicht zu 100%.
Ich habe die Multiboot-Struktur gemappt, beim Zugriff bekomme ich allerdings einen PageFault.
info mem gibt mir auch nur
00000000000b8000-000000000025c000 00000000001a4000 urw
aus und das sieht mir nach Videobuffer aus. Aber da sind doch 0x1a4000 viel zu viel?
I'm in love with /dev/null

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 27. November 2015, 11:24 »
Was meinst du mit 1:1-Mapping?
1:1-Mapping (oder Identity-Mapping)  bedeutet, dass du eine bestimmte virtuelle Adresse auf genau dieselbe physische Adresse gemappt hast. Normalerweise ist das nicht der Fall, und deswegen kannst du sobald Paging mal aktiviert ist nicht direkt den PDE als Pointer benutzen. Das PD enthält die physische Adresse der Page Table, aber Speicherzugriffe in deinem Code benutzen virtuelle Adressen.

Zitat
Wenn ich die Flags herausfiltern möchte, reicht es, diese mit '&' zu clearen, damit der Pointer funktioniert?
Ja, genau das.

Zitat
Oh, die Dereferenzierung da macht auch wenig Sinn. Das wäre dann aber die erste Page der Page Table?
Der Page-Table-Eintrag, der die erste Page beschreibt, um genau zu sein. (Vielleicht meintest du das, aber es war nicht ganz eindeutig.)
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Tele

  • Beiträge: 13
    • Profil anzeigen
Gespeichert
« Antwort #6 am: 27. November 2015, 13:03 »
Vielen Dank für die Antwort  :-)

Mir ist eine Thematik aber noch nicht klar:
Zitat
Für das Mappen eines Page Directory und allen zugehörigen Page Tables gibt es einen Trick: Wenn ein Eintrag eines Page Directory wieder auf ein Page Directory (z.B. sich selbst) zeigt, dann wird dieses Page Directory an dieser Stelle als Page Table verwendet. Dadurch werden alle enthaltenen Page Tables hintereinander in den virtuellen Speicher gemappt. Dieser Trick wird oft für das aktuelle Page Directory benutzt, d.h. ein bestimmter Eintrag in jedem PD zeigt wieder auf das PD selbst.
Das verstehe ich nicht. Ich muss alle benutzen Page Directory Einträge mappen, damit die CPU bei Übersetzung der Adressen die dazugehörige Page Table findet?
Klingt logisch. Mit "ein Page Directory Eintrag" ist doch die Adresse einer Page Table gemeint? Muss ich dann nicht auch alle Pages innerhalb einer Table mappen?

Ein anderes Thema: Um nach aktiviertem Paging auf das Page Directory zugreifen zu können, brauche ich die virtuelle Adresse des Page Directorys.
Mappe ich dazu einfach die virtuelle Adresse des Page Directory auf die Physische?
kernel_context->pagedir_virt = get_free_page(kernel_context);
map_page(kernel_context, kernel_context->pagedir_virt, kernel_context->pagedir_phys);

Wie man merkt, komme ich beim Thema Paging echt durcheinander..

Der Tele
I'm in love with /dev/null

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #7 am: 27. November 2015, 13:51 »
Das verstehe ich nicht. Ich muss alle benutzen Page Directory Einträge mappen, damit die CPU bei Übersetzung der Adressen die dazugehörige Page Table findet?
Nein, das würde zu einem Henne-Ei-Problem führen, wenn du auf eine Page Table zugreifen willst. ;) Deswegen stehen in cr3, den PDEs und PTEs immer physische, nicht virtuelle Adressen. Zum Auflösen der Adressen greift die CPU einfach direkt auf den physischen Speicher zu.

Das Mapping brauchst du dann, wenn du in deinem Code nochmal auf irgendwelche Pagingstrukturen zugreifen willst (z.B. um eine neu allozierte Page zu mappen), denn dein Code benutzt immer virtuelle Adressen.

Zitat
Mit "ein Page Directory Eintrag" ist doch die Adresse einer Page Table gemeint? Muss ich dann nicht auch alle Pages innerhalb einer Table mappen?
Ein PDE ist die Adresse einer Page Table plus Flags (eigentlich ja "oder Flags", aber das klingt verwirrend ;)).

Zitat
Ein anderes Thema: Um nach aktiviertem Paging auf das Page Directory zugreifen zu können, brauche ich die virtuelle Adresse des Page Directorys.
Mappe ich dazu einfach die virtuelle Adresse des Page Directory auf die Physische?
Du musst es auf jeden Fall irgendwohin mappen, ja. Die virtuelle Adresse kannst du dir frei aussuchen. Wenn du (wie im zitierten Wikiartikel beschrieben), ein PDE aufs PD zeigen lässt, hast du das PD damit automatisch auch gemappt. Du musst dir dann halt ausrechnen, wo das PD damit landet.

Zitat
Wie man merkt, komme ich beim Thema Paging echt durcheinander..
Mach dir keine Sorgen, das ist normal. Paging ist super, um sich Knoten ins Hirn zu denken, bis man es mal kapiert hat. ;)
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Krendor

  • Beiträge: 19
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 27. November 2015, 14:08 »
Mach dir keine Sorgen, das ist normal. Paging ist super, um sich Knoten ins Hirn zu denken, bis man es mal kapiert hat. ;)
Oh ja, das kann ich nur bestätigen. Aber wenn man mal richtig dahinter gestiegen ist, merkt man auch, dass man oftmals gar nicht mehr so kompliziert denken muss. :-D
Ich habe aber auch an meiner Methode zum mappen einer Page zu knabbern gehabt. Dafür prüft sie jetzt, ob die Page schon gemappt ist, reagiert auf 4KB wie auch auf 4MB Pages, allokiert eine neue physische Page und sogar eine neue PageTable, wenn sie denn benötigt wird.

Tele

  • Beiträge: 13
    • Profil anzeigen
Gespeichert
« Antwort #9 am: 30. November 2015, 15:16 »
Vielen Dank für Eure Antworten!

Ich habe mich am Wochenende nochmal damit beschäftigt und da leider mein Mapping immer noch kaputt war, hab ich testweise mal die ersten 4MB komplett gemappt.
ich weis, unschön ist aber zum Testen.
Leider bekomme ich eine General Protection immer wenn einer meiner Userspace Tasks irgendwas machen möchte. Ohne jetzt wieder mit viel Code zu kommen:
Kann man (pauschal) sagen woher eine General Protection kommt? Ein Page Fault ist es ja nicht, also stimmt was anderes nicht.
Oder sind die Möglichkeiten eines Fehlers da zu groß?
Wenn meine Tasks nur aus while(1); bestehen, läufts :-P Aber sobald etwas wie puts verwendet wird, knallt es. Und zwar beim Code wo der Parameter auf den Stack kommt.

LG,

Der Tele
I'm in love with /dev/null

OsDevNewbie

  • Beiträge: 282
    • Profil anzeigen
    • YourOS Kernel
Gespeichert
« Antwort #10 am: 30. November 2015, 19:28 »
Naja ein General Protection Fault kann viele Ursachen haben. Was auf jeden Fall schon mal helfen würde wäre der Errorcode, den die CPU auf den Stack schreibt.
Was ich allerdings bei dir Vermute ist, dass du das Limit und/oder die Basis der Stacksegmentsregister falsch gesetzt hast.
Viele Grüsse
OsDevNewbie

Ein Computer ohne Betriebsystem ist nicht mehr wert als ein Haufen Schrott.
Ein Computer ist eine Maschine, die einem Lebewesen das kostbarste klaut, was sie selber nicht hat:
DIE ZEIT DES LEBENS

Tele

  • Beiträge: 13
    • Profil anzeigen
Gespeichert
« Antwort #11 am: 01. December 2015, 19:29 »
Ich muss einen Schritt zurück gehen.
Die Exception hat sich als doch als Page Fault herausgestellt, ich vermute eine General Protection vorher durch veraltete Object-Dateien und Code-Reste.
Die Fehlerbeschreibung bleibt aber die Selbe.
Der Errorcode des Page Faults jedenfalls ist 0x7, also denke ich die ersten Bit's 0-2 sind gesetzt?
Das wäre dann ja
  • Nicht durch eine aktive Page, Bit 0 ist ja gesetzt
  • Ein Schreibzugriff
  • Task im Usermode
Kann ich zumindest bestätigen, auch Bit 1 mit Schreibzugriff scheint korrekt zu sein, der Instruction Pointer des Tasks zeigt auf push 0x41das wäre ja der Parameter meines putchar('A')Das macht der Task nämlich.
cr2 zeigt auf 0x402fe4 was jedenfalls laut qemu's info mem gemappt sein sollte:
0000000000000000-0000000000400000 0000000000400000 urw
0000000000400000-0000000000407000 0000000000007000 -rw

LG,

Der Tele
I'm in love with /dev/null

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #12 am: 01. December 2015, 20:58 »
Bit 0 gesetzt heißt Berechtigungsfehler. Vermutlich willst du aus dem Userspace in Code springen, der nur für den Kernel gemappt ist.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Tele

  • Beiträge: 13
    • Profil anzeigen
Gespeichert
« Antwort #13 am: 01. December 2015, 21:56 »
Ah vielen Dank!
Das war zumindest der Fehler. Der Errorcode hat sich so jetzt von 0x7 auf 0x5 verringert :-)
Bleibt noch das gesetzte 2'te Bit, welches sagt, dass das Programm Usermode-Rechte hat. Nun gut stimmt ja auch. Das Bit sollte also richtig sein.
Was sagt mir aber in diesem Falle der PageFault aus? Ein richtiger Errorcode ist das ja nicht mehr..
I'm in love with /dev/null

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #14 am: 01. December 2015, 22:12 »
Bit 0 ist immer noch gesetzt. Nur war es vorher ein Schreibzugriff, für den die Rechte gefehlt haben, jetzt ist es ein Lesezugriff.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Tele

  • Beiträge: 13
    • Profil anzeigen
Gespeichert
« Antwort #15 am: 02. December 2015, 09:26 »
Dann verstehe ich den Wikiartikel http://www.lowlevel.eu/wiki/Exception#Page_Fault falsch.
Dort steht zu Bit 0
Zitat
Wenn gelöscht, dann wurde der Fault durch eine nicht aktive Page ausgelös
Da es gesetzt ist, dachte ich es handelt sich um eine aktive Page = kein Fehler?
I'm in love with /dev/null

OsDevNewbie

  • Beiträge: 282
    • Profil anzeigen
    • YourOS Kernel
Gespeichert
« Antwort #16 am: 02. December 2015, 12:04 »
Bit 0 sagt aus, dass die Page aktiv ist, denn das Bit ist gesetzt. Allerdings ist mir aufgefallen, dass dein Programm versucht auf eine virtuelle Adresse zuzugreifen, auf welche der Userspace keine Berechtigungen hat (das Userflag in der Pagetable ist nicht gesetzt). Vielleicht solltest du da mal schauen, ob du den Stack des Programms richtig initialisierst.
« Letzte Änderung: 02. December 2015, 12:14 von OsDevNewbie »
Viele Grüsse
OsDevNewbie

Ein Computer ohne Betriebsystem ist nicht mehr wert als ein Haufen Schrott.
Ein Computer ist eine Maschine, die einem Lebewesen das kostbarste klaut, was sie selber nicht hat:
DIE ZEIT DES LEBENS

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #17 am: 02. December 2015, 12:16 »
Wenn ihr mal für zwei Sekunden nachdenkt, aus welchen Gründen eine Page noch nicht zugreifbar sein kann, wenn das Present-Bit nicht das Problem ist? Genau, entweder greift Userspace auf eine Kernelpage zu oder es wird auf eine schreibgeschützte Seite geschrieben. Oder anders gesagt: Ein Berechtigungsproblem.

Oder wenn ihr es mir immer noch nicht glaubt, lest das Intel-Manual: "The P flag indicates whether the exception was due to a not-present page (0) or to either an access rights violation or the use of a reserved bit (1)."

Edit: Aha, OsDevNewbie hat seinen Text doch noch angepasst. ;)
« Letzte Änderung: 02. December 2015, 12:19 von kevin »
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Tele

  • Beiträge: 13
    • Profil anzeigen
Gespeichert
« Antwort #18 am: 02. December 2015, 19:39 »
Es lag an meiner eigenen Schusseligkeit. Wie ihr schon geschrieben habt, waren meine Flags für vmm_alloc falsch gesetzt und so wurden die Task-Stacks
falsch alloziert. Dadurch kam der Zugriff auf eine Kernel-Page, welche ja für den Usertask gedacht war :-P

Paging funktioniert zumindest (vorerst..), jetzt gehts an den Scheduler!

Ich danke für Eure Hilfe!

Der Tele
I'm in love with /dev/null

 

Einloggen