Autor Thema: "Interface" der Pagemapping Funktion  (Gelesen 16044 mal)

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« am: 21. August 2011, 22:00 »
Mir geht es bei diesem Thema eigentlich um eine ganz einfache Frage, wie oft kommt es vor das man wirklich mehrere/viele physikalische Pages, die nicht zusammenhängend sind, mappen will?

Ich frage deswegen, weil ich gerade (oder besser immernoch :( ) dabei bin meinen Kernel nach C++ zu portieren und dabei auch gleich mal nen kleineren rewrite starte.

Gerade bin ich halt dabei den VMM zu portieren und da ist mir halt der Gedanke gekommen, das es (zumindest bei mir) sehr von Vorteil wäre, wenn man mit einem Funktionsaufruf mehrere Pages mappen könnte (ich kann das jetzt auch schon, allerdings nur zusammenhängende Pages). Aber da stellt sich mir dann die Frage ob man das eigentlich wirklich braucht? Denn wenn ich die Funktion darauf optimieren würde, das nur eine Page gemappt wird, kann sie auch ein Stückchen schneller werden und meiner Meinung nach ist das auch fast immer der Fall (außer beim Booten des Kernels) und würde so mehr Sinn machen und wenn man dann doch mal mehrere Pages mappen will, muss halt die Funktion mehrmals aufgerufen werden.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 21. August 2011, 23:49 »
Für normale Speicherallokationen ist es völlig egal, wie das Layout physisch aussieht. Eine Funktion, die Pages virtuell zusammenhängend, aber physisch einfach irgendwie verstreut alloziert, ist also für den Großteil genau das, was du willst. Treiber können unter Umständen auch mal mehr physisch am Stück wollen, wenn es dein PMM also hergibt, kann es nicht schaden, das auch anzubieten.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 22. August 2011, 10:22 »
Mir geht es nicht um das allozieren (das ist bei meinem PMM auf eine Page beschränkt), sondern um das Mappen, sprich das Eintragen in die PageTables (und alles was damit verbunden ist).

Ich würde die gerne auf den Average-Case optimieren und das sollten ja eigentlich einzelne Pages sein. Daher meine Frage wo man (außer bei Treibern, was ja eigentlich auch nur einmal passiert) mehrere Pages mit einmal mappen möchte.
Ansonsten wird ja, wenn ein Programm nach mehr Speicher fragt, dieser nicht sofort gemappt, sondern der wird ja Page für Page durch den Zugriff darauf gemappt (Paging-Exception).

Auf der einen Seite könnte ich mir die Schleife(n) sparen, was somit das Mappen einzelner Pages schneller macht und dafür wäre dann das Mappen mehrerer Pages eher langsam, weil dann die ganzen Überprüfungen die die Mapping Funktion so macht, dann für jede Page immer wieder gemacht werden und auch zwecks Locking ist das dann eher schlecht (was aber nicht so schlimm wäre, wenn sowas nicht alzu oft bis eher selten vorkommt).
Optimiere ich allerdings auf das Mappen mehrerer Pages auf einmal, dann würde dieser Vorgang sehr beschleunigt werden und vorallem wäre dann, bei meiner momentanen Idee, der Fehlerfall und das Aufräumen auch verdammt schnell.

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 22. August 2011, 10:57 »
Wenn du eine Funktion, die mehrere Seiten mappen kann, nur für eine einzelne Seite nutzt, ist der Overhead doch der Code, der für for (int i = 0; i < n; i++) mit n = 1 ausgeführt wird, oder?
Dieser Text wird unter jedem Beitrag angezeigt.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #4 am: 22. August 2011, 11:08 »
Ein wenig mehr ist es noch, weil die Funktion bei Fehlern auch noch ein wenig Aufräumarbeit macht (da muss dann ein wenig gerechnet werden und wieder ne Schleife durchgegangen werden), aber ich werde einfach meine Idee von gestern Abend noch umsetzen. Denn dadurch wird die Arbeit der Funktion noch weniger (auch für mehrere Pages).

Wenn ihr auch mehrere Pages (die nicht zusammenhängend sind) mappen könnt. Wie übergebt ihr die der Funktion?

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 22. August 2011, 12:18 »
Fehler sollten aber normalerweise nicht auftreten, also sollte der Vorgang für n=1 kaum Overhead produzieren. :evil:

Entwickle ein virtuelles Dateisystem (ähnlich /sys/) und zähle einfach mit, wie oft welche Größen gemappt werden. Dann weißt du, was das Optimierziel ist. ;-)

Die meiste größere Software (Firefox, Openoffice, ...) alloziiert relativ viel Speicher und nutzt den dann auch. Für jede einzelne Page die Exception abzuwarten und erst dann zu mappen wird in dem Fall teuer; schneller wäre es dann, wenn du bereits beim malloc() mappst (abhängig von irgendwelchen Kriterien).

Gruß,
Svenska

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #6 am: 22. August 2011, 12:34 »
Wenn ihr auch mehrere Pages (die nicht zusammenhängend sind) mappen könnt. Wie übergebt ihr die der Funktion?
tyndur mappt normal nur in Tateinheit mit Allokation, das macht die Interfaces gleich etwas einfacher. ;)

Redest du jetzt von physisch nicht zusammenhängend oder virtuell nicht zusammenhängend? Ich finde deinen Eingangsbeitrag sehr verwirrend, was das angeht. Du redest zwar von physischem Speicher, aber meinst das Mapping. Irgendwas passt da für mich nicht zusammen.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #7 am: 22. August 2011, 12:42 »
Hallo,


zum einen bin ich ebenfalls der Meinung das eine simple Schleife kein so großer Overhead ist (hier muss ich Svenska ausnahmsweise mal zustimmen ;)) und zum anderen bin auch der Meinung das MAP_NOW für die meisten Fälle die bessere Variante ist (zu diesem Thema hatte wir doch schon mal diskutiert) so das die meisten Aufrufe wohl doch mehrere Pages mappen wollen. Es gibt sicher etliche Situationen wo MAP_LAZY Vorteile bringen kann aber das ist IMHO nicht die Regel.


Grüße
Erik


PS.: "Tateinheit" klingt gut, ist hier aber auch wirklich zu treffend ;)
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 22. August 2011, 12:52 »
Zitat von: taljeth
Redest du jetzt von physisch nicht zusammenhängend oder virtuell nicht zusammenhängend? Ich finde deinen Eingangsbeitrag sehr verwirrend, was das angeht. Du redest zwar von physischem Speicher, aber meinst das Mapping. Irgendwas passt da für mich nicht zusammen.
Autor: Svenska    
Ich rede von virtuell zusammenhängend, aber nicht physisch zusammenhängend. Bisher kann ich entweder eine Page oder viele physisch zusammenhängende Pages mappen (physische Pages in den virtuellen Adressraum mappen).

Zitat von: svenska
Fehler sollten aber normalerweise nicht auftreten
Eigentlich nicht, aber da meine momentane Variante auch noch die PageTables mappt (und dafür eine Page braucht, was ja fehlschlagen könnte) und ich halt darauf überprüfe ob versucht wird, auf eine schon vorhandene Page zu mappen, kann es halt doch zu Fehlern kommen.

Ich habe bei meinem PMM (der auf Bitmaps basierte) auch immer überprüft ob die Page wirklich benutzt ist, bevor ich sie freigegeben habe und ich überprüfe auch immer das alles was auf Pages arbeitet, mir auch wirklich eine Pageadresse gegeben hat. Sollte/kann ich denn sowas weglassen?

Ich dachte eigentlich das Linux und Windows dieses MAP_LAZY machen?!

Auf eine Frage habt ihr aber noch nicht geantwortet, wie bekommt ihr mehrere Pages an die Mapping-Funktion übergeben? Ich habe ja kein malloc() und einfach mal ein statisches Array auf dem Stack kann ich auch nicht erzeugen (jedenfalls nicht zu groß), da ich im Kernel nur 4kb Stack habe.
Ich will das dann so machen, das der VMM immer schon die physischen Pages in den PageTables reinschreibt (und somit auch für das PageTable Mapping verantwortlich ist) und die Mapping-Funktion dann nur noch die Flags setzen muss und die entsprechenden TLB Einträge ungültig macht.

Zitat von: taljeth
tyndur mappt normal nur in Tateinheit mit Allokation, das macht die Interfaces gleich etwas einfacher.
Irgendwie steh ich hier aufm Schlauch  :-(

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #9 am: 22. August 2011, 13:32 »
Ich dachte eigentlich das Linux und Windows dieses MAP_LAZY machen?!
Zumindest für Linux stimmt das (bzw. bei einem anonymen Mapping wird erstmal eine Page voll Nullen mit COW gemappt, glaube ich).

Zitat
Zitat von: taljeth
tyndur mappt normal nur in Tateinheit mit Allokation, das macht die Interfaces gleich etwas einfacher.
Irgendwie steh ich hier aufm Schlauch  :-(
vaddr_t mmc_valloc(mmc_context_t* context, size_t num_pages, int flags);

Das Ding sucht sich einen virtuell zusammenhängenden Speicherbereich für num_pages Seiten raus und macht dann für jede einzelne Seite ein pmm_alloc(1), so dass die physische Anordnung egal ist. Es wird direkt gemappt, so dass auch kein zusätzliches Array oder irgendwas nötig ist.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #10 am: 22. August 2011, 14:04 »
Lazy mapping ist dann von Vorteil, wenn eine Anwendung erstmal präventiv Speicher reserviert, ihn aber erst später auch wirklich nutzt. Der Vorteil ist, dass die Anwendung schnell bereit ist und auf den Benutzer reagieren kann.

Sofortiges Mapping ist sinnvoll, wenn eine Anwendung den angeforderten Speicher sofort nutzt und führt zu einem sofort höheren Speicherbedarf, was wiederum das Speichermanagement unter Druck setzen kann (bisher nicht genutzter Speicher muss bei Knappheit nicht ausgelagert werden, wenn er aber gemappt ist, muss man ihn als benutzt ansehen). Außerdem ist es einfacher zu debuggen. ;-)

Optimal ist vermutlich eine Mischung aus beiden Systemen, wobei man die Randbedingungen sinnvoll wählen muss. Da hab ich aber keine gute Idee...

Zum Thema: Die meisten Allokationen, die durch den VMM müssen, sind größer als eine Page. Von daher sollte ein virtuell zusammenhängender Adressraum in einem Rutsch gemappt werden können. Wenn man das durch einzelne Syscalls machen muss, hat man bei mehreren Threads in einem Prozess, die gleichzeitig ein malloc() aufrufen, ein Problem.

Gruß,
Svenska

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #11 am: 22. August 2011, 15:37 »
Zitat von: taljeth
Das Ding sucht sich einen virtuell zusammenhängenden Speicherbereich für num_pages Seiten raus und macht dann für jede einzelne Seite ein pmm_alloc(1), so dass die physische Anordnung egal ist. Es wird direkt gemappt, so dass auch kein zusätzliches Array oder irgendwas nötig ist.
Das würde bei mir nicht klappen, weil ich mir dann doch noch eine extra Mapping-Funktion schreiben müsste. Zumal bei mir noch dazu kommt, das ich zw. Ein-CPU- und Mehr-CPU-System unterscheide und das zwei unterschiedliche Funktionen sind (vom Code her).
Ich habe das alles schön voneinander getrennt.

Zitat von: svenska
Die meisten Allokationen, die durch den VMM müssen, sind größer als eine Page.
Richtig, allerdings bin ich ja von MAP_LAZY ausgegangen.

Zitat von: svenska
Von daher sollte ein virtuell zusammenhängender Adressraum in einem Rutsch gemappt werden können. Wenn man das durch einzelne Syscalls machen muss, hat man bei mehreren Threads in einem Prozess, die gleichzeitig ein malloc() aufrufen, ein Problem.
Da hast du das dann nicht richtig verstanden. Speicher per Syscall anfordern und den Speicher mappen sind in dem Sinne voneinander getrennt.
Das Programm macht nen Syscall des es Speicher haben will (Vmm::allocUser()), diese Funktion versucht dann einen Bereich aus dem virtuellen Adressraum zu bekommen (VmmAddrSpace::m_Vmm.alloc()) und wenn das geklappt hat, wird in einer Schleife, immer eine Page vom PMM geholt (Pmm::alloc4kb()) und gemappt (Vmm::mapUser()).
Von daher hat das eine (Mappen einer oder mehrerer Pages), in dem Sinne, nichts mit dem anderen (allozieren von Speicher) zu tun.

Zitat von: svenska
Optimal ist vermutlich eine Mischung aus beiden Systemen, wobei man die Randbedingungen sinnvoll wählen muss. Da hab ich aber keine gute Idee...
Das ist aber schade.

Sollte man das komplett dem Programm überlassen? Ich würde dann nur den Stack per MAP_LAZY mappen und alle Allozierungen muss dann das Programm wissen ob es MAP_LAZY haben will oder nicht.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #12 am: 22. August 2011, 17:46 »
Hallo,


Das würde bei mir nicht klappen, weil ich mir dann doch noch eine extra Mapping-Funktion schreiben müsste.
Dann lagere das Mapping doch in eine kleine Extra-Funktion aus. So hast Du nur einmal diesen Code und dem Compiler steht es immer noch frei das zu inlinen (was er bei statischen und kleinen Funktionen auch gerne tut und wenn er das nicht tut dann hat er seine Gründe).

allerdings bin ich ja von MAP_LAZY ausgegangen.
Das bringt zwar manchmal was aber IMHO nur selten. Ich persönlich bin der Meinung das die Programme die wissen das sie den Speicher nur vorsorglich allozieren das dem OS auch passend mitteilen sollten und für den Rest ist, so denke ich, MAP_MAY_NOW die beste Default-Option.
Für Dinge wie den Stack ist das sicher anders aber die Mehrheit der malloc-Aufrufe dürfte dem Zweck dienen den Speicher auch zeitnah zu nutzen.

Beim MAP_LAZY sehe ich aber noch ein kleines Problem: das OS sollte wissen wie vielen Speicher es den Anwendungen insgesamt zugesichert hat und sicherstellen das dann später auch tatsächlich mit physischen Speicher hinterlegt werden kann. Das ist nicht schwer aber man muss es berücksichtigen. Es wäre mMn recht unhöflich wenn der malloc-Syscall erfolg meldet aber das Programm dann trotzdem wegen Out-of-Memory gekillt wird (ja ich weiß dass das mit dem OOM-Killer trotzdem noch passieren kann aber der sollte IMHO nur der letzte Notnagel sein, im regulären Betrieb ohne SW-Amokläufe sollte es keine OOM-Signale geben sondern nur negative Rückgabewerte beim malloc-Syscall).


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #13 am: 22. August 2011, 17:55 »
Overcommit wird im allgemeinen als Feature angesehen, nicht als Bug. Soweit ich weiß, kann man Linux entsprechend konfigurieren, dass es nicht mehr overcommittet, dann kommt auch kein OOM-Killer mehr zum Zug, sondern es kann höchstens mal eine Allokation schiefgehen. Dafür verschenkst du halt ungenutzten Speicher.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #14 am: 22. August 2011, 18:47 »
Zitat von: erik
Dann lagere das Mapping doch in eine kleine Extra-Funktion aus. So hast Du nur einmal diesen Code und dem Compiler steht es immer noch frei das zu inlinen (was er bei statischen und kleinen Funktionen auch gerne tut und wenn er das nicht tut dann hat er seine Gründe).
Ist genau das was ich mache, ich wollte eigentlich nur sagen, das es bei meinem Design (hat vorallem viel mit dem Init des Kernels zu tun) nicht geht. Auch inlinen geht nicht, da der Compiler nicht wissen kann welche Funktion (ob Ein- oder Mehr-CPU-System) zur Laufzeit genutzt wird (es wird trotzdem kein indirekter-Call gemacht).

@taljeth

Wie mappt ihr dann Speicher für Geräte? Da musst du doch den Speicher an die Mapping-Funktion übergeben und kannst das nicht den PMM erledigen lassen? Sag nur dafür habt ihr extra eine Funktion (was ja dann doppelter Code wäre)?

@all

Was den OOM-Killer betrifft, wie will man den eigentlich vernünftig implementieren?

Zumal theoretisch ja alleine schon durch Swapping eine 4kb Page ausreichen sollte damit jeder Prozess seinen gesamten Adressraum nutzen kann, dass das nicht praxistauglich ist, ist mir klar.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #15 am: 22. August 2011, 18:57 »
Soll ich dir ein Geheimnis verraten? Funkionen können sich gegenseitig aufrufen. ;)

mmc_valloc() ruft intern natürlich mmc_map() auf, um die einzelnen Pages zu mappen, und das kann man über einen anderen Syscall auch direkter erreichen. Aber in diesem Pfad wird halt kein physischer Speicher alloziert, sondern einfach nur ein bestimmte Adresse gemappt.

OOM-Killer und vernünftige Implementierung widerspricht sich. Der OOM-Killer ist ein Hack, um eine aussichtslose Situation noch halbwegs zu retten.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #16 am: 22. August 2011, 19:34 »
Zitat von: taljeth
mmc_valloc() ruft intern natürlich mmc_map() auf, um die einzelnen Pages zu mappen
Ich denke sowas gibt es by tyndur nicht? (ein paar Posts weiter zurück, hattest du doch geschrieben, das ihr direkt mappt, ohne eine Funktion dazwischen, der die Page übergeben wird)

Sprich ihr habt das genauso wie ich bisher, aber macht ihr keine Überprüfungen ob die Parameter die der Funktion übergeben werden, in Ordnung sind? Und ruft ihr dann für den Speicher für die Geräte auch immer für jede Page diue Funktion auf?

Zitat von: taljeth
Der OOM-Killer ist ein Hack, um eine aussichtslose Situation noch halbwegs zu retten.
Das wiederspricht sich für mich auch. Denn wenn gerade das Programm abgeschoßen wird, mit welchem gerade gearbeitet wurde und die Daten dann weg sind, wäre es auch egal gewesen ob der gesamte PC abgeschoßen worden wäre.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #17 am: 22. August 2011, 20:19 »
mmc_map() mappt genau einen virtuell zusammenhängenden Bereich auf genau einen physisch zusammenhängenden Bereich, braucht also auch keine aufwendigen Datenstrukturen oder Arrays als Parameter. Das reicht, um ein mmc_valloc() zu implementieren, das einen virtuell zusammenhängenden Bereich alloziert und physisch (nicht zusammenhängend) irgendwohin mappt.

Und wenn du keinen OOM-Killer willst, darfst du halt nicht overcommitten.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #18 am: 22. August 2011, 20:29 »
Wäre wahrscheinlich ein extra Thread, aber wie sehr vertraut ihr eurem eigenen Code? Ich meine damit, ob ihr bei "inneren" Funktionen (z.B. die Mapping-Funktion, die eigentlich nicht von außen, sondern nur aus dem Kernel, mit Parametern die direkt vom PMM kommen, aufgerufen werden) trotzdem bestimmte Sicherheitschecks macht?

Ich überprüfe z.B. beim PMM ob die Page die freigegeben werden soll, auch wirklich ne Page ist (das mache ich auch bei anderen Funktionen, wie nem vfree()). Selbst bei "inneren" Funktionen.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #19 am: 22. August 2011, 21:03 »
Hallo,


Overcommit wird im allgemeinen als Feature angesehen, nicht als Bug.
Da bin ich ja froh das ich nicht allgemein bin. ;)
Im Ernst, für mich persönlich ist Overcommit ein Bug, dafür gibt es doch Swapping damit Speicher der zwar alloziert ist aber nicht benutzt wird nicht zwingenst den physischen RAM zumüllen muss.


aber wie sehr vertraut ihr eurem eigenen Code?
Nicht allzu sehr, ich habe normalerweise am Anfang jeder Funktion ein paar Plausibilitätsprüfungen drin, nur das die bei inneren Funktionen oft zwischen #if DEBUG und #endif stehen.


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

 

Einloggen