Autor Thema: SLAB-Allocator als Basis fuer kmalloc?  (Gelesen 37959 mal)

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #60 am: 25. May 2011, 21:41 »
Hallo,


aber um sicher zu gehen, würde ich das in die Anforderungen an den Slab-Allocator mit aufnehmen.
Jo, besser is das.

Zumal mir auch nichts anderes einfällt außer OOM, was da in einem Konstruktor schief gehen könnte (im Kernel)!?
Hm, stimmt auch wieder, zumindest in einem Micro-Kernel.

Naja, das mit der Magazinverwaltung ist nicht das Problem, weil mein Slab-Allocator erstmal nur einzelne Objekte rausgibt
Aber was ist wenn Deine Magazinverwaltung ein neues Magazin voll machen möchte und von den X SLAB-Allocator-Aufrufen 2 Aufrufe schief gingen? Dann ist ja das Magazin gar nicht voll, wie gehst Du damit um?

da ich ein oder zwei Objekte habe die den Magazinlayer nicht benutzen sollen (zwecks Verschwendung).
Ja, ich wollte den Thread-Descriptor (wegen einem extra Cache-Mechanismus für komplette Threads) und den Process-Descriptor (wird zu selten benötigt) nicht über so einen Magazinlayer laufen lassen, alles andere wohl schon (wenn ich denn mal sowas einbaue).

Aber ich sehe da erstmal nicht so das Problem (kann aber gut sein, das es da welche gibt, dann immer her damit), zumal du eh irgendwann anfangen musst die IDs zu recyceln.
Bei guter SW sollte es überhaupt keine Probleme machen wenn IDs schnell recycelt werden aber da kann man sich ja nie ganz drauf verlassen, weswegen ich IDs lieber global in eine sortierte Kette packen möchte (also immer vorne weg nehmen und hinten dran hängen). Bei den Thread-IDs könnte man es ja so machen das immer beim Auffüllen des CPU-lokalen Cache-Mechanismus gleich X IDs auf einmal geholt werden welche dann beim tatsächlichen Ausliefern der Threads der Reihe nach benutzt werden und auch beim Abgeben von Threads werden immer erst X IDs gesammelt bevor diese dann gebündelt in den ID-Pool zurück kommen, das macht zumindest die zugehörigen Aufrufe seltener und reduziert damit die Kosten (ich hoffe einfach mal dass das reicht).

aber dann muss ich meine Thread-Struktur vergrößern, weil da ja dann alle Register mit drin gespeichert werden müssen
Was bei x86 ja so enorm viel ist, da hast Du echt mein vollstes Mitleid. Was soll ich da erst mit meinen 64 Registern (von denen ich 8 Stück noch aus speziellen Schattenregistern holen muss weil die CPU die beim Wechsel vom User-Mode in den System-Mode einfach nur schnell ausblendet) + 16 Segmentregistern sagen? Das ist einer der Gründe warum ich beim synchronen IPC direkt vom Aufrufenden Thread in den PopUp-Thread wechseln möchte, da brauch ich dann vom aufrufenden Thread nur die Callee-Saves in dessen Thread-Descriptor zu packen bzw. beim Ende vom PopUp-Thread wiederherstellen um dann da das IRET zu machen. Für den PopUp-Thread muss ich fast gar nichts vorbereiten (außer dafür sorgen das keine Register vom Caller oder vom Kernel für den PopUp-Thread sichtbar sind) so das wenn der PopUp-Thread noch innerhalb der Zeitscheibe des Caller-Threads fertig wird und er nicht geschedult werden muss gar keine Register überhaupt in dessen Thread-Descriptor landen. Eben weil der PopUp-Thread ja u.a. einen leeren Kontext bekommt ist dessen Thread-Erstellung ja so billig.

und was viel schwieriger sein wird, ich müsste zusehen, dass ich so ziemlich alle Spinlocks wegbekomme.
Ja, da muss man halt von Anfang an entsprechend aufpassen. ;)


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #61 am: 25. May 2011, 21:56 »
Zitat von: erik
Aber was ist wenn Deine Magazinverwaltung ein neues Magazin voll machen möchte und von den X SLAB-Allocator-Aufrufen 2 Aufrufe schief gingen? Dann ist ja das Magazin gar nicht voll, wie gehst Du damit um?
Gegenfrage, wie gehst du mit nem nicht ganz vollem Magazin um? Ich benutze es einfach, bis es leer ist ;)

Bei mir gibt es nicht so richtig Magazine wie es im Slab-Allocator Design vorgesehen ist. Bei mir ist ein Magazin einfach nur ein kleiner Stack (im Mom von 4 Objekten) und wenn das Magazin (bzw. in meinem Fall beide) leer ist, wird es halt wieder aufgefüllt. Wenn das Auffüllen nicht klappt (womit ich ja rechnen muss) ist das nicht weiter wild, es sei denn es konnte gar kein Objekt aufgefüllt werden.

Zitat von: erik
Bei guter SW sollte es überhaupt keine Probleme machen wenn IDs schnell recycelt werden aber da kann man sich ja nie ganz drauf verlassen
Naja, das ich als OS Programmierer um Hardwarefehler drumrum programmieren muss, ok das sehe ich noch ein, aber das ich um Softwarefehler drumrum programmieren muss, sehe ich nicht ein.

Was die Spinlocks betrifft, sind die nicht das eigentliche Problem, sondern das ich Spinlocks immer mit Algos verbinde die nicht skalieren und wenn man dann einen nicht unterbrechbaren Kernel hat, kann das schonmal in Zeiten ausarten wo es anfängt mit wehtun.
Auf der anderen Seite, würde das die Performance auf Single-CPU System (vorallem auf alten, wo cli/sti richtig teuer sind) doch ganz schön verbessern.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #62 am: 26. May 2011, 12:16 »
Hallo,


Gegenfrage, wie gehst du mit nem nicht ganz vollem Magazin um?
Gute Frage, hab ich noch nicht allzu gründlich drüber nachgedacht.
Ich schätze ich werde mich für Variante 3 entscheiden, da ich ja Bündelanfragen an den SLAB-Allocator geben möchte wird dieser eh entweder ein Magazin komplett befüllen (wenn er genug Objekte hat bzw. einen neuen SLAB-Block vom VMM bekommen kann) oder er wird nichts tun (wenn er keinen weiteren SLAB-Block vom VMM bekommt) und einen Fehler zurück melden. Ich schätze ich werde daher wohl nicht mit nur halb befüllten Magazinen umgehen müssen.

Wobei ich immer noch nicht erkennen kann worin Du den Vorteil der direkten Konstruktor-Aufrufe durch den SLAB-Allocator siehst. Ich bin eher der Meinung das Du Dir damit vor allem zusätzliche Probleme in die Speicherverwaltung holst.

Naja, das ich als OS Programmierer um Hardwarefehler drumrum programmieren muss, ok das sehe ich noch ein, aber das ich um Softwarefehler drumrum programmieren muss, sehe ich nicht ein.
lol    (das mit der Hardware siehst Du ja auch nur so weil Du die Hardware nicht ändern kannst)

Wenn Du die IDs nicht so schnell recycelst hat das OS weniger potentielle Angriffsfläche weil (absichtlich) fehlerhafte Syscall dann eher ins Leere laufen weil die IDs ja ungültig sind. Etwas "Security by Obscurity" ist auf jeden Fall besser als gar nichts.

Was schätzt Du denn wie viele IDs insgesamt (also alles inklusive Prozess-IDs, Thread-IDs, Port-IDs ....) Du so überhaupt pro Tag Laufzeit mit ordentlicher Last benötigen wirst?

und wenn man dann einen nicht unterbrechbaren Kernel hat, kann das schonmal in Zeiten ausarten wo es anfängt mit wehtun.
Das einztigste was mir in einem Micro-Kernel einfällt was auch mal erheblich Zeit kosten kann ist die Speicherverwaltung aber da davon ja wieder fast alles abhängig ist sind wohl auch fast alle Syscalls betroffen. Ich denke hier sollte man die Speicherverwaltung so gut bauen das die in den meisten Fällen schnell arbeitet und eben nur möglichst selten mal ein langsamer Fall eintritt. Wenn man das gut genug hinbekommt sollte es sich wieder rechnen.


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #63 am: 26. May 2011, 12:37 »
Zitat von: erik
Wobei ich immer noch nicht erkennen kann worin Du den Vorteil der direkten Konstruktor-Aufrufe durch den SLAB-Allocator siehst.
Die Anzahl der Konstruktor Aufrufe ist bei meinem Design wesentlich geringer als bei dir.

Bei mir wird ein neue Objectcache erstellt und alle Objekte werden gleich konstruiert (wo ich dann wirklich so Sachen wie ID und im Falle von Threads noch den Kernel-Stack machen will).
Bei dir muss jedes Mal wenn ein Objekt rausgegeben wird, erst ne ID geholt und der Stack geholt werden. Wenn du dein Objekt wieder freigibst, gibst du also auch die ID und den Stack wieder frei.
Bei mir werden ID und Stack erst wieder freigegeben, wenn der Objectcache freigegeben wird.

Zitat von: erik
das mit der Hardware siehst Du ja auch nur so weil Du die Hardware nicht ändern kannst
Jein, weil man solche Hardwarefehler im Allgemeinen nur durch neue Hardware und nicht durch ein Softwareupdate beheben kann.

Um es mal überspitzt zu sagen, du hast nen Programm wo du eine bestimmte Funktion nutzen willst. Diese Funktion kannst du aber immer nur nutzen kurz nachdem du das Programm gestartet hast, sprich jedes Mal wenn du diese Funktion nutzen willst, musst du das Programm beenden und dann wieder neustarten um dann schnell die Funktion zu nutzen.
Das ist für mich ganz klar ein Bug/Softwarefehler und um sowas programmieren oder "nutze" ich nicht herum. Das kann man durch nen Update beheben.
Ich meine stell dir mal vor, nur weil irgendein Programm nen Bug hat, muss der Kernel geändert werden, damit das Programm läuft?!

Zitat von: erik
Was schätzt Du denn wie viele IDs insgesamt (also alles inklusive Prozess-IDs, Thread-IDs, Port-IDs ....) Du so überhaupt pro Tag Laufzeit mit ordentlicher Last benötigen wirst?
Ich habe keine Ahnung, aber bei mir ist das durch meinen Algo schon nicht einfach oder praktikabel die IDs nicht schnell oder gleich wiederzuverwenden. ID Recycling ist ohnehin nicht so einfach, wenn man dann auch noch möchte das die IDs erst nach nem Überlauf wiederverwendet werden (das sollte unter POSIX so sein glaub ich).

Zitat von: erik
Das einztigste was mir in einem Micro-Kernel einfällt was auch mal erheblich Zeit kosten kann ist die Speicherverwaltung aber da davon ja wieder fast alles abhängig ist sind wohl auch fast alle Syscalls betroffen.
Richtig, deswegen muss das auch am meisten optimiert werden und ich denke mal meine Speicherverwaltung ist noch nicht so die Bombe.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #64 am: 26. May 2011, 15:03 »
Zitat von: erik
Das einztigste was mir in einem Micro-Kernel einfällt was auch mal erheblich Zeit kosten kann ist die Speicherverwaltung aber da davon ja wieder fast alles abhängig ist sind wohl auch fast alle Syscalls betroffen.
Richtig, deswegen muss das auch am meisten optimiert werden und ich denke mal meine Speicherverwaltung ist noch nicht so die Bombe.
Das ist richtig, allerdings bin ich erstmal lieber für eine langsame, aber zuverlässige Speicherverwaltung. Dann kann man anderes, für ein OS wichtigeres Zeug drauf implementieren (z.B. Userspace) und schauen, wie es im realen Leben performt.

Optimieren kann man später immernoch, wenn es zu langsam ist.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #65 am: 26. May 2011, 15:53 »
Das Problem mit der späteren Optimierung ist, dass ich bestimmte Design Entscheidung treffen muss/will und dazu muss ich dann woanders auch wieder Entscheidungen treffen und ganz konkret heißt dass, wenn ich meinen Kernel nicht unterbrechbar machen will, muss der worst-case bestimmten Anforderungen gerecht werden.
Erst später den Kernel nicht unterbrechbar zu machen, wäre dann doch ein wenig viel Arbeit, würde schon in die Richtung rewrite gehen ;) Das würde ich gerne vermeiden.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #66 am: 26. May 2011, 17:24 »
Hallo,


Die Anzahl der Konstruktor Aufrufe ist bei meinem Design wesentlich geringer als bei dir.
Hä, ich hab gar keine Konstruktoren. Ich sehe darin auch keinen echten Sinn da sich der meiste Inhalt für ein neues Objekt IMHO erst dann ergibt wenn es wirklich konkret eingesetzt werden soll. Außerdem ist der Performance-Verlust durch das Schreiben weiterer Daten ziemlich klein wenn das Objekt eh schon im lokalen CPU-Cache ist, da Du ja auf jeden Fall zumindest etwas in das neue Objekt schreiben wirst muss es eh in den CPU-Cache geladen werden und von daher ist das kein großes Problem mehr ob da nun 70% oder 100% der Daten geschrieben werden. Dafür muss mein SLAB-Allocator gar nicht in die Objekte rein greifen.
Das nächste ist das ich einige der Objekt-Größen(Typen) für unterschiedliche Dinge einsetzen möchte so das der SLAB-Allocator gar nicht einen bestimmten Konstruktor aufrufen kann. Warum sollte ich auch für 2 unterschiedliche Objekt-Typen die aber die selbe Größe haben unterschiedliche SLABs einsetzen, macht doch gar keinen Sinn.

Der Fall mit den Thread-Descriptoren ist tatsächlich etwas anders aber da habe ich doch auch beschrieben das ich dafür einen extra Cache-Mechanismus bauen möchte der eben nicht nur den nackten Thread-Descriptor sondern auch noch den Stack und passende IDs vorrätig hält.

Ich meine stell dir mal vor, nur weil irgendein Programm nen Bug hat, muss der Kernel geändert werden, damit das Programm läuft?!
Das ist schon richtig, was ich meinte ist eher das Dein Kernel eine geringere Angriffsfläche hat wenn die IDs nicht so einfach vorhersehbar sind. Wenn Du für Threads immer wieder die selben IDs benutzt dann könnte es sein das für einen Thread eine Aktion ausgeführt wird die eigentlich für einen anderen Thread gedacht war aber dieser andere Thread sich kurz vorher beendet hat. Wenn man die IDs nicht sofort sondern erst später recycelt dann fallen solche Bugs eher auf und es ist auch schwieriger mit diesem Wissen einen Angriff auf andere Threads zu unternehmen.


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

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #67 am: 29. May 2011, 09:57 »
Hallo,


Bei mir wird ein neue Objectcache erstellt und alle Objekte werden gleich konstruiert (wo ich dann wirklich so Sachen wie ID und im Falle von Threads noch den Kernel-Stack machen will).
Bei dir muss jedes Mal wenn ein Objekt rausgegeben wird, erst ne ID geholt und der Stack geholt werden. Wenn du dein Objekt wieder freigibst, gibst du also auch die ID und den Stack wieder frei.
Bei mir werden ID und Stack erst wieder freigegeben, wenn der Objectcache freigegeben wird.
Da fällt mir gerade noch ein Problem auf: wenn Du den Stack sofort recycelst dann sind da ja noch die Daten des vorherigen Threads drin und das könnte ein ernstes Sicherheitsrisiko sein. Aus diesem Grund möchte ich den Prozessen nur Speicher geben der vorher zu 100% mit 0x00 überschrieben wurde.
Effizientes Recyceln ist sicher ein wesentlicher Faktor zu hoher Performance aber das darf keine Sicherheitslöcher aufreißen.


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #68 am: 29. May 2011, 12:36 »
Zitat von: erik
Da fällt mir gerade noch ein Problem auf: wenn Du den Stack sofort recycelst dann sind da ja noch die Daten des vorherigen Threads drin und das könnte ein ernstes Sicherheitsrisiko sein.
Ich recycle sofort den KernelStack! Wieso sollte das ein Sicherheitsrisiko sein? Ich gehe doch mal davon aus, das wir unseren Kerneln vertrauen können ;)
Zumal kein Programm Zugrif auf diese Daten hat, sondern nur der Kernel.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #69 am: 29. May 2011, 21:18 »
Hallo,


Ich recycle sofort den KernelStack!
Äh, stimmt. Hab ich doch glatt übersehen, sorry. Wie machst Du das eigentlich mit dem User-Mode-Stack? Ich gehe doch mal davon aus das die Mehrheit Deiner Threads dazu dient den User-Mode mit Leben zu füllen.

Ich möchte ja die User-Mode-Stacks cachen aber eben zwischen das Recyceln noch den Memory-Cleaner stellen. Wobei das alles natürlich auch ne ganz schöne Speicherverschwendung wird. Stell sich mal einer vor: 8 CPUs mit je 32 User-Mode-Stacks a 8 kB (oder auch mehr falls mal ein Stack vergrößert werden musste) auf Vorrat und dazu noch das was so im Memory-Cleaner und sonstwo gerade steckt, das macht locker mal 2 bis 3 MB Speicher und das alles nur für die Performance. Ich schätze unter 32 MB wird es mein OS erst gar nicht machen.


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #70 am: 29. May 2011, 21:24 »
Ich handhabe es so, wenn ein Prozess der Meinung ist das er so wichtige Daten hat, das andere Prozesse diese nicht sehen dürfen, dann muss er ein Flag setzen und alle Pages aus diesem Prozess werden genullt, aber für alle anderen Prozesse mache ich mir da keinen Kopf.

Was die UserMode Stacks angeht, wie stellst du dir das bei nem normalen System vor? Problem ist ja, das ich immer nur per Prozess cachen könnte (ist ne Möglichkeit, aber da verschwendest du dann richtig Speicher, weil stell dir vor ein Prozess braucht nur einen Thread und du cachest aber immer 8!).
Eine andere Möglichkeit (habe ich so bei Haiku gesehen) ist, dass man einfach alle Threads die beendet wurden in eine Art Cache an den Prozess heftet und wenn der Prozess mal wieder nen Thread braucht ist schon alles da. Das kostet allerdings auch Speicher, aber wäre wahrscheinlich sogar für Services sehr vorteilhaft.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #71 am: 29. May 2011, 21:52 »
Hallo,


Ich handhabe es so, wenn ein Prozess der Meinung ist das er so wichtige Daten hat, das andere Prozesse diese nicht sehen dürfen, dann muss er ein Flag setzen ....
Is ne Variante aber damit rechnet wohl kaum ein Programm. Wimre sichert Windows zu das neuer Speicher immer genullt ist, wie das unter Linux läuft weiß ich jetzt nicht aber ich vermute mal in etwa ähnlich. Das nächste ist das viele Programme ja gar nicht wissen ob sie wichtige Daten bearbeiten oder nicht (das Office-Paket kann ja nicht ahnen ob Du nur nen langweiligen Brief an Deine Oma schreibst (soll keine Abwertung Deiner Oma sein) oder wichtige Firmendaten oder gar Top-Secret-Forschungsergebnisse aufbereitest).
Wenn dann würde ich Sicherheit als Default-Vorgabe benutzen oder willst Du ein weiteres Windows bauen? ;)
Das einzigste wo ich das andersrum machen möchte ist das Swappen, per Default darf alles geswappt werden und nur dann wenn ein Prozess das für ein Segment verbietet wird dieses Segment eben nicht geswappt. So wird das wimre auch unter Windows und Linux gehandhabt (dort kann man gezielt einzelne Pages vom Swapping ausschließen).

Was die UserMode Stacks angeht, wie stellst du dir das bei nem normalen System vor?
Na der lineare Speicher wird einfach nicht freigegeben (bei Dir dann der physische Speicher) und bei Bedarf in den richtigen Prozess als neues Segment eingeblendet indem in dessen LDT ein passender Segment-Descriptor eingetragen wird (bei Dir in dem in das richtige Paging-Directory ein paar passende Einträge kommen). Ich kann da erstmal keine Probleme sehen.

Problem ist ja, das ich immer nur per Prozess cachen könnte
Warum? Wo siehst Du da das Problem?
Davon würde ich eben wegen der Speicherverschwendung abraten. Die Threads möchte ich auch pro CPU cachen um eben eine möglichst hohe Cache-Trefferquote zu bekommen.

aber wäre wahrscheinlich sogar für Services sehr vorteilhaft.
So eine Situation möchte ich bei meinen IPC-PopUp-Threads (für die Handler) benutzen. Wenn ein Handler fertig ist und sein Ergebnis dem Aufrufer mitteilen möchte dann schau ich kurz nach ob noch weitere Anfragen gequeued wurden und erstelle dann sofort einen neuen Thread (der aber nicht gleich los läuft sondern nur in die runnable-Liste kommt) für den ich auch den Stack direkt recyceln möchte, ich denke innerhalb des selben Prozesses ist das auch kein Sicherheitsrisiko.


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #72 am: 29. May 2011, 22:07 »
Zitat von: erik
Das nächste ist das viele Programme ja gar nicht wissen ob sie wichtige Daten bearbeiten oder nicht (das Office-Paket kann ja nicht ahnen ob Du nur nen langweiligen Brief an Deine Oma schreibst (soll keine Abwertung Deiner Oma sein) oder wichtige Firmendaten oder gar Top-Secret-Forschungsergebnisse aufbereitest).
Der Punkt geht an dich ;) Also doch alles nullen, aber das würde ich dann den PMM machen lassen.

Zitat von: erik
Warum? Wo siehst Du da das Problem?
Naja, wenn ich nen Stack alloziere, dann brauche ich dafür physischen Speicher (Pages) und auch virtuelle Adressen und bei mir geht es um die virtuellen (oder sind die physischen Pages vom Cache her wichtiger?) und das kann ich ja nur per Prozess cachen.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #73 am: 29. May 2011, 22:29 »
Hallo,


aber das würde ich dann den PMM machen lassen.
Zum einen hab ich keinen PMM und zum anderen möchte ich das die Hardware machen lassen (lauter dumme Null-Bytes zu schreiben finde ich zu langweilig für ne wertvolle CPU die mit ihrer Zeit auch was besseres anfangen kann). Wäre ja cool wenn der klassische PC-DMA-Controller das könnte aber ich fürchte mal eher nicht.

Die virtuellen Adressen für den neuen Stack sollten sich doch recht schnell finden lassen, an die Position und Ausrichtung von Stack werden in einem Flat-Memory-OS doch üblicherweise bestimmte Kriterien gestellt. Zur Not könntest Du genau das ja dann in einer Prozess-spezifischen Struktur cachen, ich meine Du betrachtest einfach den physischen Speicher von den virtuellen Adressen getrennt und cachest beides unabhängig von einander.


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #74 am: 30. May 2011, 10:27 »
Zitat von: erik
Die virtuellen Adressen für den neuen Stack sollten sich doch recht schnell finden lassen, an die Position und Ausrichtung von Stack werden in einem Flat-Memory-OS doch üblicherweise bestimmte Kriterien gestellt.
Naja, üblicherweise heißt für mich immer klassische Unix-Architektur ;) Sprich der Stack wächst vom Ende des Adressraum nach unten und der Heap nach oben (vom Ende des Programms).

Erstens ist das seit sowas wie ALR nicht mehr drin und diese alte Architektur (wie Unix insgesamt da so seine "Probleme" hat) ist nicht gerade für Multithreading geeignet.

Von daher gibt es bei mir in dem Sinne gar keine Anforderungen an den Stack, nur das er wahrscheinlich eine festgelegte Größe haben wird (die man zur Erstellungszeit angeben kann), aber der nicht unbedingt größer werden kann.

Zitat von: erik
Zur Not könntest Du genau das ja dann in einer Prozess-spezifischen Struktur cachen, ich meine Du betrachtest einfach den physischen Speicher von den virtuellen Adressen getrennt und cachest beides unabhängig von einander.
Naja, den physischen Speicher zu cachen halte ich für überflüssig. Denn wenn man nen Stack für die freien Pages nimmt, ist das nicht wirklich ein Performanceproblem (kann zu einem werden, wenn zu viele auf einmal Pages haben wollen).

Noch was anderes zur Sicherheit, wo liegt eigentlich das Problem wenn noch die alten Daten in den Pages stehen? Ich meine woher will das Programm wissen ob das jetzt zufällige Daten sind, die in menschlich lesbarer Form zufällig Sinn ergeben oder ob das genau die Daten sind?
Treiber können ohnehin den ganzen Speicher auslesen, wenn sie denn wollen und von daher können ja nur Programme ein Sicherheitsrisiko sein.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #75 am: 30. May 2011, 11:18 »
Hallo,


Das mit dem nicht mehr ganz so starren Stack ist auch wieder wahr. Dann würde ich eventuell doch den virtuellen Adressraum des Stacks (den wirst Du doch sicher immer gleich komplett allozieren und dann später nach Bedarf mit echten Pages hinterlegen) prozess-spezifisch cachen. Wenn Du in Deinen Prozessen einfach ein paar MB virtuellen Speicher für die potentielle Benutzung als Stack zur Seite legst sollte das mMn kein Problem darstellen, echten Speicher kostet das zumindest nicht (im Gegensatz zu all den anderen Beschleunigungsmaßnahmen).


Naja, den physischen Speicher zu cachen halte ich für überflüssig. Denn wenn man nen Stack für die freien Pages nimmt, ist das nicht wirklich ein Performanceproblem (kann zu einem werden, wenn zu viele auf einmal Pages haben wollen).
Okay, vom dem Problem der Gleichzeitigkeit mal abgesehen hast Du es da natürlich einfacher als ich. Ich muss User-Segmente (und eben auch Stack) immer vom Kernel-VMM (der bei mir den einen globalen linearen Adressraum managed) holen und das ist nicht ganz so schnell wie einfach eine beliebige Page vom Stapel zu nehmen (wobei Du das IMHO auch CPU-lokal cachen solltest). Deswegen ist bei mir das Cachen von Threads gerade auch das Cachen des zugehörigen User-Mode-Stacks.
Da fällt mir ein: was ist mit dem TLS-Speicher? Möchtest Du denn auch in irgendeiner Form cachen? (Falls der Prozess überhaupt TLS benutzt aber da das jetzt auch Bestandteil der offiziellen C/C++-Spezifikation ist/wird dürfte man das in Zukunft häufiger antreffen)

Das Ausnullen des physischen Speichers bevor er verwendet wird ist eher eine Art vorbeugende Vorsichtsmaßnahme. Klar könnte man sich jetzt die Frage stellen ob das in einem Hobby-OS überhaupt lohnt oder nicht eher nur eine unnütze Zusatzkomplexität darstellt (ich denke die überwiegende Mehrheit der OS-Dever hat sich für letzteres entschieden und einfach drauf verzichtet). Das Problem ist das Computer für gewöhnlich streng deterministische Systeme sind und sich daher aus solchen Lücken oft doch gut funktionierende Angriffe bauen lassen. Ich bin zwar der Meinung das sich das Ausnullen des Speichers beim Recyceln in den selben Prozess nicht lohnt aber theoretisch könnte ja in einem Service-Handler ein Bug sein der für den Aufrufer den Stack ausließt und so vielleicht die Daten eines anderen Service-Nutzers sichtbar macht (auch wenn die Daten so nicht manipulierbar sind aber selbst die Sichtbarkeit kann schon eine ernst Lücke sein). An dieser Stelle bin ich aber eben der Meinung das ich besser den Bug im Service fixen sollte anstatt da mit Hilfe des OS-Kernels etwas zu kaschieren.

In einem Micro-Kernel-OS sollten eigentlich auch Treiber nicht in der Lage sein beliebigen Speicher zu Lesen/Schreiben und mit einer anständigen IO-MMU (die nur vom Kernel selber bedient wird) sollte das noch nicht mal mit Hilfe von Hardware möglich sein.

Das Ausnullen ansich wollte ich mit einem kleinen Hintergrund-Mechanismus erledigen der für die HW eine einfach verkettete Liste aufbaut welche dann von der HW (im Chipsatz) abgearbeitet wird. Dieses Ausnullen wird dabei auch den Speicher aus allen CPU-Caches werfen so das es auch keine Rolle spielt in welchen CPU-lokalen Thread-Cache der dann kommt (nebst dessen das bei Stack auch eh aggressiver gecachet werden kann, also Dinge wie Write-Allocation usw. da immer voll genutzt werden). Auf dem PC kenne ich leider keine Standard-Komponente die das machen könnte so dass das eben in SW erledigt werden muss und damit auch etwas Performance kostet. Und wenn dann muss das in einem Flat-Memory-OS natürlich auch in den PMM.


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #76 am: 30. May 2011, 22:16 »
Das mit dem Stack und seiner Größe ist so ein Sache. Obwohl ich da der Meinung bin, das man als OS Dever ein paar Grenzwerte festlegen sollte (so dass wahrscheinlich die meisten Programme ohne Probleme laufen) und alles andere muss der Programmierer des Programms selber wissen und festlegen (er weiß am ehesten wieviel Threads laufen werden und wieviel Speicher ansonsten vom Heap benötigt wird).

Zitat von: erik
Da fällt mir ein: was ist mit dem TLS-Speicher?
Mit TLS habe ich mich länger nicht befasst. Mein Problem damit ist, das ich eigentlich immer dachte, das jeder Thread ne Art private Tabelle hat, wo er Adressen reinschreiben kann und da der Index für jeden Thread gleich ist, greift jeder Thread auf seine eigene Datenstruktur zu.
Allerdings kommt es mir so vor (und ich möchte meinen was darüber gelesen zu haben), dass es jetzt mehr so zu sein scheint, dass jeder Thread nen anderes PD hat, aber die gleichen Pages benutzt, bis auf den TLS Bereich, der ist für jeden Thread unterschiedlich. Das empfinde ich einfach nur als falsch. Gut man könnte jetzt argumentieren das es eh unwahrscheinlich ist, dass 2 Threads des selben Prozessen nacheinander laufen werden, so dass der TLB nicht gelöscht wird. Allerdings kann man dann auch gleich einfach mehrere Prozesse laufen lassen und über shared Memory miteinander arbeiten.

Zitat von: erik
In einem Micro-Kernel-OS sollten eigentlich auch Treiber nicht in der Lage sein beliebigen Speicher zu Lesen/Schreiben
Was (wie du schon richtig sagst) aber nur klappt wenn eine IO-MMU vorhanden ist und das trifft nur auf neuere PCs zu und dort auch nicht auf alle (das war doch sowas was nicht alle Intel CPUs haben?).
Ansonsten ist auch ein Treiber von nem MikroKernel sehr wohl in der Lage den gesamten Speicher auszulesen. Ich meine was hält ihn davon ab, einfach alle physikalischen Pages durchzugehen und zu senden?

Was mir noch zum ausnullen einfällt, ist das nicht der Cache-Trasher schlecht hin?

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #77 am: 31. May 2011, 01:22 »
Hallo,

Zitat von: erik
In einem Micro-Kernel-OS sollten eigentlich auch Treiber nicht in der Lage sein beliebigen Speicher zu Lesen/Schreiben
Was (wie du schon richtig sagst) aber nur klappt wenn eine IO-MMU vorhanden ist und das trifft nur auf neuere PCs zu und dort auch nicht auf alle (das war doch sowas was nicht alle Intel CPUs haben?).
Das stimmt so einfach nicht. Eine IOMMU verhindert, dass ein Treiber den gesamten Speicher ausliest, indem er die Hardware anweist, per DMA von allen Adressen zu lesen und die gleichen Daten wieder per DMA oder PIO zurückzuschreiben. Performance ist was anderes, außerdem kann das nicht jede Hardware.

Ansonsten ist auch ein Treiber von nem MikroKernel sehr wohl in der Lage den gesamten Speicher auszulesen. Ich meine was hält ihn davon ab, einfach alle physikalischen Pages durchzugehen und zu senden?
Ein General Protection Fault? Wenn der Treiber auf Speicher oder Adressen zugreift, der ihm nicht gehört, dann gibt das nunmal eine Exception: In einem Mikrokernel sind Treiber auch "nur" (Ring3-)Tasks.

Gruß,
Svenska

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #78 am: 31. May 2011, 10:22 »
Zitat von: Svenska
Das stimmt so einfach nicht. Eine IOMMU verhindert, dass ein Treiber den gesamten Speicher ausliest, indem er die Hardware anweist, per DMA von allen Adressen zu lesen und die gleichen Daten wieder per DMA oder PIO zurückzuschreiben.
Ist doch genau das was ich gesagt habe ;)

Zitat von: Svenska
Ein General Protection Fault? Wenn der Treiber auf Speicher oder Adressen zugreift, der ihm nicht gehört, dann gibt das nunmal eine Exception: In einem Mikrokernel sind Treiber auch "nur" (Ring3-)Tasks.
Davon abgesehen das wir das Thema schonmal diskutiert hatten, versteht ihr beide mich nicht ;) Der Treiber kann den gesamten Speicher auslesen, weil er halt Zugriff auf die Hardware hat und diese anweisen kann per DMA den gesamten Speicher auslesen. Das war das was ich meinte!

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #79 am: 31. May 2011, 10:31 »
Hallo,


Das mit dem Stack und seiner Größe ist so ein Sache.....
Ja, das stimmt. Das Problem bei Flat-Memory-System ist eben das all das aus einem einzigen Adressraum kommen muss und daher alles voneinander abhängig ist. Der typische Programmierer wird sich aber nur wenig Gedanken über die Bedürfnisse des von ihm getippten Codes machen so dass das OS mit nur sehr wenigen Informationen eine möglichst gute Automatik füttern muss. Ich persönlich bin auch ehrlich der Meinung das es nicht unbedingt Aufgabe des Programmierers ist sich darum zu kümmern wie das System dafür sorgt das sein Programm erwartungsgemäß läuft. Gerade dieser Denkansatz ist einer der wesentlichen Gründe dafür das ich mich für Segmentierung entschieden hab. Aber auch ich muss mit Grenzen kämpfen, z.B. mit der festen Größe der LDT welche die maximale Anzahl an Segmenten und damit auch an Stacks für Threads pro Prozess begrenzt. Mehr als knapp 15000 Threads kann ich pro Prozess nicht zulassen (es sind doppelt so viele wenn der Prozess kein TLS benutzt).

Mit TLS habe ich mich länger nicht befasst....
Die Idee mit einem extra Paging-Directory pro Thread wird extrem viel Speicher kosten (vor allem unnützen da ja eigentlich fast alles Redundant ist) und auch Performance wenn bei jeder Änderung im virtuellen Adressraum des Prozess X Paging-Directorys angefasst werden müssen (von den vielen TLB-Flushs mal ganz abgesehen). Auf x86 wird man für TLS eher ein Register opfern und zwar ein Segment-Register (meist wohl FS oder GS, die selbst im Long-Mode noch gerade so genug vorhanden sind damit man damit TLS realisieren kann). Auf den meisten anderen Plattformen wird man wohl ebenfalls eines der normalen Register opfern. Man könnte aber auch eine Funktion oder einen Syscall benutzen der die TLS-Adresse des aktuellen Threads aus einer Tabelle holt aber auch das ist nicht allzu performant. Für meine Plattform habe ich mich daher ebenfalls für das Opfern eines Segment-Registers entschieden aber da ich davon 16 Stück habe ist das nur ein sehr kleines Opfer.


Die IOMMU ist nicht Teil der CPU sondern des Chipsatzes bzw. des PCI-(Express-)Root-Controllers (okay das wird bei neueren Intel-CPUs doch wieder auf dem selben Silizium wie die CPU sein), aber es stimmt natürlich das sowas tolles nicht immer vorhanden ist.

Mit dem Auslesen des gesamten Speichers bei Treibern meinte ich eigentlich das direkte Auslesen durch Software, was ja eben eine Exception auslösen sollte, und nicht das Missbrauchen der Hardware (dagegen hilft eben nur eine IOMMU).


Was mir noch zum ausnullen einfällt, ist das nicht der Cache-Trasher schlecht hin?
Nein eigentlich nicht. Wenn Du das in SW machst dann ist diese Page komplett im Cache der aktuellen CPU und genau deswegen würde ich freie physische Pages auch CPU-lokal cachen. Auf meiner Plattform ist das Ausnullen aber tatsächlich ein echter Cache-Trasher und zwar für alle CPUs, die entsprechenden Cache-Lines werden in allen CPUs invalidiert ohne das sie zurückgeschrieben werden und der entsprechende Heimat-Speicher-Controller kennzeichnet diese Cache-Lines wieder als "nicht entliehen" und als "leer" (so das beim nächsten Zugriff erst gar nicht auf die Speicherchips zugegriffen wird sondern einfach nur eine Antwort die nur aus 0x00-Bytes besteht an die anfragende CPU geschickt wird, erst mit einem Schreibzugriff wird diese Cache-Line im Speicher-Controller wieder als "befüllt" gekennzeichnet). Aber gerade bei Stack macht das mit dem Cache-Trashing auch nichts (auf keiner CPU) da ja beim Stack üblicherweise nur die Daten gelesen werden die auch selber vor kurzem dort hin geschrieben wurden. Es erfolgt auf eine bestimmte Speicherstelle also zumeist erst ein Schreibzugriff und danach dann Lesezugriffe und die Schreibzugriffe können mit Write-Allocation ganz gut abgefangen werden ohne das der CPU-Kern für die Dauer der Speicher-Latenz blockiert werden muss.


Performance ist was anderes
Auch in einer IOMMU kann man einen TLB implementieren. Klar kostet eine IOMMU immer ein paar Nanosekunden zusätzliche Latenz (der Durchsatz sollte davon nicht betroffen sein) bei Hardware-Zugriffen auf den normalen RAM, aber so viel ist das nun auch wieder nicht und man bekommt dafür eben einiges an Sicherheit.


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

 

Einloggen