Lowlevel

Lowlevel => Lowlevel-Coding => Thema gestartet von: FlashBurn am 04. November 2010, 21:32

Titel: Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 04. November 2010, 21:32
Ich arbeite gerade an einem allgemeinen Ressourcen-Allocator (inspiriert durch Bonwick und seinen SlabAllocator) und stecke ein wenig fest.

Es geht darum das man viele ja auf Integerwerte und Bereich zurück führen kann (z.B. IDs und virtuelle Speicherbereiche).

Man initialisert eine sogenannte Map mit einem Startwert und einer Größe (z.B. beim UserSpace mit 0 als Startwert und 0xC0000000 als Größe). Desweiteren gibt man noch die Größe einer Einheit an (bei virtuellen Adressen 0x1000 und bei IDs 1).

Eine Map-Datenstruktur könnte also so aussehen:
struct map_t {
 uint32t start;
 uint32t size;
 uint32t sizeItem;
};

Probleme habe ich jetzt bei der Organisation der einzelnen bzw. des gesamten Bereichs einer Map. Ich würde pro Bereich folgende Datenstruktur haben:
struct mapSegment_t {
 uint32t start;
 uint32t size;
};

Wenn man ein Segment/Bereich aus einer Map haben möchte ist es hilfreich wenn die freien Segmente/Bereiche de größe nach geordnet sind. Damit es im extrem Fall auch schnell geht würde ich nen Baum (AVL/Rot-Schwarz) verwenden.

Wenn man ein Segment/Bereich wieder freigibt und 2 oder 3 Segmente/Bereiche zu einem zusammengefügt werden sollen, wäre es nicht schlecht wenn sie auch nach dem Start-Wert sortiert sind, also noch nen Baum.
Auch macht es eine Sortierung nach dem Start-Wert einfacher beim zurückgeben, eines Segments/Bereichs, zu überprüfen ob das Segement/der Bereich überhaupt benutzt war.

Das ist ja alles gut und schön, aber wenn ich z.b. an IDs denke dann finde ich den Speicherverbrauch ziemlich hoch. Wenn es um virtuelle Adressbereiche geht ist der Speicherverbrauch in Ordnung (meine momentane Variante verbraucht genauso viel).

Hat jemand eine Idee wie man das mit dem Sortieren noch anders lösen könnte, so dass der Speicherverbrauch geringer wird, aber die Geschwindigkeit erhalten bleibt?
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 05. November 2010, 09:08
Du solltest grundsätzlich 64-Bit-Werte oder größer in deinen Strukturen verwenden, um z.B. einen ID-Adressraum oder den virtuellen Adressraum im 64-Bit-Modus abbilden zu können.

So wie ich das verstehe, steigt der Speicherverbrauch nur mit dem Fragmentierungsgrad der Nutzung aller Ressourcen. Das heißt, am Anfang enthalten beide Bäume genau einen Eintrag (mapSegment:start=map:start, mapSegment:size=mapSegment:size), wird dann ein Teil in Anspruch genommen, so existieren je zwei Einträge ("genutzter Bereich", dahinter "freier Bereich"). Gibst du den Bereich wieder frei, dann hast du nur noch einen Eintrag, wenn du direkt zusammenfügst. Der Speicherverbrauch hält sich somit in Grenzen.

Den Maximalspeicherverbrauch erreichst du also, wenn der Ressourcen-Adressraum den Aufbau "genutzt"-"ungenutzt"-"genutzt"-"ungenutzt"-... hat. Das ist allerdings äußerst unwahrscheinlich, da du z.B. IDs immer von vorne zuweist (Descriptoren nach POSIX müssen immer der kleinstmögliche sein) und Adressraum meist im Block alloziiert wird (und auch als Block freigegeben wird).

Es hängt also von der konkreten Ressource und insbesondere von deren Granularität (map:sizeItem) ab, wieviel Speicher für die Verwaltung der Strukturen draufgeht. Grundsätzlich sehe ich da aber keine Problem. Es sei denn, ich übersehe was. :-)

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 05. November 2010, 09:27
Zitat von: svenska
Du solltest grundsätzlich 64-Bit-Werte oder größer in deinen Strukturen verwenden, um z.B. einen ID-Adressraum oder den virtuellen Adressraum im 64-Bit-Modus abbilden zu können.
Da es im Moment eh nur unter 32bit laufen soll, ist das erstmal nicht nötig und würde unnötig Spiecherplatzverschwenden.

Wenn es mal nach 64bit portiert werden sollte, kann man das ja ganz einfach ändern.

Zitat von: svenska
So wie ich das verstehe, steigt der Speicherverbrauch nur mit dem Fragmentierungsgrad der Nutzung aller Ressourcen. Das heißt, am Anfang enthalten beide Bäume genau einen Eintrag (mapSegment:start=map:start, mapSegment:size=mapSegment:size), wird dann ein Teil in Anspruch genommen, so existieren je zwei Einträge ("genutzter Bereich", dahinter "freier Bereich"). Gibst du den Bereich wieder frei, dann hast du nur noch einen Eintrag, wenn du direkt zusammenfügst. Der Speicherverbrauch hält sich somit in Grenzen.
Wenn ich dich jetzt richtig verstanden habe, dann hast du mich nicht richtig verstanden ;)

Ich will nur die Bereiche speichern, die auch frei sind, die genutzten tauchen nirgends auf! Spart Speicher und zum Nachgucken ob ein Bereich der freigegeben werden soll auch wirklich genutzt ist, brauch ich einfach nur die "Liste" der freien Bereiche durchgehen, wo sie dem Start-Wert nach sortiert sind.

Zitat von: svenska
Den Maximalspeicherverbrauch erreichst du also, wenn der Ressourcen-Adressraum den Aufbau "genutzt"-"ungenutzt"-"genutzt"-"ungenutzt"-... hat. Das ist allerdings äußerst unwahrscheinlich, da du z.B. IDs immer von vorne zuweist (Descriptoren nach POSIX müssen immer der kleinstmögliche sein) und Adressraum meist im Block alloziiert wird (und auch als Block freigegeben wird).
Ja (Fragmentierung) und Nein (IDs). Ich gehe jetzt mal von Task, Thread und Port IDs aus. Da wäre es schön wenn man erstmal die Liste bis zum größt möglichen Wert durchgeht und dann wieder von vorne (natürlich nur die freien) anfängt.
Das wäre deswegen schön, weil dann die wahrscheinlich sinkt, das du als Programm ne Thread-ID von nem anderen Thread gespeichert hast und mit dem irgendetwas machen willst, aber inzwischen schon ein anderer Thread die ID hat (noch schlimmer ist das bei Ports).

Im Moment ist es bei mir sogar ziemlich einfach diesen worst-Case (genutzt-frei-genutzt-...) herzustellen. Man muss einfach nur zwei Programme haben, die jeweils das andere Programm starten und sich dann selbst beenden, da ich benutzte IDs nicht wiederverwende (weil es zu aufwendig wäre nen ganzen Baum nach einer freien ID zu durchsuchen).
Das gleiche kannst du im Adressraum machen, einfach den ganzen Adressraum allokieren und dann jede zweite "Page" wieder freigeben.

Also werde ich wohl den Baum benutzen, es sei denn ich finde noch ne schönere Datenstruktur.

Ich würde diese Map dann noch dahingehend erweitern, dass man seinen eigenen Allocator (für die mapSegment_t und die node_t Strukturen) mitgeben kann. Damit erreiche ich dann, dass ich diesen Ressourcen-Allocator auch für meinen VMM nutzen kann und kein Henne-Ei-Problem erschaffe. Theoretisch könnte man den Allocator sogar für nen PMM verwenden.

Ich überlege auch, ob es Sinn macht wenn man einer solchen Map noch Bereiche hinzufügen/adden könnte (wenn z.B. bei einem PMM "Löcher" im physikalischen Speicher sind).
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 05. November 2010, 19:23
Hallo,

Ich will nur die Bereiche speichern, die auch frei sind, die genutzten tauchen nirgends auf! Spart Speicher und zum Nachgucken ob ein Bereich der freigegeben werden soll auch wirklich genutzt ist, brauch ich einfach nur die "Liste" der freien Bereiche durchgehen, wo sie dem Start-Wert nach sortiert sind.
Stimmt auch wieder. Ich suche nur noch nach einem Grund, warum das schlecht ist. Mir fällt keiner ein. :-)

Ich gehe jetzt mal von Task, Thread und Port IDs aus. Da wäre es schön wenn man erstmal die Liste bis zum größt möglichen Wert durchgeht und dann wieder von vorne (natürlich nur die freien) anfängt.
In einer Baumstruktur den ersten Eintrag zu finden ist aber einfacher als einen Zwischenzeiger auf das "nächste" Element immer abzuspeichern - besonders, wenn du den Baum zwischenzeitlich änderst.

Das wäre deswegen schön, weil dann die wahrscheinlich sinkt, das du als Programm ne Thread-ID von nem anderen Thread gespeichert hast und mit dem irgendetwas machen willst, aber inzwischen schon ein anderer Thread die ID hat (noch schlimmer ist das bei Ports).
Das ist dann aber ein Bug im Programm (z.B. Leaking File Descriptor). Sachen wie Thread-IDs sollten übrigens für jede Anwendung in einem eigenen Adressraum stattfinden, denn Threads kommunizieren nie direkt mit anwendungsfremden Threads, sondern immer nur über IPC. Der Kernel sollte solche Interna besser nicht öffentlich machen.

Im Moment ist es bei mir sogar ziemlich einfach diesen worst-Case (genutzt-frei-genutzt-...) herzustellen. Man muss einfach nur zwei Programme haben, die jeweils das andere Programm starten und sich dann selbst beenden, da ich benutzte IDs nicht wiederverwende (weil es zu aufwendig wäre nen ganzen Baum nach einer freien ID zu durchsuchen).
Den Worst-Case herstellen ist immer einfach, wenn man den Algorithmus kennt. Auf diese Weise kriegst du jedes Betriebssystem in den Keller gefahren (es gibt Wettbewerbe, um einen Algorithmus so zu implementieren, dass er auf einem OS richtig krass langsam ist).

Wenn du in deinem Baum nur freie IDs speicherst, dann ist jedes Blatt des Baumes doch eine freie ID? Und die erste freie ID ist die, wenn du immer nur links abbiegst. Irre ich da? Wie gesagt, die Wiederverwertung von manchen Ressourcen wird von POSIX vorausgesetzt (wobei ich nicht weiß, inwieweit sich Anwendungen auch darauf verlassen).

Also werde ich wohl den Baum benutzen, es sei denn ich finde noch ne schönere Datenstruktur.
Ich kenne nur die einfachen Grundstrukturen, und simple binäre Bäume sind da Goldstandard... ;-)

Ich würde diese Map dann noch dahingehend erweitern, dass man seinen eigenen Allocator (für die mapSegment_t und die node_t Strukturen) mitgeben kann. Damit erreiche ich dann, dass ich diesen Ressourcen-Allocator auch für meinen VMM nutzen kann und kein Henne-Ei-Problem erschaffe. Theoretisch könnte man den Allocator sogar für nen PMM verwenden.
Naja, das bietet sich doch an, den für VMM zu verwenden. Adressraum ist schließlich auch "bloß" eine Ressource unter vielen. Ich merk mir den Grundgedanken auch selbst vor, denn darauf wär ich wahrscheinlich spontan nicht gekommen und gut ist es, soweit ichs einschätzen kann, auch.

Aber warum brauchst du einen eigenen Allokator, um das für dein VMM nutzen zu können? Baue doch ein 1:1-Mapping für ein paar KB auf und stelle eine statische Grundstruktur. Da du Bäume verwendest, ist jeder weitere Eintrag von den vorhandenen Einträgen unabhängig, zumindest was die Erzeugung betrifft.

Du erwähnst mir das Henne-Ei-Problem zu häufig - mach doch einfach eine komplett statische, im Code vorgegebene Struktur, die als Henne dient. Die Einschränkung, die sich ergibt, wäre beispielsweise, dass im Adressraum an der Stelle 216KB (oder halt woanders) kein Loch sein darf, da du dort den Anfang deines Baumes hinlegst. Wenn du das statisch vorcodierst, muss dein VMM-Allokator niemals Speicher alloziieren, um betriebsbereit zu werden.

Dann würde der Allokator auch für den PMM funktionieren.

Ich überlege auch, ob es Sinn macht wenn man einer solchen Map noch Bereiche hinzufügen/adden könnte (wenn z.B. bei einem PMM "Löcher" im physikalischen Speicher sind).
Du unterteilst deine Map in Bereiche, indem du die Löcher vom Kernel in Beschlag nimmst. Also der Kernel sucht die Löcher im Ressourcenadressraum und markiert diese als "belegt/unswapbar" für sich selbst. Schon ist das Loch gefüllt. :-)

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 05. November 2010, 19:47
Zitat von: svenska
Sachen wie Thread-IDs sollten übrigens für jede Anwendung in einem eigenen Adressraum stattfinden, denn Threads kommunizieren nie direkt mit anwendungsfremden Threads, sondern immer nur über IPC. Der Kernel sollte solche Interna besser nicht öffentlich machen.
Es gibt aber so Sachen wie die Priorität eines Threads ändern und dafür brauchst du dann die ID und es gibt bestimmt noch andere Sachen. Es wird die Situation das ein Thread seine ID oder die eines anderen Wissen möchte (so fern er es darf) also schon geben.

Und die ProzessID wirst du irgendwann auf jeden Fall mal brauchen.

Zitat von: svenska
Wenn du in deinem Baum nur freie IDs speicherst, dann ist jedes Blatt des Baumes doch eine freie ID? Und die erste freie ID ist die, wenn du immer nur links abbiegst. Irre ich da? Wie gesagt, die Wiederverwertung von manchen Ressourcen wird von POSIX vorausgesetzt (wobei ich nicht weiß, inwieweit sich Anwendungen auch darauf verlassen).
Nope. Bei meiner momentanen Implementierung stehen in dem Baum z.B. Thread-Diskriptoren und jeder Thread hat ne ID. Also stehen in dem Baum nur benutzte IDs, wenn ich jetzt ne freie finden möchte (und nicht den einfachsten Fall nehme -> größte ID + 1) muss ich den ganzen Baum durchsuchen (im worst-case, obwohl der average-case auch nicht besser sein wird).

Zitat von: svenska
Aber warum brauchst du einen eigenen Allokator, um das für dein VMM nutzen zu können?
Um mir ne endlos-Schleife zu vermeiden. Denn der Map-Code ruft den Slab-Allocator auf, der ruft den VMM auf und der wiederrum den Map-Code und das Spiel geht wieder von vorne los.

Das lässt sich leider nicht so leicht lösen und deswegen nutze ich da nen eigenen Allocator, der nur aus nem bestimmten statischen Bereich die Objekte holt.

Auch habe ich in meinem KernelSpace für jeden Prozess ne Art privaten Bereich reserviert, für genau diese Datenstrukturen und nutze da dann auch nen eigenen Allocator.

Zitat von: svenska
Du unterteilst deine Map in Bereiche, indem du die Löcher vom Kernel in Beschlag nimmst. Also der Kernel sucht die Löcher im Ressourcenadressraum und markiert diese als "belegt/unswapbar" für sich selbst. Schon ist das Loch gefüllt.
Dann hast du das Prinzip nicht verstanden. Ich speichere nicht welche Bereiche in Benutzung sind und von wem, sondern nur was noch frei ist und wie groß der "Ur-Bereich" ist.

Deswegen könnte es durch einen Fehler/Bug passieren das man einfach ein solches Loch freigeben könnte. Wenn ich aber in einer Map mehrere Unter-Maps speichern könnte, dann wäre das kein Problem.

Was den PMM betrifft, wird das bei mir nichts werden (es sei denn ich nutze viele Maps, aber das lohnt sich einfach nicht), weil ich auch 4MB Pages unterstütze und mein PMM daher immer darauf achtet das er nur Pages rausgibt, so dass die größtmöglichste Anzahl an 4MB Pages frei ist.

Ansonsten kann man solch einen Allocator für viele Sachen benutzen, was natürlich den Code kleiner und robuster macht.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 06. November 2010, 13:55
Hallo,

Zitat von: svenska
Sachen wie Thread-IDs sollten übrigens für jede Anwendung in einem eigenen Adressraum stattfinden, denn Threads kommunizieren nie direkt mit anwendungsfremden Threads, sondern immer nur über IPC. Der Kernel sollte solche Interna besser nicht öffentlich machen.
Es gibt aber so Sachen wie die Priorität eines Threads ändern und dafür brauchst du dann die ID und es gibt bestimmt noch andere Sachen. Es wird die Situation das ein Thread seine ID oder die eines anderen Wissen möchte (so fern er es darf) also schon geben.
Mein Ansatz war, dass jeder Prozess mehrere Threads haben kann, die immer durchnummeriert werden, es für jeden Prozess also einen eigenen ID-Raum gibt. Nur der Kernel kennt die systemweite ID des Threads, der Thread selbst nicht. Damit kann es solche leckenden ThreadIDs vom Userspace aus nicht geben.

Und die ProzessID wirst du irgendwann auf jeden Fall mal brauchen.
Prozesse sind an sich unabhängig, daher kriegen die eine systemweit eindeutige ID (sonst gäbe es ja sonst nur ProzessID=0 für jeden Prozess); Threads sind nicht unabhängig, da jeweils mehrere zu einem Prozess als Oberklasse gehören (und bekommen darum einen eigenen Raum für ihre IDs).

Zitat von: svenska
Wenn du in deinem Baum nur freie IDs speicherst, dann ist jedes Blatt des Baumes doch eine freie ID? Und die erste freie ID ist die, wenn du immer nur links abbiegst. Irre ich da? Wie gesagt, die Wiederverwertung von manchen Ressourcen wird von POSIX vorausgesetzt (wobei ich nicht weiß, inwieweit sich Anwendungen auch darauf verlassen).
Nope. Bei meiner momentanen Implementierung stehen in dem Baum z.B. Thread-Diskriptoren und jeder Thread hat ne ID. Also stehen in dem Baum nur benutzte IDs, wenn ich jetzt ne freie finden möchte (und nicht den einfachsten Fall nehme -> größte ID + 1) muss ich den ganzen Baum durchsuchen (im worst-case, obwohl der average-case auch nicht besser sein wird).
Achso. Vor 2 Posts schriebst du nämlich, du speicherst nur die freien Bereiche. Wenn du nur die belegten Bereiche speicherst, wird das natürlich aufwendiger.

Zitat von: svenska
Du unterteilst deine Map in Bereiche, indem du die Löcher vom Kernel in Beschlag nimmst. Also der Kernel sucht die Löcher im Ressourcenadressraum und markiert diese als "belegt/unswapbar" für sich selbst. Schon ist das Loch gefüllt.
Dann hast du das Prinzip nicht verstanden. Ich speichere nicht welche Bereiche in Benutzung sind und von wem, sondern nur was noch frei ist und wie groß der "Ur-Bereich" ist.
Hier schreibst du, du speicherst nur freie Ressourcen. Einige dich mal auf eine Version. Die Vorteile von beiden Varianten bekommst du übrigens, wenn du zwei Bäume hast - einen mit den freien Bereichen, den anderen mit den belegten Bereichen. Die kleinste unbenutzte ID findet sich dann im "Frei-Baum" und wenn du eine bestimmte Ressource suchst, dann findest du die im "Belegt-Baum".

Deswegen könnte es durch einen Fehler/Bug passieren das man einfach ein solches Loch freigeben könnte. Wenn ich aber in einer Map mehrere Unter-Maps speichern könnte, dann wäre das kein Problem.
Mein Gedanke ist, dass der Kernel die Löcher als belegt markiert und sie nie freigegeben werden dürfen. Wenn du ein solches Loch freigibst, ist das ein Bug und sollte gefixt werden, statt das Problem zu verschleiern. Je weniger Code, desto weniger Bugs. ;-)

Zitat von: svenska
Aber warum brauchst du einen eigenen Allokator, um das für dein VMM nutzen zu können?
Um mir ne endlos-Schleife zu vermeiden. Denn der Map-Code ruft den Slab-Allocator auf, der ruft den VMM auf und der wiederrum den Map-Code und das Spiel geht wieder von vorne los.
Das ist schon klar. Aber die Endlosschleife entsteht doch nur Erzeugen der allerersten Datenstrukturen, weil diese Ressourcen sind und sich somit selbst verwalten. In dem Moment, wo du diese erzeugt hast, ist dein Allokator doch funktionsfähig. Also erzeugst du die statisch (bereits im Code), dann umgehst du die ganzen Schwierigkeiten. Das ähnelt dann der Lebendgeburt von Eriks System.

Das lässt sich leider nicht so leicht lösen und deswegen nutze ich da nen eigenen Allocator, der nur aus nem bestimmten statischen Bereich die Objekte holt.
Dynamisch zur Laufzeit. Mach das Ergebnis doch statisch zur Compilezeit, dann sparst du dir den eigenen Allokator und das Henne-Ei-Problem.

Auch habe ich in meinem KernelSpace für jeden Prozess ne Art privaten Bereich reserviert, für genau diese Datenstrukturen und nutze da dann auch nen eigenen Allocator.
Das verstehe ich nicht.

Was den PMM betrifft, wird das bei mir nichts werden (es sei denn ich nutze viele Maps, aber das lohnt sich einfach nicht), weil ich auch 4MB Pages unterstütze und mein PMM daher immer darauf achtet das er nur Pages rausgibt, so dass die größtmöglichste Anzahl an 4MB Pages frei ist.
Mach doch eine Map je Pagegröße, die du miteinander synchronisierst. Belegst du eine 4MB-Page, so werden 4MB in der 4KB-Map als belegt markiert und belegst du eine 4KB-Page, so wird in der 4MB-Map eine einzelne Page markiert. Für andere Pagegrößen gilt entsprechendes. Kostet ein bisschen RAM, sollte aber dennoch schneller sein, als alles aus einer Map mit der kleinsten Pagegröße rauszupopeln.

Allerdings weiß ich nicht, wie effizient dein jetziger PMM arbeitet. Gehe ich richtig in der Annahme, dass dein VMM sowieso ständig auf den PMM zugreift? Dann lohnt sich es wahrscheinlich trotzdem, den PMM ebenfalls in dieses Schema zu stopfen. Spart man sich zwei verschiedene Allokatoren mit verschiedenen Bugs und von Bugfixes profitieren dann alle Beteiligten.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 06. November 2010, 14:44
Zitat von: svenska
Mein Ansatz war, dass jeder Prozess mehrere Threads haben kann, die immer durchnummeriert werden, es für jeden Prozess also einen eigenen ID-Raum gibt. Nur der Kernel kennt die systemweite ID des Threads, der Thread selbst nicht. Damit kann es solche leckenden ThreadIDs vom Userspace aus nicht geben.
Willst du wirklich 2 IDs für Threads vergeben, weil sich deine Erklärung so anhört und das wäre doch quatsch.
Ob du nun nen eigenen ID-Raum für Threads hast oder nicht, du brauchst nur eine ID. Denn wenn du sagst das die Prozess und die Thread IDs nur 16bit groß sind (was für 32bit Systeme vollkomen ausreichend sein sollte) dann bildest du eine globale ID aus den beiden zusammen.
Ich plane halt so das ich bestimmt mal irgendwo die Thread ID brauche.

Zitat von: svenska
Achso. Vor 2 Posts schriebst du nämlich, du speicherst nur die freien Bereiche. Wenn du nur die belegten Bereiche speicherst, wird das natürlich aufwendiger.
Nee, ich habe geschrieben dass das so bei meiner momentanen Implementierung ist und da wird dieser allgemeine Allocator noch nicht genutzt.

Bei dem vorgeschlagenen allgemeinen Ressourcen Allocator sollen nur die freien Bereiche gespeichert werden.

Zitat von: svenska
Mein Gedanke ist, dass der Kernel die Löcher als belegt markiert und sie nie freigegeben werden dürfen. Wenn du ein solches Loch freigibst, ist das ein Bug und sollte gefixt werden, statt das Problem zu verschleiern. Je weniger Code, desto weniger Bugs.
Ich dachte da dann auch an irgendwelche "Löcher" im UserSpace, die das Programm zwar versuchen könnte freizugeben, was aber nicht passieren darf. Dagegen habe ich im moment noch keinen Schutz, aber den bräuchte man und da kannst du dann nicht so eine einfache Lösung nehmen.

Zitat von: svenska
Das ist schon klar. Aber die Endlosschleife entsteht doch nur Erzeugen der allerersten Datenstrukturen, weil diese Ressourcen sind und sich somit selbst verwalten. In dem Moment, wo du diese erzeugt hast, ist dein Allokator doch funktionsfähig. Also erzeugst du die statisch (bereits im Code), dann umgehst du die ganzen Schwierigkeiten.
Nope! Das Starten ist nicht das Problem, sondern die Sachen die während der Laufzeit passieren.

Ich allokieren alle Datenstrukturen dynamisch, bei mir ist in dem Sinne nichts statisch und dadurch kann es passieren das der VMM dynamisch ne Datenstruktur allokiert. Das ganze würde dann über den allgemeinen SlabAllocator-Code gemacht werden und dieser stellt fest das er keine Objekte mehr in seinem Cache hat und muss vom VMM neuen Speicher anfordern und dadurch entsteht die endlos Schleife.

Ich weiß net ob du da mit dem VMM vom Linuxkernel durcheinander kommst, denn den finde ich mal richtig scheiße ;) Meiner funktioniert etwas anders.
Ich mappe im KernelSpace nicht 1:1 sondern bei mir passiert alles dynamisch, ich nutze also Paging auch im KernelSpace aus.

Zitat von: svenska
Zitat von: FlashBurn
Auch habe ich in meinem KernelSpace für jeden Prozess ne Art privaten Bereich reserviert, für genau diese Datenstrukturen und nutze da dann auch nen eigenen Allocator.
Das verstehe ich nicht.
Normalerweise ist doch der KernelSpace für alle Prozesse gleich. Dem ist bei mir nicht so, ich habe nen Bereich der für jeden Prozess anders ist und aus diesem Bereich kommen die Objekte/Datenstrukturen die der VMM für den UserSpace braucht.
Dadurch verbrauche ich weniger virtuellen Speicher und könnte meinen Kernel wahrscheinlich sogar auf 512MB anstatt 1GB virtuellen Speicher beschränken.

Zitat von: svenska
Mach doch eine Map je Pagegröße, die du miteinander synchronisierst. Belegst du eine 4MB-Page, so werden 4MB in der 4KB-Map als belegt markiert und belegst du eine 4KB-Page, so wird in der 4MB-Map eine einzelne Page markiert. Für andere Pagegrößen gilt entsprechendes. Kostet ein bisschen RAM, sollte aber dennoch schneller sein, als alles aus einer Map mit der kleinsten Pagegröße rauszupopeln.

Allerdings weiß ich nicht, wie effizient dein jetziger PMM arbeitet. Gehe ich richtig in der Annahme, dass dein VMM sowieso ständig auf den PMM zugreift? Dann lohnt sich es wahrscheinlich trotzdem, den PMM ebenfalls in dieses Schema zu stopfen.
Ich behaupte das mein jetziger PMM schneller (wesentlich schneller) ist als es eine zusammengefrieckelte Map Variante je sein könnte.

Zitat von: svenska
Spart man sich zwei verschiedene Allokatoren mit verschiedenen Bugs und von Bugfixes profitieren dann alle Beteiligten.
Das ist auch eine Idee hinter diesem allgemeinen Allocator und trotzdem werde ich meinen PMM extra behandeln (das hat mehrere Gründe).
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 06. November 2010, 15:14
Hallo,

Zitat von: svenska
Mein Ansatz war, dass jeder Prozess mehrere Threads haben kann, die immer durchnummeriert werden, es für jeden Prozess also einen eigenen ID-Raum gibt. Nur der Kernel kennt die systemweite ID des Threads, der Thread selbst nicht. Damit kann es solche leckenden ThreadIDs vom Userspace aus nicht geben.
Willst du wirklich 2 IDs für Threads vergeben, weil sich deine Erklärung so anhört und das wäre doch quatsch.
Ja, daran dachte ich wirklich. Außer bissl RAM-Verbrauch (bei 1k Threads etwa 8k RAM) und bissl CPU-Verbrauch sehe ich da auch keine Probleme.

Ob du nun nen eigenen ID-Raum für Threads hast oder nicht, du brauchst nur eine ID. Denn wenn du sagst das die Prozess und die Thread IDs nur 16bit groß sind (was für 32bit Systeme vollkomen ausreichend sein sollte) dann bildest du eine globale ID aus den beiden zusammen.
Das ist natürlich eine elegantere Lösung. Zwar kennt der Thread dann seine kernelinterne ID, aber es ist halt unwahrscheinlicher, dass er durch Rechenfehler plötzlich auf einen prozessfremden Thread zugreifen will. Darum ging es mir.

Bei dem vorgeschlagenen allgemeinen Ressourcen Allocator sollen nur die freien Bereiche gespeichert werden.
Damit bist du doch einmal das Problem der Löcher los (die sind nie frei) und kannst gleichzeitig immer den ersten freien Descriptor (das erste Baumblatt) zurückgeben.

Zitat von: svenska
Mein Gedanke ist, dass der Kernel die Löcher als belegt markiert und sie nie freigegeben werden dürfen. Wenn du ein solches Loch freigibst, ist das ein Bug und sollte gefixt werden, statt das Problem zu verschleiern. Je weniger Code, desto weniger Bugs.
Ich dachte da dann auch an irgendwelche "Löcher" im UserSpace, die das Programm zwar versuchen könnte freizugeben, was aber nicht passieren darf. Dagegen habe ich im moment noch keinen Schutz, aber den bräuchte man und da kannst du dann nicht so eine einfache Lösung nehmen.
Da sehe ich das Problem nicht. Was meinst du für Löcher im Userspace? Ein Programm darf nur Ressourcen in Anspruch nehmen, die es vom System angefordert und zugewiesen bekommen hat - und auch nur diese Ressourcen darf es wieder freigeben. Löcher werden aber niemals zugewiesen, können also auch nicht freigegeben werden. Und die Verwaltung der zugewiesenen Ressourcen obliegt dem Programm selbst, also hat sich der Kernel nicht drum zu kümmern.

Ich allokieren alle Datenstrukturen dynamisch, bei mir ist in dem Sinne nichts statisch und dadurch kann es passieren das der VMM dynamisch ne Datenstruktur allokiert. Das ganze würde dann über den allgemeinen SlabAllocator-Code gemacht werden und dieser stellt fest das er keine Objekte mehr in seinem Cache hat und muss vom VMM neuen Speicher anfordern und dadurch entsteht die endlos Schleife.
Achso. Dann ist das Problem im Design enthalten und nicht lösbar... zirkuläre Abhängigkeiten sind für mich schlechtes Design. ;-) Erklärt aber dein Problem.

Ich weiß net ob du da mit dem VMM vom Linuxkernel durcheinander kommst, denn den finde ich mal richtig scheiße ;) Meiner funktioniert etwas anders.
Nö, hab ich mir nie angeschaut, kann ich nicht beurteilen.

Normalerweise ist doch der KernelSpace für alle Prozesse gleich. Dem ist bei mir nicht so, ich habe nen Bereich der für jeden Prozess anders ist und aus diesem Bereich kommen die Objekte/Datenstrukturen die der VMM für den UserSpace braucht.
Dadurch verbrauche ich weniger virtuellen Speicher und könnte meinen Kernel wahrscheinlich sogar auf 512MB anstatt 1GB virtuellen Speicher beschränken.
Klingt kompliziert.

Theoretische Zwischenfrage: Ist es möglich, in den Adressraum jeden Prozesses nur eine einzelne Page einzublenden, die nur eine IPC-Funktion enthält und auf diese Weise Kernelfunktionen bereitzustellen?

Gruß
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 06. November 2010, 15:39
Zitat von: svenska
Damit bist du doch einmal das Problem der Löcher los (die sind nie frei) und kannst gleichzeitig immer den ersten freien Descriptor (das erste Baumblatt) zurückgeben.
Es geht mir nicht darum das man diese "Löcher" allokieren könnte, sondern das man sie freigeben kann und dann könnten sie allokiert werden!

Zitat von: svenska
Ein Programm darf nur Ressourcen in Anspruch nehmen, die es vom System angefordert und zugewiesen bekommen hat - und auch nur diese Ressourcen darf es wieder freigeben. Löcher werden aber niemals zugewiesen, können also auch nicht freigegeben werden. Und die Verwaltung der zugewiesenen Ressourcen obliegt dem Programm selbst, also hat sich der Kernel nicht drum zu kümmern.
Du vertraust einem Programm aber ganz schön. Denn dann würde es auch keine Buffer-Overflows geben, wenn die Programme nur soviele Daten "schicken" wie sie dürfen!
Es geht um das freigeben von Speicher/virtuellem Speicher und da gibt es Sachen im UserSpace die ich gerne davor schützen würde.
Zumal sich mein Kernel wahrscheinlich mehr um den UserSpace kümmert als das normalerweise der Fall ist.

Zitat von: svenska
Achso. Dann ist das Problem im Design enthalten und nicht lösbar... zirkuläre Abhängigkeiten sind für mich schlechtes Design. wink Erklärt aber dein Problem.
Dann wäre meine Frage wie du das Problem lösen willst, das der VMM Datenstrukturen dynamisch allokiert und damit er das machen kann muss er ja wieder (irgendwann) auf den VMM zugreifen.
Du brauchst also ne Möglichkeit im VMM Speicher zu allokieren ohne das du den VMM dazu brauchst.

Dafür wäre übrigens der Thread wo ich mein VMM Design vorgestellt habe nicht schlecht.

Zitat von: svenska
Klingt kompliziert.
Nicht wirklich, ich hab halt nen bestimmten Bereich (am Ende des KernelSpace) von 8MB (glaub ich, habe jetzt nicht nachgesehen) wo in jedem Prozess andere physikalische Pages gemappt sind und aus diesem Bereich hole ich mir die Datenstrukturen für den VMM (der für den UserSpace zuständig ist).

Zitat von: svenska
Theoretische Zwischenfrage: Ist es möglich, in den Adressraum jeden Prozesses nur eine einzelne Page einzublenden, die nur eine IPC-Funktion enthält und auf diese Weise Kernelfunktionen bereitzustellen?
Das musst du genauer erklären!

Meinst du das du den Funktions-Code von einer Kernel-Funktion in den UserSpace mappst und dort dann vom "User" der Code ausgeführt werden kann?
Wenn ja, wird nichts werden, weil du dann ja auch die Datenstrukturen mit in den UserSpace mappen musst und dafür muss genug freier virtueller Speicher vorhanden sein, dann kommt noch hinzu das du den Speicher mit den Datenstrukturen erstmal finden musst und du erlaubst dem User damit nicht nur deine Daten lesen zu können, sondern auch noch zu schreiben und dann kannst du das Konzept des getrennten Kernel- und UserSpace gleich abschaffen.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 06. November 2010, 17:35
Hallo,

Zitat von: svenska
Damit bist du doch einmal das Problem der Löcher los (die sind nie frei) und kannst gleichzeitig immer den ersten freien Descriptor (das erste Baumblatt) zurückgeben.
Es geht mir nicht darum das man diese "Löcher" allokieren könnte, sondern das man sie freigeben kann und dann könnten sie allokiert werden!
Freigeben kann man Ressourcen aber nur dann, wenn man sie besitzt oder liege ich da falsch? Wenn dein Kernel sich die Löcher selbst greift, kann nur er sie wieder freigeben und genau das tut er ja nie...

Zitat von: svenska
Ein Programm darf nur Ressourcen in Anspruch nehmen, die es vom System angefordert und zugewiesen bekommen hat - und auch nur diese Ressourcen darf es wieder freigeben. Löcher werden aber niemals zugewiesen, können also auch nicht freigegeben werden. Und die Verwaltung der zugewiesenen Ressourcen obliegt dem Programm selbst, also hat sich der Kernel nicht drum zu kümmern.
Du vertraust einem Programm aber ganz schön. Denn dann würde es auch keine Buffer-Overflows geben, wenn die Programme nur soviele Daten "schicken" wie sie dürfen!
Darum gibt es Zugriffsrechte. Prozesse werden gekillt, wenn sie auf ihnen nicht gehörende Ressourcen zugreifen wollen. Wenn ein Programm Ressourcen freigeben möchte, die ihm nicht gehören, wird es gekillt. Wenn ein Programm Ressourcen anfordern möchte, die bereits ein anderes Programm hat, wird die Anforderung abgelehnt. Will ein Programm Daten ausführen, wird es gekillt. Will ein Programm auf nur lesbare Pages schreiben, wird es gekillt. Und so weiter.

In seinen eigenen Ressourcen darf sich ein jeder Prozess frei austoben, der Kernel hat nur die Aufgabe, die Prozesse voreinander zu schützen, gegenseitig voneinander abzusichern und Dienste anzubieten, die Interprozesskommunikation ermöglichen. Buffer-Overflows sind Programmfehler, keine Betriebssystemfehler. Du solltest deinen Kernel also gegen Buffer-Overflows absichern, wie es jedes Programm auch tun muss.

Es geht um das freigeben von Speicher/virtuellem Speicher und da gibt es Sachen im UserSpace die ich gerne davor schützen würde.
Wenn dein Programm Speicher freigeben möchte, dann wird es damit schon Recht haben. Greift es danach nochmal auf diesen Speicher zu, wird es gekillt - das darf man nämlich nicht.

Zumal sich mein Kernel wahrscheinlich mehr um den UserSpace kümmert als das normalerweise der Fall ist.
Ich denke da wohl anders drüber.

Dann wäre meine Frage wie du das Problem lösen willst, das der VMM Datenstrukturen dynamisch allokiert und damit er das machen kann muss er ja wieder (irgendwann) auf den VMM zugreifen.
Du brauchst also ne Möglichkeit im VMM Speicher zu allokieren ohne das du den VMM dazu brauchst.
Es geht also darum, dass der VMM Speicher für sich selbst unter Verwendung des VMM reservieren muss. Genau das darf er aber nicht tun. Da fallen mir spontan nur zwei Möglichkeiten ein, die ich (mangels eigenem OS) nicht ausführlich erklären kann. Also keine Garantie auf Funktion [oder das ich überhaupt etwas verstehe]. ;-)

(a) Der VMM reserviert sich selbst keinen Speicher dynamisch.
Das setzt voraus, dass die benötigten Datenstrukturen für den VMM in ihrer Größe zur Laufzeit statisch sind. Keine Ahnung, ob das funktioniert. Immerhin kann man die Größe der Datenstrukturen ermitteln und festlegen, bevor der VMM funktionsbereit sein muss.

Führt zwangsweise zu einem sehr sparsamen VMM mit vielen Einstellmöglichkeiten, für Mikrocontroller oder sowas sicherlich gut machbar. Ansonsten unelegant.

(b) Der VMM reserviert sich Speicher nicht über den VMM.
Da der VMM selbst die Hoheit über den virtuellen Adressraum hat, muss er seine eigenen Datenstrukturen ja nicht in den gleichen Büchern führen, wie die restlichen Datenstrukturen. Er darf - im Gegensatz zu allen anderen - die Ressourcen auch erst verwenden und danach die Buchführung betreiben, schließlich verwaltet er ja die Ressourcen und was nicht durch seine Finger gegangen ist, darf auch niemand anders nutzen.

Konkret darf der VMM einfach einen Block virtuellen, bisher ungenutzten Adressraum für sich selbst nehmen, ihn benutzen und erst im Anschluss daran die Bäume pflegen.

Soweit ich dich und das Slab Allocator-Prinzip verstehe, fragt die Anwendung beim Slab Allocator nach Objekten, die der Slab Allocator aus einem Cache zurückgibt. Ist im Cache nichts mehr frei, muss ein neues Stück Speicher zu einem neuen Cache für Objekte umfunktioniert werden und dazu fragt der Slab Allocator beim VMM an. In diesem Augenblick darf der VMM niemals beim Slab Allocator nach Objekten für sich selbst fragen, denn das wäre unzulässiges Layering und eine zirkuläre Abhängigkeit. Dafür gibt es wieder zwei Auswege, die mir einfallen:

(a) Der VMM benutzt keinen Slab Allocator.
Der VMM kümmert sich vollautonom um den virtuellen Speicher und benutzt selbst den PMM für den Zugriff auf den physischen Speicher. Mehr nicht. Der Slab Allocator nutzt dann den VMM, um seine Caches zu bauen, für alles, was so von den oberen Schichten (Kernel, Treiber, Anwendungen) anfällt. Da er den VMM benötigt, darf er diesen nicht verwalten. Du hast also mehrere getrennte Schichten.

(b) Der VMM ist gleichzeitig der Slab Allocator.
Damit verwischst du die Schichten, indem du grundsätzlich VMM und Slab Allocator gemeinsam implementierst. Du verwaltest somit keinen virtuellen Speicher mehr, sondern direkt Slab-Caches im physischen Speicher. Dein Slab Allocator redet also nach unten nicht mit einem VMM, sondern direkt mit dem PMM und verwaltet den virtuellen Speicher für sich selbst (und niemand anders hat darauf Zugriff).

Das geht natürlich nicht, wenn du Objekte irgendwo außerhalb des physischen Speichers alloziieren möchtest. Moderne nVidia-/ATI-Grafikkarten haben eine eigene MMU und die Speicherverwaltung im Grafikspeicher unterliegt gewissen Grenzen, wenn man so Sachen wie CUDA haben will; das lässt sich nicht ausschließlich über Slabs lösen. (Allerdings kann man das natürlich im Grafiktreiber implementieren, statt im System. Und für dich sollte das ohnehin irrelevant sein.)

Hoffe, das hilft. Im VMM-Thread kann ich nicht wirklich etwas beitragen, da ich das Problem an anderer Stelle sehe. Du verwischst die Grenzen nicht vollständig, sondern implementierst schnittstellenfremde Durchgriffe durch die Ebenen, sodaß dein VMM den Slab Allocator kennt und dein Slab Allocator den VMM mit dem Ziel, dass dein VMM selbst Slabs zu seiner eigenen Verwaltung verwenden kann. Das ist nicht einfach, nicht sauber und führt zu Problemen, wenn du Teile davon austauschen möchtest.

Zitat von: svenska
Theoretische Zwischenfrage: Ist es möglich, in den Adressraum jeden Prozesses nur eine einzelne Page einzublenden, die nur eine IPC-Funktion enthält und auf diese Weise Kernelfunktionen bereitzustellen?
Das musst du genauer erklären!
Ich mache mal einen eigenen Thread auf, denn das ist hier OT. (Gehört nicht dieser Thread nach OS-Design? Hier geht es doch um Ideen, nicht um die konkrete Implementierung - oder hab ich deinen Thread versehentlich theoretisiert?)

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 06. November 2010, 17:55
Zitat von: svenska
Freigeben kann man Ressourcen aber nur dann, wenn man sie besitzt oder liege ich da falsch? Wenn dein Kernel sich die Löcher selbst greift, kann nur er sie wieder freigeben und genau das tut er ja nie...
Ich sags mal so, mein Kernel stellt eine Funktion zur Verfügung womit du als User wieder virtuellen (und damit verbunden physischen) Speicher wieder freigeben kannst.
Da ich nicht Buch führe wer welche Ressource (was die Map allgemein und die virtuellen Bereiche speziell betrifft) hat, kann ich auch nicht nachprüfen ob jemand eine Ressource freigibt, die ihm gar nicht gehört. Im Kernel sollte das nicht passieren, da wäre es ein Bug, aber wenn der User sowas probiert sollte man das schon abfangen können.

Um auf das Bsp. mit dem Freigeben von virtuellem Speicher zurück zu kommen, so könnte der User einfach diesen Syscall aufrufen mit einer Adresse die er gar nicht "besitzt" sondern die ich als OS "besitze" (die aber im UserSpace sind) und genau das will ich verhindern.

Zitat von: svenska
Da der VMM selbst die Hoheit über den virtuellen Adressraum hat, muss er seine eigenen Datenstrukturen ja nicht in den gleichen Büchern führen, wie die restlichen Datenstrukturen.
Äh, das ist im Prinzip genau das was ich mache.

Zitat von: svenska
Der VMM kümmert sich vollautonom um den virtuellen Speicher und benutzt selbst den PMM für den Zugriff auf den physischen Speicher.
Ist ja fast wieder das selbe wie oben. Auch ich nutze den SlabAllocator nicht (mehr) für den VMM, aber den Allocator den ich mir geschrieben habe, funktioniert genauso wie der SlabAllocator (weil der einfach besser ist).

Zitat von: svenska
Das ist nicht einfach, nicht sauber und führt zu Problemen, wenn du Teile davon austauschen möchtest.
Ich behaupte mal dieser Satz ist quatsch. Denn er impleziert ja (so wie ich ihn verstehe) das du alles so allgemein wie möglich halten musst und damit dürftest du den SlabAllocator ja sowieso nie effizient (denn man kann den SlabAllocator auch für malloc/free einsetzen, aber das ist sehr unschön) einsetzen und das wäre ja quatsch.
Irgendwelche Komponenten auszutauschen wird immer irgendwo zu Problemen führen.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 06. November 2010, 18:58
Hallo,


Es geht also darum, dass der VMM Speicher für sich selbst unter Verwendung des VMM reservieren muss. Genau das darf er aber nicht tun.
Warum? Genau das möchte ich auch tun. Nur mit einer kleinen Abwandlung im SlabAllocator für die Heap-Objekte des Kernels, der SlabAllocator holt die doch immer aus einem (oder mehreren) großen Block und wenn dort der Platz fast aufgebraucht ist dann wird ein Flag gesetzt aber die Heap-Anforderung des VMM (z.B. nach einem neuen Baum-Element für die VMM-Verwaltungsstrukturen) trotzdem noch bedient (es ist ja noch ein kleiner Rest übrig). Wenn der VMM dann fertig ist mit seiner Arbeit sollte er als letztes den VMM-Lock abgeben (der VMM ist eine zentrale Ressource die IMHO nur einmal gleichzeitig benutzt werden kann) aber unmittelbar bevor der Lock freigegeben wird wird noch geprüft ob das Heap-SlabAllocator-NearEmpty-Flag gesetzt ist und dann für den SlabAllocator noch mal ein dicker Brocken alloziert, das kann gemacht werden weil der VMM von seiner eigentlichen Arbeit ja nicht mehr in einem kritischen Zustand ist. Mit diesem Trick kann ich den Kernel-Heap voll dynamisch verwalten und auch der VMM kann den Kernel-Heap benutzen um dort seine Verwaltungsstrukturen zu holen und trotzdem kann der Heap aus großen Blöcken vom VMM geholt werden. Der Heap würde also die Verwaltungsstrukturen die ihn beschreiben selber enthalten.

(a) Der VMM reserviert sich selbst keinen Speicher dynamisch.
Das bedeutet das der VMM nur eine begrenzte Anzahl an Speicherblöcken verwalten kann und ist IMHO damit nicht mal für Mikrocontroller einsetzbar.

(b) Der VMM reserviert sich Speicher nicht über den VMM.
Das bedeutet das es mehrere unabhängige Allozierungsechanismen geben muss und das halte ich für ungeschickt.
Du weißt ja:
Je weniger Code, desto weniger Bugs. ;-)
Deswegen sollte es jede Funktionalität nur genau ein mal geben.

Konkret darf der VMM einfach einen Block virtuellen, bisher ungenutzten Adressraum für sich selbst nehmen, ihn benutzen und erst im Anschluss daran die Bäume pflegen.
Das stimmt schon, der VMM muss die Verwaltungsstrukturen nicht sofort aktualisieren aber eben doch bevor er sein return macht.

In diesem Augenblick darf der VMM niemals beim Slab Allocator nach Objekten für sich selbst fragen, denn das wäre unzulässiges Layering und eine zirkuläre Abhängigkeit.
Du meinst also das der VMM niemals ein kmalloc benutzen darf? Das würde bedeuten Du musst extra/exklusiv für den VMM was spezielles bauen das diese Funktionalität anbietet. Wie gesagt, das halte ich für ungeschickt wegen dem doppeltem Code und wenn man ganz ungeniert, auch in den Unterfunktionen des VMM, ein kmalloc benutzen darf dürfte das sicher einige Sachen erleichtern. Dazu kommt das die Verwaltungsstrukturen für den VMM eben nur so viel Speicher belegen wie auch tatsächlich benötigt wird. Den Pointer zur Baumwurzel würde ich aber trotzdem in einem statischen Bereich packen (ist ja auch winzig) damit man immer schnell drauf zugreifen kann.

Du verwischst die Grenzen nicht vollständig, sondern implementierst schnittstellenfremde Durchgriffe durch die Ebenen, sodaß dein VMM den Slab Allocator kennt und dein Slab Allocator den VMM mit dem Ziel, dass dein VMM selbst Slabs zu seiner eigenen Verwaltung verwenden kann. Das ist nicht einfach, nicht sauber und führt zu Problemen, wenn du Teile davon austauschen möchtest.
Ja, es gibt eine zirkuläre Abhängigkeit wenn der Kernel-Heap den VMM benötigt und der VMM den Kernel-Heap benötigt aber das ist IMHO lösbar weil man die maximalen Bedürfnisse des VMM vom Heap pro VMM-Aufruf recht gut abschätzen kann.


Ob du nun nen eigenen ID-Raum für Threads hast oder nicht, du brauchst nur eine ID. Denn wenn du sagst das die Prozess und die Thread IDs nur 16bit groß sind (was für 32bit Systeme vollkomen ausreichend sein sollte) dann bildest du eine globale ID aus den beiden zusammen.
Das ist natürlich eine elegantere Lösung. Zwar kennt der Thread dann seine kernelinterne ID, aber es ist halt unwahrscheinlicher, dass er durch Rechenfehler plötzlich auf einen prozessfremden Thread zugreifen will. Darum ging es mir.
Also ich würde auch eher für einen einzelnen globalen ID-Raum plädieren. Wenn man mehrere davon hat kommt man eventuell irgendwann mal durcheinander und man hat auch noch doppelten (zumindest erhöhten) Speicherverbrauch. Was ein Programm mit der Thread-ID eines fremden Threads alles machen darf hängt von den Zugriffsrechten ab und ob ein bestimmter Thread zum aktuellem Prozess gehört sollte sich doch recht einfach feststellen lassen (im Thread-Descriptor-Struct steht bei mir die Prozess-ID des Besitzers).


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 06. November 2010, 19:07
Zitat von: erik
Genau das möchte ich auch tun. Nur mit einer kleinen Abwandlung im SlabAllocator für die Heap-Objekte des Kernels, der SlabAllocator holt die doch immer aus einem (oder mehreren) großen Block und wenn dort der Platz fast aufgebraucht ist dann wird ein Flag gesetzt aber die Heap-Anforderung des VMM (z.B. nach einem neuen Baum-Element für die VMM-Verwaltungsstrukturen) trotzdem noch bedient (es ist ja noch ein kleiner Rest übrig). Wenn der VMM dann fertig ist mit seiner Arbeit sollte er als letztes den VMM-Lock abgeben (der VMM ist eine zentrale Ressource die IMHO nur einmal gleichzeitig benutzt werden kann) aber unmittelbar bevor der Lock freigegeben wird wird noch geprüft ob das Heap-SlabAllocator-NearEmpty-Flag gesetzt ist und dann für den SlabAllocator noch mal ein dicker Brocken alloziert, das kann gemacht werden weil der VMM von seiner eigentlichen Arbeit ja nicht mehr in einem kritischen Zustand ist. Mit diesem Trick kann ich den Kernel-Heap voll dynamisch verwalten und auch der VMM kann den Kernel-Heap benutzen um dort seine Verwaltungsstrukturen zu holen und trotzdem kann der Heap aus großen Blöcken vom VMM geholt werden. Der Heap würde also die Verwaltungsstrukturen die ihn beschreiben selber enthalten.
Sowas in der Art habe ich probiert und das hat auch funktioniert, bis ich zuviele CPUs (unter QEMU) hatte. Problem ist ja, das wenn du als VMM den SlabAllocator aufrufst und dein Objekt bekommst, wieder eine andere CPU den SlabAllocator aufrufen kann und da reicht dann so ein Flag nicht mehr aus (ich habe halt auch mit 255 CPUs getestet), weil bei zu vielen CPUs reichen die Objekte in einem Slab nicht mehr aus um alle CPUs bedienen zu können (vorallem nicht wenn du den SlabAllocator in seiner aktuellsten Abwandlung einsetzt).
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 07. November 2010, 13:07
So ich habe mal etwas gecodet und ich bin noch nicht ganz mit meiner mapFree-Funktion zufrieden. Ich finde sie ist in dem Zustand zwar elegant geschrieben, aber der Algo ist nicht die schnellste Möglichkeit ein Segment/Bereich wieder freizugeben.

Optimierungen die mir schon bekannt sind (sparen Allocator- und Baumänderungs-Aufrufe):

Erstmal der Code:
uint32t mapFree(struct map_t *map, uint32t start, uint32t size) {
struct boundaryTag_t *tag= 0, *merge;
struct avlNode_t *nodeStart, *nodeSize;

if(unlikely(start < map->start || (start + size) > (map->start + map->size)))
goto error0;
//look if we can merge a segment before/after and look if the segment is used
spinAcquire(&map->lock);

nodeStart= map->freeStart;
//we first search for a segment after ours
while(likely(nodeStart)) {
merge= nodeStart->obj;
//is the start or the end in this segment
if(unlikely((start >= merge->start && start < (merge->start + merge->size)) || ((start + size) > merge->start && (start + size) < (merge->start + merge->size))))
goto error1;
//is our end == the start of the node segment
if(unlikely((start + size) == merge->start)) {
//we need to remove the original tag out of the size-tree
nodeSize= avlFind(map->freeSize,merge->size,&mapSizeComparatorVal);
//shouldn´t happen, just to be sure
if(unlikely(!nodeSize))
goto error1;

nodeSize->obj= listRemove((struct list_t *)nodeSize->obj,(struct list_t *)merge)
//look if we need to take the size-node out of the tree and free it
if(!nodeSize->obj) {
map->freeSize= avlDelete(nodeSize);
map->freeNode(nodeSize);
}
//get the start-node out of the tree and free it
map->freeStart= avlDelete(nodeStart);
map->freeNode(nodeStart);
//actualize the new segment, given size and set tag to the just merged tag
merge->start= start;
merge->size+= size;
size= merge->size;
tag= merge;

break;
}
//if our segment comes after the node segment then go right
if(start > merge->start)
nodeStart= nodeStart->right;
else
nodeStart= nodeStart->left;
}
//we found no segment after us for merging, now we search for a segment before us
nodeStart= map->freeStart;

while(likely(nodeStart)) {
merge= nodeStart->obj;
//is start == merge->start + merge->size
if(unlikely(start == (merge->start + merge->size))) {
//we need to remove the node segment out of the size-tree
nodeSize= avlFind(map->freeSize,merge->size,&mapSizeComparatorVal);
//shouldn´t happen, just to be sure
if(unlikely(!nodeSize))
goto error1;

nodeSize->obj= listRemove((struct list_t *)nodeSize->obj,(struct list_t *)merge)
//look if we need to take the size-node out of the tree and free it
if(!nodeSize->obj) {
map->freeSize= avlDelete(nodeSize);
map->freeNode(nodeSize);
}
//get the start-node out of the tree
map->freeStart= avlDelete(nodeStart);
//actualize the tag start, tag size, set nodeStart to our tag and free the merge tag
tag->start= merge->start;
tag->size+= merge->size;
nodeStart->obj= tag;
map->freeTag(merge);

break;
}
//if our segment comes after the node segment then go left
if(start > (merge->start + merge->size))
nodeStart= nodeStart->left;
else
nodeStart= nodeStart->right;
}
//if tag isn´t set, alloc a new one
if(!tag) {
if(unlikely(!(tag= map->allocTag())))
goto error1;

tag->start= start;
tag->size= size;
}
//if nodeStart isn´t set, alloc a new one
if(!nodeStart) {
if(unlikely(!(nodeStart= map->allocNode())))
goto error2

nodeStart->obj= tag;
}
//look for a node with the same size
nodeSize= avlFind(map->freeSize,size,&mapSizeComparatorVal);
//insert the tag into the size-tree
if(!nodeSize) {
if(unlikely(!(nodeSize= map->allocNode())))
goto error3;

nodeSize->obj= listAddFirst((struct list_t *)tag);

map->size= avlInsert(map->size,nodeSize,&mapSizeComparatorNode);
} else {
nodeSize->obj= listAddTail((struct list_t *)nodeSize->obj,(struct list_t *)tag);
}
//now insert the tag into the start-tree
map->freeStart= avlInsert(map->freeStart,nodeStart,&mapStartComparatorNode);
end0:
spinRelease(&map->lock);

return 1;
error3:
map->freeNode(nodeStart);
error2:
map->freeTag(tag);
error1:
spinRelease(&map->lock);
error0:
return 0;
}

Ich hoffe die Funktionsaufrufe sind selbsterklärend, wenn nicht fragen.

Ich hätte jetzt gerne gewusst ob noch jemand ne Optimierung des Algos einfällt. Blöd ist halt das der Baum zweimal durchgegangen werden muss (einmal um nach einem Bereich vor uns und einmal um nach einem Bereich nach uns zu suchen mit dem wir mergen können) und wie schon gesagt, die zuvielen Allocator-Aufrufe.

Es kann auch sein das noch Fehler drin sind, ich habe den Code noch nicht getestet!
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 08. November 2010, 10:17
Hallo,

(a) Der VMM reserviert sich selbst keinen Speicher dynamisch.
Das bedeutet das der VMM nur eine begrenzte Anzahl an Speicherblöcken verwalten kann und ist IMHO damit nicht mal für Mikrocontroller einsetzbar.
Richtig. Man kann das allerdings etwas aufweichen, indem man "so gut wie nie" eine Veränderung zulässt, beispielsweise durch Erzeugen eines neuen Arrays und anschließendem Umkopieren. Tritt das "hinreichend selten" (sehr konkret, ich weiß) auf, stört das auch nicht zu stark. Währenddessen steht natürlich die komplette Speicherverwaltung.

(b) Der VMM reserviert sich Speicher nicht über den VMM.
Das bedeutet das es mehrere unabhängige Allozierungsechanismen geben muss und das halte ich für ungeschickt.
Warum? Wenn du sauberes Layering machst, dann hast du einen PMM, der vom VMM als Allocator genutzt wird - und zwar sowohl für die oberen Schichten als auch für sich selbst. Du brauchst in jedem Fall zwei Mechanismen, um Kreisabhängigkeiten zu vermeiden.

In diesem Augenblick darf der VMM niemals beim Slab Allocator nach Objekten für sich selbst fragen, denn das wäre unzulässiges Layering und eine zirkuläre Abhängigkeit.
Du meinst also das der VMM niemals ein kmalloc benutzen darf? Das würde bedeuten Du musst extra/exklusiv für den VMM was spezielles bauen das diese Funktionalität anbietet. Wie gesagt, das halte ich für ungeschickt wegen dem doppeltem Code und wenn man ganz ungeniert, auch in den Unterfunktionen des VMM, ein kmalloc benutzen darf dürfte das sicher einige Sachen erleichtern. Dazu kommt das die Verwaltungsstrukturen für den VMM eben nur so viel Speicher belegen wie auch tatsächlich benötigt wird. Den Pointer zur Baumwurzel würde ich aber trotzdem in einem statischen Bereich packen (ist ja auch winzig) damit man immer schnell drauf zugreifen kann.
Jein. Der VMM darf kein kmalloc() benutzen, weil er ein kmalloc() implementieren soll. Du musst aber trotzdem nichts spezielles bauen, denn dein virtueller Speichermanager baut auf dem physischen Speicherverwalter auf und kann diesen auch selbst benutzen. Ich sehe keine besondere Einschränkung, wenn im VMM nur ein getPhysMem() existiert, was nur ganze Pages bereitstellen kann. Mit virtuellen Adressen kannst du im VMM ohnehin nichts anfangen, weil du diese ja erst bereitstellst.

Warum sollte der VMM selbst einen VMM nutzen müssen?

Du verwischst die Grenzen nicht vollständig, sondern implementierst schnittstellenfremde Durchgriffe durch die Ebenen, sodaß dein VMM den Slab Allocator kennt und dein Slab Allocator den VMM mit dem Ziel, dass dein VMM selbst Slabs zu seiner eigenen Verwaltung verwenden kann. Das ist nicht einfach, nicht sauber und führt zu Problemen, wenn du Teile davon austauschen möchtest.
Ja, es gibt eine zirkuläre Abhängigkeit wenn der Kernel-Heap den VMM benötigt und der VMM den Kernel-Heap benötigt aber das ist IMHO lösbar weil man die maximalen Bedürfnisse des VMM vom Heap pro VMM-Aufruf recht gut abschätzen kann.
Und sich dann, wenn man nicht genau aufpasst, unglaublich viele Race Conditions und Abhängigkeiten ins Haus holt.

Ich hoffe die Funktionsaufrufe sind selbsterklärend, wenn nicht fragen.
Was ist unlikely() ? Das habe ich schon öfter gesehen und kann mir darunter garnichts vorstellen...

Es kann auch sein das noch Fehler drin sind, ich habe den Code noch nicht getestet!
Schaue ich mir eventuell nachher an, ist aber Praxis und die kann ich nicht. :-D

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 08. November 2010, 11:46
Zitat von: svenska
Jein. Der VMM darf kein kmalloc() benutzen, weil er ein kmalloc() implementieren soll. Du musst aber trotzdem nichts spezielles bauen, denn dein virtueller Speichermanager baut auf dem physischen Speicherverwalter auf und kann diesen auch selbst benutzen. Ich sehe keine besondere Einschränkung, wenn im VMM nur ein getPhysMem() existiert, was nur ganze Pages bereitstellen kann. Mit virtuellen Adressen kannst du im VMM ohnehin nichts anfangen, weil du diese ja erst bereitstellst.
Was macht denn ein kmalloc()? Der weiß wieviel Speicher er noch hat und wenn dieser nicht mehr reicht dann wird der VMM aufgerufen. Also müsste auch ein kmalloc() was vom VMM implementiert (hört sich doof an und weiß auch nicht so richtig was du damit meinst) ist den VMM aufrufen.
Desweiteren kannst du ja so nicht mal eben deinen kmalloc() austauschen und das ist ja auch doof.

Zitat von: svenska
Warum sollte der VMM selbst einen VMM nutzen müssen?
Woher soll denn der VMM wissen welche virtuellen Bereiche frei sind und welche nicht, wenn er es nicht irgendwo speichert? Um diese Infos irgendwo zu speichern, brauchst du Speicher (virtuellen und physischen). Um das ganze möglichst flexibel zu machen, sollte man nicht immer schon den ganzen Speicher für den worst-case allokieren (dann würde dein OS sehr sehr viel Speicher brauchen um nur starten zu können und zur Laufzeit wird das pro Prozess immer und immer mehr).
Also muss der VMM die Möglichkeit haben nur Objekte zu allokieren und dafür kann es (und wird es) notwendig sein das du nen freien virtuellen Bereich brauchst und dafür brauchst du wieder den VMM.

Zitat von: svenska
Was ist unlikely() ? Das habe ich schon öfter gesehen und kann mir darunter garnichts vorstellen...
Sorry, wenn ich mal so treist frage, aber kannst du englisch?

Likely = Wahrscheinlich
Unlikely = Unwahrscheinlich

Das sind Makros um dem Compiler mehr Infos zum Optimieren zu geben, da ich ja besser als er weiß welche Pfade wahrscheinlicher sind als andere.

Zitat von: svenska
Schaue ich mir eventuell nachher an, ist aber Praxis und die kann ich nicht.
Das ist immer schlecht. Ich (persönlich) finde ja das man nur mit Praxis weiter kommt als nur mit Theorie!
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 08. November 2010, 13:12
...

Die Übersetzung von likeley/unlikely ist mir schon klar, vielen Dank für dein Verständnis. Es gibt aber keine manpage dazu, und wenn es ein Compiler-Makro ist, dann sieht man dem Ding eben nicht an. Und meine C-Kenntnisse sind C-Kenntnisse, keine gcc-Kenntnisse. Vielen Dank.

Zum Rest: Du verstehst nicht, was ich meine, wenn ich sage, dass der VMM den VMM nicht aufrufen darf.

Zitat von: svenska
Jein. Der VMM darf kein kmalloc() benutzen, weil er ein kmalloc() implementieren soll. Du musst aber trotzdem nichts spezielles bauen, denn dein virtueller Speichermanager baut auf dem physischen Speicherverwalter auf und kann diesen auch selbst benutzen. Ich sehe keine besondere Einschränkung, wenn im VMM nur ein getPhysMem() existiert, was nur ganze Pages bereitstellen kann. Mit virtuellen Adressen kannst du im VMM ohnehin nichts anfangen, weil du diese ja erst bereitstellst.
Was macht denn ein kmalloc()? Der weiß wieviel Speicher er noch hat und wenn dieser nicht mehr reicht dann wird der VMM aufgerufen. Also müsste auch ein kmalloc() was vom VMM implementiert (hört sich doof an und weiß auch nicht so richtig was du damit meinst) ist den VMM aufrufen.
Nein.
void *kmalloc(size_t size, int flags) alloziiert Speicher der Größe size, wobei flags angibt, ob es User-/Kernel-/Interrupthandler-Speicher ist. So steht es hier (http://ftp://flashdeath.static.corbina.ru/docs/rulinux.faq/%ED%E5%EC%ED%EE%E3%EE%20%EF%F0%EE%20%EB%E8%ED%F3%F5/linux%20programming/hw/kernel-api/kernel-api-html/r2415.html) zumindest.

Wo implementierst du denn dein kmalloc()? In der Userspace-C-Library? Wahrscheinlich nicht.

Zumindest in meinem Kopf ist das ein Dreischichtenmodell: physischer Speicher, virtueller Speicher, Objekte. Die bauen aufeinander auf, aber nicht umgekehrt.

(a) Der physische Speicherverwalter (ab hier PMM) nimmt physischen Speicher, arbeitet mit physischen Adressen und kann nur PAGESIZE-große (=4k, 2M, 4M, ...) Blöcke verwalten. Diese Blöcke sind also physische Speicherblöcke mit RAM dahinter.

(b) Der virtuelle Speicherverwalter (ab hier VMM) nimmt diese physischen Speicherblöcke, die ihm vom PMM zur Verfügung gestellt wurden und verwaltet die. Er implementiert ein kmalloc(). Er darf kmalloc() niemals aufrufen. Wenn er für sich selbst Speicher braucht, muss er den PMM danach fragen und kann auch nur physische Speicherblöcke für sich selbst verwenden. Diesen Blöcken darf er gerne virtuelle Adressen geben und damit weiterarbeiten.

(c) Der Slab-Allocator sitzt oben auf dem VMM drauf und verwaltet Objekte. Er darf kmalloc() benutzen und mit dem Speicher tun, was immer er möchte.

Tut mir ja Leid, wenn dein VMM in dem Schema den Slab-Allocator nicht benutzen darf. Der PMM darf den VMM auch nicht benutzen.

Desweiteren kannst du ja so nicht mal eben deinen kmalloc() austauschen und das ist ja auch doof.
Warum? Ein kmalloc() ist eng mit der Speicherverwaltung verbunden, wird also vom VMM bereitgestellt. Du kannst kmalloc() nicht vom VMM trennen, ohne zirkuläre Abhängigkeiten und damit Extra-Codepfade zu bekommen.

Zitat von: svenska
Warum sollte der VMM selbst einen VMM nutzen müssen?
Woher soll denn der VMM wissen welche virtuellen Bereiche frei sind und welche nicht, wenn er es nicht irgendwo speichert?
Warum sollte der VMM selbst einen VMM nutzen müssen? Ich wiederhole die Frage gerne nochmals. Er ist der VMM. Er ruft ihn nicht auf. Er ist es.

Um diese Infos irgendwo zu speichern, brauchst du Speicher (virtuellen und physischen). Um das ganze möglichst flexibel zu machen, sollte man nicht immer schon den ganzen Speicher für den worst-case allokieren (dann würde dein OS sehr sehr viel Speicher brauchen um nur starten zu können und zur Laufzeit wird das pro Prozess immer und immer mehr).
Du brauchst, um Daten speichern zu können, in erster Linie physischen Speicher. Den kriegst du vom PMM. Um daraus virtuellen Speicher zu machen, wirst du den VMM nicht aufrufen, denn das kannst du selbst (du bist der VMM).

Also muss der VMM die Möglichkeit haben nur Objekte zu allokieren und dafür kann es (und wird es) notwendig sein das du nen freien virtuellen Bereich brauchst und dafür brauchst du wieder den VMM.
Warum sollte der VMM den VMM aufrufen, wenn er es doch selber kann?

Zitat von: svenska
Schaue ich mir eventuell nachher an, ist aber Praxis und die kann ich nicht.
Das ist immer schlecht. Ich (persönlich) finde ja das man nur mit Praxis weiter kommt als nur mit Theorie!
Tut mir Leid, dass ich derzeit  kein OS auf die Beine stelle. Ich habe dazu weder Zeit noch Lust, dennoch Interesse daran.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 08. November 2010, 13:52
Zitat von: svenska
void *kmalloc(size_t size, int flags) alloziiert Speicher der Größe size, wobei flags angibt, ob es User-/Kernel-/Interrupthandler-Speicher ist.
Ein kmalloc ist nichts anderes als ein malloc! Der einzige Unterschied ist, das er im Kernel benutzt wird.

Zitat von: svenska
Der physische Speicherverwalter (ab hier PMM) nimmt physischen Speicher, arbeitet mit physischen Adressen und kann nur PAGESIZE-große (=4k, 2M, 4M, ...) Blöcke verwalten. Diese Blöcke sind also physische Speicherblöcke mit RAM dahinter.
Wo läuft der Code des PMM und wo speichert der PMM seine Daten?

Zitat von: svenska
Der virtuelle Speicherverwalter (ab hier VMM) nimmt diese physischen Speicherblöcke, die ihm vom PMM zur Verfügung gestellt wurden und verwaltet die. Er implementiert ein kmalloc(). Er darf kmalloc() niemals aufrufen. Wenn er für sich selbst Speicher braucht, muss er den PMM danach fragen und kann auch nur physische Speicherblöcke für sich selbst verwenden. Diesen Blöcken darf er gerne virtuelle Adressen geben und damit weiterarbeiten.
Woher weiß der VMM welche virtuelle Adresse frei ist? Nach deinem Verständnis ist virtuelle Adresse == physischer Adresse. Dem ist aber fast nie so, dafür gibt es ja Paging und genau deswegen braucht man ja einen PMM und einen VMM.

Zitat von: svenska
Der Slab-Allocator sitzt oben auf dem VMM drauf und verwaltet Objekte. Er darf kmalloc() benutzen und mit dem Speicher tun, was immer er möchte.
Warum sollte der SlabAllocator kmalloc() benutzen? Der SlabAllocator holt sich seinen Speicher vom VMM und nur mal so, in Linux steckt hinter dem kmalloc() der SlabAllocator.

Zitat von: svenska
Warum? Ein kmalloc() ist eng mit der Speicherverwaltung verbunden, wird also vom VMM bereitgestellt. Du kannst kmalloc() nicht vom VMM trennen, ohne zirkuläre Abhängigkeiten und damit Extra-Codepfade zu bekommen.
Sorry, aber du hast nicht verstanden was ein kmalloc() ist und was es macht. Genauso weißt du eigentlich nicht was ein VMM macht und wie Paging funktioniert.

Zitat von: svenska
Tut mir Leid, dass ich derzeit  kein OS auf die Beine stelle. Ich habe dazu weder Zeit noch Lust, dennoch Interesse daran.
Naja, der Code hat erstmal gar nichts mit einem OS zu tun und ich bin daher davon ausgegangen das du theoretisch was vom Programmieren weißt, es aber praktisch noch nicht wirklich gemacht hast.

Der Code verwendet im Endeffekt einfach nur 2 Bäume um seine Daten zu organisieren und dafür muss man sich nicht mal im entferntesten mit einem OS beschäftigt habe.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 08. November 2010, 16:22
Ein kmalloc ist nichts anderes als ein malloc! Der einzige Unterschied ist, das er im Kernel benutzt wird.
Richtig.

Wo läuft der Code des PMM und wo speichert der PMM seine Daten?
Der PMM wird seine Daten höchstwahrscheinlich im physischen RAM speichern... und der VMM hat sicherzustellen, dass der PMM auch weiß, wo das im virtuellen Adressraum ist (dieser Bereich kann 1:1 gemappt werden).

Woher weiß der VMM welche virtuelle Adresse frei ist?
Er ist der VMM, er kümmert sich um die Verwaltung des virtuellen Speichers. Also weiß er, wo da im Adressraum etwas frei ist...

Nach deinem Verständnis ist virtuelle Adresse == physischer Adresse. Dem ist aber fast nie so, dafür gibt es ja Paging und genau deswegen braucht man ja einen PMM und einen VMM.
Ist mir klar. Der PMM kümmert sich um die physischen Adressen, der VMM kümmert sich um die Umrechnung von physischen Adressen in virtuelle Adressen und die Steuerung der MMU. Und für die Speicherbereiche des PMM kann man das 1:1-Mapping garantieren, da es sich um elementare (und recht kleine) Strukturen handelt.

Zitat von: svenska
Der Slab-Allocator sitzt oben auf dem VMM drauf und verwaltet Objekte. Er darf kmalloc() benutzen und mit dem Speicher tun, was immer er möchte.
Warum sollte der SlabAllocator kmalloc() benutzen? Der SlabAllocator holt sich seinen Speicher vom VMM und nur mal so, in Linux steckt hinter dem kmalloc() der SlabAllocator.
Man kann beides auch trennen.

Ich hatte überlegt, die Dinger anders zu bezeichnen, mit getPhysMem(), was vom PMM bereitgestellt wird, getVirtMem(), was vom VMM bereitgestellt wird und kmalloc(), was vom Slab-Allocator bereitgestellt wird. Das ist aber nur eine Lösung. Du kannst das kmalloc() auch vom VMM bereitstellen, und einen Slab-Allocator oben draufsetzen - das war mein Ausgangspunkt. Oder du kannst Slab-Allocator und VMM gemeinsam, monolithisch, implementieren - dann ist kmalloc() der Slab-Allocator, der als darunterliegende Schicht den PMM direkt benutzt. Der Slab-Allocator wäre somit identisch dem VMM.

Zitat von: svenska
Warum? Ein kmalloc() ist eng mit der Speicherverwaltung verbunden, wird also vom VMM bereitgestellt. Du kannst kmalloc() nicht vom VMM trennen, ohne zirkuläre Abhängigkeiten und damit Extra-Codepfade zu bekommen.
Sorry, aber du hast nicht verstanden was ein kmalloc() ist und was es macht. Genauso weißt du eigentlich nicht was ein VMM macht und wie Paging funktioniert.
Wie Paging funktioniert, ist mir durchaus klar. Was kmalloc() tut, ist mir auch klar. Dazwischen liegt eine Speicherverwaltung, und wie man die implementiert, ist relativ frei... ich habe halt eine Vorstellung davon, wie man das tun kann. Du tust das offensichtlich anders. Kein Grund, mir Inkompetenz vorzuwerfen.

Zitat von: svenska
Tut mir Leid, dass ich derzeit kein OS auf die Beine stelle. Ich habe dazu weder Zeit noch Lust, dennoch Interesse daran.
Naja, der Code hat erstmal gar nichts mit einem OS zu tun und ich bin daher davon ausgegangen das du theoretisch was vom Programmieren weißt, es aber praktisch noch nicht wirklich gemacht hast.
Der Code verwendet im Endeffekt einfach nur 2 Bäume um seine Daten zu organisieren und dafür muss man sich nicht mal im entferntesten mit einem OS beschäftigt habe.
Ich habe seit dem Abitur keine binären Baumstrukturen mehr gebraucht oder verwendet. Das ist inzwischen über 3 Jahre her. Außerdem habe ich sie damals nur in Delphi umgesetzt, nicht in C und die Compilerinterna vom gcc kenne ich auch nicht. Ich nutze einen "Compiler" und für Sachen, die sich auf stdio beschränken, brauche ich keinerlei Erweiterungen. Außerdem bin ich auf Arbeit und habe nebenher noch andere Dinge zu tun. Code analysieren kostet halt Zeit.

Zumal ich mich nie groß mit Optimierungen auseinandergesetzt habe (maximal QuickSort fürs Abi) und durch deinen Code steige ich spontan ebenfalls nicht durch.

Lässt sich das Mergen nicht durchführen, indem du den Baum mit den Adressen als Suchschlüssel stupide in-order traversierst und, wenn du deine freizugebende Adresse findest, das vorhergehende und das nachfolgende Element vergleichst? (Die drei Elemente fallen beim Traversieren eh raus.) Wenn du bei jeder Freigabe jeweils deinen Block mit dem vorhergehenden und nachfolgendem Block zusammenfügst (sofern möglich), brauchst du nicht den gesamten Baum durchsuchen, sondern nur diese drei Blöcke und die Wege dazwischen und du hast trotzdem die größtmöglichen Blöcke.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 08. November 2010, 16:58
Zitat von: svenska
Er ist der VMM, er kümmert sich um die Verwaltung des virtuellen Speichers. Also weiß er, wo da im Adressraum etwas frei ist...
Und wo speichert er diese Infos? Die nächste Frage wäre dann woher er den Speicher bekommt das er diese Infos Speichern kann.
Wie gesagt, alles im Vorraus zu allokieren ist nicht praktikabel.

Zitat von: svenska
der VMM kümmert sich um die Umrechnung von physischen Adressen in virtuelle Adressen
Nicht wirklich, das macht die MMU, wie du dann weiterschreibst steuert der VMM die MMU. Die eigentliche Aufgabe des VMM ist es, zu wissen welche Bereiche, in einem Adressraum, noch frei sind.

Also gut nur mal so als Bsp. wie ein Pseudo-Code von einem malloc() aussehen könnte:
void *malloc(uint32t size) {
 void *addr= guckeObFreierBlockVorhanden(size + 4); //man kann das in einer Liste machen

 if(!addr) {
  void *newMem= vmmGetVirtMem((size + 3 & ~0xFFF) + 0x1000); //das in der Klammer aligned size+4 auf Page-Größe
  packeNeuenSpeicherBlockInList(newMem);
  addr= guckeObFreierBlockVorhanden(size);
 }

 *((uint32t *)addr)= size; //ich merke mir wie groß der rausgegebene Block ist

 return addr + 4; //und gebe die Addr auf den eigentlichen Speicher an den User weiter
}

Der malloc()-Code hat in dem Sinne erstmal gar nichts mit dem VMM zu tun, sondern er benutzt ihn. Wenn du als User mit 4KB-Pages an Speicher klarkommst, könntest du auch malloc() gar nicht benutzen. malloc() ermöglicht dir einfach das du dich nicht darum kümmern brauchst.

Der SlabAllocator funktioniert ähnlich, auch er holt sich immer ein vielfaches der Page-Größe vom VMM (was anderes bekommst du auch nicht und das ist der Unterschied zu malloc()) und gibt dann einen Pointer auf ein freies Objekt raus, hat auch nichts mit dem VMM zu tun.

Zitat von: svenska
Kein Grund, mir Inkompetenz vorzuwerfen.
Sorry, aber nachdem was du geschrieben hast, hast du halt nicht verstanden wie das normalerweise so miteinander funktioniert.

Zitat von: svenska
Außerdem bin ich auf Arbeit und habe nebenher noch andere Dinge zu tun. Code analysieren kostet halt Zeit.
Du darfst auf Arbeit nebenbei surfen ;)

Ich meinte damit auch gar nicht das du den Code schnell mal analysieren sollst. Es ging mir darum, das nur weil ich den Code in meinem OS verwende, muss man sich nicht mit OS Programmierung auskennen, um den Code zu verstehen.

Ich kann mir mit der folgenden Aussage jetzt ziemlichen Ärger einhandeln, aber was solls ;)

Ich denke als Programmierer sollte man schon so grundlegende Datenstrukturen wie Bäume (auch ausgeglichene) kennen. Ich weiß nun nicht ob du beruflich programmierst oder nicht (wenn nicht dann schiebe ich das jetzt mal auf noch keine Erfahrung), aber Bäume gibt es schon ziemlich lange ;)
Auch ist es keine Ausrede in welcher Sprache du das mal geschrieben hast, wer programmieren kann, dem ist die Sprache (nicht das Paradigma!) egal.

Zitat von: svenska
und die Compilerinterna vom gcc kenne ich auch nicht.
Ich denke mal du spielst auf likely/unlikely an? Ich hab mal "gcc likely" bei google eingegeben und gleich die ersten beiden Ergebnisse waren dahingehend sehr hilfreich ;)

Zitat von: svenska
Lässt sich das Mergen nicht durchführen, indem du den Baum mit den Adressen als Suchschlüssel stupide in-order traversierst und, wenn du deine freizugebende Adresse findest, das vorhergehende und das nachfolgende Element vergleichst? (Die drei Elemente fallen beim Traversieren eh raus.) Wenn du bei jeder Freigabe jeweils deinen Block mit dem vorhergehenden und nachfolgendem Block zusammenfügst (sofern möglich), brauchst du nicht den gesamten Baum durchsuchen, sondern nur diese drei Blöcke und die Wege dazwischen und du hast trotzdem die größtmöglichen Blöcke.
Ich bin mir jetzt nicht sicher ob ich dich verstanden habe, aber in einem Baum muss das vorhergehende (der Vater) Element (Node) nicht auch der Vorgänger sein!

Du bringst mich allerdings auf die Idee, das ich gleichzeitig gucken kann, ob ich einen Bereich am Ende oder am Anfang gefunden habe, mit dem ich mergen kann (das mach ich momentan in 2 Baum-Traversierungen). Denn wenn ich eins gefunden haben kann der eventuell andere Bereich nur noch in den Kind-Knoten davon liegen. Damit würde ich mir eine weitere Traversierung sparen.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 08. November 2010, 22:50
Zitat von: svenska
Er ist der VMM, er kümmert sich um die Verwaltung des virtuellen Speichers. Also weiß er, wo da im Adressraum etwas frei ist...
Und wo speichert er diese Infos? Die nächste Frage wäre dann woher er den Speicher bekommt das er diese Infos Speichern kann.
Der PMM kann Speicher bereitstellen und funktioniert auch ohne VMM und ohne Paging. Also bezieht der VMM seinen Speicher vom PMM und setzt das Mapping selbst auf.

Zitat von: svenska
der VMM kümmert sich um die Umrechnung von physischen Adressen in virtuelle Adressen
Nicht wirklich, das macht die MMU, wie du dann weiterschreibst steuert der VMM die MMU. Die eigentliche Aufgabe des VMM ist es, zu wissen welche Bereiche, in einem Adressraum, noch frei sind.
Die Umrechnung macht die MMU, aber das Mapping wird vom VMM aufgebaut. Ebenso die Verwaltung, welcher Speicher wo wie gemappt ist (sofern benötigt) und was wo frei ist.

Also gut nur mal so als Bsp. wie ein Pseudo-Code von einem malloc() aussehen könnte:
(schnipp)
Jo, so kann man das machen.

Der malloc()-Code hat in dem Sinne erstmal gar nichts mit dem VMM zu tun, sondern er benutzt ihn. Wenn du als User mit 4KB-Pages an Speicher klarkommst, könntest du auch malloc() gar nicht benutzen. malloc() ermöglicht dir einfach das du dich nicht darum kümmern brauchst.
Richtig. Damit implementierst du malloc() nicht im VMM, sondern eine Ebene darüber, auf der gleichen Ebene wie z.B. den Slab Allocator.

Der SlabAllocator funktioniert ähnlich, auch er holt sich immer ein vielfaches der Page-Größe vom VMM (was anderes bekommst du auch nicht und das ist der Unterschied zu malloc()) und gibt dann einen Pointer auf ein freies Objekt raus, hat auch nichts mit dem VMM zu tun.
Es sei denn, du implementierst deinen VMM so, dass er ein malloc() bereitstellt und eben nicht mit Pagegrößen arbeitet, sondern mit Speicherblöcken variabler Größe umgehen kann.

Zitat von: svenska
Kein Grund, mir Inkompetenz vorzuwerfen.
Sorry, aber nachdem was du geschrieben hast, hast du halt nicht verstanden wie das normalerweise so miteinander funktioniert.
Ich mache mir die Welt, widewidewie sie mir gefällt... ich hätte das halt anders gelöst.

Zitat von: svenska
Außerdem bin ich auf Arbeit und habe nebenher noch andere Dinge zu tun. Code analysieren kostet halt Zeit.
Du darfst auf Arbeit nebenbei surfen ;)
Ist "nur" ein Praktikum. Und wenn der Chef überarbeitet ist und ich nichts zu tun habe, dann ergibt sich das halt...

Ich meinte damit auch gar nicht das du den Code schnell mal analysieren sollst. Es ging mir darum, das nur weil ich den Code in meinem OS verwende, muss man sich nicht mit OS Programmierung auskennen, um den Code zu verstehen.
Wenn man den Code hat und relativ wenig Kontext dazu (Dokumentation!) und aus dem Code rauspopeln muss, wie die Datenstrukturen aussehen, ohne ihn ausführen und nachschauen zu können, dann ist das recht schwer, wenn man den Code nicht geschrieben hat oder nicht so sehr in der Thematik drin ist.

Ich denke als Programmierer sollte man schon so grundlegende Datenstrukturen wie Bäume (auch ausgeglichene) kennen. Ich weiß nun nicht ob du beruflich programmierst oder nicht (wenn nicht dann schiebe ich das jetzt mal auf noch keine Erfahrung), aber Bäume gibt es schon ziemlich lange ;)
Ich programmiere hauptsächlich sowas (http://wwwiti.cs.uni-magdeburg.de/~hollatz/c/a8-struct-neu.html), sowas (http://wwwiti.cs.uni-magdeburg.de/~hollatz/c/a9-klassen-1.htm) oder sowas (http://wwwiti.cs.uni-magdeburg.de/~hollatz/c/a12-operatoren.htm). Das ist ein bisschen simpler.

Ich kenne einfache binäre Suchbäume und eine Möglichkeit, diese auszugleichen. Sich selbst ausgleichende Bäume (wie z.B. der von dir verwendete AVL-Baum) sind mir vom Prinzip her bekannt, jedoch nicht von der Implementierung. (Ich könnte mir das schon selbst implementieren, aber eben nicht aus dem Effeff.)

Auch ist es keine Ausrede in welcher Sprache du das mal geschrieben hast, wer programmieren kann, dem ist die Sprache (nicht das Paradigma!) egal.
Das ist korrekt. Und Delphi/C sind nicht die einzigen Programmiersprachen, in denen ich programmieren kann. Es ist aber ein Unterschied, wenn du gewisse Paradigmen vor Jahren in einer Programmiersprache letztmalig angefasst hast, das ewig nicht benutzt hast und dann von dir erwartet wird, das spontan hochzuhusten. Das kann ich nicht.

Zitat von: svenska
und die Compilerinterna vom gcc kenne ich auch nicht.
Ich denke mal du spielst auf likely/unlikely an? Ich hab mal "gcc likely" bei google eingegeben und gleich die ersten beiden Ergebnisse waren dahingehend sehr hilfreich ;)
Ich habe "man likely" und "man unlikely" eingegeben und anschließend dein Angebot für Nachfragen angenommen...

Zitat von: svenska
Lässt sich das Mergen nicht durchführen, indem du den Baum mit den Adressen als Suchschlüssel stupide in-order traversierst und, wenn du deine freizugebende Adresse findest, das vorhergehende und das nachfolgende Element vergleichst? (Die drei Elemente fallen beim Traversieren eh raus.)
Ich bin mir jetzt nicht sicher ob ich dich verstanden habe, aber in einem Baum muss das vorhergehende (der Vater) Element (Node) nicht auch der Vorgänger sein!
Das meinte ich nicht.

Wenn du InOrder-Traversierst, dann bekommst du die Einträge des Baumes in sortierter Reihenfolge, bei entsprechender Strukturierung also den Adressraum von Null bis Ende. Da du nur aufeinanderfolgende Bereiche mergen kannst, brauchst du also deinen Suchschlüssel, das vorhergehende und das nachfolgende Element - also speicherst du immer das aktuelle und die beiden letzten Elemente extra ab und traversierst exakt einmal mehr, als du für das Finden brauchst und brichst dann ab.

Damit hast du das "Suchschlüssel+1" (das aktuelle), "Suchschlüssel" (das letzte) und "Suchschlüssel-1" (das vorletzte) Element gleichzeitig aus dem Baum gepopelt und brauchst nur nachschauen, ob diese auch aufeinander folgen (dann sind sie mergebar) oder nicht.

Du bringst mich allerdings auf die Idee, das ich gleichzeitig gucken kann, ob ich einen Bereich am Ende oder am Anfang gefunden habe, mit dem ich mergen kann (das mach ich momentan in 2 Baum-Traversierungen). Denn wenn ich eins gefunden haben kann der eventuell andere Bereich nur noch in den Kind-Knoten davon liegen. Damit würde ich mir eine weitere Traversierung sparen.
Ist mir spontan zu hoch, klingt aber für mich falsch. Der "hintere" Bereich liegt immer hinter dem "vorderen" Bereich. Da du die Bäume balanciert hältst, kann der hintere Bereich auch im anderen Zweig liegen und nicht in einem Kindknoten, nämlich dann, wenn dein Suchwort in irgendeinem Zweig bereits das rechteste Element ist.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 09. November 2010, 10:49
Zitat von: svenska
Der PMM kann Speicher bereitstellen und funktioniert auch ohne VMM und ohne Paging. Also bezieht der VMM seinen Speicher vom PMM und setzt das Mapping selbst auf.
Das klappt aber nur, wenn du vorher weißt wieviel RAM du verwalten musst und du dann deine Datenstrukturen schon im vorraus reservierst (so mache ich das auch, ich wüsste auch nicht wie man es anders lösen sollte).

Zitat von: svenska
Es sei denn, du implementierst deinen VMM so, dass er ein malloc() bereitstellt und eben nicht mit Pagegrößen arbeitet, sondern mit Speicherblöcken variabler Größe umgehen kann.
Könntest du denn nicht mal pseudo-Code schreiben, so wie du dir das vorstellst. Denn egal wie ich das drehe oder wende es läuft immer darauf hinaus, das du nen vmmAllocMem(uint32t size) zur Verfügung stellst und diese Funktion wird halt irgendeine Funktion aus deinem VMM aufrufen wo sie mehr Speicher bekommt (Page-Größe), weil der VMM kann auch nur Page-Größe verwalten, weil die MMU nichts anderes zulässt.
In dem Sinne ist das auch losgelöst vom VMM, ich weiß gerade nicht so richtig was du damit meinst das der VMM auch ein malloc() implementieren soll.

Der Punkt ist einfach, das du die Zirkularität nicht raus bekommst.

Zitat von: svenska
Wenn man den Code hat und relativ wenig Kontext dazu (Dokumentation!) und aus dem Code rauspopeln muss, wie die Datenstrukturen aussehen, ohne ihn ausführen und nachschauen zu können, dann ist das recht schwer, wenn man den Code nicht geschrieben hat oder nicht so sehr in der Thematik drin ist.
Stimmt schon. Mir ging es auch nur darum wie ich die Suche in einem Baum nach einem Bereich bzw. 2 Elementen gleichzeitig und effizient machen kann.

Zitat von: svenska
Ich programmiere hauptsächlich sowas, sowas oder sowas. Das ist ein bisschen simpler.
Darf ich also annehmen das du in Magdeburg studierst? Da habe ich auch angefangen zu studieren (jetzt bin ich woanders) ;)

Zitat von: svenska
Wenn du InOrder-Traversierst, dann bekommst du die Einträge des Baumes in sortierter Reihenfolge, bei entsprechender Strukturierung also den Adressraum von Null bis Ende. Da du nur aufeinanderfolgende Bereiche mergen kannst, brauchst du also deinen Suchschlüssel, das vorhergehende und das nachfolgende Element - also speicherst du immer das aktuelle und die beiden letzten Elemente extra ab und traversierst exakt einmal mehr, als du für das Finden brauchst und brichst dann ab.

Damit hast du das "Suchschlüssel+1" (das aktuelle), "Suchschlüssel" (das letzte) und "Suchschlüssel-1" (das vorletzte) Element gleichzeitig aus dem Baum gepopelt und brauchst nur nachschauen, ob diese auch aufeinander folgen (dann sind sie mergebar) oder nicht.
Da mir gerade klar wird was du mit InOrder-Traversierung meinst, kann ich nur sagen, das ist nicht im Zwecke des Erfinders ;)

Der Vorteil eines Baums ist ja das du gerade nicht alle Elemente anfassen musst, wenn du was bestimmtes suchst und du willst ja ne Art Liste machen (das wäre das Ergebnis der InOrder-Traversierung).

Mir ist gestern Abend dann noch die entscheidene Idee gekommen.

In meinem aktuellen Code ist auch noch ein Fehler. Denn ich muss den Baum immer bis zu einem Ende durchlaufen, ansonsten kann ich nicht sicher sein das ich nicht gerade einen "illegalen" Bereich freigeben möchte.

Ich habe mir das so vorgestellt das ich 2 Pointer habe *before und *behind, die auf Baum-Knoten zeigen können.
Ich gehe halt den Baum von der Wurzel an durch und überprüfe jeden Knoten darauf, ob der Bereich sich irgendwie mit dem freizugebenden überschneidet (wenn ja, dann wird ein Fehler zurückgegeben). Desweiteren wird jeder Knoten darauf überpüft ob der Bereich da drinnen sich mit dem freizugebenden irgendwie angrenz (deswegen before und behind) und wenn das der Fall ist, wird der entsprechende Pointer auf diese Node zeigen.
Ich gehe dann den Baum noch bis zu einem Ende durch um sicher zu sein, das der Bereich auch freigegeben werden darf.

Wenn ich dann mit der Schleife durch bin, brauche ich einfach nur 3 Fälle unterscheiden:

Und schon gehe ich den Baum nur noch einmal anstatt zweimal durch und ich kann dann auch ganz leicht sagen welche "struct avlNode_t" und "struct boundaryTag_t" ich freigeben kann und kann mir so unnötig Allocator-Aufrufe sparen.

Ich denke das sollte das Optimum sein was man rausholen kann und eine Halbierung im worst-case sollte nicht schlecht sein.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 09. November 2010, 17:20
Hallo,

Zitat von: svenska
Der PMM kann Speicher bereitstellen und funktioniert auch ohne VMM und ohne Paging. Also bezieht der VMM seinen Speicher vom PMM und setzt das Mapping selbst auf.
Das klappt aber nur, wenn du vorher weißt wieviel RAM du verwalten musst und du dann deine Datenstrukturen schon im vorraus reservierst (so mache ich das auch, ich wüsste auch nicht wie man es anders lösen sollte).
Solange die RAM-Größe sich nicht im Betrieb ändert, erhältst du die zu verwaltende RAM-Größe (physisch) vom BIOS bzw. einer Probe-Funktion deiner Wahl. Ich weiß nicht, worauf du hinaus möchtest. Der PMM bekommt die RAM-Größe mitgeteilt und gibt diese Information an den VMM weiter, der daraufhin seine Datenstrukturen anlegt.

Zitat von: svenska
Es sei denn, du implementierst deinen VMM so, dass er ein malloc() bereitstellt und eben nicht mit Pagegrößen arbeitet, sondern mit Speicherblöcken variabler Größe umgehen kann.
Könntest du denn nicht mal pseudo-Code schreiben, so wie du dir das vorstellst.
Nein, denn dazu müsste ich solchen Code zumindest real mal angefasst haben...

Denn egal wie ich das drehe oder wende es läuft immer darauf hinaus, das du nen vmmAllocMem(uint32t size) zur Verfügung stellst und diese Funktion wird halt irgendeine Funktion aus deinem VMM aufrufen wo sie mehr Speicher bekommt (Page-Größe), weil der VMM kann auch nur Page-Größe verwalten, weil die MMU nichts anderes zulässt.
In dem Sinne ist das auch losgelöst vom VMM, ich weiß gerade nicht so richtig was du damit meinst das der VMM auch ein malloc() implementieren soll.
Ich verstehe dein Problem. In meiner Welt besteht der VMM aus zwei Teilen, einmal einem auf Page-Größen basierendem System zur Verwaltung der MMU, zum anderen aus einem System zur Verwaltung der Blockgrößen (aller Speicherblöcke) in einem zentralen Register.

In deinem Pseudocode speicherst du die Informationen zu jedem Speicherblock (die Größe) direkt vor den Speicherblock, das heißt es gibt kein zentrales Register, damit fällt auch die gesamte Funktionalität aus dem VMM heraus. Bei dir gibt es damit einen VMM, der nur Pagegrößen kennt und damit arbeitet, und darüber den Slab-Allocator, welcher malloc() implementiert.

Da ich vor diesem Thread mit einem Slab-Allocator nichts anfangen konnte, gab es bei mir keine Slabs, und damit musste die Verwaltung von beliebig großen Speicherblöcken (die malloc()-Implementierung) woanders erledigt werden. Für meine Welt im VMM.

Der Punkt ist einfach, das du die Zirkularität nicht raus bekommst.
Doch, wenn du nämlich dem VMM verbietest, den VMM zu nutzen. ;-) Das heißt, dass der VMM kein allgemeines malloc() benutzen darf, da er es entweder selbst implementiert (ich) oder die malloc()-Implementation einen VMM voraussetzt.

Wenn der VMM nur mit Pages arbeitet, so darf er halt nur Pages für seine interne Verwaltung nutzen. Das ist eine Einschränkung, aber das vermeidet zirkuläre Abhängigkeiten. Der VMM implementiert also nur ein getVirtMem(int size) mit size = n*PAGESIZE.

Stimmt schon. Mir ging es auch nur darum wie ich die Suche in einem Baum nach einem Bereich bzw. 2 Elementen gleichzeitig und effizient machen kann.
Und genau das ist ein Optimierungsproblem. Dazu müsstest du einen Informatiker fragen. Ich bin keiner.

Darf ich also annehmen das du in Magdeburg studierst? Da habe ich auch angefangen zu studieren (jetzt bin ich woanders) ;)
Darfst du. Bin allerdings so gut wie fertig. Was hast du studiert? (Ich bin an der FEIT.)

Da mir gerade klar wird was du mit InOrder-Traversierung meinst, kann ich nur sagen, das ist nicht im Zwecke des Erfinders ;)
Das ist mir schon klar. ;-)

Der Vorteil eines Baums ist ja das du gerade nicht alle Elemente anfassen musst, wenn du was bestimmtes suchst und du willst ja ne Art Liste machen (das wäre das Ergebnis der InOrder-Traversierung).
Exakt, dabei fallen dann das vorherige/nachfolgende Element mit bei raus. Ein Baum ist relativ ungeeignet, um das vorherige/nachfolgende Element zu einem bestimmten Element zu finden, zumindest wüsste ich nicht, wie man das tut. (Im Gegensatz zu einer doppelt verketteten Liste, wo das trivial ist.)

Mir ist gestern Abend dann noch die entscheidene Idee gekommen.

In meinem aktuellen Code ist auch noch ein Fehler. Denn ich muss den Baum immer bis zu einem Ende durchlaufen, ansonsten kann ich nicht sicher sein das ich nicht gerade einen "illegalen" Bereich freigeben möchte.
Ich verstehe nicht, was du meinst. Inwiefern illegal?

Ich stelle mir einen Baum vor, der Blöcke anhand ihrer Anfangsadresse(!) im Adressraum enthält und zu jedem Block abspeichert, ob er gerade frei oder belegt ist (und wenn ja, von wem). Jede Ressource (Adressraum) gehört zu exakt einem Block, das heißt es gibt prinzipbedingt keine Überschneidungen. Ist derjenige, der die Freigabe fordert nicht der Eigentümer, darf nicht freigegeben werden. (Möchte der Kernel ein "Loch" freigeben, so darf er das. Jemand könnte ja RAM nachgesteckt haben oder was auch immer.)

Ich habe mir das so vorgestellt das ich 2 Pointer habe *before und *behind, die auf Baum-Knoten zeigen können.
Ich gehe halt den Baum von der Wurzel an durch und überprüfe jeden Knoten darauf, ob der Bereich sich irgendwie mit dem freizugebenden überschneidet (wenn ja, dann wird ein Fehler zurückgegeben).
In einer Baumstruktur können zwei Nachbarn aber prinzipiell weit voneinander entfernt sein, du brauchst aber den Vorgänger zu einem gegebenen Knoten. Der kann im worst case einen vollständigen Weg über die Wurzel nötig machen, gleiches gilt für den Nachfolger.

Desweiteren wird jeder Knoten darauf überpüft ob der Bereich da drinnen sich mit dem freizugebenden irgendwie angrenz (deswegen before und behind) und wenn das der Fall ist, wird der entsprechende Pointer auf diese Node zeigen.
Das funktioniert so nicht, weil Vorgänger und Nachfolger weit voneinander entfernt sein können und nicht auf einem Wege erreichbar sein müssen. Ist dein Suchschlüssel in irgendeinem Unterbaum das rechteste Element, so wird dessen Nachfolger in einem Nachbarbaum das linkste Element sein und du müsstest bis zur Wurzel nach oben schauen.

Dein Ansatz funktioniert, wird aber nicht in jedem Fall den angrenzenden Bereich finden. Oder ich übersehe etwas.

Ich gehe dann den Baum noch bis zu einem Ende durch um sicher zu sein, das der Bereich auch freigegeben werden darf.
Damit fasst du also auch jedes Element im Baum an. Die Information, ob du einen Bereich freigeben darfst, muss vollständig im Baumknoten enthalten sein, sonst musst du zwangsweise jedes Element überprüfen.

Wenn ich dann mit der Schleife durch bin, brauche ich einfach nur 3 Fälle unterscheiden:
  • beide Pointer sind NULL
  • nur *before ist NULL
  • nur *behind ist NULL
In meinem Fall stehen im Baum alle Blöcke mit der Information, ob sie belegt oder frei sind; das heißt, jeder Block existiert. Du müsstest also auf Freiheit der Blöcke prüfen.

Und schon gehe ich den Baum nur noch einmal anstatt zweimal durch und ich kann dann auch ganz leicht sagen welche "struct avlNode_t" und "struct boundaryTag_t" ich freigeben kann und kann mir so unnötig Allocator-Aufrufe sparen.
Was die letzte Struktur darstellen soll, weiß ich nicht.

Ich denke das sollte das Optimum sein was man rausholen kann und eine Halbierung im worst-case sollte nicht schlecht sein.
Wenn du zuviel optimierst, kommen am Ende falsche Werte raus. :-Pint rand()
{
    return 4; /* Wurde statistisch korrekt mit einem Würfel ermittelt. */
}

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 09. November 2010, 18:06
Zitat von: svenska
Das heißt, dass der VMM kein allgemeines malloc() benutzen darf
Richtig ;)

Genau das macht meiner ja auch nicht (mehr ;) ). Ich habe mir noch einen Allocator geschrieben, der aus einem vorher festgelegten Bereich allokiert. Damit weiß ich immer welche Adresse noch frei ist und muss nicht den VMM fragen.

Wenn ich es mir recht überlege, dann könnte das genau sowas sein was du die ganze Zeit meinst ;)

Aber dieser Allocator ist nicht wirklich praktikabel wenn es um den ganzen Adressraum geht, nen kleines, zur Compile-Zeit feststehendes Stückchen vom virtuellen Adressraum geht gerade noch so.

Zitat von: svenska
Was hast du studiert?
Ich habe und mache es noch immer Informatik studiert (ich habe an eine Fachhochschule gewechselt, weil ich mit theoretischer Informatik rein gar nichts am Hut habe, sondern eher mit der Praxis).

Zitat von: svenska
Ein Baum ist relativ ungeeignet, um das vorherige/nachfolgende Element zu einem bestimmten Element zu finden, zumindest wüsste ich nicht, wie man das tut.
Ist gar nicht schwierig. Du guckst dir den Knoten an (fängst natürlich mit der Wurzel an) und vergleichst den Wert im Knoten mit deinem Wert (in unserem Bsp. also den Start-Wert), ist der Wert im Knoten größer als deiner gehst du nach links und ansonsten nach rechts.
Auf diesem Weg findest du (sofern vorhanden) einen Knoten der deinen Vorgänger und einen Knoten der deinen Nachfolger enthält.

Zitat von: svenska
Ich verstehe nicht, was du meinst. Inwiefern illegal?
Gehen wir mal davon aus das eine Map IDs speichert. Jemand ruft den Code auf um einen Bereich von IDs wieder freizugeben, er möchte die IDs 16 bis 18 freigeben.

Ich fange bei der Wurzel an und die hat die ID 20, also gehe ich nach links.
Der nächste Knoten hat die ID 15, ich habe also die ID vor meinen IDs gefunden (wird also in *before gespeichert). Da meine Start-ID größer ist gehe ich nach rechts.
Der nächste Knoten hat die ID 19, ich habe also die ID nach meinen IDs gefunden (wird also in *behind gespeichert).

Jetzt kommt das Problem meines alten Algos. Denn ich habe jetzt einfach aufgehört den Baum zu durchsuchen.

Ich gehe jetzt nach links (weil 19 ist größer als 16)
Der nächste Knoten hat die ID 17! Damit weiß ich jetzt, der Aufrufer wollte IDs freigeben die gar nicht in Benutzung waren und gebe einen Fehler zurück (das meine ich mit "illegal").

Wenn du jetzt wieder meinst das ja eigentlich nur IDs freigegeben werden die in Benutzung sind, sag ich einfach mal, dann könnte man sich auch solche Sachen wie auf nen NULL-Pointer überpüfen oder gucken ob eine PageAddr auch wirklich 4KB aligned ist sparen! Macht man aber nicht.

Entweder um sich vor Bugs zu schützen oder in meinem Fall wird dieser Map-Code auch aus dem UserSpace aufgerufen (wenn das App Speicher wieder freigeben will) und da kann dann schonmal ein böses Programm versuchen Speicher freizugeben was es eigentlich gar nicht darf!

Zitat von: svenska
In meinem Fall stehen im Baum alle Blöcke mit der Information, ob sie belegt oder frei sind; das heißt, jeder Block existiert. Du müsstest also auf Freiheit der Blöcke prüfen.
Glaub mir wenn ich die sage, dass das ne ganz schlechte Idee ist!

Ich will nur die freien Bereiche speichern, weil ich erstens daraus ableiten kann welche Bereich benutzt sind und weil ich damit den wenigst nötigen Speicher brauche.

Mal nen kleines Bsp. Du speicherst in einem Baum alle Bereiche die frei oder benutzt sind. Das ganze wird vom VMM genutzt, bildet also den UserSpace ab.

Wir haben also einen 3GB Adressraum. Ich nehme mir jetzt mal bewusst den worst-case (wenn er auch unwahrscheinlich ist, aber er dürfte relativ einfach herbei zu führen sein).
Du hast also ein Schema frei-belegt-frei-belegt-... und jeder Bereich ist genau eine Page (4KB) groß.

Dann hast du 393216 frei und 393216 benutzte Bereiche, dein Baum würde also aus 786432 Einträgen bestehen. Ich gehe mal optimistischer Weise von einer Knoten-Größe von 16bytes aus, macht insgesamt 12.582.912bytes = 12MB.

Meine Variante würde mit "nur" 6MB auskommen und ich könnte das gleiche machen wie du auch.

Zitat von: svenska
Wenn du zuviel optimierst, kommen am Ende falsche Werte raus. tongue
int rand()
{
    return 4; /* Wurde statistisch korrekt mit einem Würfel ermittelt. */
}
Darf ich diese auf Geschwindigkeit optimierte rand() Funktion übernehmen ;) "Schlimmere" Werte als die normale liefert die auch nicht ;)
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 09. November 2010, 22:08
Hallo,


Sowas in der Art habe ich probiert und das hat auch funktioniert, bis ich zuviele CPUs (unter QEMU) hatte....
Ja, das ist ein kniffliges Problem. Das könnte man aber im SlabAllocator damit lösen indem man erstmal prüft ob überhaupt Knappheit herrscht (wenn nicht kann der SlabAllocator einfach ein Objekt rausgeben) und wenn doch dann muss geschaut werden ob der VMM gerade benutzt wird (sollte sich am VMM-Lock bestimmen lassen, wenn das VMM-Lock belegt ist dann läuft der VMM auch gerade und wird am Ende auch neuen Speicher für den SlabAllocator bringen da das Knapp-Flag in jedem Fall gesetzt werden sollte). Wenn der VMM schon in Benutzung ist dann darf der SlabAllocator nur dann ein Objekt zurückgeben wenn er auch vom VMM aufgerufen wurde (hierzu muss eben ein passendes bool-Parameter zu kmalloc hinzugefügt werden) ansonsten muss gewartet werden (ja das ist unangenehm aber sicher auch nicht gerade häufig), wenn der VMM noch nicht läuft dann muss der SlabAllocator ihn direkt aufrufen. Ich weiß das klingt ein bisschen kompliziert und umständlich aber ich denke es steckt nur wenig Fehlerpotential drin und ermöglicht einem so auch das man selbst im VMM ganz normales kmalloc/kfree benutzen kann (für diesen Komfort bin ich schon bereit etwa 50 Code-Zeilen zu investieren). Wenn man das nicht macht benötigt man auf jeden Fall im VMM eine zusätzliche primitive malloc/free-Implementierung nur für die Verwaltungsstrukturen die der VMM selber benutzt (was wohl deutlich mehr als 50 Code-Zeilen werden dürften und zu primitiv sollte man das auch nicht machen wenn man ordentlich Performance will). Damit hätte ich also einen SlabAllocator der das kmalloc/kfree zur Verfügung stellt (für die etwa 5 oder 6 möglichen Objekt-Größen, mehr wird der Micro-Kernel intern nicht brauchen) der auf einem VMM aufsetzt welcher seinerseits auch kmalloc/kfree benutzen kann. Einen PMM benötige ich nicht in der normalen Form da bei mir ja normalerweise linearer Speicher identisch zum physischen Speicher ist und das Paging nur für Speichermagie und Swapping benutzt werden soll. Der eine lineare Speicher (welcher vom VMM in Page-Granularität verwaltet wird) bleibt also der Master von dem alles abhängt und eine zusätzliche Verwaltung physischen Speichers findet nur statt wenn diese auch nötig ist.


Das heißt, dass der VMM kein allgemeines malloc() benutzen darf
Genau das find ich doof. Ich verstehe das damit ein (eventuell schwerwiegendes) Henne-Ei-Problem entsteht aber ich vermute das es mit meinem Vorschlag (elegant und performant) lösbar ist. Was ich auf jeden Fall vermeiden möchte ist doppelter Code.


@FlashBurn:
Ich würde den VMM auch die benutzten Speicher-Blöcke verwalten lassen damit bei einem free (was ja nur den Anfangspointer eines Bereiches mitbekommt) auch ermittelt werden kann wie groß der freizugebende Bereich eigentlich ist. Dein Beispiel mit den abwechselden Pages ist IMHO schon ziemlich extrem daneben, wer so nen Scheiß macht muss sich auch nicht wundern wenn das OS lahmt, alternativ könnte das OS ja die Menge der zugewiesenen Speicherblöcke pro Prozess begrenzen (ein einfacher Zähler im Prozess-Descriptor-Struct) so das ein Prozess maximal X beliebig große Blöcke an Speicher holen kann (wenn ein Programm seinen Speicherbedarf nicht in anständig großen Blöcken holt bekommt es eben irgendwann keine mehr und fertig, 3GB in 4kB-Häppchen zu holen ist schon ziemlich dumm, für X würde ich einfach mal 4096 nehmen so das Dein Baum maximal 8193-Elemente enthält).


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 10. November 2010, 08:25
Zitat von: erik
Ich würde den VMM auch die benutzten Speicher-Blöcke verwalten lassen damit bei einem free (was ja nur den Anfangspointer eines Bereiches mitbekommt) auch ermittelt werden kann wie groß der freizugebende Bereich eigentlich ist.
Und was ist wenn er nicht den ganzen Speicher des vorher geholten Blocks freigeben möchte (mache ich im Kernel oft)?

Zitat von: erik
Dein Beispiel mit den abwechselden Pages ist IMHO schon ziemlich extrem daneben, wer so nen Scheiß macht muss sich auch nicht wundern wenn das OS lahmt
Nicht der User wird sich wundern warum das System so lahmt, sondern der Programmierer wird sich freuen das er gerade das System verlangsamt hat.

Ein anderes Bsp, selbes Prinzip. Eine DoS-Attacke verlangsamt ja nen Server auch. Wimre habe ich mal in einer Arbeit (wo es um Bäume ging) gelesen das die IP-Adressen (glaub ich jedenfalls) nicht immer in einem Baum verwaltet wurden und dadurch eine DoS-Attacke erst richtig effektiv möglich war, seit dem man sowas in einem Baum abspeichert kann der Server mehr Anfragen schneller beantworten und es ist nicht mehr so einfach eine DoS-Attacke durchzuführen.

Zumal ich immer auch den worst-case mit einplane und selbst du sagst sogar, das man das machen soll (ich erinnere mich da an einen 64bit Counter der ja überlufen kann, was aber unwahrscheinlich bis unmöglich war und du hast ja auch da gesagt, dass man sowas behandeln soll ;) ).

Zitat von: erik
3GB in 4kB-Häppchen zu holen ist schon ziemlich dumm
Kommt ganz drauf an wie das passiert. Wenn du 3GB auf einmal haben willst dann ist es dumm, wenn du aber immer nur wenig Speicher brauchst und dann immer von Zeit zu Zeit 4KB holst, ist das schon nicht mehr dumm.

Zitat von: erik
alternativ könnte das OS ja die Menge der zugewiesenen Speicherblöcke pro Prozess begrenzen (ein einfacher Zähler im Prozess-Descriptor-Struct) so das ein Prozess maximal X beliebig große Blöcke an Speicher holen kann (wenn ein Programm seinen Speicherbedarf nicht in anständig großen Blöcken holt bekommt es eben irgendwann keine mehr
Damit zwingst du ein Programm ja zur Verschwendung. Lösung wäre jetzt nur den Bereich zu reservieren und keine physischen Pages zu mappen, sondern das erst machen wenn das erste Mal auf die Page zugegriffen wird.

Ich weiß das ich das bei mir machen kann, aber du (möchte ich doch meinen) nicht bei dir.

Zumal ich möchte das ein Programm selbst entscheiden darf, ob erstmal nur der Bereich reserviert wird und dann bei einem PageFault erst Speicher gemappt wird oder ob das gleich gemacht wird.

@erik

Was deinen Vorschlag betrifft, würde bei mir zu Performance Problemen führen und ob es funktioniert bin ich mir auch nicht sicher.
Denn bei mir kommt (bzw. kam) noch ein Problem dazu. Ich verwende zum Verwalten ja nen AVL-Baum und der wird noch ganz oft in meinem Kernel verwendet und jedes Mal wenn er verwendet wird und nicht für den VMM benötigt wird könntest du den VMM ja ganz normal aufrufen (denkt man) ohne etwas überprüfen zu müssen.
Braucht der VMM (der vom SlabAllocator aufgerufen wurde, weil er ne Page für AVL-Nodes braucht) jetzt aber doch noch ne Node, hast du ne Dead-Lock (weil der Lock für den Slab ja schon von jemand anderes gehalten wird).

Deswegen habe ich mir halt nen kleinen eigenen Allocator geschrieben.

edit::

Welchen Vorteil versprecht ihr euch eigentlich davon, wenn man die benutzten Bereich mit speichert?
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 10. November 2010, 11:58
Hallo,

Zitat von: svenska
Das heißt, dass der VMM kein allgemeines malloc() benutzen darf
Genau das macht meiner ja auch nicht (mehr ;) ). Ich habe mir noch einen Allocator geschrieben, der aus einem vorher festgelegten Bereich allokiert. Damit weiß ich immer welche Adresse noch frei ist und muss nicht den VMM fragen.
Richtig.

Wenn ich es mir recht überlege, dann könnte das genau sowas sein was du die ganze Zeit meinst ;)
Richtig.

Aber dieser Allocator ist nicht wirklich praktikabel wenn es um den ganzen Adressraum geht, nen kleines, zur Compile-Zeit feststehendes Stückchen vom virtuellen Adressraum geht gerade noch so.
Es ist aber ein neuer Allokator und damit genau das, was erik nicht möchte. Kannst du den PMM nicht dafür benutzen? Dir geht es an sich ja nicht um virtuellen Adressraum, sondern um Speicher für die internen Datenstrukturen des VMMs. Den Speicher schenkt dir der PMM und das Mapping dazu denkst du dir halt selbst aus, genau so, wie du es für andere Datenblöcke auch machst.

Zitat von: svenska
Ein Baum ist relativ ungeeignet, um das vorherige/nachfolgende Element zu einem bestimmten Element zu finden, zumindest wüsste ich nicht, wie man das tut.
Ist gar nicht schwierig. Du guckst dir den Knoten an (fängst natürlich mit der Wurzel an) und vergleichst den Wert im Knoten mit deinem Wert (in unserem Bsp. also den Start-Wert), ist der Wert im Knoten größer als deiner gehst du nach links und ansonsten nach rechts.
Auf diesem Weg findest du (sofern vorhanden) einen Knoten der deinen Vorgänger und einen Knoten der deinen Nachfolger enthält.
Das heißt, ich durchsuche den Baum insgesamt dreimal... einmal suche ich im Baum nach meinem Block. Beim zweiten Mal untersuche ich alle Blöcke, die kleiner sind als mein Block (und damit potentiell links angrenzen), beim dritten Mal untersuche ich alle Blöcke, die größer sind. Da lohnt es sich, jeden Block exakt einmal anzuschauen (Liste/Traversierung); im Durchschnitt untersuche ich ohnehin nur die Hälfte aller Blöcke. Somit leider O(n/2).

Zitat von: svenska
Ich verstehe nicht, was du meinst. Inwiefern illegal?
Gehen wir mal davon aus das eine Map IDs speichert. Jemand ruft den Code auf um einen Bereich von IDs wieder freizugeben, er möchte die IDs 16 bis 18 freigeben.
Stop, das darf er nicht. Entweder er hat sich einen Block aus 3 IDs (16-17-18) geholt, dann darf er diesen Block als Ganzes freigeben. Wenn nicht, dann muss(!) er diese IDs auch einzeln zurückgeben. Die Permissions werden dann einzeln geprüft.

Wenn du jetzt wieder meinst das ja eigentlich nur IDs freigegeben werden die in Benutzung sind, sag ich einfach mal, dann könnte man sich auch solche Sachen wie auf nen NULL-Pointer überpüfen oder gucken ob eine PageAddr auch wirklich 4KB aligned ist sparen! Macht man aber nicht.
Darum solltest du auch überprüfen, ob der Block in exakt dieser Form auch existiert (NULL-Pointer, PageAlignment, ... werden beim Erstellen geprüft; "unrunde" Adressen existieren somit nicht, brauchen also nicht extra geprüft werden) UND belegt ist UND der Eigentümer auch derjenige ist, der freigeben möchte.

Entweder um sich vor Bugs zu schützen oder in meinem Fall wird dieser Map-Code auch aus dem UserSpace aufgerufen (wenn das App Speicher wieder freigeben will) und da kann dann schonmal ein böses Programm versuchen Speicher freizugeben was es eigentlich gar nicht darf!
Also guckst du nach, ob das böse Programm den Speicher auch besitzt, wenn nicht wird es gekillt.

Zitat von: svenska
In meinem Fall stehen im Baum alle Blöcke mit der Information, ob sie belegt oder frei sind; das heißt, jeder Block existiert. Du müsstest also auf Freiheit der Blöcke prüfen.
Glaub mir wenn ich die sage, dass das ne ganz schlechte Idee ist!
Warum?

Ich will nur die freien Bereiche speichern, weil ich erstens daraus ableiten kann welche Bereich benutzt sind und weil ich damit den wenigst nötigen Speicher brauche.
Speicher ist heutzutage wirklich genug da... mal abgesehen ist bei einem "freien" Block nur bekannt, dass niemand ihn besitzt; bei einem "belegten" Block gibt es aber noch weitere, interessante Informationen: Eigentümer, Permissions (r/w/x) usw. Dann speichere lieber die belegten Blöcke.

Mal nen kleines Bsp. Du speicherst in einem Baum alle Bereiche die frei oder benutzt sind. Das ganze wird vom VMM genutzt, bildet also den UserSpace ab.

Wir haben also einen 3GB Adressraum. Ich nehme mir jetzt mal bewusst den worst-case (wenn er auch unwahrscheinlich ist, aber er dürfte relativ einfach herbei zu führen sein).
Du hast also ein Schema frei-belegt-frei-belegt-... und jeder Bereich ist genau eine Page (4KB) groß.

Dann hast du 393216 frei und 393216 benutzte Bereiche, dein Baum würde also aus 786432 Einträgen bestehen. Ich gehe mal optimistischer Weise von einer Knoten-Größe von 16bytes aus, macht insgesamt 12.582.912bytes = 12MB.
Das heißt, im Worst Case kostet mich das 12 MB. Wenn ich 3 GB Speicher in Blöcken zu je 4k belege. Das ist erstmal weit hergeholt, zum anderen habe ich - wenn ich 3 GB RAM habe - auch 12 MB übrig. Das ist kein Grund.

Meine Variante würde mit "nur" 6MB auskommen und ich könnte das gleiche machen wie du auch.
Nein, du kannst keine Permissions prüfen.

Zitat von: svenska
Wenn du zuviel optimierst, kommen am Ende falsche Werte raus. :P
int rand()
{
    return 4; /* Wurde statistisch korrekt mit einem Würfel ermittelt. */
}
Darf ich diese auf Geschwindigkeit optimierte rand() Funktion übernehmen ;) "Schlimmere" Werte als die normale liefert die auch nicht ;)
Darfst du. ;-)

Das heißt, dass der VMM kein allgemeines malloc() benutzen darf
Genau das find ich doof. Ich verstehe das damit ein (eventuell schwerwiegendes) Henne-Ei-Problem entsteht aber ich vermute das es mit meinem Vorschlag (elegant und performant) lösbar ist. Was ich auf jeden Fall vermeiden möchte ist doppelter Code.
Naja, Durchgriffe durch verschiedene Layer halte ich nicht für elegant. Relativ schnell implementierbar mag es sein, performant auch, aber nicht elegant...

Und was ist wenn er nicht den ganzen Speicher des vorher geholten Blocks freigeben möchte (mache ich im Kernel oft)?
Dann benutzt du eine Funktion, die nicht free() heißt. Diese kann einen Block einfach in zwei Teile aufspalten: einen freigegebenen und einen weiterhin belegten Block.

Nicht der User wird sich wundern warum das System so lahmt, sondern der Programmierer wird sich freuen das er gerade das System verlangsamt hat.
Da gibt es elegantere Methoden. Außerdem hast du als Userspace-Prozess kein Wissen darüber, wieviel Speicher nun exakt zur Verfügung steht [ein malloc() braucht mehr Speicher als den angeforderten] und kannst somit den Prozess nicht rechtzeitig anhalten; damit wird am Ende des Tages der OOM-Sensenmann kommen und dich umbringen. ;-)

Ein anderes Bsp, selbes Prinzip. Eine DoS-Attacke verlangsamt ja nen Server auch. Wimre habe ich mal in einer Arbeit (wo es um Bäume ging) gelesen das die IP-Adressen (glaub ich jedenfalls) nicht immer in einem Baum verwaltet wurden und dadurch eine DoS-Attacke erst richtig effektiv möglich war, seit dem man sowas in einem Baum abspeichert kann der Server mehr Anfragen schneller beantworten und es ist nicht mehr so einfach eine DoS-Attacke durchzuführen.
Betonung auf "nicht mehr so einfach". Die Leute benutzen aber trotzdem Apache.

Zumal ich immer auch den worst-case mit einplane und selbst du sagst sogar, das man das machen soll (ich erinnere mich da an einen 64bit Counter der ja überlufen kann, was aber unwahrscheinlich bis unmöglich war und du hast ja auch da gesagt, dass man sowas behandeln soll ;) ).
Richtig. Aber 12 MB Speicherverlust in einem Fall, den man quasi nur absichtlich herbeiführen kann, ist für mich jetzt kein Weltuntergang.

Die Zeit zum Traversieren der Bäume halte ich da für tödlicher.

Kommt ganz drauf an wie das passiert. Wenn du 3GB auf einmal haben willst dann ist es dumm, wenn du aber immer nur wenig Speicher brauchst und dann immer von Zeit zu Zeit 4KB holst, ist das schon nicht mehr dumm.
Doch, ist es. Ich kann mir keinen Anwendungsfall (mal abgesehen von richtig miesen memory leaks) vorstellen, bei dem du den Endspeicherverbrauch nicht mal näherungsweise abschätzen kannst und er trotzdem so groß werden kann. Das bedeutet schließlich, dass der Speicherverbrauch unbegrenzt ist (memleak). Im Normalfall sollte dann wieder ein OOM-Killer bereitstehen. Man kann Programme ja auch präventiv killen, wenn sie zuviel RAM verbrauchen... ;-)

Zitat von: erik
alternativ könnte das OS ja die Menge der zugewiesenen Speicherblöcke pro Prozess begrenzen (ein einfacher Zähler im Prozess-Descriptor-Struct) so das ein Prozess maximal X beliebig große Blöcke an Speicher holen kann (wenn ein Programm seinen Speicherbedarf nicht in anständig großen Blöcken holt bekommt es eben irgendwann keine mehr
Damit zwingst du ein Programm ja zur Verschwendung. Lösung wäre jetzt nur den Bereich zu reservieren und keine physischen Pages zu mappen, sondern das erst machen wenn das erste Mal auf die Page zugegriffen wird.
Nein. Ein Programm sollte eine dem Anwendungsfall angepasste Menge an Speicher reservieren und nicht das zwingend minimalste Minimum. Schließlich muss jeder mit malloc() geholte Block auch irgendwo verwaltet werden. Außerdem sind bei einer Programmgröße von z.B. 200 MB (typischer Firefox) ein paar hundert KB Verschwendung irrelevant.

Ich weiß das ich das bei mir machen kann, aber du (möchte ich doch meinen) nicht bei dir.
Optimieren ja, aber nicht zu viel. Außerdem kannst du immer Rechenzeit gegen Speicherverbrauch eintauschen, der Optimalfall liegt nie auf der einen oder anderen Seite. (Es sei denn, die Ressourcen sind wirklich zu knapp, wie z.B. ein TCP/IP-Stack auf einem 8-bit-µC.)

Zumal ich möchte das ein Programm selbst entscheiden darf, ob erstmal nur der Bereich reserviert wird und dann bei einem PageFault erst Speicher gemappt wird oder ob das gleich gemacht wird.
Wozu? Damit kann ein Programm dein System lahmlegen, indem es Speicher reserviert und ihn nicht nutzt. Dazu kommt dann noch der Gedanke des "Working Set" einer Anwendung, d.h. nachdem die wesentlichen Teile per PageFault gemappt wurden, spielt der Zeitverlust kaum noch eine Rolle.

Alternativ kannst du auch sagen, dass Blöcke größer 16 MB nicht automatisch gemappt werden, Blöcke kleiner schon. Das geschieht unter der Annahme, dass kleine Blöcke auch genutzt werden, sehr große Blöcke erstmal für später reserviert werden.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 10. November 2010, 13:35
Zitat von: svenska
Dir geht es an sich ja nicht um virtuellen Adressraum
Doch genau darum geht es ja. Ich brauche eine freie virtuelle Adresse um physischen Speicher mappen zu können und wenn ich vorher nen kleinen Bereich genau dafür abzwacke und auch meine Datenstrukturen zur Verwaltung dieses Bereichs schon vorher allokiere dann habe ich das erreicht.

Zitat von: svenska
Das heißt, ich durchsuche den Baum insgesamt dreimal... einmal suche ich im Baum nach meinem Block. Beim zweiten Mal untersuche ich alle Blöcke, die kleiner sind als mein Block (und damit potentiell links angrenzen), beim dritten Mal untersuche ich alle Blöcke, die größer sind. Da lohnt es sich, jeden Block exakt einmal anzuschauen (Liste/Traversierung); im Durchschnitt untersuche ich ohnehin nur die Hälfte aller Blöcke.
Wieso so umständlich, dass machst du alles in einem Durchlauf! Du überprüfst jeden Knoten darauf, ob er eine der 3 Eigenschaften erfüllt und fertig.

Zitat von: svenska
Stop, das darf er nicht.
Wenn er das bei dir nicht darf, ok, aber bei mir darf er das. Ist halt ne Design-Entscheidung.

Zitat von: svenska
Speicher ist heutzutage wirklich genug da
Sorry, aber mit dieser Einstellung kommst du bei mir nicht weiter, das ist wie CPU Leistung ist doch genug da, wozu nen komplizierten Baum nehmen, ne Liste ist viel einfacher!

Wegen solcher Einstellungen werden die CPUs immer schneller, haben wir immer mehr RAM, aber die Programme werden immer langsamer!

Zitat von: svenska
Nein, du kannst keine Permissions prüfen.
Richtig, brauch ich nicht und will ich nicht. Wozu eigentlich?

Dann kommt noch hinzu das es hier um allgemeinen Code geht, den man zur Verwaltung von all den Sachen nutzen kann, die sich halt auf Integer-Werte abbilden lassen.

Zitat von: svenska
Ich kann mir keinen Anwendungsfall (mal abgesehen von richtig miesen memory leaks) vorstellen, bei dem du den Endspeicherverbrauch nicht mal näherungsweise abschätzen kannst und er trotzdem so groß werden kann.
Nen Server, ne DB usw.

Du weißt vorher nie mit wievielen Clients du es zu tun bekommst usw. und bei einer DB kannst du vorher auch nicht wissen wie groß die im Endeffekt wird.

Zitat von: svenska
Optimieren ja, aber nicht zu viel. Außerdem kannst du immer Rechenzeit gegen Speicherverbrauch eintauschen, der Optimalfall liegt nie auf der einen oder anderen Seite. (Es sei denn, die Ressourcen sind wirklich zu knapp, wie z.B. ein TCP/IP-Stack auf einem 8-bit-µC.)
Der Satz war für erik und seine Architektur gedacht ;)

Zitat von: svenska
Wozu? Damit kann ein Programm dein System lahmlegen, indem es Speicher reserviert und ihn nicht nutzt. Dazu kommt dann noch der Gedanke des "Working Set" einer Anwendung, d.h. nachdem die wesentlichen Teile per PageFault gemappt wurden, spielt der Zeitverlust kaum noch eine Rolle.
Ok, falsch ausgedrückt, ich meinte das ein Programm sagen, das es einen Bereich im virtuellen Speicher reservieren kann, der erst mit physischen Speicher hinterlegt wird, wenn das erste Mal darauf zugegriffen wird. Damit kannst du kein System lahmlegen.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 10. November 2010, 15:14
Hallo,


Und was ist wenn er nicht den ganzen Speicher des vorher geholten Blocks freigeben möchte (mache ich im Kernel oft)?
Meinst Du das ernst? Sowas hab ich noch nie (bewusst) gesehen.

Edit:  natürlich kenne ich das, das heißt realloc() (http://www.cplusplus.com/reference/clibrary/cstdlib/realloc/) aber für physischen Speicher finde ich das trotzdem unangemessen.

sondern der Programmierer wird sich freuen das er gerade das System verlangsamt hat.
Deswegen ja das Limit bei der gewährten Block-Anzahl und schon sind solche Probleme gegessen. Bei mir ist dieses Limit automatisch dadurch gegeben das eine LDT nur ein begrenzte Anzahl an Einträgen hat, ich sehe damit auch kein Problem.

Zumal ich immer auch den worst-case mit einplane und selbst du sagst sogar, das man das machen soll (ich erinnere mich da an einen 64bit Counter der ja überlufen kann, was aber unwahrscheinlich bis unmöglich war und du hast ja auch da gesagt, dass man sowas behandeln soll ;) ).
Natürlich musst Du als OS-Dever auch immer an den Worst-Case denken, sonst wärt Du ein schlechter Programmierer, deswegen hab ich ja das Limit vorgeschlagen.

Kommt ganz drauf an wie das passiert. Wenn du 3GB auf einmal haben willst dann ist es dumm, wenn du aber immer nur wenig Speicher brauchst und dann immer von Zeit zu Zeit 4KB holst, ist das schon nicht mehr dumm.
Alle normalen malloc-Implementierungen holen den virtuellen Speicher in größeren Blöcken herein und nicht jede Page einzeln. Wenn das OS in den virtuellen Speicher nicht sofort physische Pages mappt dann entsteht noch nicht mal eine Verschwendung des physischen Speichers.

Ich weiß das ich das bei mir machen kann, aber du (möchte ich doch meinen) nicht bei dir.
Ich habe dieses Problem (mit den zu umfangreichen Bäumen) nicht, bei mir haben die Prozesse Segmente, deren Anzahl per HW limitiert ist, und diese können dafür vergrößert (oder auch verkleinert) werden. Mit den LDTs verwalte ich also nur den belegten Speicher (mitsamt den Zugriffsrechten usw.) und der VMM verwaltet alle Blöcke im linearen Speicher (das können Segmente für die User-Space-Programme oder Slab-Blöcke für den Kernel-SlabAllocator sein und natürlich die freien Bereiche aber sonst nichts).

Zumal ich möchte das ein Programm selbst entscheiden darf, ob erstmal nur der Bereich reserviert wird und dann bei einem PageFault erst Speicher gemappt wird oder ob das gleich gemacht wird.
Das ist natürlich ein nettes Feature, aber dabei muss man aufpassen das trotzdem nur Speicher herausgegeben wird der sich auch wirklich mit Pages hinterlegen lässt (da gab es mal bestimmte 2.6er Linux-Kernel die das anders gehandhabt haben).

... und jedes Mal wenn er verwendet wird und nicht für den VMM benötigt wird könntest du den VMM ja ganz normal aufrufen (denkt man) ohne etwas überprüfen zu müssen.
Braucht der VMM (der vom SlabAllocator aufgerufen wurde, weil er ne Page für AVL-Nodes braucht) jetzt aber doch noch ne Node, hast du ne Dead-Lock (weil der Lock für den Slab ja schon von jemand anderes gehalten wird).
Wenn der SlabAllocator aufgerufen wird dann muss er als erstes seinen Lock holen (bei mir will ich pro Objekt-Größe einen eigenen Lock benutzen weil die unterschiedlichen Größen ja aus unterschiedlichen Blöcken kommen und damit auch keine Überschneidungen entstehen) und dann ein neues Objekt allozieren (für das allozieren selber wird kein VMM benötigt, die Slab-Verwaltungsstrukturen sind ja schon alle vorhanden). Wenn das allozieren nicht klappt dann muss als erstes das Lock wieder frei gegeben werden und dann der VMM aufgefordert werden einen neuen Block in den entsprechenden Slab-Pool hinzuzufügen (wofür natürlich der entsprechende Slab-Lock benötigt wird weil an den Slab-Verwaltungsstruckturen geändert wird) und erst danach kann der SlabAllocator den ganzen Vorgang erneut versuchen (also auch wieder den Lock neu holen).

Welchen Vorteil versprecht ihr euch eigentlich davon, wenn man die benutzten Bereich mit speichert?
Damit Du eben die Größe und auch andere Attribute der benutzten Blöcke kennst.


Das heißt, dass der VMM kein allgemeines malloc() benutzen darf
Genau das find ich doof. Ich verstehe das damit ein (eventuell schwerwiegendes) Henne-Ei-Problem entsteht aber ich vermute das es mit meinem Vorschlag (elegant und performant) lösbar ist. Was ich auf jeden Fall vermeiden möchte ist doppelter Code.
Naja, Durchgriffe durch verschiedene Layer halte ich nicht für elegant. Relativ schnell implementierbar mag es sein, performant auch, aber nicht elegant...
Wo siehst Du da einen Durchgriff? VMM und SlabAllocator sind auf einer Ebene und (leider) wechselseitig voneinander abhängig. Der SlabAllocator wird (neben dem VMM) von den verschiedenen Teilen im Kernel benutzt (er stellt ja den Kernel-Heap zur Verfügung) und der User-Space benötigt Segmente und holt sich diese vom VMM (natürlich über Kernel-Funktionen die zur Verwaltung der Segmente wieder den SlabAllocator benötigen). Ich sehe darin keine Probleme, wichtig ist nur das wenn SlabAllocator und VMM sich gegenseitig aufrufen der jeweils eigene Lock frei ist damit eben kein Dead-Lock entsteht.
Warum das nicht elegant ist musst Du mir mal genauer erklären! Gerade den Umstand das ich keinen doppelten Code habe betrachte ich als ziemlich elegant an meinem Konzept.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 10. November 2010, 15:52
Zitat von: erik
natürlich kenne ich das, das heißt realloc() aber für physischen Speicher finde ich das trotzdem unangemessen.
Also in meinem Kernel gibt es kein malloc/free/realloc und ich habe den VMM nur als ein Bsp genutzt wie man solch einen allgemeinen Ressourcen Allocator verwenden kann.

Wie kommst du von realloc() auf physischen Speicher?

Zitat von: erik
Natürlich musst Du als OS-Dever auch immer an den Worst-Case denken, sonst wärt Du ein schlechter Programmierer, deswegen hab ich ja das Limit vorgeschlagen.
Ich mag künstlich geschaffene Limits nicht. Da hätte ich ja gleich bei meinem alten Kernel-Design bleiben können ;)
Da war alles künstlich begrenzt, Prozesse, Threads, Ports usw. Macht die Verwaltung natürlich wesentlich einfacher und schneller, aber inzwischen finde ich soll das System halt so genutzt werden was der Speicher hergibt ohne das ich es irgendwo künstlich begrenze.

Zitat von: erik
Alle normalen malloc-Implementierungen holen den virtuellen Speicher in größeren Blöcken herein und nicht jede Page einzeln. Wenn das OS in den virtuellen Speicher nicht sofort physische Pages mappt dann entsteht noch nicht mal eine Verschwendung des physischen Speichers.
Um mal wieder Haare zu spalten, wenn meine malloc-Implementation das nicht so macht ist deine Aussage schon automatisch falsch ;)
Ansonsten fordert malloc halt immer 16KB an, aber ich finde halt trotzdem das ein Programm die Möglichkeit haben sollte auch nur 4KB anzufordern und einen angeforderten Block muss es auch anders wieder freigeben dürfen.

Zitat von: erik
Ich habe dieses Problem (mit den zu umfangreichen Bäumen) nicht, bei mir haben die Prozesse Segmente, deren Anzahl per HW limitiert ist, und diese können dafür vergrößert (oder auch verkleinert) werden. Mit den LDTs verwalte ich also nur den belegten Speicher (mitsamt den Zugriffsrechten usw.) und der VMM verwaltet alle Blöcke im linearen Speicher (das können Segmente für die User-Space-Programme oder Slab-Blöcke für den Kernel-SlabAllocator sein und natürlich die freien Bereiche aber sonst nichts).
Ich meinte eigentlich das du nicht einfach nen Bereich im virtuellen Adressraum reservieren kannst und erst später (am besten Page-weise) mit physischen RAM hinterlegst.

Zitat von: erik
Das ist natürlich ein nettes Feature, aber dabei muss man aufpassen das trotzdem nur Speicher herausgegeben wird der sich auch wirklich mit Pages hinterlegen lässt
Meinst du damit jetzt, das ich nicht 16MB an virtuellem Speicher rausgebe, obwohl ich nur noch 8MB an physischen RAM frei habe?
Dafür gibt es dann swapping. Wobei ich das für den Anfang nicht implementieren werde, da ich gar nicht wüsste wie.

Zitat von: erik
Wenn der SlabAllocator aufgerufen wird dann muss er als erstes seinen Lock holen
Guck dir mal das Dokument von Bonwick aus dem Jahre 2001 zum SlabAllocator an. Denn deine Variante skaliert nämlich gar nicht mit mehreren CPUs.

Zitat von: erik
Damit Du eben die Größe und auch andere Attribute der benutzten Blöcke kennst.
Zwei Sachen dazu, dass soll hier ein allgemeiner Ressourcen Allocator werden und wozu braucht man das?

Ich bin bisher ganz gut damit ausgekommen, nur die freien Bereiche zu speichern und habe noch keinen Nachteil davon getragen (der etwas anderes rechtfertigen würde).

Zum Thema Speicherverschwendung. Nochmal zum worst-case, wenn man jetzt immer nur 4KB Blöcke allokiert und halt die vollen 3GB ausgenutzt hat, dann ist mein Baum leer und eurer mit 786432 Elementen voll. Finde ich ein wenig übertrieben ;)

Insbesondere rechne das mal auf 64bit hoch, da wird mein Baum seine größe behalten oder kleiner werden und eurer wird immer größer und das pro Prozess.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 10. November 2010, 17:11
Hallo,

Zitat von: svenska
Dir geht es an sich ja nicht um virtuellen Adressraum
Doch genau darum geht es ja. Ich brauche eine freie virtuelle Adresse um physischen Speicher mappen zu können und wenn ich vorher nen kleinen Bereich genau dafür abzwacke und auch meine Datenstrukturen zur Verwaltung dieses Bereichs schon vorher allokiere dann habe ich das erreicht.
Achso. Ich würde das dann wahrscheinlich mit in die Verwaltung der virtuellen Adressen mit reinziehen, d.h. du nutzt den gleichen Code zum Verwalten deiner eigenen Strukturen wie auch zum Verwalten der fremden Strukturen. Wichtig ist halt, dass du nicht den gesamten VMM dafür nutzt, denn das geht nicht.

Zitat von: svenska
Das heißt, ich durchsuche den Baum insgesamt dreimal... einmal suche ich im Baum nach meinem Block. Beim zweiten Mal untersuche ich alle Blöcke, die kleiner sind als mein Block (und damit potentiell links angrenzen), beim dritten Mal untersuche ich alle Blöcke, die größer sind. Da lohnt es sich, jeden Block exakt einmal anzuschauen (Liste/Traversierung); im Durchschnitt untersuche ich ohnehin nur die Hälfte aller Blöcke.
Wieso so umständlich, dass machst du alles in einem Durchlauf! Du überprüfst jeden Knoten darauf, ob er eine der 3 Eigenschaften erfüllt und fertig.
Dann muss ich mir ohnehin jedes Element anschauen. Was sowieso eine Pflicht ist, wenn im Gesamtbereich Löcher existieren.

Zitat von: svenska
Stop, das darf er nicht.
Wenn er das bei dir nicht darf, ok, aber bei mir darf er das. Ist halt ne Design-Entscheidung.
Das heißt, ich darf mir ein Megabyte Speicher geben lassen und dann in einem Befehl die mittleren 333 KB freigeben? Seltsames Design.

Zitat von: svenska
Speicher ist heutzutage wirklich genug da
Sorry, aber mit dieser Einstellung kommst du bei mir nicht weiter, das ist wie CPU Leistung ist doch genug da, wozu nen komplizierten Baum nehmen, ne Liste ist viel einfacher!
Stop. Es geht um zwölf Megabyte im Worst Case bei einer vorhandenen Ressourcenmenge von drei Gigabytes. Das sind 0,39%. Im Worst Case.

Ein System mit 64 MB RAM wird in den seltensten Fällen etwas davon haben, wenn ein Prozess 3 GB RAM alloziiert. Mit RAM hinterlegen geht nur begrenzt (die arme Festplatte) und ohne RAM ist das eh fürn Popo.
Wenn man 1% der verfügbaren Ressourcen für die Verwaltung derselben gebraucht, dann ist das für mich ein sehr akzeptabler Kompromiss. Vergleiche mal die formatierte/unformatierte Größe deiner Diskette/Festplatte - nicht in Bezug auf Dateisysteme, sondern den realen Unterschied.

Wegen solcher Einstellungen werden die CPUs immer schneller, haben wir immer mehr RAM, aber die Programme werden immer langsamer!
Das ist eine Frage von Verhältnismäßigkeit.

Zitat von: svenska
Nein, du kannst keine Permissions prüfen.
Richtig, brauch ich nicht und will ich nicht. Wozu eigentlich?
Damit niemand (außer dem Kernel) ein Loch im Adressraum freigeben kann? Damit ein Userspace-Programm nicht durch wildgewordene Zeiger Speicher eines anderen Programms (oder des Kernels) freigeben kann? Von mir aus auch "basic sanity checks".

Dann kommt noch hinzu das es hier um allgemeinen Code geht, den man zur Verwaltung von all den Sachen nutzen kann, die sich halt auf Integer-Werte abbilden lassen.
Och, so wirklich auf Integerwerte beschränkt ist der Ansatz auch nicht... allerdings hat jede Ressource die Eigenschaft, (mindestens) einen Eigentümer zu besitzen, sofern sie benutzt ist, und keinen, wenn sie frei ist.

Zitat von: svenska
Ich kann mir keinen Anwendungsfall (mal abgesehen von richtig miesen memory leaks) vorstellen, bei dem du den Endspeicherverbrauch nicht mal näherungsweise abschätzen kannst und er trotzdem so groß werden kann.
Nen Server, ne DB usw.
Hä? Konkretisier mal.

Du weißt vorher nie mit wievielen Clients du es zu tun bekommst usw. und bei einer DB kannst du vorher auch nicht wissen wie groß die im Endeffekt wird.
Ich werde die Datenbank aber nicht in Bröckchen verarbeiten, d.h. der RAM-Maximalverbrauch ist in jedem Fall durch die Größe der DB beschränkt. Außerdem werden DBs grundsätzlich so optimiert, dass die mindestmögliche Menge an I/O stattfinden muss (Transaktionen), das heißt, die DB-Dateien werden in möglichst großen Blöcken von der Festplatte und damit in große Blöcke im RAM gelesen.

Bei reinen Datenservern wächst nur der Plattencache an, der ist aber für das System irrelevant und darf jederzeit wieder in Anspruch genommen werden - ist also auch kein Grund.

Ok, falsch ausgedrückt, ich meinte das ein Programm sagen, das es einen Bereich im virtuellen Speicher reservieren kann, der erst mit physischen Speicher hinterlegt wird, wenn das erste Mal darauf zugegriffen wird. Damit kannst du kein System lahmlegen.
Richtig, das meinte ich auch. Das würde ich wahrscheinlich mit einer automatischen Grenze lösen (die genannten 16 MB).

Wo siehst Du da einen Durchgriff? VMM und SlabAllocator sind auf einer Ebene und (leider) wechselseitig voneinander abhängig. Der SlabAllocator wird (neben dem VMM) von den verschiedenen Teilen im Kernel benutzt (er stellt ja den Kernel-Heap zur Verfügung) und der User-Space benötigt Segmente und holt sich diese vom VMM (natürlich über Kernel-Funktionen die zur Verwaltung der Segmente wieder den SlabAllocator benötigen). Ich sehe darin keine Probleme, wichtig ist nur das wenn SlabAllocator und VMM sich gegenseitig aufrufen der jeweils eigene Lock frei ist damit eben kein Dead-Lock entsteht.
Wenn man das so betrachtet, dann sind Slab-Allocator und VMM ein monolithischer Blob. Solange man dann nicht behauptet, dass es verschiedene Ebenen seien (wie ich es tue), dann ist das auch eine Lösung und das hatte das auch so gekennzeichnet. ;-)

Ich mag künstlich geschaffene Limits nicht. Da hätte ich ja gleich bei meinem alten Kernel-Design bleiben können ;)
Da war alles künstlich begrenzt, Prozesse, Threads, Ports usw. Macht die Verwaltung natürlich wesentlich einfacher und schneller, aber inzwischen finde ich soll das System halt so genutzt werden was der Speicher hergibt ohne das ich es irgendwo künstlich begrenze.
Dynamische Verwaltung von Zeugs erfordert immer mehr Ressourcen als statische Verwaltung von Zeugs. Mal abgesehen davon haben Limits durchaus eine Berechtigung, sei es für Zugriffe (wieder die Permissions), sei es, um einen amok laufenden Prozess loszuwerden.

Wann tritt denn dein OOM-Killer in Kraft? Wenn der RAM [ohne Plattencache] voll ist, wenn ein Prozess mehr RAM möchte als physisch vorhanden oder wenn die Festplatte wegen Swap voll ist? Ist das System überhaupt noch benutzbar, wenn es swappt? (Mein Linux bricht komplett zusammen, wenn der RAM nahezu voll ist und der Auslagerungsmarathon losgeht. Je schneller der OOM-Killer dann zuschlägt, umso eher läuft das System überhaupt wieder.)

Zitat von: erik
Alle normalen malloc-Implementierungen holen den virtuellen Speicher in größeren Blöcken herein und nicht jede Page einzeln. Wenn das OS in den virtuellen Speicher nicht sofort physische Pages mappt dann entsteht noch nicht mal eine Verschwendung des physischen Speichers.
Um mal wieder Haare zu spalten, wenn meine malloc-Implementation das nicht so macht ist deine Aussage schon automatisch falsch ;)
Dann ist deine malloc()-Implementierung nicht 'normal'.

Ansonsten fordert malloc halt immer 16KB an, aber ich finde halt trotzdem das ein Programm die Möglichkeit haben sollte auch nur 4KB anzufordern und einen angeforderten Block muss es auch anders wieder freigeben dürfen.
Darum geht es nicht. Wenn 16 MB angefordert werden, dann wird dein VMM hoffentlich nicht anfangen, 4096 einzelne Pages zu verarbeiten, oder?

Ich meinte eigentlich das du nicht einfach nen Bereich im virtuellen Adressraum reservieren kannst und erst später (am besten Page-weise) mit physischen RAM hinterlegst.
Klar. Das ist recht teuer, wenn du sofort den RAM benötigst (durch die Masse an PageFaults) - dem kannst du vorbeugen, wenn du den RAM sofort bereitstellst. Letzteres ist aber RAM-Verschwendung, wenn die Anwendung den RAM eben nicht nutzt und nur vorbeugend reserviert.

Zitat von: erik
Das ist natürlich ein nettes Feature, aber dabei muss man aufpassen das trotzdem nur Speicher herausgegeben wird der sich auch wirklich mit Pages hinterlegen lässt
Meinst du damit jetzt, das ich nicht 16MB an virtuellem Speicher rausgebe, obwohl ich nur noch 8MB an physischen RAM frei habe?
Dafür gibt es dann swapping. Wobei ich das für den Anfang nicht implementieren werde, da ich gar nicht wüsste wie.
Swapping ist so sehr langsam und für das System so sehr tot, wenn die Daten nicht im RAM liegen, dass du das nicht unbegrenzt erlauben darfst. Sonst legt ein malloc(2TB, MAP_NOW) für hinreichend große Festplatte dein System für mehrere Stunden vollständig lahm.

Eine sinnvolle Grenze (für kleine Systeme) liegt bei etwa 2*RAMSIZE für alle Prozesse und 0.75*RAMSIZE für einen einzelnen Prozess. Und ja, das ist ein künstliches Limit und kann für bestimmte Anwendungsfälle auch zur Laufzeit korrigierbar sein (vgl. Linux /sys/*).

Zitat von: erik
Damit Du eben die Größe und auch andere Attribute der benutzten Blöcke kennst.
Zwei Sachen dazu, dass soll hier ein allgemeiner Ressourcen Allocator werden und wozu braucht man das?
Und? Was für Ressourcen hast du denn? Hier geht es konkret um virtuellen Adressraum, in meinem Geiste zusätzlich zur Verwaltung von z.B. I/O-Ports und IRQ-Leitungen, in deinem Geiste zusätzlich zur Verwaltung von IDs.

Für Adressraum/Ports/IRQs gibt es jeweils Eigentümer (oder freigegeben), außerdem ist das Ding in Bereiche sortiert, die Elemente werden selten einzeln alloziiert. Für IDs gibt es einen Eigentümer, nur in seltenen Fällen wird Blockweise gearbeitet (jedes Dateihandle eines Prozesses steht für sich allein, eine Page eines Prozesses eher nicht).

Ich bin bisher ganz gut damit ausgekommen, nur die freien Bereiche zu speichern und habe noch keinen Nachteil davon getragen (der etwas anderes rechtfertigen würde).
Dann werden wir dich wohl nicht davon überzeugen können. Eh ich jetzt wieder einen Streit lostrete, lasse ich es damit sein.

Zum Thema Speicherverschwendung. Nochmal zum worst-case, wenn man jetzt immer nur 4KB Blöcke allokiert und halt die vollen 3GB ausgenutzt hat, dann ist mein Baum leer und eurer mit 786432 Elementen voll. Finde ich ein wenig übertrieben ;)
Korrekt. Mach das mal unter einem normalen Betriebssystem und schaue, wieviel RAM du rauskriegst, wenn du (a) immer 4k-Blöcke alloziierst und (b) alles an einem Stück haben möchtest. Beobachte bitte die Performance des Systems in beiden Fällen... mich würde interessieren, ob die bekannten Betriebssysteme mit so einer Workload klarkommen oder nicht.

Insbesondere rechne das mal auf 64bit hoch, da wird mein Baum seine größe behalten oder kleiner werden und eurer wird immer größer und das pro Prozess.
Da der Ansatz Blödsinn ist, ist die Schlussfolgerung ebenfalls Blödsinn. ;-) Außerdem betrifft das, wenn man sinnvolle Limits einstellt (ja, das ist sinnvoll), nur Rechner, die auch genug RAM haben.

Andere Sache, die aber mehr Dateisysteme betrifft: Schiebe in einen Ordner mal 10k Dateien mit unterschiedlichen Größen (z.B. den WINXP-Installationsordner, für langsamere Systeme reicht auch der WIN31-Installationsordner). Gehe dann mit einem Dateimanager deiner Wahl dort rein. Merkt man die Wartezeit?

Du wirst mit jedem System eine gezielte Workload aufbauen können, die sämtliche Algorithmen in den worst case treibt. Die Frage ist, ob das System als Ganzes dann noch benutzbar und vor allem stabil bleibt (dann ist es auch DoS-sicher) oder ob dann Grenzfälle und race conditions auftreten, die dann Bugs triggern. Das ist für die Qualität eines OS mMn wichtiger... auch, wenn es hier nur um ein Hobby geht.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 10. November 2010, 17:45
Zitat von: svenska
Dann muss ich mir ohnehin jedes Element anschauen. Was sowieso eine Pflicht ist, wenn im Gesamtbereich Löcher existieren.
Entweder wir reden mal wieder aneinander vorbei oder du hast es immernoch nicht verstanden ;)

Nimm einfach an, du suchst in einem binären Baum ein bestimmtes Element, du fässt da ja nicht alle Elemente an (dann hätte der binäre Suchbaum ja keinen Vorteil gegenüber einer Liste), sondern nur die Elemente die auf deinem Suchweg liegen und auch nur die überprüfe ich auf die 3 Eigenschaften.

Zitat von: svenska
Das heißt, ich darf mir ein Megabyte Speicher geben lassen und dann in einem Befehl die mittleren 333 KB freigeben? Seltsames Design.
Jein. Wenn du geschrieben hättest das du 332KB (zwecks 4KB aligned) freigeben möchtest (und der Start ebenfalls 4KB aligned ist) dann ja ;)

Ich denke halt an eine Situation wo du halt erstmal nen größere Brocken allokierst und dann später einen Teil wieder freigibst, weil du ihn nicht mehr brauchst.

Zitat von: svenska
Ein System mit 64 MB RAM wird in den seltensten Fällen etwas davon haben, wenn ein Prozess 3 GB RAM alloziiert. Mit RAM hinterlegen geht nur begrenzt (die arme Festplatte) und ohne RAM ist das eh fürn Popo.
Du vergisst den SharedMemory in deiner Rechnung, also kannst du durchaus mehr Speicher mappen als physisch vorhanden ist ohne dass das über Swapping laufen muss.

Wenn ich jetzt viele 4KB SharedMemory Bereiche habe (was bei mir der Fall sein wird) wird dein Baum immer größer weil du diese Bereiche ja auch mit speicherst.

Zitat von: svenska
Damit ein Userspace-Programm nicht durch wildgewordene Zeiger Speicher eines anderen Programms (oder des Kernels) freigeben kann?
Da wären wir dann wieder dabei, dass du einen Denkfehler drin hast oder das Konzept des virtuellen Speichers/Paging noch nicht ganz verstanden hast. Denn ich kann nicht den Speicher eines anderen Programms freigeben, auch nicht vom Kernel!

Jeder Prozess hat seine eigene Map wo die virtuellen Adressen verwaltet werden, das gleiche gilt für den Kernel.

Und ich kann ja nachprüfen ob das Programm mist macht oder nicht (von Löchern mal abgesehen) ohne das ich die benutzten Bereiche mitspeichern muss.

Zitat von: svenska
Och, so wirklich auf Integerwerte beschränkt ist der Ansatz auch nicht
Was schwebt dir denn da noch so vor, denn mir fällt nichts anders ein?

Zitat von: svenska
Hä? Konkretisier mal.
Am Bsp. vom Server. Jeder Client der Kontakt zum Server aufnimmt, erfordert nen neuen Thread und neue Datenstrukturen und da du nie wissen kannst wieviele Clients connecten, kannst du auch vorher nicht den Speicherverbrauch wissen.

Am Bsp der Datenbank, ich gehe jetzt mal davon aus das die komplett im Speicher liegt (wenn auch Teile ausgelager sein können).
Desto mehr Daten du in die DB einträgst desto größer wird die und auch dort kannst du ja vorher nicht wissen wie groß die mal wird oder ob sie klein bleibt.

Zitat von: svenska
Wann tritt denn dein OOM-Killer in Kraft? Wenn der RAM [ohne Plattencache] voll ist, wenn ein Prozess mehr RAM möchte als physisch vorhanden oder wenn die Festplatte wegen Swap voll ist? Ist das System überhaupt noch benutzbar, wenn es swappt? (Mein Linux bricht komplett zusammen, wenn der RAM nahezu voll ist und der Auslagerungsmarathon losgeht. Je schneller der OOM-Killer dann zuschlägt, umso eher läuft das System überhaupt wieder.)
Ich habe noch keinen OOM-Killer und auch noch kein Swapping, bei mir können dann einfach irgendwelche Speicheranforderungen nicht mehr gemacht werden ;)

Aber wie schaffst du es das dein Linux einbricht (bzw. was verstehst du darunter). Ich habe das noch nicht (wieder) erlebt.

Zitat von: svenska
Dann ist deine malloc()-Implementierung nicht 'normal'.
Ich sags erstmal so, ich sehe den Zweck eines OS programmieren nicht darin, ein schon vorhandenes nachzuprogrammieren, also musst du auch mal was machen was nicht normal ist.

Mein SlabAllocator fordert auch nur 4KB an wenn da genügend Objekte reinpassen. Zumal müssen wir auch mal von malloc weg. Ich denke auch an die Fälle die direkt meine OS-API und nicht die libc nutzen.

Zitat von: svenska
Sonst legt ein malloc(2TB, MAP_NOW) für hinreichend große Festplatte dein System für mehrere Stunden vollständig lahm.
An so ein Bsp hatte ich noch gar nicht gedacht (auch wenn es eh nicht möglich ist, da ich mich erstmal auf 32bit beschränke, aber auch da wäre was in der Region von 2GB nicht so toll bei 128MB RAM).

Spontan würde ich halt sagen, bekommt er einfach nicht ;) Da ich eh kein Swapping habe, geht das sowieso nicht.

Zitat von: svenska
außerdem ist das Ding in Bereiche sortiert, die Elemente werden selten einzeln alloziiert.
Das ist halt das Ding an einem allgemeinen Ressourcen Allocator, er muss damit klarkommen, das ganze Bereiche (virtuelle Adressen/IO-Ports) oder nur einzelne Elemente (IDs) allokiert werden.

Ich meine wie sinnvoll ist es denn für jede mögliche ID einen Eintrag zu haben das sie benutzt ist.

Im Endeffekt ist es doch sowieso so, dass es nur Probleme geben kann bei virtuellen Adressen die ausm UserSpace kommen, alles andere sollte/darf keine Probleme machen da es ansonsten nur aus dem Kernel kommt und da gehe ich mal davon aus, das ich nicht versuche meinen eigenen Kernel zu ärgern ;)

Soll ich denn meinen neuen Code zum freigeben von Bereichen posten oder kann ich mir das auch sparen? Ich kann auch gleich sagen, dass der alte Code noch richtig viele Fehler hatte und um diese Fehler zu beseitigen und um einen schnelleren Code zu bekommen ist der Code nicht wirklich "schön".
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 10. November 2010, 23:07
Nimm einfach an, du suchst in einem binären Baum ein bestimmtes Element, du fässt da ja nicht alle Elemente an (dann hätte der binäre Suchbaum ja keinen Vorteil gegenüber einer Liste), sondern nur die Elemente die auf deinem Suchweg liegen und auch nur die überprüfe ich auf die 3 Eigenschaften.
Naja, ich suche ja kein bestimmtes Element (dessen Suchschlüssel ich kenne), sondern ich suche ein Element mit bestimmten Eigenschaften... damit muss ich alle Elemente, die kleiner/größer sind als mein freizugebendes Element untersuchen, ob sie eine gemeinsame Grenze haben. Haben sie diese nicht, so existiert kein Nachbarelement.

Du kennst doch die Anfangsadressen dieser Nachbarelemente nicht, also kannst du danach nicht suchen, oder irre ich da?

Zitat von: svenska
Das heißt, ich darf mir ein Megabyte Speicher geben lassen und dann in einem Befehl die mittleren 333 KB freigeben? Seltsames Design.
Jein. Wenn du geschrieben hättest das du 332KB (zwecks 4KB aligned) freigeben möchtest (und der Start ebenfalls 4KB aligned ist) dann ja ;)

Ich denke halt an eine Situation wo du halt erstmal nen größere Brocken allokierst und dann später einen Teil wieder freigibst, weil du ihn nicht mehr brauchst.
Das ist Unfug. Entweder du alloziierst genau das, was du brauchst, oder du alloziierst mehr. Und zwar so, dass du den Brocken dann von Vorne mit Daten füllst, und wenn du alle Daten hast, dann schneidest du hinten den ungenutzten Teil ab und gibst ihn wieder frei. (Das macht realloc() so.)

Einen belegten Block in drei Teile zu spalten, von denen nur der zweite Teil freizugeben ist, ist unsinnig. Außerdem bringt das nur bei auf 4k ausgerichteten Speicherblöcken was, da für den dritten Teil eine Anfangsadresse mit Alignment da sein muss.

Mir fällt kein sinnvoller Anwendungsfall dafür ein. Eher, dass du den vorderen und den hinteren Teil abschneidest (z.B. von einem HTTP-Request den Header und den hinteren Teil vom Body), aber das ist aufgrund der Alignment-Vorgabe unpraktisch.

Zitat von: svenska
Ein System mit 64 MB RAM wird in den seltensten Fällen etwas davon haben, wenn ein Prozess 3 GB RAM alloziiert. Mit RAM hinterlegen geht nur begrenzt (die arme Festplatte) und ohne RAM ist das eh fürn Popo.
Du vergisst den SharedMemory in deiner Rechnung, also kannst du durchaus mehr Speicher mappen als physisch vorhanden ist ohne dass das über Swapping laufen muss.

Wenn ich jetzt viele 4KB SharedMemory Bereiche habe (was bei mir der Fall sein wird) wird dein Baum immer größer weil du diese Bereiche ja auch mit speicherst.
Wieder eine Designentscheidung deinerseits, wobei du sicherlich keine 100k ShMem-Blöcke je 4 KB haben wirst. Ansonsten glaube ich nicht, dass das so ins Gewicht fallen wird. Verwalten musst du die Blöcke ohnehin irgendwo. In der globalen Verwaltung ist es schon aus CPU-Cachegründen besser aufgehoben.

Zitat von: svenska
Damit ein Userspace-Programm nicht durch wildgewordene Zeiger Speicher eines anderen Programms (oder des Kernels) freigeben kann?
Da wären wir dann wieder dabei, dass du einen Denkfehler drin hast oder das Konzept des virtuellen Speichers/Paging noch nicht ganz verstanden hast. Denn ich kann nicht den Speicher eines anderen Programms freigeben, auch nicht vom Kernel!
Ein Userspace-Programm kann aber Teile des Kernel-Speichers freigeben, wenn dieser Bereich im Userspace-Programm gemappt ist.

Jeder Prozess hat seine eigene Map wo die virtuellen Adressen verwaltet werden, das gleiche gilt für den Kernel.
Du willst also einen Baum pro Prozess haben?

Und ich kann ja nachprüfen ob das Programm mist macht oder nicht (von Löchern mal abgesehen) ohne das ich die benutzten Bereiche mitspeichern muss.
Wenn der Baum pro Prozess ist, ist der Eigentümer-Eintrag natürlich unsinnig, da es nur einen Eigentümer geben kann.

Zitat von: svenska
Och, so wirklich auf Integerwerte beschränkt ist der Ansatz auch nicht
Was schwebt dir denn da noch so vor, denn mir fällt nichts anders ein?
Spontan GUIDs. Die sind keine Integerwerte, lassen sich aber als Bereiche darstellen. Prinzipiell ist jeder Elementtyp möglich, welchen du irgendwie sortieren kannst (auf dem du also "<" und "=" definieren kannst).

Am Bsp. vom Server. Jeder Client der Kontakt zum Server aufnimmt, erfordert nen neuen Thread und neue Datenstrukturen und da du nie wissen kannst wieviele Clients connecten, kannst du auch vorher nicht den Speicherverbrauch wissen.
Falsch. Wenn jeder Client einen Thread erzeugt, dann braucht jeder Thread exakt einen Satz an Datenstrukturen. Da es recht teuer ist, hunderte/tausende Threads gleichzeitig zu erzeugen, werden die meist im Voraus erzeugt (z.B. im Apache gibst du an, wieviele Prozesse du gerne hättest und wieviele Threads jeder Prozess maximal erzeugen soll). Das gibt eine praktische Grenze an. Ansonsten hast du ein riesiges Einfallstor für DoS-Angriffe (Erzeugen/Freigeben von Threads kostet CPU-Zeit im Kernel; was ist, wenn die Daten mit 2 Byte/sek in die Threads geschoben werden?).

Theoretisch unlimitierte Anzahl gleichzeitiger Clients mit lange aktiven Verbindungen - also eben kein HTTP - erfordert asynchrones I/O. Schließlich kostet ein Thread durchaus eine Menge Speicher, da jeder Thread nicht nur einen Datensatz hat, sondern auch eigenen Heap & Stack etc. und das ganze zudem noch auf Page-Granularität liegt. Wenn du AIO verwendest, kannst du den Speicherverbrauch vorher recht gut abschätzen.

Am Bsp der Datenbank, ich gehe jetzt mal davon aus das die komplett im Speicher liegt (wenn auch Teile ausgelager sein können).
Desto mehr Daten du in die DB einträgst desto größer wird die und auch dort kannst du ja vorher nicht wissen wie groß die mal wird oder ob sie klein bleibt.
Solche Datenbanken sind nur für kleine Datenbestände geeignet. Außerdem wird die Datenbank, wenn sie komplett im Speicher gehalten wird, auch am Stück im Speicher gehalten - es ist sehr unhandlich, wenn du zigtausende einzelne Zeiger auf jeden Speicherblock vorhalten musst. Da wirst du dich in der Anwendung auf wenige, übersichtliche Blöcke beschränken, allein schon um sinnvoll Zeigerarithmetik machen zu können und im Zweifelsfall mit realloc() deinen Block vergrößern. (Realloc wird dann, wenn möglich, einfach Adressraum hinten dranhängen oder wenn das nicht geht, den Block umkopieren. Zumindest laut POSIX.)

Ich habe noch keinen OOM-Killer und auch noch kein Swapping, bei mir können dann einfach irgendwelche Speicheranforderungen nicht mehr gemacht werden ;)
Also geht das System komplett kaputt.

Aber wie schaffst du es das dein Linux einbricht (bzw. was verstehst du darunter). Ich habe das noch nicht (wieder) erlebt.
Einfach ein "dd if=/dev/zero of=/dev/null bs=BLOCKSIZE count=5" machen und die BLOCKSIZE so groß wie möglich setzen. Optimalerweise auf den maximal möglichen Wert, so dass für jeden Block geswappt werden muss, der Block selbst aber noch in den RAM passt. Bei genügend hohem 'count' (oder ohne Angabe) dürfte das System fast unbenutzbar werden, da du keinen Speicher mehr hast und das I/O-Subsystem komplett ausgelastet ist.

Eine zweite Variante, die allerdings bekannt ist, ist ein langsames USB-1.1-Gerät, wo du mal schnell einige hundert MB draufkopierst [was dauert] und dann wird der RAM knapp oder du forderst eine große Menge RAM an. Dann geht auch nichts mehr, bis der Kopiervorgang beendet ist.

Zitat von: svenska
Dann ist deine malloc()-Implementierung nicht 'normal'.
Ich sags erstmal so, ich sehe den Zweck eines OS programmieren nicht darin, ein schon vorhandenes nachzuprogrammieren, also musst du auch mal was machen was nicht normal ist.
Naja, ich würde schon ein POSIX-artiges System bauen. Allein der Software wegen. Aber du hast recht, neue Wege zu beschreiten ist durchaus schön.

Mein SlabAllocator fordert auch nur 4KB an wenn da genügend Objekte reinpassen. Zumal müssen wir auch mal von malloc weg. Ich denke auch an die Fälle die direkt meine OS-API und nicht die libc nutzen.
Diese Interfaces sollte eigentlich nur die libc nutzen (und Treiber).

Zitat von: svenska
Sonst legt ein malloc(2TB, MAP_NOW) für hinreichend große Festplatte dein System für mehrere Stunden vollständig lahm.
An so ein Bsp hatte ich noch gar nicht gedacht (auch wenn es eh nicht möglich ist, da ich mich erstmal auf 32bit beschränke, aber auch da wäre was in der Region von 2GB nicht so toll bei 128MB RAM).

Spontan würde ich halt sagen, bekommt er einfach nicht ;) Da ich eh kein Swapping habe, geht das sowieso nicht.
Warum sollte er es nicht kriegen? Du hast doch gesagt, dass jeder Prozess so viel Speicher anfordern kann wie geht, schließlich gibt es ja Swapspace. :-P

Das ist halt das Ding an einem allgemeinen Ressourcen Allocator, er muss damit klarkommen, das ganze Bereiche (virtuelle Adressen/IO-Ports) oder nur einzelne Elemente (IDs) allokiert werden.

Ich meine wie sinnvoll ist es denn für jede mögliche ID einen Eintrag zu haben das sie benutzt ist.
Wie gesagt, eine Übersicht über die Eigentümer finde ich schon recht nett. Zumindest, wenn man die Ressourcen (IO-Ports) systemweit verwaltet. Mal abgesehen davon ist es unnötig, dass ich mehrere einzeln alloziierte Ressourcen als Block freigeben können muss. (Dazu müsste ich als Anwendung ja im Voraus schon das mergen vornehmen. Das kann der Kernel - der dafür optimiert sein sollte - ohnehin besser.) Der einzige Vorteil dabei ist die Syscall-Ersparnis, allerdings sind die von mir genannten Ressourcen relativ statisch und auch Filedescriptoren werden nicht zu hunderttausenden jede Sekunde freigegeben.

Im Endeffekt ist es doch sowieso so, dass es nur Probleme geben kann bei virtuellen Adressen die ausm UserSpace kommen, alles andere sollte/darf keine Probleme machen da es ansonsten nur aus dem Kernel kommt und da gehe ich mal davon aus, das ich nicht versuche meinen eigenen Kernel zu ärgern ;)
Deinem eigenen Kernel solltest du schon trauen können, ebenso wie du allen Hardwaretreibern vertrauen musst.

Soll ich denn meinen neuen Code zum freigeben von Bereichen posten oder kann ich mir das auch sparen? Ich kann auch gleich sagen, dass der alte Code noch richtig viele Fehler hatte und um diese Fehler zu beseitigen und um einen schnelleren Code zu bekommen ist der Code nicht wirklich "schön".
Kannst du dir sparen, es sei denn, erik oder taljeth wollen drübergucken. ;-)
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 10. November 2010, 23:35
Hallo,


Ich mag künstlich geschaffene Limits nicht
[......]
aber inzwischen finde ich soll das System halt so genutzt werden was der Speicher hergibt ohne das ich es irgendwo künstlich begrenze.
Dann lass das Limit weg, was kann schon so schlimmes passieren? Im Worst-Case verbraucht ein Prozess der ganze 3 GB benötigt noch mal zusätzliche 6 oder 12 MB zur Verwaltung, da stimme ich mit svenska überein: "was solls". Wenn ein Prozess seinen Speicher in 4kB-Häppchen holt dann muss er eben damit leben das seine Speicherwünsche nur mit gemächlicher Geschwindigkeit (wegen dem riesigen Baum) bearbeitet werden können (selber schuld) und wenn Du ausgerechnet wegen den 6 oder 12 MB das Swappen anfangen musst dann war eh zu wenig RAM im Rechner (so dicht an der Grenze sollte man nicht arbeiten).

Zitat von: erik
Alle normalen malloc-Implementierungen holen den virtuellen Speicher in größeren Blöcken herein und nicht jede Page einzeln. Wenn das OS in den virtuellen Speicher nicht sofort physische Pages mappt dann entsteht noch nicht mal eine Verschwendung des physischen Speichers.
Um mal wieder Haare zu spalten, wenn meine malloc-Implementation das nicht so macht ist deine Aussage schon automatisch falsch ;)
Ansonsten fordert malloc halt immer 16KB an,
Also wenn Deine malloc-Implementierung (für die User-Space-libc) wirklich in 16kB-Häppchen den virtuellen Speicher holt dann würde ich da tatsächlich das Prädikat "kaputt" benutzen. Sorry, aber sowas macht man nicht.
Was Okay wäre ist wenn Du zur Compilezeit der libc sagst wie viel Speicher ein bestimmtes Programm maximal zur Laufzeit benötigen wird so das die libc ihre Allozierungen an virtuellem Speicher in optimal passenden Häppchen machen kann (falls man überhaupt zur Compilezeit sagen kann wie viel Speicher ein Programm maximal braucht) aber gute malloc-Implementierungen passen sich dem zur Laufzeit dynamisch an und versuchen den virtuellen Speicher in möglichst (angemessen) großen Stücken zu holen (z.B. in 4MB-Häppchen um die Benutzung von 4MB-Pages zu unterstützen).

aber ich finde halt trotzdem das ein Programm die Möglichkeit haben sollte auch nur 4KB anzufordern und einen angeforderten Block muss es auch anders wieder freigeben dürfen.
Natürlich muss ein Programm auch die Möglichkeit haben kleinste Pages einzeln anzufordern aber damit sollte es nicht seinen generischen Heap versorgen sondern nur spezielle Sachen wo so kleine Pages auch wirklich erforderlich und sinnvoll sind.

Ich meinte eigentlich das du nicht einfach nen Bereich im virtuellen Adressraum reservieren kannst und erst später (am besten Page-weise) mit physischen RAM hinterlegst.
Warum sollte ich das nicht können? Ich hab doch trotzdem ein Paging in der Hinterhand das ich bei Bedarf dazu schalten kann. Wenn ich noch 2GB freien Speicher hab und ein Prozess 16MB anfordert dann bringt es einfach nichts da erst mal nur ein paar Pages rein zulegen und den Rest mit teuren Page-Not-Present-Exceptions zu managen aber wenn das System kurz vor dem Swappen ist (oder aus anderen Gründen der physische Adressraum knapp wird) dann kann ich prinzipiell auch zu diesem Trick greifen. Ich möchte auf jeden Fall das es in einem 32Bit-System von mir auch bei nur 512MB RAM möglich ist ein 1GB Segment zu erstellen und damit arbeiten zu können, egal wie extrem langsam das wird es soll zumindest grundsätzlich funktionieren (ich sehe auch keinen Grund warum das nicht funktionieren sollte).

Zitat von: erik
aber dabei muss man aufpassen das trotzdem nur Speicher herausgegeben wird der sich auch wirklich mit Pages hinterlegen lässt
Meinst du damit jetzt, das ich nicht 16MB an virtuellem Speicher rausgebe, obwohl ich nur noch 8MB an physischen RAM frei habe?
Fast, als maximale Obergrenze an virtuellem Speicher der für alle Prozesse zusammen (inklusive Kernel) zugewiesen werden darf zählt für mich tatsächlich vorhandener physischer RAM + tatsächlich vorhandener Swapp-Space (und wenn Du kein Swapping hast dann eben nur der physische RAM). Shared-Memory ist was anderes, der darf immer nur ein mal zählen egal in wie vielen Prozessen er gemappt wird (es wird ja auch nur ein mal physischer RAM dafür benötigt).
Manche 2.6er Linux-Kernel haben das mal anders gemacht so das man auf Embedded-Systemen (die eben kein Swapping haben) zwar deutlich mehr virtuellem RAM anfordern konnte als physisch da ist aber wenn man dann diesen virtuellen RAM benutzt (und er langsam mit echten Pages hinterlegt werden muss) kommt irgendwann eine OOM-Excpetion und der Prozess wird gekillt (was soll man auch sonst machen, der Prozess denkt ja das er Speicher hat und will den auch benutzen). Meiner persönlichen Meinung nach ist dieses Verhalten des Linux-Kernel ein Bug aber es wurde als Feature angepriesen, ob das in der Zwischenzeit behoben ist weiß ich jetzt gar nicht aber es ist einer der Gründe warum für viele kleine Embedded-Sachen immer noch gerne ein 2.4er Linux-Kernel benutzt wird.

Guck dir mal das Dokument von Bonwick aus dem Jahre 2001 zum SlabAllocator an. Denn deine Variante skaliert nämlich gar nicht mit mehreren CPUs.
Der schaltet vor den eigentlichen SlabAllocator noch diese "Magazin-Ebene", das geht bei mir prinzipiell auch, ich habe nur versucht meine Erklärung so einfach wie möglich zu halten und mich auf das Wesentliche (Wechselwirkung zwischen SlabAllocator und VMM) zu beschränken. Dieser Magazin-Mechanismus würde bei meinem nichtunterbrechbaren Kernel sogar besser funktionieren weil ich dafür keinerlei Lock o.ä. benötige.

Ich bin bisher ganz gut damit ausgekommen, nur die freien Bereiche zu speichern und habe noch keinen Nachteil davon getragen (der etwas anderes rechtfertigen würde).
Ich muss ehrlich sagen das mir die Idee das der VMM nur die freien Bereiche verwaltet mit längerem Nachdenken immer besser gefällt. Die benutzten Bereiche meines linearen Adressraumes sind ja schon wo anders (z.B. in den LDTs der Prozesse) ausreichend gut gemanagt.


Das heißt, ich darf mir ein Megabyte Speicher geben lassen und dann in einem Befehl die mittleren 333 KB freigeben? Seltsames Design.
Wenn der VMM nur die freien Bereiche verwaltet sollte sowas tatsächlich möglich sein (ohne negative Folgen), ob das auch sinnvoll ist sei mal dahin gestellt.

Wo siehst Du da einen Durchgriff? VMM und SlabAllocator sind auf einer Ebene und (leider) wechselseitig voneinander abhängig. Der SlabAllocator wird (neben dem VMM) von den verschiedenen Teilen im Kernel benutzt (er stellt ja den Kernel-Heap zur Verfügung) und der User-Space benötigt Segmente und holt sich diese vom VMM (natürlich über Kernel-Funktionen die zur Verwaltung der Segmente wieder den SlabAllocator benötigen). Ich sehe darin keine Probleme, wichtig ist nur das wenn SlabAllocator und VMM sich gegenseitig aufrufen der jeweils eigene Lock frei ist damit eben kein Dead-Lock entsteht.
Wenn man das so betrachtet, dann sind Slab-Allocator und VMM ein monolithischer Blob. Solange man dann nicht behauptet, dass es verschiedene Ebenen seien (wie ich es tue), dann ist das auch eine Lösung und das hatte das auch so gekennzeichnet. ;-)
Ich bin da nicht der Meinung das SlabAllocator und VMM ein monolithischer Block sind, schon weil ich jeden von beiden unabhängig vom anderen durch eine völlig andere Implementierung austauschen kann so lange die offizielle API nach oben gleich bleibt und auch die Randbedingen, wie das jeder seinen eigenen Lock freigeben muss wenn er den anderen aufrufen möchte, eingehalten werden (also void* kmalloc(uint objectsize,bool isVMM)/free(void* obj) beim Heap und void* vmalloc(size_t size,size_t allignment,uint attribute)/vfree(void* obj,size_t size) für den VMM, ja wenn man im VMM nur die freien Bereiche verwaltet dann muss man beim vfree auch die Größe des zu befreienden Blocks korrekt mitgeben was innerhalb meines Kernels aber auch absolut kein Problem sein dürfte).

Sonst legt ein malloc(2TB, MAP_NOW) für hinreichend große Festplatte dein System für mehrere Stunden vollständig lahm.
Wenn nur 4GB physicher RAM noch frei sind dann darf ein malloc(2TB, MAP_NOW) trotzdem nicht versuchen die gesamten 2TB mit physischen Pages zu hinterlegen, das wäre auch offensichtlicher Schwachsinn. Vielmehr muss einfach 2TB des Swapp-Space für diesen Bereich reserviert werden und fertig. Wenn auch mit dem Swapp-Space keine 2TB mehr verfügbar sind dann muss NULL zurück kommen. Offensichtlich unsinnige Anforderungen darf ein OS auch nicht ausführen!

Mach das mal unter einem normalen Betriebssystem und schaue, wieviel RAM du rauskriegst, wenn du (a) immer 4k-Blöcke alloziierst und (b) alles an einem Stück haben möchtest. Beobachte bitte die Performance des Systems in beiden Fällen... mich würde interessieren, ob die bekannten Betriebssysteme mit so einer Workload klarkommen oder nicht.
Ich vermute das die meisten aktuellen/bedeutenden OSe da zumindest Schwächen zeigen auch wenn zu hoffen ist das keines dieser OSe damit in ernste Schwierigkeiten gerät. Entweder wird der Prozess gekillt oder das OS lahmt ein klein wenig (theoretisch noch nicht mal das, weil ja alle anderen Prozesse und der Kernel selber davon eigentlich nicht negativ beeinflusst werden dürften).

Die Frage ist, ob das System als Ganzes dann noch benutzbar und vor allem stabil bleibt (dann ist es auch DoS-sicher) oder ob dann Grenzfälle und race conditions auftreten, die dann Bugs triggern. Das ist für die Qualität eines OS mMn wichtiger
Full ACK!

auch, wenn es hier nur um ein Hobby geht.
"Hobby" ist keine Ausrede für schlechte Software. ;)


Zitat von: svenska
Wann tritt denn dein OOM-Killer in Kraft? Wenn der RAM [ohne Plattencache] voll ist, wenn ein Prozess mehr RAM möchte als physisch vorhanden oder wenn die Festplatte wegen Swap voll ist? Ist das System überhaupt noch benutzbar, wenn es swappt? (Mein Linux bricht komplett zusammen, wenn der RAM nahezu voll ist und der Auslagerungsmarathon losgeht. Je schneller der OOM-Killer dann zuschlägt, umso eher läuft das System überhaupt wieder.)
Ich habe noch keinen OOM-Killer und auch noch kein Swapping, bei mir können dann einfach irgendwelche Speicheranforderungen nicht mehr gemacht werden ;)
Dem muss ich erst mal zustimmen. Ich frage mich da auch was ein "OOM-Killer" eigentlich machen soll, das einzigste was ich mir vorstellen kann ist das es bestimmte Prozesse gibt die ihren Speicher, auf Anforderung vom Kernel, wieder freigeben können. Der Cache im VFS wäre da ein Beispiel, aber mehr fällt mir dazu auch nicht ein.

Aber wie schaffst du es das dein Linux einbricht (bzw. was verstehst du darunter). Ich habe das noch nicht (wieder) erlebt.
Das würde mich auch mal interessieren.
Kleines Gegenbeispiel: nehmen wir mal an wir hätten auf einem PC mit 512MB RAM ein Officeprogramm das eine 1GB große Wörterdatenbank für die Rechtschreibkontrolle besitzt, da im aktuellen Text ja wohl kaum alle möglichen Worte vorkommen dürfte diese Datenbank nur sehr spärlich benutzt werden so das es hier reicht wenn nur wenige Prozent der Pages tatsächlich im physischen RAM liegen. Mir ist klar das dieses Beispiel nicht ganz real ist aber es wäre eine Situation in der mehr virtueller RAM tatsächlich benötigt wird als physischer RAM vorhanden ist und trotzdem beim normalen Arbeiten keine Page eingelagert oder ausgelagert werden müsste.
(@svenska: Unter anderem wegen solcher Szenarien bin ich der Meinung das segmentweises Swappen keinen Sinn macht.)

Ich sags erstmal so, ich sehe den Zweck eines OS programmieren nicht darin, ein schon vorhandenes nachzuprogrammieren, also musst du auch mal was machen was nicht normal ist.
Sehr gutes Argument. (auch wenn eine ordentliche User-Space-malloc-Implementierung eher große Brocken an virtuellem Speicher holen sollte, ist einfach praktischer)

Ich denke auch an die Fälle die direkt meine OS-API und nicht die libc nutzen.
Ein Programm das direkt Deine OS-API benutzt weiß auch wie es das am besten tun sollte um ein Optimum an Performance zu holen (wenn es das nicht wollte könnte es auch gleich bei malloc bleiben).


Ich habe noch keinen OOM-Killer und auch noch kein Swapping, bei mir können dann einfach irgendwelche Speicheranforderungen nicht mehr gemacht werden ;)
Also geht das System komplett kaputt.
Quatsch, nur ein Programm wird sich (hoffentlich mit einer ordentlichen Fehlermeldung) beenden oder irgendeine Aktion nicht korrekt ausführen, weil es bei irgendeinem malloc NULL zurück bekommen hat. Wenn der Speicher (inklusive Swapp) endgültig zu Ende ist dann passiert sowas schon mal. Aber das OS bleibt stabil und das zählt.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 11. November 2010, 10:00
Ich bin bisher ganz gut damit ausgekommen, nur die freien Bereiche zu speichern und habe noch keinen Nachteil davon getragen (der etwas anderes rechtfertigen würde).
Ich muss ehrlich sagen das mir die Idee das der VMM nur die freien Bereiche verwaltet mit längerem Nachdenken immer besser gefällt. Die benutzten Bereiche meines linearen Adressraumes sind ja schon wo anders (z.B. in den LDTs der Prozesse) ausreichend gut gemanagt.
Klar, in meinem Kopf wird das halt zentral im VMM gemanagt und dafür ist es wichtig, dass es da eine Übersicht gibt.

Ich bin da nicht der Meinung das SlabAllocator und VMM ein monolithischer Block sind, schon weil ich jeden von beiden unabhängig vom anderen durch eine völlig andere Implementierung austauschen kann so lange die offizielle API nach oben gleich bleibt und auch die Randbedingen, wie das jeder seinen eigenen Lock freigeben muss wenn er den anderen aufrufen möchte, eingehalten werden (also void* kmalloc(uint objectsize,bool isVMM)/free(void* obj) beim Heap und void* vmalloc(size_t size,size_t allignment,uint attribute)/vfree(void* obj,size_t size) für den VMM, ja wenn man im VMM nur die freien Bereiche verwaltet dann muss man beim vfree auch die Größe des zu befreienden Blocks korrekt mitgeben was innerhalb meines Kernels aber auch absolut kein Problem sein dürfte).
Du kannst in einem monolithischem Kernel auch Teile austauschen, wenn du sinnvolle Abstraktionen da drin hast. Deswegen bleibt der trotzdem monolithisch und als Ganzes trotzdem ein Blob (vgl. BSD-Kernel, auch wenn es dort inzwischen Module gibt).

Sonst legt ein malloc(2TB, MAP_NOW) für hinreichend große Festplatte dein System für mehrere Stunden vollständig lahm.
Wenn nur 4GB physicher RAM noch frei sind dann darf ein malloc(2TB, MAP_NOW) trotzdem nicht versuchen die gesamten 2TB mit physischen Pages zu hinterlegen, das wäre auch offensichtlicher Schwachsinn. Vielmehr muss einfach 2TB des Swapp-Space für diesen Bereich reserviert werden und fertig. Wenn auch mit dem Swapp-Space keine 2TB mehr verfügbar sind dann muss NULL zurück kommen. Offensichtlich unsinnige Anforderungen darf ein OS auch nicht ausführen!
Ich stelle mir die Dokumentation dazu gerade vor... "Wenn MAP_LAZY angegeben wird, so wird RAM erst zugewiesen, wenn er benötigt wird. Wenn MAP_NOW angegeben wird, wird der Speicher sofort zugewiesen, allerdings nur, wenn das System der Meinung ist, dass das so einfach möglich ist." ;-) Wenn du ein Flag bereitstellst, solltest du dieses Flag niemals ignorieren. Daher würde ich dort kein Flag angeben.

Jedes System erlaubt es dir, unsinnige Anforderungen auszuführen, solange sie möglich sind. Schließlich kannst du als Programmierer in der Regel garnicht abschätzen, ob sie unsinnig sind oder nicht. (Das NX-Bit verhindert das Ausführen von Daten und das ist gut. Aber ein JIT-Compiler benutzt das, obwohl der Gedanke an sich unsinnig ist. Präventiv verbieten bringt nichts.)

auch, wenn es hier nur um ein Hobby geht.
"Hobby" ist keine Ausrede für schlechte Software. ;)
Aber durchaus ein Argument für beschränkte Software. Wichtig hierbei: Man muss die Grenzen kennen (und evtl. dokumentieren). Das gilt für mich aber grundsätzlich - das Wissen um das Nichtwissen ist wichtiger, als das Wissen selbst.

Zumal du ein OpenSolaris mit ZFS eher nicht auf deinem Router laufen lassen magst, oder Windows 3.1 nativ im Rechenzentrum. Das sind solche Grenzen.


Ich habe noch keinen OOM-Killer und auch noch kein Swapping, bei mir können dann einfach irgendwelche Speicheranforderungen nicht mehr gemacht werden ;)
Dem muss ich erst mal zustimmen. Ich frage mich da auch was ein "OOM-Killer" eigentlich machen soll, das einzigste was ich mir vorstellen kann ist das es bestimmte Prozesse gibt die ihren Speicher, auf Anforderung vom Kernel, wieder freigeben können. Der Cache im VFS wäre da ein Beispiel, aber mehr fällt mir dazu auch nicht ein.
Wenn ein Prozess eben nicht auf malloc()=NULL reagiert, sondern munter weitermacht (mit eventuell kleineren Größen), dann ist das eine Endlosschleife oder systematisches Ausbluten des RAMs. Der OOM-Killer tritt dann in Kraft, wenn der Kernel für sich selbst Speicher benötigt, z.B. Puffer um Hardware anzusteuern oder Baumknoten für den VMM. Dieser Speicher ist u.U. wichtig, um Swapping überhaupt zu ermöglichen (man stelle sich ein network block device via TCP/IP vor) und daher grundsätzlich wichtiger als jedes Userspace-Programm. Dann werden nach Speicherverbrauch in absteigender Reihenfolge sortiert die Prozesse abgeschossen, um das System am Laufen zu halten.

Ich habe noch keinen OOM-Killer und auch noch kein Swapping, bei mir können dann einfach irgendwelche Speicheranforderungen nicht mehr gemacht werden ;)
Also geht das System komplett kaputt.
Quatsch, nur ein Programm wird sich (hoffentlich mit einer ordentlichen Fehlermeldung) beenden oder irgendeine Aktion nicht korrekt ausführen, weil es bei irgendeinem malloc NULL zurück bekommen hat. Wenn der Speicher (inklusive Swapp) endgültig zu Ende ist dann passiert sowas schon mal. Aber das OS bleibt stabil und das zählt.
Und was ist, wenn der Kernel zum Funktionieren Speicher braucht? Das kann man nicht ablehnen, denn es würde zum DoS und/oder zum Absturz führen (wenn Hardware dann halt woanders hinschreibt, weil der Treiber damit nicht rechnet).

Kleines Gegenbeispiel: nehmen wir mal an wir hätten auf einem PC mit 512MB RAM ein Officeprogramm das eine 1GB große Wörterdatenbank für die Rechtschreibkontrolle besitzt, da im aktuellen Text ja wohl kaum alle möglichen Worte vorkommen dürfte diese Datenbank nur sehr spärlich benutzt werden so das es hier reicht wenn nur wenige Prozent der Pages tatsächlich im physischen RAM liegen. Mir ist klar das dieses Beispiel nicht ganz real ist aber es wäre eine Situation in der mehr virtueller RAM tatsächlich benötigt wird als physischer RAM vorhanden ist und trotzdem beim normalen Arbeiten keine Page eingelagert oder ausgelagert werden müsste.
(@svenska: Unter anderem wegen solcher Szenarien bin ich der Meinung das segmentweises Swappen keinen Sinn macht.)
Beim Einlesen der Datenbank geht ein Swapmarathon los, da du als Anwendung ja eher keine Lust hast, die Daten von Platte direkt in Swapspace zu laden. Also wirst du dich hüten, die gesamte DB im RAM halten zu wollen und richtest deine DB-Struktur so ein, dass du schon im Voraus weißt, welche Teile der DB du benötigen wirst und lädst diese bei Bedarf von Platte.

Um das ganze performant zu machen, gibt es präventives Readahead, welches im Blocklayer ausgeführt wird. Die DB hängt dann nicht vollständig im RAM der Anwendung, sondern (eventuell komprimiert) im Plattencache. Damit spart man sich die I/O-Zugriffszeit für das Teile-Laden, die I/O-Zeit für das Swapping und gleichzeitig Unmengen an RAM, weil die ungenutzten Daten nicht im RAM liegen müssen. (Plattencache ist semi-freier Speicher).

Außerdem kann man als Anwendung das auch so bauen, dass man einen großen virtuellen Block für die ganze DB anfordert und den mit den benötigten Daten füllt. Das wird man natürlich nicht mit MAP_NOW machen, sondern mit MAP_LAZY... und das ganze Konstrukt fällt performanceseitig vollständig zusammen, wenn die DB-Größe auch nur in die Nähe der RAM-Größe kommt. Darum ist das unüblich für große DBs.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 11. November 2010, 10:57
Zitat von: svenska
Du kennst doch die Anfangsadressen dieser Nachbarelemente nicht, also kannst du danach nicht suchen, oder irre ich da?
Jein ;)

Deswegen hatte ich ja ein konkrettes Bsp gebracht. Ich mache einfach nochmal eins.

Du willst die ID 15 freigeben und fängst mir der Wurzel des Baums an.

Die Wurzel hat den Wert 20, d.h. deine Nachbarn, so fern vorhanden, können sich nur noch im linken Teil-Baum befinden.
Da 20 > 15 gehst du nach links.
Der nächste Knoten hat den Wert 10 (und size 1), d.h. deine Nachbarn, so fern vorhanden können sich nur noch im rechen Teil-Baum befinden.
Da 10 < 15 gehst du nach rechts.
Der nächste Knoten hat den Wert 16, d.h. du hast einen Nachbarn gefunden und der andere Nachbar kann sich, so fern vorhanden, nur noch im linken Teilbaum befinden.
Da 16 > 15 gehst du nach links.
Der nächste Knoten hat den Wert 14 (und size 1), d.h. du hast deinen anderen Nachbarn gefunden und musst jetzt aber trotzdem noch bis zum Ende des Baums durchgehen (weil der Code ja allgemein bleiben soll).
Da 14 < 15 gehst du nach rechts.
Der nächste Knoten ist NULL (also keiner vorhanden), du bist also fertig und die ID darf freigegeben werden und du hast sogar deine beiden Nachbarn gefunden.

Ich hoffe du kannst es jetzt nachvollziehen. Du musst die Start-Werte deiner Nachbarn nicht kennen um sie zu finden. Denn sie können nur auf deinem Suchweg sein!

Zitat von: svenska
Außerdem bringt das nur bei auf 4k ausgerichteten Speicherblöcken was, da für den dritten Teil eine Anfangsadresse mit Alignment da sein muss.
Und genau darum geht es auch nur bei einem VMM, der verwaltet immer Bereiche die an PAGE_SIZE aligned sind und die ein vielfaches von PAGE_SIZE haben.

Zitat von: svenska
Ein Userspace-Programm kann aber Teile des Kernel-Speichers freigeben, wenn dieser Bereich im Userspace-Programm gemappt ist.
Richtig, da ich aber im Moment eh nur eine derartig Page im UserSpace habe und das die letzte ist, kann ich auch ganz einfach meine Map eine Page kleiner machen und falls ich wirklich mal sowas derartiges brauchen sollte, dann will ich das so implementieren, das du einer Map eine andere Map hinzufügen kannst. Dann kannst du die Löcher zw. den Maps auch nicht freigeben.

Was anderes, überprüft ihr ob das Programm versucht seinen eigenen Code freizugeben?

Zitat von: svenska
Du willst also einen Baum pro Prozess haben?
Sorry, aber das ist wieder so eine Frage, wo ich mich dann frage, ob du das mit dem VMM wirklich verstanden hast ;)

Ich will nicht ich muss einen Baum pro Prozess haben. Denn in dem Sinne haben ja alle Prozesse den gleichen Adressraum (ich meine damit der geht immer von 0 - 3GB) und da musst du nunmal einen Baum pro Prozess nehmen.

Zitat von: svenska
Diese Interfaces sollte eigentlich nur die libc nutzen (und Treiber).
Falsch! Z.b. GUI-Programme werden entweder ne Library nutzen (welche dann die jeweilige OS-API nutzen wird) oder wird direkt die OS-API nutzen.
Ich behaupte mal das gerade sowas wie Spiele und wahrscheinlich auch bzw. gerade DBs kein malloc, jedenfalls nicht für größere Daten nutzen wird. Die werden immer nen eigenen Allocator nutzen oder halt im Falle der DB einfach den VMM des OS, weil die Daten werden nachher eh in 4KB Blöcken gespeichert, da kann man den Speicher auch gleich so anfordern.

Zitat von: erik
Also wenn Deine malloc-Implementierung (für die User-Space-libc) wirklich in 16kB-Häppchen den virtuellen Speicher holt dann würde ich da tatsächlich das Prädikat "kaputt" benutzen. Sorry, aber sowas macht man nicht.
D.h. wenn ich ein kleines Sinnlos-Programm schreibe (wie man es oft an der Uni macht), dann wird ein "new int" gleich 16MB vom System holen (oder setzt was anderes ein, was nicht auf dem Stack passiert)?
Das finde ich kaputt ;)

Zitat von: erik
Dieser Magazin-Mechanismus würde bei meinem nichtunterbrechbaren Kernel sogar besser funktionieren weil ich dafür keinerlei Lock o.ä. benötige.
Der Sinn und Zweck des ganzen war, das man auch in jedem anderen Kernel keinen Lock benötigt, sondern einfach nur die Ints ausmacht.

Zitat von: svenska
Wenn ein Prozess eben nicht auf malloc()=NULL reagiert, sondern munter weitermacht (mit eventuell kleineren Größen), dann ist das eine Endlosschleife oder systematisches Ausbluten des RAMs.
Wenn er auf ein malloc()=NULL nicht reagiert greift er endweder auf Speicher zu wo er es nicht darf (wird also gekillt) oder er wird zu einer endlos Schleife. Beides sehe ich nicht als Problem an und auch der RAM wird so nicht ausgeblutet.
Wenn er mit kleineren Größen weitermacht, ist es das selbe als wenn ich noch andere Prozesse starte die auch RAM brauchen, sehe ich also auch nicht das Problem.

Zitat von: svenska
Und was ist, wenn der Kernel zum Funktionieren Speicher braucht? Das kann man nicht ablehnen, denn es würde zum DoS und/oder zum Absturz führen (wenn Hardware dann halt woanders hinschreibt, weil der Treiber damit nicht rechnet).
Das nenne ich mal kaputtes Design ;)

Also mein Kernel kommt damit klar, wenn er Speicher braucht und dem nicht nachgekommen werden kann. Genau aus dem Grund ist mein neuer Code ja etwas schwieriger zu lesen und der Code der noch benutzt wird ist deswegen auch eigentlich fehlerhaft (der kommt damit zwar auch klar, aber dadurch gibt es dann Memory-Leaks).

Du musst doch Speicher eh immer vorher allokieren, also kannst du auch nicht einfach welchen benutzen ohne das du ihn allokiert hast und wenn das Allokieren schon nicht ging, gibst du einfach nen Fehler zurück und fertig!
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 11. November 2010, 12:36
Hallo,


Ich muss ehrlich sagen das mir die Idee das der VMM nur die freien Bereiche verwaltet mit längerem Nachdenken immer besser gefällt. Die benutzten Bereiche meines linearen Adressraumes sind ja schon wo anders (z.B. in den LDTs der Prozesse) ausreichend gut gemanagt.
Klar, in meinem Kopf wird das halt zentral im VMM gemanagt und dafür ist es wichtig, dass es da eine Übersicht gibt.
Ja, da bei mir die User-Space-Prozesse auch nie direkt mit dem VMM in Kontakt kommen sondern ihren Speicher immer über (Kernel kontrollierte) Segmente managen müssen kann es auch nicht passieren das der VMM mit unsinnigen/fehlerhaften Parametern benutzt wird. Bei FlashBurn ist es wohl so das er für jeden Prozess (also jeden virtuellen Adressraum) eine eigene Verwaltung will und wenn dort ein Prozess 30 Pages anfordert und die mittleren 10 frei gibt dann ist das auch in Ordnung denn wenn der Prozess später auf diese mittleren Pages zugreift gibt es eine Exception und der Exception-Handler kann beim VMM klar ermitteln das dieser Bereich frei ist und killt den Prozess. Es ist zwar eine bizarre Möglichkeit einfach Löcher in den virtuellen Adressraum zu schießen (oder auch mehrere hintereinander liegende Bereiche die einzeln angefordert wurden mit einem einzigen free wieder los zu werden) aber grundsätzlich entsteht daraus erst mal keine negative Konsequenz.

Du kannst in einem monolithischem Kernel auch Teile austauschen, wenn du sinnvolle Abstraktionen da drin hast. Deswegen bleibt der trotzdem monolithisch und als Ganzes trotzdem ein Blob (vgl. BSD-Kernel, auch wenn es dort inzwischen Module gibt).
Okay, aber dann sage auch nicht mehr das ich da durch verschiedene Layer hindurch greife. ;) Modularisieren will ich meinen Kernel nur auf Quell-Code-Ebene, wenn er kompiliert ist ist er nur noch ein Stück.

Ich stelle mir die Dokumentation dazu gerade vor... "Wenn MAP_LAZY angegeben wird, so wird RAM erst zugewiesen, wenn er benötigt wird. Wenn MAP_NOW angegeben wird, wird der Speicher sofort zugewiesen, allerdings nur, wenn das System der Meinung ist, dass das so einfach möglich ist." ;-) Wenn du ein Flag bereitstellst, solltest du dieses Flag niemals ignorieren. Daher würde ich dort kein Flag angeben.
Also für ein Flat-Memory-System empfinde ich dieses Flag schon als sinnvoll. Der SlabAllocator (der im User-Space das malloc anbietet) wird für neue Slabs wohl eher MAP_LAZY benutzen weil er weiß das es unwahrscheinlich ist das der neue Slab sofort komplett benutzt wird aber wenn er mit einem malloc(100MByte) konfrontiert wird, was er mit Sicherheit nicht mehr über Slabs macht sondern direkt virtuellen Speicher für diese Anforderung hollt, wir er wohl eher MAP_NOW benutzen da damit zu rechnen ist dass das Programm diesen neuen Block auch wirklich sofort benutzen will. Ich sehe dieses Flag auch eher als freundliche Bitte an das OS um ihm die Möglichkeit zu geben sich so zu verhalten das die Applikation möglichst performant arbeiten kann. Dieses Flag ist mMn kein absoluter Befehl dem das OS unbedingt gehorchen muss.

"Hobby" ist keine Ausrede für schlechte Software. ;)
Aber durchaus ein Argument für beschränkte Software.
Okay, das ist richtig.

das Wissen um das Nichtwissen ist wichtiger, als das Wissen selbst.
Vielleicht nicht unbedingt wichtiger aber zumindest genau so wichtig.

Wenn ein Prozess eben nicht auf malloc()=NULL reagiert
Dann ist er fehlerhaft, die Rückgabe von malloc muss immer geprüft werden!

sondern munter weitermacht (mit eventuell kleineren Größen)
Wenn der Prozess statt dessen versuch mit weniger Speicher aus zu kommen dann ist das IMHO ein guter Kompromiss. Schließlich hat der User diesen Prozess ja gestartet weil er will das der Prozess eine bestimmte Aufgabe erledigt und das sollte auch erst mal nach besten Möglichkeiten versucht werden.

dann ist das eine Endlosschleife oder systematisches Ausbluten des RAMs.
Dann ist das ein Bug im OS, die letzten Speicher-Reserven darf das OS nur noch für sich selber und für Prozesse benutzen die mit root-Rechten laufen. Wenn root dann 50 Shells startet und das System endgültig an die Wand fährt ist das einfach nur Dummheit von root aber wenn root gar keine Shell mehr starten kann (um z.B. andere Prozesse zu killen) dann ist das IMHO ein Bug im OS.

... und daher grundsätzlich wichtiger als jedes Userspace-Programm.
Okay, verstehe.

Dann werden nach Speicherverbrauch in absteigender Reihenfolge sortiert die Prozesse abgeschossen, um das System am Laufen zu halten.
Also bevor das System anfängt nach eigenem Gutdünken einfach beliebige Prozesse zu killen wäre es IMHO schon schön wenn root die Möglichkeit hat selber (mit Verstand) zu entscheiden welcher Prozess gekillt wird.

Und was ist, wenn der Kernel zum Funktionieren Speicher braucht?
Der Kernel braucht Speicher weil er eine Aufgabe erfüllen soll und diese Aufgabe kann eben nicht ausgeführt werden wenn es nicht genug Speicher dafür gibt. Bei mir bekommt jeder Syscall einen Rückgabewert der auch Fehlercodes darstellen kann und die User-Space-Programme dürfen das grundsätzlich nicht einfach ignorieren sondern müssen immer entsprechend reagieren.
Außerdem kann man da eine weitere Regel einführen:
Kernel-Speicher ist normalerweise nicht auslagerbar also immer davon abhängig das noch physischer RAM verfügbar ist. Der VMM muss einfach immer mitzählen wie viel nicht auslagerbarer Speicher zugewiesen wurde und wenn das mehr als 75% des real vorhandenen physischen RAM sind dann arbeitet der VMM nur noch im Dienste von root-Prozessen. Bei mir möchte ich sogar so weit gehen das Speicher von root-Prozessen (wozu auch alle Treiber und die Personality zählen) auch zu dem nicht auslagerbaren Speicher zählt, damit sowas wie ne Shell auch während eines extremen Swapp-Marathons noch funktionsfähig bleiben, das bedeutet natürlich das root-Prozesse immer möglichst sorgsam mit dem Speicher umgehen müssen (und root natürlich aufpassen muss was er alles startet, ne fette Datenbank wird man sicher nicht als root ausführen).

(wenn Hardware dann halt woanders hinschreibt, weil der Treiber damit nicht rechnet).
Wenn ein Treiber eine ungültige/falsche physische Adresse an eine HW-Komponente gibt dann ist das ein Bug im Treiber! Gerade ein Treiber darf ein NULL von malloc nicht ignorieren. Außerdem gehören Treiber IMHO nicht gerade zu den Programmen die einen ständig wechselnden Speicherbedarf haben, außer das da permanent Job-Descriptoren o.ä. alloziert und freigegeben werden (was in kBytes gemessen sicher nicht viel ist und somit von der malloc-Implementierung abgefangen werden sollte) dürfte sich am virtuellen Adressraum eines Treibers nicht viel tun (mal abgesehen davon das er die Nutzdaten eventuell per Shared-Memory o.ä. in seinen virtuellen Adressraum eingeblendet bekommt).

(@svenska: Unter anderem wegen solcher Szenarien bin ich der Meinung das segmentweises Swappen keinen Sinn macht.)
Beim Einlesen der Datenbank geht ein Swapmarathon los .....
Ich glaube Du hast nicht ganz verstanden was ich eigentlich ausdrücken wollte. Es geht mir nicht um Datenbanken o.ä. sondern um ein Szenario in dem ein Programm einen großen Bereich an virtuellem Speicher benötigt und diesen auch mit echten Daten befüllt aber dann zur weiteren Laufzeit nur einen kleinen Anteil davon tatsächlich benutzt, das bedeutet das von diesem Bereich durchaus ein hoher Anteil an Pages ausgelagert sein können ohne dass das negative Konsequenzen (für die Performance) hat. Das selbe trifft auch auf übliche Code-Bereiche zu, kaum ein Programm wird ständig all seinen Programm-Code ausführen.


Was anderes, überprüft ihr ob das Programm versucht seinen eigenen Code freizugeben?
Ja, jedes Segment das bereits in der Executable drin war (also .code , .const , .data und .tls_master) kann nicht freigeben werden, nur Segmente die zur Laufzeit angefordert werden können auch wieder freigegeben werden. Stack-Segmente (und thread-spezifische TLS-Segmente) können nur von KillThread freigegeben werden.

Zitat von: erik
Also wenn Deine malloc-Implementierung (für die User-Space-libc) wirklich in 16kB-Häppchen den virtuellen Speicher holt dann würde ich da tatsächlich das Prädikat "kaputt" benutzen. Sorry, aber sowas macht man nicht.
D.h. wenn ich ein kleines Sinnlos-Programm schreibe (wie man es oft an der Uni macht), dann wird ein "new int" gleich 16MB vom System holen (oder setzt was anderes ein, was nicht auf dem Stack passiert)?
Das finde ich kaputt ;)
Klar ist das doof wenn ein Miniprogramm gleich große Mengen an Speicher verbraucht aber wen stört das? Willst Du 100'000 solcher Programme parallel starten? Außerdem wird die libc schon oft genug malloc für eigene Sachen benutzen so das Dein "new int" auch nicht mehr viel macht. Nebst dessen das eine gute malloc-Implementierung sich dem dynamisch anpasst, die greift also nicht gleich beim ersten malloc(4) in die vollen sondern erst wenn das Programm auch wirklich viel Speicher will.

Der Sinn und Zweck des ganzen war, das man auch in jedem anderen Kernel keinen Lock benötigt, sondern einfach nur die Ints ausmacht.
In Kaptel 3.5 (auf Seite 7 oben rechts) von http://www.usenix.org/event/usenix01/full_papers/bonwick/bonwick.pdf (http://www.usenix.org/event/usenix01/full_papers/bonwick/bonwick.pdf) (ich hoffe mal wir redeten immer vom selben Dokument) ist klar erklärt warum man sich für ein Lock entschieden hat.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 11. November 2010, 12:56
Zitat von: erik
aber wen stört das?
Mich ;)

Ich sollte mal erwähnen das ich mein OS dahin trimmen möchte, das es mit 4-8MB RAM lauffähig ist. Mein Fern-Ziel ist dann mit diesem RAM auch eine GUI zu haben (Win3.11 und Win95 haben das ja auch hinbekommen und laut der Meinung vieler ist Win Schrott und MS unfähig ;) ).

Zitat von: erik
In Kaptel 3.5 (auf Seite 7 oben rechts) von http://www.usenix.org/event/usenix01/full_papers/bonwick/bonwick.pdf (ich hoffe mal wir redeten immer vom selben Dokument) ist klar erklärt warum man sich für ein Lock entschieden hat.
Ja wir reden vom selben Dokument.

Blöd nur, das bei mir Locks im Kernel grundsätzlich die Ints ausmachen, Real-Time ist auch kein Hindernis für mich und den letzten Punkt wüsste ich jetzt nicht wie ich ihn umgehen sollte, außer (wie du) grundsätzlich den Kernel nicht unterbrechbar zu machen.

Der Punkt ist aber das die Zeit ein cli und ein sti auszuführen wohl wesentlich kürzer ist, als wenn auf nem Single CPU System ständig die Threads gewechselt werden, weil der Thread der den Lock hält erstmal wieder an die Reihe kommen muss (bzw. müsste ich dann ja auch zw. Single und SMP System unterscheiden, was ich nicht mache und nicht will).

Interessant wird es erst auf nem SMP System. Denn da kann es durchaus passieren das ich ziemlich oft die Ints an und aus machen (in ganz kurzen Abständen). Das könnte dann doch ganz schön Zeit kosten, aber wie gesagt, sollte nicht so viel sein wie die Thread-Wechsel.

Zumal das bei mir die Regel ist, ein Lock (also Ints aus) wird nur da benutzt wo ich Pi mal Daumen sagen, kann das es weniger Zeit benötigt als die Zeit für 3 Thread-Wechsel.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 11. November 2010, 13:10
Zitat von: svenska
Du kennst doch die Anfangsadressen dieser Nachbarelemente nicht, also kannst du danach nicht suchen, oder irre ich da?
Jein ;) [...] Ich hoffe du kannst es jetzt nachvollziehen. Du musst die Start-Werte deiner Nachbarn nicht kennen um sie zu finden. Denn sie können nur auf deinem Suchweg sein!
Aaahh!! Dann ist das natürlich sehr vorteilhaft.

Zitat von: svenska
Außerdem bringt das nur bei auf 4k ausgerichteten Speicherblöcken was, da für den dritten Teil eine Anfangsadresse mit Alignment da sein muss.
Und genau darum geht es auch nur bei einem VMM, der verwaltet immer Bereiche die an PAGE_SIZE aligned sind und die ein vielfaches von PAGE_SIZE haben.
Damit ist es aber für allgemeine Anwendungsfälle nutzlos, sich da einen Kopf drüber zu machen. Außerdem sollte sich ein Userspace-Programm nicht um Alignments kümmern müssen (es sei denn, es geht um Performance oder Ansteuerung einer Hardware), das heißt die API - solltest du sie im Userspace bereitstellen - exponiert hardwareabhängige Dinge und das ist schlecht.

Wenn du das nur innerhalb des Kernels nutzt, kannst du das natürlich tun. Sinnvoll finde ich das trotzdem nicht, da reicht für meine Verhältnisse ein naher realloc()-Verwandter hin.

Zitat von: svenska
Ein Userspace-Programm kann aber Teile des Kernel-Speichers freigeben, wenn dieser Bereich im Userspace-Programm gemappt ist.
Richtig, da ich aber im Moment eh nur eine derartig Page im UserSpace habe und das die letzte ist, kann ich auch ganz einfach meine Map eine Page kleiner machen und falls ich wirklich mal sowas derartiges brauchen sollte, dann will ich das so implementieren, das du einer Map eine andere Map hinzufügen kannst. Dann kannst du die Löcher zw. den Maps auch nicht freigeben.
Achso.

Was anderes, überprüft ihr ob das Programm versucht seinen eigenen Code freizugeben?
Das solltest du tun. Denn tust du es nicht, so stürzt das Programm in der nachfolgenden Anweisung ab (weil der Speicher nicht mehr vorhanden ist) und wenn du dann den Task killst, wird dein Kernel die vorhandenen Ressourcen freigeben wollen, was u.U. zu einem double-free() und damit zu einer Exception/Panic im Kernel führen kann.

Zitat von: svenska
Du willst also einen Baum pro Prozess haben?
Sorry, aber das ist wieder so eine Frage, wo ich mich dann frage, ob du das mit dem VMM wirklich verstanden hast ;)
Hmm... ;-) Warum kann die Welt nicht einfach sein? Ich habe halt immernoch "globale" Ressourcen im Hinterkopf (eben I/O-Ports, GUIDs, ...) und da trifft das natürlich nicht zu. Stimmt, der virtuelle Adressraum ist per-process, kann sich aber mit anderen überschneiden. *seufz*

Zitat von: svenska
Diese Interfaces sollte eigentlich nur die libc nutzen (und Treiber).
Falsch! Z.b. GUI-Programme werden entweder ne Library nutzen (welche dann die jeweilige OS-API nutzen wird) oder wird direkt die OS-API nutzen.
Ich behaupte mal das gerade sowas wie Spiele und wahrscheinlich auch bzw. gerade DBs kein malloc, jedenfalls nicht für größere Daten nutzen wird. Die werden immer nen eigenen Allocator nutzen oder halt im Falle der DB einfach den VMM des OS, weil die Daten werden nachher eh in 4KB Blöcken gespeichert, da kann man den Speicher auch gleich so anfordern.
Anwendungen sollten niemals die OS-API direkt benutzen, weil sie das inhärent unportabel macht und außerdem dich dazu zwingt, deine Kernel-ABI (nicht nur die API!) stabil zu halten. Sonst funktionieren die Applikationen nicht mehr. Das ist ein Maintenance-Burden und ein Grund für schlechten Linux-Hersteller-Treibersupport - der Kernel garantiert eine einigermaßen stabile API, keine stabile ABI. Darum müssen die Treiber auch immer an die aktuellen Kernel-Interna angepasst werden und das will kein Hersteller selbst tun (vgl. NVidia). Für den Userspace wäre das eine Katastrophe, nach jedem Kernelupdate müsstest du alle Anwendungen neu kompilieren.

Üblicherweise abstrahiert man das also in Libraries raus, diese sind die einzigen Nutzer der OS-API. Was Datenbanken angeht, so organisieren diese ihren Speicher selbst, das hat mit dem VMM erstmal nichts zu tun (schließlich soll ein MySQL auf mehreren Plattformen laufen können, und für Apache gibt es die Apache Portable Runtime, APR) und 3D-Spiele greifen auch nicht auf den Kernel direkt zu, sondern über Libraries wie OpenGL oder DirectX und damit über Bindings. Selbst 2D-Programme nutzen das X11-Protokoll nicht direkt, sondern die Xlib (oder darauf aufbauende Systeme wie Motif) oder Toolkits wie Qt oder GTK.

Wenn du Kernel-Interfaces änderst, brauchst du dann nur die Libraries anpassen, solange deren ABI stabil bleibt, funktionieren auch Binärprogramme weiter.

Zitat von: erik
Also wenn Deine malloc-Implementierung (für die User-Space-libc) wirklich in 16kB-Häppchen den virtuellen Speicher holt dann würde ich da tatsächlich das Prädikat "kaputt" benutzen. Sorry, aber sowas macht man nicht.
D.h. wenn ich ein kleines Sinnlos-Programm schreibe (wie man es oft an der Uni macht), dann wird ein "new int" gleich 16MB vom System holen (oder setzt was anderes ein, was nicht auf dem Stack passiert)?
Das finde ich kaputt ;)
Hä? Du solltest die Blockgröße schon von der angeforderten Größe abhängig machen. Ein "new int" sollte also maximal eine 4KB-Page zurückgeben, ein "new buf[16M]" sollte 4 Pages je 4MB zurückgeben...

Zitat von: erik
Dieser Magazin-Mechanismus würde bei meinem nichtunterbrechbaren Kernel sogar besser funktionieren weil ich dafür keinerlei Lock o.ä. benötige.
Der Sinn und Zweck des ganzen war, das man auch in jedem anderen Kernel keinen Lock benötigt, sondern einfach nur die Ints ausmacht.
Geht das bei SMP? Ints ausmachen wirkt doch nur für jede CPU...

Zitat von: svenska
Wenn ein Prozess eben nicht auf malloc()=NULL reagiert, sondern munter weitermacht (mit eventuell kleineren Größen), dann ist das eine Endlosschleife oder systematisches Ausbluten des RAMs.
Wenn er auf ein malloc()=NULL nicht reagiert greift er endweder auf Speicher zu wo er es nicht darf (wird also gekillt) oder er wird zu einer endlos Schleife. Beides sehe ich nicht als Problem an und auch der RAM wird so nicht ausgeblutet.
Wenn er mit kleineren Größen weitermacht, ist es das selbe als wenn ich noch andere Prozesse starte die auch RAM brauchen, sehe ich also auch nicht das Problem.

void* p = NULL;
int size=1024*1024*1024;
while(true) {
    p = malloc(size, MAP_NOW); /* alternativ den erfolgreich reservierten Speicher mit 0xFF füllen, um Mapping zu erzwingen */
    if( p == NULL ) {
        size = size/2;
    }
}
Würde also so lange weitermachen, bis der reale Speicher bis aufs letzte Byte gefüllt ist. Was passiert, wenn dir im Kernel ein malloc()==NULL passiert? Du hast keinen OOM-Killer.

Also mein Kernel kommt damit klar, wenn er Speicher braucht und dem nicht nachgekommen werden kann. Genau aus dem Grund ist mein neuer Code ja etwas schwieriger zu lesen und der Code der noch benutzt wird ist deswegen auch eigentlich fehlerhaft (der kommt damit zwar auch klar, aber dadurch gibt es dann Memory-Leaks).
Also braucht dein Kernel keinen dynamisch reservierten Speicher, und auch alle Hardwaretreiber (jetzt und in Zukunft) alloziieren sämtlichen benötigten Speicher bereits beim Laden?

Du musst doch Speicher eh immer vorher allokieren, also kannst du auch nicht einfach welchen benutzen ohne das du ihn allokiert hast und wenn das Allokieren schon nicht ging, gibst du einfach nen Fehler zurück und fertig!
Was aber, wenn du übers Netzwerk swapst, dein TCP/IP-Stack aus Speichermangel größtenteils auf Platte liegt, der RAM randvoll ist und ein seltsames Paket im TCP/IP-Stack landet und verarbeitet werden muss? Du müsstest erstmal Teile deines TCP/IP-Stacks wieder in den RAM laden, wozu du Zeugs auslagern müsstest, was du nicht kannst, weil es den TCP/IP-Stack erfordert.

Da kann man genug Beispiele konstruieren, die unter Last zu seltsamen Effekten führen, weil du u.U. gewisse Randfälle nicht bedacht hast. (Darum ist Swapping in ein Swapfile via NFS oder direkt in ein NBD noch nicht besonders lange stabil und auch Swapfiles im Dateisystem erst seit Kernel 2.6 so performant wie Swap-Partitionen. Das liegt an ebendiesen Zusammenhängen zwischen Speicherverwaltung und Rest des Systems.)

Und das Verhalten eines Systems unter diesen Randbedingungen muss immer absolut stabil sein, auch auf Kosten von Applikationen.

Also für ein Flat-Memory-System empfinde ich dieses Flag [MAP_NOW, MAP_LAZY] schon als sinnvoll. Der SlabAllocator (der im User-Space das malloc anbietet) wird für neue Slabs wohl eher MAP_LAZY benutzen weil er weiß das es unwahrscheinlich ist das der neue Slab sofort komplett benutzt wird aber wenn er mit einem malloc(100MByte) konfrontiert wird, was er mit Sicherheit nicht mehr über Slabs macht sondern direkt virtuellen Speicher für diese Anforderung hollt, wir er wohl eher MAP_NOW benutzen da damit zu rechnen ist dass das Programm diesen neuen Block auch wirklich sofort benutzen will. Ich sehe dieses Flag auch eher als freundliche Bitte an das OS um ihm die Möglichkeit zu geben sich so zu verhalten das die Applikation möglichst performant arbeiten kann. Dieses Flag ist mMn kein absoluter Befehl dem das OS unbedingt gehorchen muss.
Naja, ich als Anwendungsprogrammierer erwarte schon von einem Flag, dass es exakt das tut, was ich möchte und nichts anderes. Daher würde ich eine Automatik machen (MAP_NOW wenn <16M, MAP_LAZY wenn größer). Ich bin der Meinung, dass kleine Blöcke sofort benutzt werden und große Blöcke nicht/teilweise. Der Slab-Allocator holt sich ja nur darum eine begrenzte Anzahl an Pages im Voraus, damit es schnell wird, Pagefaults sind aber langsam.

Alternativ kann man das Flag anbieten, aber den gesamten Request fehlschlagen lassen, wenn es nicht geht. Niemals so, dass ich ein Flag explizit angebe und es dann ignoriert wird.

dann ist das eine Endlosschleife oder systematisches Ausbluten des RAMs.
Dann ist das ein Bug im OS, die letzten Speicher-Reserven darf das OS nur noch für sich selber und für Prozesse benutzen die mit root-Rechten laufen. Wenn root dann 50 Shells startet und das System endgültig an die Wand fährt ist das einfach nur Dummheit von root aber wenn root gar keine Shell mehr starten kann (um z.B. andere Prozesse zu killen) dann ist das IMHO ein Bug im OS.
Sehe ich etwas anders, denn die Größe der "letzten Reserven" hängt von root ab. Wenn er nur "bash", "ps" und "kill" nutzen möchte, mag das sein - aber eventuell möchte er erstmal seine X11-Umgebung mit KDE starten, um darin nachzuschauen, was überhaupt kaputt ist... Daher bin ich der Meinung, dass - sofern im Kernel kein Speicher mehr da ist - der Prozess mit dem größten Speicherverbrauch gekillt werden sollte.

Dann werden nach Speicherverbrauch in absteigender Reihenfolge sortiert die Prozesse abgeschossen, um das System am Laufen zu halten.
Also bevor das System anfängt nach eigenem Gutdünken einfach beliebige Prozesse zu killen wäre es IMHO schon schön wenn root die Möglichkeit hat selber (mit Verstand) zu entscheiden welcher Prozess gekillt wird.
Das unterscheidet ihn von einem OOM-Killer, richtig. Man kann ja beides kombinieren. Allerdings ist ein Programm, welches so viel RAM für sich in Anspruch nimmt, dass es das System nahezu lahmlegt für den Rechner dann ohnehin ungeeignet und man sollte das Programm anpassen oder RAM nachstecken.

Ein System, was konstant am Anschlag lebt, sollte man überdenken.

Kernel-Speicher ist normalerweise nicht auslagerbar also immer davon abhängig das noch physischer RAM verfügbar ist. Der VMM muss einfach immer mitzählen wie viel nicht auslagerbarer Speicher zugewiesen wurde und wenn das mehr als 75% des real vorhandenen physischen RAM sind dann arbeitet der VMM nur noch im Dienste von root-Prozessen. Bei mir möchte ich sogar so weit gehen das Speicher von root-Prozessen (wozu auch alle Treiber und die Personality zählen) auch zu dem nicht auslagerbaren Speicher zählt, damit sowas wie ne Shell auch während eines extremen Swapp-Marathons noch funktionsfähig bleiben, das bedeutet natürlich das root-Prozesse immer möglichst sorgsam mit dem Speicher umgehen müssen (und root natürlich aufpassen muss was er alles startet, ne fette Datenbank wird man sicher nicht als root ausführen).
Wenn du allgemeine Treiber nutzt, ist das nicht möglich; deren Speicheranforderung kann sich stark unterscheiden, je nach Auslastung. Außerdem willst du wirklich 25% des RAMs für root reservieren? Das stelle ich mir grad in einem Rechenzentrum mit 64 GB RAM/Node vor... :-P

(@svenska: Unter anderem wegen solcher Szenarien bin ich der Meinung das segmentweises Swappen keinen Sinn macht.)
Beim Einlesen der Datenbank geht ein Swapmarathon los .....
Ich glaube Du hast nicht ganz verstanden was ich eigentlich ausdrücken wollte. Es geht mir nicht um Datenbanken o.ä. sondern um ein Szenario in dem ein Programm einen großen Bereich an virtuellem Speicher benötigt und diesen auch mit echten Daten befüllt aber dann zur weiteren Laufzeit nur einen kleinen Anteil davon tatsächlich benutzt, das bedeutet das von diesem Bereich durchaus ein hoher Anteil an Pages ausgelagert sein können ohne dass das negative Konsequenzen (für die Performance) hat. Das selbe trifft auch auf übliche Code-Bereiche zu, kaum ein Programm wird ständig all seinen Programm-Code ausführen.
Ich hab das schon so verstanden. Ursprünglich ging es aber um die Verwaltung des virtuellen Speichers - jeder dort vorhandene Block wird (in meiner Welt) durch mehrere Pages ausgedrückt, d.h. die Verwaltung, welche Pages nun wirklich im RAM sind, liegt anderswo. Und wenn deine Rechtschreibprüfung jedes Wort in eine eigene Page (mit eigenem malloc) stopfen will, dann wird die Anwendung halt langsam. Der aktuelle Auslagerungszustand ist davon unabhängig und ich muss Blöcke auch nicht unbedingt vollständig ausgelagert haben.

Ich sollte mal erwähnen das ich mein OS dahin trimmen möchte, das es mit 4-8MB RAM lauffähig ist. Mein Fern-Ziel ist dann mit diesem RAM auch eine GUI zu haben (Win3.11 und Win95 haben das ja auch hinbekommen und laut der Meinung vieler ist Win Schrott und MS unfähig ;) ).
Auf solch einem System wird aber kaum ein Prozess anfangen, 3GB virtuellen Speicher in 4KB-Häppchen zu alloziieren. Und selbst wenn, dann geht das eben nicht.

Zumal das bei mir die Regel ist, ein Lock (also Ints aus) wird nur da benutzt wo ich Pi mal Daumen sagen, kann das es weniger Zeit benötigt als die Zeit für 3 Thread-Wechsel.
Was passiert, wenn du (weils schnell geht) kein Lock benutzt und während der Arbeit an den Strukturen dein Thread unterbrochen wird - und bei SMP auf einer anderen CPU gleichzeitig an den Strukturen gearbeitet wird? Du solltest Locks lieber möglichst billig machen und an jeder erdenklichen Stelle ein Lock setzen, wo es potentiell krachen kann. Wenn immer nur ein Thread auf ein Lock zugreift [es also unnötig ist, z.B. Singlecore-Systeme], sollte es sogar (fast) schmerzlos sein.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 11. November 2010, 13:39
Zitat von: svenska
Damit ist es aber für allgemeine Anwendungsfälle nutzlos, sich da einen Kopf drüber zu machen. Außerdem sollte sich ein Userspace-Programm nicht um Alignments kümmern müssen (es sei denn, es geht um Performance oder Ansteuerung einer Hardware), das heißt die API - solltest du sie im Userspace bereitstellen - exponiert hardwareabhängige Dinge und das ist schlecht.
Du und deine libc ;)

Ich behaupte mal das jedes OS einen Syscall hast mit dem du Speicher allokieren kannst und das geht immer nur in vielfachen von PAGE_SIZE. Warum sollte sich auch der Kernel um alles was kleiner ist kümmern? Das macht dein malloc(), das wiederrum nutzt genau solch einen Syscall.

Wenn man nur ein kleines schnelles Programm oder einen eigenen Allocator schreiben will, wird man genau solch einen Syscall nutzen.

Zitat von: erik
Das solltest du tun. Denn tust du es nicht, so stürzt das Programm in der nachfolgenden Anweisung ab (weil der Speicher nicht mehr vorhanden ist) und wenn du dann den Task killst, wird dein Kernel die vorhandenen Ressourcen freigeben wollen, was u.U. zu einem double-free() und damit zu einer Exception/Panic im Kernel führen kann.
Ich sags mal so, wenn das App scheiße baut und seinen eigenen Code freigibt, dann ne Exception bekommt und gekillt wird, wo ist das Problem?
Zu einem double-free() kann es bei mir nicht kommen. Ich hatte auch keine Lust mir ständig nen Kopf darüber zu machen wie oft ne Page gemappt ist und ob ich noch Copy-On-Write machen muss und sowas. Deswegen habe ich für jede Page (physische Adresse) nen Counter, wie oft die gemappt ist und wenn das mehr als 1 ist wird sie nicht freigegeben und wenn der Counter 1 ist dann wird sie erst freigegeben (und ist damit auch aus der PageTable raus).

Zitat von: svenska
Anwendungen sollten niemals die OS-API direkt benutzen, weil sie das inhärent unportabel macht und außerdem dich dazu zwingt, deine Kernel-ABI (nicht nur die API!) stabil zu halten. Sonst funktionieren die Applikationen nicht mehr.
Du solltest anfangen deinen Kopf von den ganzen Linux Sachen zu befreien ;)

Ich hab nen Mikrokernel und da sollte sich die API nun wirklich nicht ändern und wenn sie es doch tut, dann so schlimm das du das auch über Libraries nicht abfangen kannst.

Das sich die API von nem monolithen öfter ändert kann ich nachvollziehen. Denn der bietet ja wesentlich mehr Services an.

Zitat von: svenska
3D-Spiele greifen auch nicht auf den Kernel direkt zu, sondern über Libraries wie OpenGL oder DirectX
Wir sind immernoch bei der Speicherverwaltung und da behaupte ich mal das die besseren Engines kein libc malloc() sondern ihren eigenen Allocator benutzen und dieser ruft direkt die Speicher-Funktionen vom OS auf (würde ich jedenfalls so machen, zwecks Performance).

Zitat von: svenska
Du solltest die Blockgröße schon von der angeforderten Größe abhängig machen.
Ich befürchte so langsam das entweder du oder ihr beide mich missverstanden haben ;) Denn das ist doch genau das was ich die ganze Zeit sage.
Wenn ich malloc(4) aufrufe und malloc() keinen freien Speicher mehr hat, dann holt sich malloc() nicht 16MB, sondern nur 4KB an Speicher.
Wenn ich aber malloc(128000) aufrufe, dann werden nicht mehrere 4KB Aufrufe gemacht sondern ein Aufruf (an den Kernel) das ich 128KB haben will.

Zitat von: svenska
Geht das bei SMP? Ints ausmachen wirkt doch nur für jede CPU...
Dazu musst du wissen das jede CPU sozusagen ihren eigenen kleinen Cache hat und deswegen reicht es wenn du die Ints ausmachst, denn in dem Moment ist die CPU ja belegt und die anderen CPUs greifen auf ihre kleinen Caches zu. Dadurch kannst du dir das Locking sparen (so fern in diesem kleinen Cache was drin ist).

Zitat von: svenska
Also braucht dein Kernel keinen dynamisch reservierten Speicher
Doch, aber wenn du einen Syscall machst und der Kernel kann keinen Speicher mehr bekommen, dann wird halt ein Fehler zurück gegeben (vergleichbar mit malloc()= NULL).

Zitat von: svenska
Und selbst wenn, dann geht das eben nicht.
Mein reden ;)

Zitat von: svenska
Was passiert, wenn du (weils schnell geht) kein Lock benutzt und während der Arbeit an den Strukturen dein Thread unterbrochen wird - und bei SMP auf einer anderen CPU gleichzeitig an den Strukturen gearbeitet wird? Du solltest Locks lieber möglichst billig machen und an jeder erdenklichen Stelle ein Lock setzen, wo es potentiell krachen kann. Wenn immer nur ein Thread auf ein Lock zugreift [es also unnötig ist, z.B. Singlecore-Systeme], sollte es sogar (fast) schmerzlos sein.
Ich hätte wahrscheinlich noch hinzufügen sollen, das wenn kein Lock benutzt wird, eine Semaphore benutzt wird, aber die lohnt sich halt erst, wenn der kritische Bereich wirklich lange dauert (meine Meinung und ich weiß das erik das anders sieht).
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 11. November 2010, 14:59
Ich behaupte mal das jedes OS einen Syscall hast mit dem du Speicher allokieren kannst und das geht immer nur in vielfachen von PAGE_SIZE. Warum sollte sich auch der Kernel um alles was kleiner ist kümmern? Das macht dein malloc(), das wiederrum nutzt genau solch einen Syscall.
Richtig. Deswegen kann ich trotzdem ein malloc(12) machen und du musst es im Kernel auf Alignments ausrichten.

Wenn man nur ein kleines schnelles Programm oder einen eigenen Allocator schreiben will, wird man genau solch einen Syscall nutzen.
Richtig. Der Hauptnutznießer dieser API sind also Anwendungen, welche ganz genau die Interna des Systems kennen und nutzen wollen, um Performance zu schinden - und die Libraries, die ein allgemeineres Interface für Anwendungen bereitstellen. Letztere werden wahrscheinlich häufiger genutzt.

Zitat von: svenska
Das solltest du tun. Denn tust du es nicht, so stürzt das Programm in der nachfolgenden Anweisung ab (weil der Speicher nicht mehr vorhanden ist) und wenn du dann den Task killst, wird dein Kernel die vorhandenen Ressourcen freigeben wollen, was u.U. zu einem double-free() und damit zu einer Exception/Panic im Kernel führen kann.
Ich sags mal so, wenn das App scheiße baut und seinen eigenen Code freigibt, dann ne Exception bekommt und gekillt wird, wo ist das Problem?
Da ist kein Problem. Räumst du nicht auf, wenn ein Task endet/gekillt wird? Weil dann müsstest du alle Pages unmappen und den [physischen] RAM freigeben, der von diesem Task genutzt wurde. Das geht aber nur, wenn das Programm seinen Code eben nicht freigegeben hat (oder es abgefangen wurde).

Zu einem double-free() kann es bei mir nicht kommen. Ich hatte auch keine Lust mir ständig nen Kopf darüber zu machen wie oft ne Page gemappt ist und ob ich noch Copy-On-Write machen muss und sowas. Deswegen habe ich für jede Page (physische Adresse) nen Counter, wie oft die gemappt ist und wenn das mehr als 1 ist wird sie nicht freigegeben und wenn der Counter 1 ist dann wird sie erst freigegeben (und ist damit auch aus der PageTable raus).
Und wenn der Counter 0 ist, weil die Page schon vorher freigegeben wurde (weil das Programm sie eben selbst freigegeben hat und der Kernel das nochmal möchte)?

Das war nur ein Ansatz, ob es bei dir auftreten kann, weiß ich nicht.

Ich hab nen Mikrokernel und da sollte sich die API nun wirklich nicht ändern und wenn sie es doch tut, dann so schlimm das du das auch über Libraries nicht abfangen kannst.
Hä? Die API/ABI eines Kernels gibt an, wie Prozesse mit dem Kernel reden. Das betrifft damit auch jeden Treiberprozess.

Das sich die API von nem monolithen öfter ändert kann ich nachvollziehen. Denn der bietet ja wesentlich mehr Services an.
Auch da ändern sich nicht sämtliche APIs auf einen Schlag, sondern nur die des betreffenden Services. Die Aufgabe von Libraries ist es, Unterschiede in den Systemen wegzuabstrahieren, sodaß das Anwendungsprogramm sich nicht drum kümmern muss, auf welchem OS es läuft - wenn es das nicht will. Die Zahl der Anwendungen, die sich darum kümmern, ist gering.

Zitat von: svenska
3D-Spiele greifen auch nicht auf den Kernel direkt zu, sondern über Libraries wie OpenGL oder DirectX
Wir sind immernoch bei der Speicherverwaltung und da behaupte ich mal das die besseren Engines kein libc malloc() sondern ihren eigenen Allocator benutzen und dieser ruft direkt die Speicher-Funktionen vom OS auf (würde ich jedenfalls so machen, zwecks Performance).
Ich würde eher Funktionen wie memcpy() optimieren, aber davon abgesehen: Warum? Eher richte ich meine Zugriffe auf malloc() und Freunde so aus, wie das OS sie optimal verarbeitet. Schließlich soll mein App1.0 unter Kernel1.0 lauffähig sein, aber exakt das gleiche Binary auch unter Kernel2.0 ... und das geht nur, wenn ich eine Schicht dazwischen habe, die mir die Unterschiede zwischen Kernel1.0 und Kernel2.0 wegabstrahiert. Das ist üblicherweise die libc/libX11/etc.

Und die stellt auch optimierte Funktionen bereit.

Zitat von: svenska
Geht das bei SMP? Ints ausmachen wirkt doch nur für jede CPU...
Dazu musst du wissen das jede CPU sozusagen ihren eigenen kleinen Cache hat und deswegen reicht es wenn du die Ints ausmachst, denn in dem Moment ist die CPU ja belegt und die anderen CPUs greifen auf ihre kleinen Caches zu. Dadurch kannst du dir das Locking sparen (so fern in diesem kleinen Cache was drin ist).
Gut, das sind jetzt Details, aber trotzdem könnte eine andere CPU ja auf die gleichen Datenstrukturen zugreifen...

Zitat von: svenska
Also braucht dein Kernel keinen dynamisch reservierten Speicher
Doch, aber wenn du einen Syscall machst und der Kernel kann keinen Speicher mehr bekommen, dann wird halt ein Fehler zurück gegeben (vergleichbar mit malloc()= NULL).
Schon klar. Wie reagiert der Kernel aber, wenn diese Speicheranforderung essentiell wichtig war für den Fortbestand des Systems? Da sollte man durchaus Sicherheiten einbauen. In meiner Welt zumindest.

Zitat von: svenska
Und selbst wenn, dann geht das eben nicht.
Mein reden ;)
Schlecht zitiert. Ein "es geht nicht" ist im Userspace akzeptabel, im Kernel hingegen nicht. Mit "Kernel" schließe ich auch explizit Hardwaretreiber mit ein. Sinn eines stabilen OSs ist, dass ein Userspaceprogramm unter keinen Umständen den Kernel gegen eine Wand fahren kann. Geht das, hast du einen DoS-Exploit.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 11. November 2010, 15:41
Zitat von: svenska
Räumst du nicht auf, wenn ein Task endet/gekillt wird? Weil dann müsstest du alle Pages unmappen und den [physischen] RAM freigeben, der von diesem Task genutzt wurde. Das geht aber nur, wenn das Programm seinen Code eben nicht freigegeben hat (oder es abgefangen wurde).
Doch ich räume auf.

Das Problem welches du ansprichst wird bei mir von meinem Counter abgefangen. Meine vmmMap() erhöht den Counter und meine vmmUnmap() verringert den Counter.
Ist der Counter > 1 (bevor der Counter verringert wurde) dann wird die physische Adresse aus der PageTable gelöscht und kann nicht mehr freigegeben werden.
Ist der Counter = 1 (bevor der Counter verringer wurde) dann wird die physische Adresse in der PageTable stehen gelassen.
Mein Code der dann die ganzen Pages dann an den PMM weitergibt, der guckt ob der Wert in der PageTable ungleich 0 ist und gibt die Page nur dann an den PMM weiter.

Zitat von: svenska
Eher richte ich meine Zugriffe auf malloc() und Freunde so aus, wie das OS sie optimal verarbeitet. Schließlich soll mein App1.0 unter Kernel1.0 lauffähig sein, aber exakt das gleiche Binary auch unter Kernel2.0 ... und das geht nur, wenn ich eine Schicht dazwischen habe, die mir die Unterschiede zwischen Kernel1.0 und Kernel2.0 wegabstrahiert. Das ist üblicherweise die libc/libX11/etc.
Naja, erstmal ist das wieder auf Linux bezogen (und auch noch direkt für X11, das ist nicht wirklich portabel!), für Windows sollte das nicht zutreffen, bzw. ist es ja dort so das nicht alle Programme mit einer neueren Version laufen.
Und warum soll ich das nicht in einer eigenen Library abstrahieren. Der Punkt ist doch das du nie wissen kannst was für ein Allocator hinter malloc() steckt und wie effizient der ist und wenn du deinen eigenen schreibst kannst du alle Sachen die du schon weißt und die für die Speicherverwaltung wichtig sind, mit einfließen lassen und das geht bei malloc() halt nicht.
Aber eigentlich ist das auch egal, weil es nichts zum Thema beiträgt ;) Wir sollten vllt mal eine Multi-Platform Engine angucken um es besser zu wissen.

Zitat von: svenska
Gut, das sind jetzt Details, aber trotzdem könnte eine andere CPU ja auf die gleichen Datenstrukturen zugreifen...
Nein! Jede CPU greift nur auf die Daten zu die zu ihrer CPU ID gehören und das kann ich garantieren weil ich den Code geschrieben habe. Denn wenn du jetzt argumentieren würdest dass das ja anderen Code nicht interessiert, dann gilt das gleiche für Locks. Nur weil du die benutzt müssen die andere nicht auch nutzen.

Zitat von: svenska
Wie reagiert der Kernel aber, wenn diese Speicheranforderung essentiell wichtig war für den Fortbestand des Systems? Da sollte man durchaus Sicherheiten einbauen.
Ich muss dir da schon recht geben, sowas wie nen Sicherheitspuffer sollte man zumindest im Kernel einbauen.
Denn bei mir ist es noch möglich eine relativ "lustige" Situation herbeizuführen. Es kann nämlich passieren das du keinen Speicher mehr freigeben kannst, weil kein Speicher mehr vorhanden ist ;)
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 11. November 2010, 17:03
Hallo,


Zitat von: erik
aber wen stört das?
Mich ;)
Tja, pech! ;)

Ich sollte mal erwähnen das ich mein OS dahin trimmen möchte, das es mit 4-8MB RAM lauffähig ist. Mein Fern-Ziel ist dann mit diesem RAM auch eine GUI zu haben (Win3.11 und Win95 haben das ja auch hinbekommen und laut der Meinung vieler ist Win Schrott und MS unfähig ;) ).
Mit 4 bis 8 MB aus zu kommen ist schon ein ziemlich schwieriges Ziel. Vor allem ein hochperformanter SlabAllocator dürfte dem nicht gerade zuträglich sein weil er ja möglichst große Slabs will. Ich denke unter 32 bis 64 MB dürfte auch bei meinem OS nicht viel gehen, was vor allem am großzügigen SlabAllocator und daran das eine LDT 512 kBytes belegt liegt. Trotzdem bin ich der Meinung das, wenn mein OS bei 8GB RAM mit etlichen Programmen unter Action ist, es immer noch relativ sparsam mit RAM umgeht. Mein Designziel ist >1GB.

Ja wir reden vom selben Dokument.
Dann ist gut.


Du solltest die Blockgröße schon von der angeforderten Größe abhängig machen. Ein "new int" sollte also maximal eine 4KB-Page zurückgeben, ein "new buf[16M]" sollte 4 Pages je 4MB zurückgeben...
Für ein "new int" wird doch hoffentlich nicht eine eigene Page zurück gegeben sondern eher im Heap was passendes gesucht. Und wenn im Heap nichts mehr frei ist dann sollte die malloc-Implementierung auch nicht nur ne einzelne 4k-Page holen sondern vielleicht 128kB (oder ne ähnliche Größe) am Stück. Nur wenn malloc mehr als (sagen wir mal) 256kB am Stück zurück geben soll ist es vernünftig das direkt vom OS zu holen.

.....Würde also so lange weitermachen, bis der reale Speicher bis aufs letzte Byte gefüllt ist.
Nur wenn er mit root-Rechten läuft, ansonsten bekommt er bereits vorher keinen neuen Speicher mehr (also nur noch NULL).

Was aber, wenn du übers Netzwerk swapst, dein TCP/IP-Stack aus Speichermangel größtenteils auf Platte liegt, der RAM randvoll ist und ein seltsames Paket im TCP/IP-Stack landet und verarbeitet werden muss? Du müsstest erstmal Teile deines TCP/IP-Stacks wieder in den RAM laden, wozu du Zeugs auslagern müsstest, was du nicht kannst, weil es den TCP/IP-Stack erfordert.
Erstmal finde ich ist TCP/IP ein wichtiger Treiber und sollte daher gar nicht ausgelagert werden (so fett wird der auch nicht sein das sich das lohnt) und außerdem schreibst Du da eher von komplexen Abhängigkeiten zwischen den Komponenten eines OS und die können noch ganz andere Probleme machen als nur knappen Speicher.

Naja, ich als Anwendungsprogrammierer erwarte schon von einem Flag, dass es exakt das tut, was ich möchte und nichts anderes.
Also ich erwarte das Flags genau das bewirkten was dokumentiert ist und nicht das was ich da gerne hinein interpretieren möchte. Wenn in der Doku steht das dieses Flag dazu dient dem OS zu sagen was ich machen will damit sich das OS möglichst optimal verhalten kann, falls es das auch wirklich kann, dann ist das Okay für mich. Es gibt ne Menge derartiger kann-Flags die eben dem OS sagen was für das Programm optimal wäre aber das OS nicht zu einem bestimmten Verhalten verpflichten. Solange das OS mir den Speicher, den ich in meinem virtuellen Adressraum haben will, auch wirklich zusichern kann sollte der malloc-Syscall auch erstmal funktionieren. Ob die Pages alle sofort oder erst später eingeblendet werden macht ja funktional keinen Unterschied sondern beeinflusst nur die Performance.

Daher würde ich eine Automatik machen (MAP_NOW wenn <16M, MAP_LAZY wenn größer). Ich bin der Meinung, dass kleine Blöcke sofort benutzt werden und große Blöcke nicht/teilweise.
Wenn wenn der Programmierer vom nächsten Programm Deine Meinung nicht kennt? Solche Automatismen haben immer den Nachteil das sie eben nie wirklich wissen was los ist sondern nur vermuten können und Vermutungen sind auch mal falsch (liegt in der Natur der Sache). Also da bevorzuge ich ein Flag das nur ne Empfehlung ist als das ich einen Automatismus haben möchte der von irgend welchen wagen Annahmen und Meinungen abhängt.

Niemals so, dass ich ein Flag explizit angebe und es dann ignoriert wird.
Du bist also der Meinung "lieber einen Syscall der nichts macht obwohl er eigentlich könnte wenn auch nicht ganz optimal als einen Syscall der versucht sich so gut wie möglich zu verhalten aber das nicht zusichert"? Diese Einstellung kann ich nicht nachvollziehen.

Sehe ich etwas anders, denn die Größe der "letzten Reserven" hängt von root ab. Wenn er nur "bash", "ps" und "kill" nutzen möchte, mag das sein - aber eventuell möchte er erstmal seine X11-Umgebung mit KDE starten, um darin nachzuschauen, was überhaupt kaputt ist...
Dann macht man die Größe der letzten Reserve eben konfigurierbar, entscheidend (für die Stabilität des OS) ist das es überhaupt eine gibt.
Ein root der bei Speicherknappheit erst mal KDE starten will ist IMHO auch selber schuld wenn das System mit Rums gegen die Wand fährt. ;)

Daher bin ich der Meinung, dass - sofern im Kernel kein Speicher mehr da ist - der Prozess mit dem größten Speicherverbrauch gekillt werden sollte.
Also spätestens wenn Du mal nen halben Arbeitstag auf das Ende einer Simulation wartest und dann kurz vor Schluss das Simulationsprogramm noch mal etwas Speicher braucht und dann, nur weil es am meisten Speicher braucht, vom OS gekillt wird entwickelst Du eine andere Meinung. Das ModelSim kann wimre mit NULL von malloc so umgehen das es fragt ob es noch mal malloc versuchen soll oder ob es die Simulation abbrechen soll (und man wenigstens den bereits fertigen Teil der Simulationsergebnisse noch verwenden kann). In der Zeit wo das Fragefenster wartet kann man auch schnell noch ein paar andere Programme schließen und danach erst mal auf den "nochmal"-Knopf drücken. Ein OS das einfach nach belieben irgendwelche Prozesse killt möchte ich nicht haben, lieber soll es keinen unprivilegierten Programmen neuen Speicher geben so das diese Programme die Möglichkeit haben angemessen zu reagieren.

Allerdings ist ein Programm, welches so viel RAM für sich in Anspruch nimmt, dass es das System nahezu lahmlegt für den Rechner dann ohnehin ungeeignet und man sollte das Programm anpassen oder RAM nachstecken.
Das ist ja prinzipiell richtig aber sollte ich als User nicht auch die Möglichkeit haben erst mal andere Programme weg zu räumen bevor das eine Programm, das meinen Rechner bis zum Anschlag auslastet, gekillt wird und diesem Programm die Gelegenheit geben doch noch fertig zu werden?

Ein System, was konstant am Anschlag lebt, sollte man überdenken.
Das ist immer eine Frage der Rahmenbedingungen. Ich als Privatperson werde mir nicht einen neuen Rechner kaufen nur weil ich einmal im Monat ein Programm benutze das meinen Rechner bis an die Grenze bringt. Solange es doch noch irgendwie geht möchte ich diese Geldausgabe lieber vermeiden. Erst wenn das Programm dann doch die Grenze überschreitet so das ich keine Chance habe das durchlaufen zu lassen bin ich wirklich gezwungen einen neuen Rechner zu kaufen, ich erwarte von meinem OS das es mir ermöglicht es wenigstens zu versuchen ob das Programm irgendwie zum Abschluss kommt.

Wenn du allgemeine Treiber nutzt, ist das nicht möglich; deren Speicheranforderung kann sich stark unterscheiden, je nach Auslastung.
Durch was schwankt der Speicherverbrauch eines Treibers so stark? Wenn ich jetzt mal an einen SATA-AHCI-Treiber denke dann bekommt der maximal wie viele Jobs gleichzeitig? Sagen wir mal 100 und nehmen für jeden Job mal 4 kB zur internen Verwaltung (ist das zu wenig?) dann schwankt der RAM-Bedarf dieses Treiber um maximal 400kB. Wo ist da das Problem? Das sollte die malloc-Implementierung doch recht gut abfangen können so das der Treiber normalerweise gar keine Speicheranforderungen an den Kernel schickt.

Außerdem willst du wirklich 25% des RAMs für root reservieren? Das stelle ich mir grad in einem Rechenzentrum mit 64 GB RAM/Node vor...
Die 25% waren doch nur mal so dahingeschrieben, es wäre natürlich besser wenn man das konfigurieren kann so das root eben klar festlegen kann was er im Notfall als Reserve wirklich braucht (zuzüglich einen gewissen Überhang fürs OS).

Ich glaube Du hast nicht ganz verstanden was ich eigentlich ausdrücken wollte. Es geht mir nicht um Datenbanken o.ä. sondern um ein Szenario in dem ein Programm einen großen Bereich an virtuellem Speicher benötigt und diesen auch mit echten Daten befüllt aber dann zur weiteren Laufzeit nur einen kleinen Anteil davon tatsächlich benutzt, das bedeutet das von diesem Bereich durchaus ein hoher Anteil an Pages ausgelagert sein können ohne dass das negative Konsequenzen (für die Performance) hat. Das selbe trifft auch auf übliche Code-Bereiche zu, kaum ein Programm wird ständig all seinen Programm-Code ausführen.
Ich hab das schon so verstanden.
Sicher? Diesen Eindruck habe ich irgendwie nicht.

Ursprünglich ging es aber um die Verwaltung des virtuellen Speichers - jeder dort vorhandene Block wird (in meiner Welt) durch mehrere Pages ausgedrückt, d.h. die Verwaltung, welche Pages nun wirklich im RAM sind, liegt anderswo. Und wenn deine Rechtschreibprüfung jedes Wort in eine eigene Page (mit eigenem malloc) stopfen will, dann wird die Anwendung halt langsam. Der aktuelle Auslagerungszustand ist davon unabhängig und ich muss Blöcke auch nicht unbedingt vollständig ausgelagert haben.
Was hat das alles mit vollständig belegten aber nur dünn benutzen virtuellem Speicher zu tun? Wieso sollte ich für eine Rechtschreibprüfung jedes Wort in eine eigene Page legen?


Wenn man nur ein kleines schnelles Programm oder einen eigenen Allocator schreiben will, wird man genau solch einen Syscall nutzen.
Dann muss man sicher aber auch mit der exakten Funktionsweise des OS beschäftigen und sollte auch nicht jeden Speicherbedarf in 4kB-Häppchen decken sondern sich überlegen wie das Programm den Speicher benutzen möchte und in entsprechender Größe die Blöcke holen. Da fällt mir ein: willst Du bei Deinem Speicher-Mapping-Syscal für den virtuellen Speicher vorgeben können wo hin der Kernel die neuen Pages in den virtuellen Adressraum des Prozesses legen soll? Wenn ja dann lohnt es sich eventuell schon den virtuellen Speicher, der von der malloc-Implementierung für den Heap benutzt wird, in sehr kleinen aber zusammenhängenden Schritten zu vergrößern.

Ich hab nen Mikrokernel und da sollte sich die API nun wirklich nicht ändern
Warum nicht? Kann es nicht mal sein das Du zu einem bereits bestehenden Syscall noch ein Parameter hinzufügen möchtest?

und wenn sie es doch tut, dann so schlimm das du das auch über Libraries nicht abfangen kannst.
Trifft das auch auf Programme zu die nur von der libc (kein POSIX oder sonstwas) abhängig sind? ;)
Es ist ja gerade die Aufgabe von Librarys sowas abzufangen und das funktioniert oft sogar über viele OSe hinweg, also warum soll das nicht zwischen verschiedenen Versionen eines OS funktionieren?

Wir sind immernoch bei der Speicherverwaltung und da behaupte ich mal das die besseren Engines kein libc malloc() sondern ihren eigenen Allocator benutzen und dieser ruft direkt die Speicher-Funktionen vom OS auf (würde ich jedenfalls so machen, zwecks Performance).
Die Hersteller der besseren Engines kaufen sich eine ordentlich hochperformante malloc-Implementierung (und bleiben in ihrem Code bei malloc).

das wenn kein Lock benutzt wird, eine Semaphore benutzt wird, aber die lohnt sich halt erst, wenn der kritische Bereich wirklich lange dauert (meine Meinung und ich weiß das erik das anders sieht).
Dann musst Du erst mal genau erklären was für Dich der Unterschied zwischen einem Lock und einer Semaphore ist. In meinem Kernel wird es nur simple Locks und eventuell Reader/Writer-Locks (obwohl mir da jetzt im Moment gar kein Anwendungsfall für einfällt) geben.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 11. November 2010, 17:22
Zitat von: erik
Mit 4 bis 8 MB aus zu kommen ist schon ein ziemlich schwieriges Ziel.
Bisher nicht ;) Auch sollte der Speicher für ein Text-Mode OS reichen (was die meisten Hobby OSs ja sind).

Die bei MS müssen das ja auch irgendwie geschafft haben, dann schaffe ich das auch irgendwann ;)

Zitat von: erik
Vor allem ein hochperformanter SlabAllocator dürfte dem nicht gerade zuträglich sein weil er ja möglichst große Slabs will.
Wie kommst du darauf? Ich habe den SlabAllocator so verstanden und implementiert, das er versucht möglichst wenig Verschnitt zu haben und das mind. 8 Objekte in einem Slab drin sind bzw. sind mind. 8 Objekte in einem Slab wird der Slab nicht weiter vergrößert.

Zitat von: erik
Und wenn im Heap nichts mehr frei ist dann sollte die malloc-Implementierung auch nicht nur ne einzelne 4k-Page holen sondern vielleicht 128kB (oder ne ähnliche Größe) am Stück. Nur wenn malloc mehr als (sagen wir mal) 256kB am Stück zurück geben soll ist es vernünftig das direkt vom OS zu holen.
Das ist genau der Punkt wo ich am meisten von "normalen" OSs abweiche. Denn was bitte ist ein Heap oder besser was ist an dem anders, als wenn du das OS nach Speicher fragst?

Bei mir gibt es den klassischen Heap nicht, sondern du fragst immer beim OS nach Speicher und bekommst nen Pointer zurück.

Zitat von: erik
Da fällt mir ein: willst Du bei Deinem Speicher-Mapping-Syscal für den virtuellen Speicher vorgeben können wo hin der Kernel die neuen Pages in den virtuellen Adressraum des Prozesses legen soll?
Theoretisch möglich wäre es (habe eigentlich schon vor, noch ne Funktion mapAllocAt(uint32t start, uint32t size) zu schreiben), aber ich sehe noch keinen Bedarf dafür.

Kannst du mir denn ein paar (ok, es reicht auch einer) Gründe nennen (die ich nachvollziehen kann bzw. einsehe) warum man sowas unbedingt braucht?

Zitat von: erik
Warum nicht? Kann es nicht mal sein das Du zu einem bereits bestehenden Syscall noch ein Parameter hinzufügen möchtest?
Ich wüsste halt im Moment nicht was sich bei den paar Syscalls ändern sollte, bzw. bis ich soweit bin, das ich wirkliche Anwendungen auf meinem OS laufen habe, sollte die API stabil sein.

Zitat von: erik
Trifft das auch auf Programme zu die nur von der libc (kein POSIX oder sonstwas) abhängig sind?
Warum sollte das da nicht zutreffen. Nehmen wir mal an du hast nen Syscall da irgendetwas macht und du änderst hin dahingehend, das noch ein size-Parameter dazu kommt und den kannst du auch in der Library nicht rausbekommen, sondern den Wert weiß nur der User, wie willst du das wegabstrahieren?

Zitat von: erik
Die Hersteller der besseren Engines kaufen sich eine ordentlich hochperformante malloc-Implementierung (und bleiben in ihrem Code bei malloc).
Also ich persönlich würde ja sowas wie nen SlabAllocator oder wenn es wirklich was bringt für bestimmte Objekte sogar nen eigenen Allocator benutzen.

Zitat von: erik
Dann musst Du erst mal genau erklären was für Dich der Unterschied zwischen einem Lock und einer Semaphore ist. In meinem Kernel wird es nur simple Locks und eventuell Reader/Writer-Locks (obwohl mir da jetzt im Moment gar kein Anwendungsfall für einfällt) geben.
Ich muss dringend an meinem Ausdruck arbeiten ;)

Mit Lock ist ne Spinlock oder halt ne Read-Write-Lock (was im Endeffekt auch ne Spinlock ist) gemeint, wo du busy-waiting machst und ne Semaphore wirst du als Thread schlafen gelegt und irgendwann wieder aufgeweckt.
Ich habe bisher auch noch keine Semaphore bei mir verwendet und denke auch nicht das ich eine brauchen werde, aber man kann ja nie wissen ;)
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 11. November 2010, 20:34
Hallo,


Zitat von: erik
Mit 4 bis 8 MB aus zu kommen ist schon ein ziemlich schwieriges Ziel.
Bisher nicht
Dann viel Glück dabei. Ließ mal ab hier (http://forum.lowlevel.eu/index.php?topic=2402.msg27387#msg27387).

Die bei MS müssen das ja auch irgendwie geschafft haben, dann schaffe ich das auch irgendwann ;)
Win95 lief deswegen noch mit 4 MB weil es zu großen Teilen noch auf 16Bit-Code basierte und der (und auch dessen Daten) ist kleiner. Bei Win98 war wimre 8 MB das absolute Minimum um überhaupt den Desktop zu sehen von arbeiten kann man da noch nicht reden. Ich bin ehrlich der Meinung wenn Dein OS mindestens 32 MB will dann geht das völlig in Ordnung. Unter nem Pentium machts doch Dein OS auch nicht.

Wie kommst du darauf? Ich habe den SlabAllocator so verstanden und implementiert, das er versucht möglichst wenig Verschnitt zu haben und das mind. 8 Objekte in einem Slab drin sind bzw. sind mind. 8 Objekte in einem Slab wird der Slab nicht weiter vergrößert.
Also ich hab das SlabAllocator-Konzept eher so verstanden das man sich bemüht möglichst große Slabs zu benutzen um eben möglichst selten die normale page-basierte Speicherverwaltung zu belästigen. Ich möchte für meinem Kernel-Heap die Slabs 512 kBytes groß machen. Schau Dir noch mal dieses Magazin-Zeugs an und überlege wie viele Objekte Du schon allein brauchst um genügend Magazine (wo jedes ja wohl mindestens 8 Objekte aufnimmt) für 8 CPUs zu befüllen. Variable Magazin-Größe möchte ich nicht umsetzen, das ist mir zu viel Aufwand, ich denke eine feste Größe von 16 Objekten pro Magazin ist eine recht gute Basis.

Das ist genau der Punkt wo ich am meisten von "normalen" OSs abweiche. Denn was bitte ist ein Heap oder besser was ist an dem anders, als wenn du das OS nach Speicher fragst?
Der Heap ist dafür da Objekte mit beliebiger Größe möglichst optimal zu verwalten. Der, mit Pages arbeitende, VMM des OS-Kernels kann das nicht.

Bei mir gibt es den klassischen Heap nicht, sondern du fragst immer beim OS nach Speicher und bekommst nen Pointer zurück.
Wirklich? Bedeutet das wenn ich "new int" mache das ich dann ein komplette Page nur für die 4 Bytes bekomme (und 4092 Byte verschwende)? Was ist wenn ich extrem oft "new int" (oder für andere recht kleine Objekte) mache? Dann entsteht doch ein unheimlicher Verschnitt.

Zitat von: erik
willst Du bei Deinem Speicher-Mapping-Syscal für den virtuellen Speicher vorgeben können wo hin der Kernel die neuen Pages in den virtuellen Adressraum des Prozesses legen soll?
Theoretisch möglich wäre es (habe eigentlich schon vor, noch ne Funktion mapAllocAt(uint32t start, uint32t size) zu schreiben), aber ich sehe noch keinen Bedarf dafür.

Kannst du mir denn ein paar (ok, es reicht auch einer) Gründe nennen (die ich nachvollziehen kann bzw. einsehe) warum man sowas unbedingt braucht?
Also wirklich brauchen tut man das IMHO nicht aber es macht die malloc-Implementierung leichter wenn man den Bereich im virtuellen Adressraum, der für den Heap benutzt wird, einfach so vergrößern kann und der Heap sich nicht über mehrere zusammenhanglose Stücken im virtuellen Adressraum verteilt. Im Prinzip habe ich das mit meinen Segmenten ja auch (nur das ich 32k solcher virtuellen Adressräume hab), ich kann jedes Segment beliebig vergrößern und auch verkleinern. Der Vorteil bei Deinem Flat-Memory-System ist das Du auch mitten aus diesem Bereich kleine Stücken wieder rauswerfen kannst, wogegen ich meine Segmente nur vorne und hinten beschneiden kann.

Ich wüsste halt im Moment nicht was sich bei den paar Syscalls ändern sollte
Das weiß man nie vorher. Aber ich bin mir sicher das auch Dein Kernel nicht von Anfang an perfekt ist, so wie jeder andere auch.

bzw. bis ich soweit bin, das ich wirkliche Anwendungen auf meinem OS laufen habe, sollte die API stabil sein.
Gerade erst dann wenn Du wirklich anfängst die ersten richtigen Programme laufen zu lassen merkst Du was alles noch nicht so ist wie es die Praxis gerne hätte und dann kommen die Änderungswünsche schneller als Die lieb ist. Meißle Deine Syscall-API lieber noch nicht in Stein, das hat mindestens Zeit bis die ersten größeren Programme ordentlich laufen, mach Dir lieber ein gutes Konzept wie Du die Syscall-API später mal bequem ändern kannst ohne das Du an irgend einer Stelle vergisst was anzupassen.

Zitat von: erik
Trifft das auch auf Programme zu die nur von der libc (kein POSIX oder sonstwas) abhängig sind?
Warum sollte das da nicht zutreffen. Nehmen wir mal an du hast nen Syscall da irgendetwas macht und du änderst hin dahingehend, das noch ein size-Parameter dazu kommt und den kannst du auch in der Library nicht rausbekommen, sondern den Wert weiß nur der User, wie willst du das wegabstrahieren?
Sowas gibt es in der Standard-libc nicht. Alle Dienste die die libc laut C-Standard anbietet muss auch Deine libc-Implementierung können egal wie die Syscalls aussehen. Auf allen anderen OSen funktioniert das auch (die libc hat nach oben hin immer die selbe API), warum sollte das auf Deinem OS anders sein?

Zitat von: erik
Die Hersteller der besseren Engines kaufen sich eine ordentlich hochperformante malloc-Implementierung (und bleiben in ihrem Code bei malloc).
Also ich persönlich würde ja sowas wie nen SlabAllocator oder wenn es wirklich was bringt für bestimmte Objekte sogar nen eigenen Allocator benutzen.
Dann schau Dir doch mal an wie die kommerziellen Heap-Librarys so arbeiten, da steckt oft ein optimierter SlabAllocator drin. Übrigens sind diese Librarys nicht portabel (was manchmal schon an verschiedenen Windows-Versionen scheitert) sondern der Hersteller verkauft Dir für jedes OS (unde jede Version davon) eine extra angepasste Library-Version (damit eben bei jedem OS die spezifischen Gegebenheiten optimal ausgereizt werden können).

Mit Lock ist ne Spinlock oder halt ne Read-Write-Lock (was im Endeffekt auch ne Spinlock ist) gemeint, wo du busy-waiting machst und ne Semaphore wirst du als Thread schlafen gelegt und irgendwann wieder aufgeweckt.
Aha, gut zu wissen, bevor wir später an einander vorbei schreiben. Semaphoren kann es in meinem OS-Kernel nicht geben, weil ununterbrechbar, die gibt es bei mir nur auf User-Mode-Ebene und müssen per Syscall vom Kernel angeboten werden (aber das kommt erst später).


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 11. November 2010, 21:04
Zitat von: erik
Ich bin ehrlich der Meinung wenn Dein OS mindestens 32 MB will dann geht das völlig in Ordnung. Unter nem Pentium machts doch Dein OS auch nicht.
Ich habe gerade mal damn small linux ausprobiert und das läuft (halbwegs "normal") ab 24MB (waren es glaub ich).
Es ist sogar schwierig für mich mein OS mit nur 4MB zu testen (ist das momentane Minimum) und das geht auch nur in einem Emulator auf richtiger Hardware habe ich dann 8MB und die sind auch das Ziel.
Ich weiß auch gar nicht wo ich im Moment schon bin.

Zitat von: erik
Also ich hab das SlabAllocator-Konzept eher so verstanden das man sich bemüht möglichst große Slabs zu benutzen um eben möglichst selten die normale page-basierte Speicherverwaltung zu belästigen.
Ich hatte mir auch mal direkt den Code angesehen und da war es noch so das sie versucht haben mind.8 Objekte in einen Slab zu bekommen und den Verschnitt unter einer bestimmte Grenze gehalten haben.

Zitat von: erik
Ich möchte für meinem Kernel-Heap die Slabs 512 kBytes groß machen.
Da passen ja von meiner AVL-Struktur 26214 Objekte rein. Aber du bist da ja mit deinen Segmenten auch ein wenig "eingeschränkt" (muss jetzt kein Nachteil sein, jeder hat seine eigenen Ziele).

Zitat von: erik
Schau Dir noch mal dieses Magazin-Zeugs an und überlege wie viele Objekte Du schon allein brauchst um genügend Magazine (wo jedes ja wohl mindestens 8 Objekte aufnimmt) für 8 CPUs zu befüllen. Variable Magazin-Größe möchte ich nicht umsetzen, das ist mir zu viel Aufwand, ich denke eine feste Größe von 16 Objekten pro Magazin ist eine recht gute Basis.
Ich habe dieses Konzept komplett anders implementiert. Bei mir ist ein Magazin immer 4 Objekte groß und jede CPU hat 2 Magazine, ein volles und ein leeres.
Sind beide leer wird eins wieder voll gemacht und sind beide voll wird eins leer gemacht (dafür werden jeweils die "normalen" SlabAllocator Funktionen aufgerufen, es geht von daher an dieser Stelle noch optimaler, aber es läuft erstmal).

Auch die Objekt-Initialisierung findet bei mir nicht statt wenn die Objekte in den Cache/Slab kommen, sondern wenn das Objekt rausgegeben wird (das selbe mit dem Dekonstruktor, da dann wenn das Objekt zurück gegeben wird).
Ich nutze diese Initialisierung im Moment sowieso nur dazu den Speicher des Objekts zu nullen.

Zitat von: erik
Der Heap ist dafür da Objekte mit beliebiger Größe möglichst optimal zu verwalten. Der, mit Pages arbeitende, VMM des OS-Kernels kann das nicht.
Richtig, aber der Heap wird auch nur um ein vielfaches der Page-Größe vergrößert und darum kümmert sich ja malloc().

Zitat von: erik
Wirklich? Bedeutet das wenn ich "new int" mache das ich dann ein komplette Page nur für die 4 Bytes bekomme (und 4092 Byte verschwende)? Was ist wenn ich extrem oft "new int" (oder für andere recht kleine Objekte) mache? Dann entsteht doch ein unheimlicher Verschnitt.
Nope. Wie gesagt ich habe schon ein malloc(), aber eben kein Heap der zusammenhängend ist und nach oben wächst.

Ich habe mit diesem Heap-Konzept so meine Probleme. Eine normale Speicheraufteilung sieht doch so aus, dass der Stack von Oben (oberes Ende des Adressraums) nach unten wächst und der Heap vom Ende des Programm Images nach Oben wächst.
Wo werden dann irgendwelche Libraries hingemappt und was ist wenn man mehrere Threads hat, die ja auch ihre Stacks brauchen und was ist mit den Lücken im Adressraum die ja irgendwann entstehen? Können die dann auch noch benutzt werden oder sind die weg?

Da ich das alles nicht so richtig nachvollziehen kann, habe ich mich dafür entschieden, das der "Heap" halt zerstückelt wird und das bekommt was frei ist. Im Endeffekt wächst alles bei mir nach Oben und auch die Größe des Stacks muss vorher bekannt sein (also der max. Wert).

Zitat von: erik
Also wirklich brauchen tut man das IMHO nicht aber es macht die malloc-Implementierung leichter wenn man den Bereich im virtuellen Adressraum, der für den Heap benutzt wird, einfach so vergrößern kann und der Heap sich nicht über mehrere zusammenhanglose Stücken im virtuellen Adressraum verteilt.
Wieso ist das vorteilhafter? Der Heap wird im Endeffekt doch eh zerstückelt/fragmentiert und du verwaltest den Heap ja auch "nur" in irgendwelchen Datenstrukturen die mit Pointer auf einen bestimmten Speicherbereich zeigen, von daher ist es doch egal ob der zusammenhängend ist oder nicht.

Zitat von: erik
Sowas gibt es in der Standard-libc nicht.
Ich muss wieder lesen üben ;) Habe ich total überlesen das du die libc meinst.

Zitat von: erik
Aha, gut zu wissen, bevor wir später an einander vorbei schreiben. Semaphoren kann es in meinem OS-Kernel nicht geben, weil ununterbrechbar, die gibt es bei mir nur auf User-Mode-Ebene und müssen per Syscall vom Kernel angeboten werden (aber das kommt erst später).
Heißt das auch um die CPU an einen anderen Thread abzugeben, musst du erst wieder zurück in den UserMode oder anders ausgedrückt, du kannst aus deinem Kernel heraus nicht deinen Scheduler aufrufen? Wie machst du es dann das du vom UserMode aus an den Scheduler abgeben kannst ohne das dieser von einem Timer aufgerufen wurde?
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 11. November 2010, 21:12
Zitat von: svenska
Eher richte ich meine Zugriffe auf malloc() und Freunde so aus, wie das OS sie optimal verarbeitet. Schließlich soll mein App1.0 unter Kernel1.0 lauffähig sein, aber exakt das gleiche Binary auch unter Kernel2.0 ... und das geht nur, wenn ich eine Schicht dazwischen habe, die mir die Unterschiede zwischen Kernel1.0 und Kernel2.0 wegabstrahiert. Das ist üblicherweise die libc/libX11/etc.
Naja, erstmal ist das wieder auf Linux bezogen (und auch noch direkt für X11, das ist nicht wirklich portabel!), für Windows sollte das nicht zutreffen, bzw. ist es ja dort so das nicht alle Programme mit einer neueren Version laufen.
Doch. Die Windows-ABI ist seit Windows 3.0 vollständig stabil (GDI etc.), und wenn man von den modernen 64-Bit-Betriebssystemen absieht, die keine 16-Bit-Programme mehr unterstützen, dann geschah die Erweiterung des Systems immer durch Einfügen NEUER API-Funktionen.

Zumal bei Windows viele interne Kernelfunktionen nicht exportiert werden.

Und warum soll ich das nicht in einer eigenen Library abstrahieren. Der Punkt ist doch das du nie wissen kannst was für ein Allocator hinter malloc() steckt und wie effizient der ist und wenn du deinen eigenen schreibst kannst du alle Sachen die du schon weißt und die für die Speicherverwaltung wichtig sind, mit einfließen lassen und das geht bei malloc() halt nicht.
Ich kann ein paar Benchmarks auf übliche Optimierungen loslassen und eine OS-Weiche einbauen, welche malloc am effizientesten benutzt... oder meine Nutzung auf deine malloc-Implementierung abstimmen. Dein malloc sollte jedenfalls, wenn es schon mit Optimalwerten gefüttert wird, auch optimal arbeiten; und es sollte am besten auf deinen VMM abgestimmt sein.

Niemals so, dass ich ein Flag explizit angebe und es dann ignoriert wird.
Du bist also der Meinung "lieber einen Syscall der nichts macht obwohl er eigentlich könnte wenn auch nicht ganz optimal als einen Syscall der versucht sich so gut wie möglich zu verhalten aber das nicht zusichert"? Diese Einstellung kann ich nicht nachvollziehen.
Diese Einstellung unterliegt folgendem Gedanken: Wenn ich der Meinung bin, dass ich es besser weiß, dann soll sich nichts in meinen Weg stellen. Also entweder mir ist es egal oder ich traue dem System, dann gebe ich kein Flag an und alles geht wahrscheinlich so, wie ich es möchte (die Automatik). Wenn ich es besser weiß, dann gebe ich ein Flag an und erzwinge damit ein gewünschtes Verhalten auch auf die Gefahr hin, dass es schlechter ist. Dann möchte ich bitte nicht von der Automatik in die Ecke gedrängt werden.

Also spätestens wenn Du mal nen halben Arbeitstag auf das Ende einer Simulation wartest und dann kurz vor Schluss das Simulationsprogramm noch mal etwas Speicher braucht und dann, nur weil es am meisten Speicher braucht, vom OS gekillt wird entwickelst Du eine andere Meinung.
Ein Prozess darf nur [Limit hier einsetzen] des verfügbaren Speichers belegen und bekommt dann ein NULL vom malloc. Mir ging es darum, dass der verfügbare Speicher im System komplett ausgeschöpft ist (nicht durch einen einzelnen Prozess) und der Kernel seine internen Speicheranforderungen nicht mehr erfüllen kann. Dann eher killen als abstürzen.

Ein OS das einfach nach belieben irgendwelche Prozesse killt möchte ich nicht haben, lieber soll es keinen unprivilegierten Programmen neuen Speicher geben so das diese Programme die Möglichkeit haben angemessen zu reagieren.
Das hatte ich nicht gemeint. ;-)

Wenn du allgemeine Treiber nutzt, ist das nicht möglich; deren Speicheranforderung kann sich stark unterscheiden, je nach Auslastung.
Durch was schwankt der Speicherverbrauch eines Treibers so stark? Wenn ich jetzt mal an einen SATA-AHCI-Treiber denke dann bekommt der maximal wie viele Jobs gleichzeitig? Sagen wir mal 100 und nehmen für jeden Job mal 4 kB zur internen Verwaltung (ist das zu wenig?) dann schwankt der RAM-Bedarf dieses Treiber um maximal 400kB. Wo ist da das Problem? Das sollte die malloc-Implementierung doch recht gut abfangen können so das der Treiber normalerweise gar keine Speicheranforderungen an den Kernel schickt.
Ich denke da an so semi-offene WLAN-Treiber, wo die Hauptarbeit in der Firmware gemacht wird und die Aufgabe des Treibers nur darin besteht, der Firmware eine Umgebung bereitzustellen. Wieviel Speicher die Firmware dann für die Hardware benötigt, kannst du weder abschätzen noch beeinflussen.

Zitat von: erik
Trifft das auch auf Programme zu die nur von der libc (kein POSIX oder sonstwas) abhängig sind?
Warum sollte das da nicht zutreffen. Nehmen wir mal an du hast nen Syscall da irgendetwas macht und du änderst hin dahingehend, das noch ein size-Parameter dazu kommt und den kannst du auch in der Library nicht rausbekommen, sondern den Wert weiß nur der User, wie willst du das wegabstrahieren?
Wenn du eine bash oder eine sonstige Anwendung auf dein System portierst, dann kennt diese den Wert auch nicht. Ansonsten implementierst du in der Library die Funktion zweimal: einmal die alte Variante ohne Größenangabe (die setzt den vorher verwendeten Standardwert) und einmal mit Größenangabe für neue Programme. Das nennt man Kompatiblitätshölle.

Zitat von: erik
Sowas gibt es in der Standard-libc nicht.
Ich muss wieder lesen üben ;) Habe ich total überlesen das du die libc meinst.
Du mochtest es nicht, wenn ich libc sage und wirfst mir Linux-Denke vor, also habe ich auf "Library" verallgemeinert. Solange du irgendwann Programme portieren möchtest, wirst du definitiv irgendwelche davon brauchen.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 11. November 2010, 21:23
Zitat von: svenska
Du mochtest es nicht, wenn ich libc sage und wirfst mir Linux-Denke vor, also habe ich auf "Library" verallgemeinert. Solange du irgendwann Programme portieren möchtest, wirst du definitiv irgendwelche davon brauchen.
Naja, ich dachte da eher an OS spezifische Libraries wie unter Haiku die Kits und nicht an allgemeine wie libc oder QT und solche Konsorten. Das die API da gleichbleibt ist logisch, aber wenn ich halt so OS spezifische Sachen habe, kann ich das dann auch mit der Library meistens nicht abfangen.

Zitat von: svenska
Ich kann ein paar Benchmarks auf übliche Optimierungen loslassen und eine OS-Weiche einbauen, welche malloc am effizientesten benutzt... oder meine Nutzung auf deine malloc-Implementierung abstimmen. Dein malloc sollte jedenfalls, wenn es schon mit Optimalwerten gefüttert wird, auch optimal arbeiten; und es sollte am besten auf deinen VMM abgestimmt sein.
Ich meine vorallem das du ja nicht statisch linken wirst, sondern die libc als ne shared library nutzen wirst und da kannst du nicht wissen was für eine malloc() Implementation da gerade läuft. Deswegen nutzt du halt deine eigene und die ist direkt in deinem Programm mit drin.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 11. November 2010, 22:51
Hallo,


Ich hatte mir auch mal direkt den Code angesehen und da war es noch so das sie versucht haben mind.8 Objekte in einen Slab zu bekommen und den Verschnitt unter einer bestimmte Grenze gehalten haben.
Naja, dann wird eben häufiger doch der VMM bemüht neue Slabs herbei zu schaffen.

Zitat von: erik
Ich möchte für meinem Kernel-Heap die Slabs 512 kBytes groß machen.
Da passen ja von meiner AVL-Struktur 26214 Objekte rein.
So ist das ja auch gedacht. Der 512kB-Slab ist zwar erst mal ein dicker Brocken aber dafür hält der ne Weile. Und da bei meinem VMM die Größe des angeforderten Speicherblocks nicht in die zum Allozieren benötigte Zeit eingeht (ich hab ja beim Kernel-Heap kein Paging dahinter das erst mal alle PD-Einträge richten muss) will ich lieber etwas größer ansetzen.

Aber du bist da ja mit deinen Segmenten auch ein wenig "eingeschränkt" (muss jetzt kein Nachteil sein, jeder hat seine eigenen Ziele).
Ich denke ich werde den Kernel-Heap nicht auf Segmenten aufbauen sondern direkt auf den physischen Speicher ganz ohne Segmente und Paging, das bedeutet aber auch das ich den Kernel-Heap nicht im physischen RAM defragmentieren kann (das ist auch einer der Gründe warum ich lieber richtig große Slabs haben will). Aber eine endgültige Entscheidung hab ich da noch nicht getroffen.

Ich nutze diese Initialisierung im Moment sowieso nur dazu den Speicher des Objekts zu nullen.
Das wollte ich mir ganz sparen, wenn kmalloc ein Objekt liefert dann steht da irgendetwas drin und der entsprechende Code der mit diesem Objekt was machen will schreibt da eh rein was er für sinnvoll hält so das diese Initialisierung IMHO unnötig ist.

Ich habe mit diesem Heap-Konzept so meine Probleme. Eine normale Speicheraufteilung sieht doch so aus, dass der Stack von Oben (oberes Ende des Adressraums) nach unten wächst und der Heap vom Ende des Programm Images nach Oben wächst.
Ja, üblicherweise macht man das so.

Wo werden dann irgendwelche Libraries hingemappt und was ist wenn man mehrere Threads hat, die ja auch ihre Stacks brauchen und was ist mit den Lücken im Adressraum die ja irgendwann entstehen? Können die dann auch noch benutzt werden oder sind die weg?
Für Librarys und die vielen Stacks wird oft jeweils ein festgelegter Bereich in dem einen virtuellen Adressraum benutzt. Das ist ja gerade der Punkt wo ich meine Segmente im Vorteil sehe, jedes davon ist absolut unabhängig von den anderen. Bei mir hat quasi jeder Prozess bis zu 32k virtuelle Adressräume und theoretisch kann jeder dieser Adressräume bis nahe 4GB groß werden (solange die Summe aller Segmente in den Speicher passt). Meine Stacks sind daher auch in keinster Weise Limitiert außer natürlich von der tatsächlich vorhandenen Menge an Speicher (aber vermutlich werde ich auch ein Limit vorsehen bis zu dem der Kernel die Stacks automatisch vergrößert und darüber aber doch den Prozess killt).

auch die Größe des Stacks muss vorher bekannt sein (also der max. Wert).
Das ist aber ein konstruktives Limit in Deinem OS, damit schränkst Du die maximale Anzahl an Threads pro Prozess eventuell erheblich ein (wenn Du die Stacks zu groß machst) oder Deine Threads können sich nicht richtig auf dem Stack austoben (wenn Du diese zu klein machst).

Habe ich total überlesen das du die libc meinst.
Das ist einfach nur eine Library von vielen die es auf verschiedenen OSen gibt und die die entsprechenden OS-Services erfolgreich weg abstrahiert.

Heißt das auch um die CPU an einen anderen Thread abzugeben, musst du erst wieder zurück in den UserMode oder anders ausgedrückt, du kannst aus deinem Kernel heraus nicht deinen Scheduler aufrufen?
Doch, natürlich kann ich im Kernel-Mode den laufenden User-Mode-Thread wechseln. Wie soll das den auch sonst gehen? Ich hab sogar noch ein kleines Extra-Feature: der Scheduler muss kein RET machen und den ganzen Call-Chain vom Kernel-Mode wieder Rückwerts durchlaufen sondern er bereitet einfach alle Register mit den zuletzt vom Thread benutzten Werten vor und macht ein RFS (Return-From-Systemmode) und schon ist die CPU wieder im User-Mode und genau an der Stelle wo der Thread unterbrochen wurde. Das bedeutet vor allem das ich meine Dispatcher-Funktion von überall aus dem Kernel aufrufen kann, sie kommt nur nie zurück. Ein Mode-Wechsel beeinflusst bei mir nicht den Speicher (auch nicht den Stack, weder den User-Mode-Stack noch den CPU-spezifischen Kernel-Mode-Stack) sondern nur die Register und die Schatten-Register. Mit dem RFS ist auch der gesamte Kernel-Mode-Stack der aktuellen CPU verschwunden (also der Stack-Pointer vergessen) und wenn diese CPU wieder in den System-Mode wechselt (durch Syscall/Yield/IRQ/Exception oder eine abgelaufene Zeitscheibe) hat die CPU wieder einen frischen und jungfräulichen Kernel-Mode-Stack (also der Stack-Pointer steht wieder am Anfang).

Wie machst du es dann das du vom UserMode aus an den Scheduler abgeben kannst ohne das dieser von einem Timer aufgerufen wurde?
Dafür hab ich extra eine Yield-Instruktion vorgesehen die das selbe macht wie wenn der Zeitscheiben-Counter abgelaufen ist (so das ich dafür nicht extra einen Syscall benötige). Der Counter bleibt dabei erhalten so das der Kernel sieht wie viel von der Zeitscheibe noch übrig ist um das eventuell wohlwollend zu berücksichtigen (keine Ahnung ob ich sowas mal implementiere). Es wird aber auch blockierende Syscalls geben wo der aufrufende Thread zwar eine Aktion auslöst aber dann doch in seinem Thread-Descriptor gesichert und als blockiert markiert wird, anschließend ruft der Kernel dann den Dispatscher auf der den nächsten Thread aus der runnable-Liste holt. Irgendwann ist die ausgelöste Aktion fertig (aufgrund von externen Ereignissen) und der Thread wird wieder als runnable markiert, sein Zustand so geändert das in den gesicherten Registern die richtigen Rückgabewerte für den ursprünglichen Syscall drin sind und dann kommt der Thread in die runable Liste und wird irgendwann vom Dispatcher wieder aufgerufen. Der Thread macht dann mit dem Befehl weiter der unmittelbar hinter den ursprünglichen Syscall-Befehl steht und wertet ganz normal die Rückgabewerte aus. Ein Semaphore-Enter-Syscall ist da ein gutes Beispiel, der kommt entweder sofort wieder zurück (weil die Semaphore frei war) oder er blockiert (weil die Semaphore belegt ist) und irgendwann (wenn die Semaphore wieder frei gegeben wird) wird dieser Thread wieder als runnable markiert (also aufgeweckt) und ist dann neuer Besitzer der Semaphore.


Diese Einstellung unterliegt folgendem Gedanken: Wenn ich der Meinung bin, dass ich es besser weiß, dann soll sich nichts in meinen Weg stellen. Also entweder mir ist es egal oder ich traue dem System, dann gebe ich kein Flag an und alles geht wahrscheinlich so, wie ich es möchte (die Automatik). Wenn ich es besser weiß, dann gebe ich ein Flag an und erzwinge damit ein gewünschtes Verhalten auch auf die Gefahr hin, dass es schlechter ist. Dann möchte ich bitte nicht von der Automatik in die Ecke gedrängt werden.
Sorry, aber das kann ich immer noch nicht nachvollziehen. Wenn ich mit einem Flag das OS bitte sich auf eine bestimmte Art zu verhalten (weil das für mich ideal wäre) dann kann es sein dass das OS dem nachkommt und ich hab Glück oder das OS kann dem nicht nachkommen (weil es dafür einen triftigen Grund gibt) und dann muss ich eben damit klar kommen. Solange letzteres keine funktionalen Einschränkungen nach sich zieht kann ich an diesem Konzept nichts verwerfliches finden. Eine Automatik ist IMHO von vornherein zum Scheitern verurteilt weil sie (bzw. der Programmierer) nie die realen Bedingungen zur Laufzeit vorhersehen kann.

Wenn ich es besser weiß, dann gebe ich ein Flag an und erzwinge damit ein gewünschtes Verhalten auch auf die Gefahr hin, dass es schlechter ist.
Selbst wenn dieses Verhalten schlechter ist als wenn das OS Deiner Bitte nur "so gut es geht" nach kommt?

Was hältst Du dann eigentlich von den ganzen Read-Ahead-Mechanismen die mache File-APIs anbieten? Das sind auch nur kann-Angelegenheiten denen das OS nachkommen darf wenn es denn die Möglichkeit hat aber es eben nicht muss wenn es gerade nicht geht.

Mir ging es darum, dass der verfügbare Speicher im System komplett ausgeschöpft ist (nicht durch einen einzelnen Prozess)
Das hab ich schon so verstanden (das es um mehrere Prozesse geht), trotzdem sollte das OS nicht einfach den Prozess killen der am meisten Speicher braucht, das ist manchmal gerade der Prozess der mir als User am wichtigsten ist (oder aber auch der Prozess der gerade Amok läuft).

und der Kernel seine internen Speicheranforderungen nicht mehr erfüllen kann. Dann eher killen als abstürzen.
Diese Situation kann eigentlich nur root herbei schaffen, weil nur der auch noch das letzte bisschen Speicher aufbrauchen kann. Wenn es wirklich so weit kommt dann ist es natürlich egal welchen Prozess das OS killt denn daran ist dann definitiv root selber schuld.

Ich denke da an so semi-offene WLAN-Treiber, wo die Hauptarbeit in der Firmware gemacht wird und die Aufgabe des Treibers nur darin besteht, der Firmware eine Umgebung bereitzustellen. Wieviel Speicher die Firmware dann für die Hardware benötigt, kannst du weder abschätzen noch beeinflussen.
Auch hier sehe ich nicht warum der Speicherbedarf zur Laufzeit (also nach dem alle Vorbereitungshandlungen abgeschlossen sind) noch großartig schwanken sollte.


Deswegen nutzt du halt deine eigene und die ist direkt in deinem Programm mit drin.
Wenn Du eine eigene Heap-Library benutzen möchtest dann solltest Du aber auch eine haben die wirklich gut auf das aktuell benutzte OS abgestimmt/optimiert ist.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 12. November 2010, 08:01
Zitat von: erik
Naja, dann wird eben häufiger doch der VMM bemüht neue Slabs herbei zu schaffen.
Eine Möglichkeit das noch etwas weiter zu optimieren, wäre, wenn du dem SlabAllocator beim Erstellen der "Slab-Beschreibung" mitteilst wieviele Objekte du mind. in einem Objekt haben willst.
Denn du als Programmierer solltest ja eher wissen wieviel Objekte du so im Normalfall benötigst.

Würde ich jetzt aber deine Variante benutzen, könnte ich das mit den 8MB RAM für mein OS gleich knicken. Denn mit 512KB pro Objekt-Typ werde ich wohl schon bei über 8MB sein ;)

Zitat von: erik
Das ist aber ein konstruktives Limit in Deinem OS, damit schränkst Du die maximale Anzahl an Threads pro Prozess eventuell erheblich ein (wenn Du die Stacks zu groß machst) oder Deine Threads können sich nicht richtig auf dem Stack austoben (wenn Du diese zu klein machst).
Also mit vorher bekannt meine ich damit, wenn du den Thread erstellst, musst du die max Größe des Threads angeben.

Wie würdest du es denn auf x86 lösen bzw. wie wird es denn gelöst? Wäre nicht schlecht wenn mir mal jemand den Adressraumaufbau unter z.B. Linux erklären könnte, das ich das vielleicht mal verstehe ;)

Zitat von: erik
Wenn Du eine eigene Heap-Library benutzen möchtest dann solltest Du aber auch eine haben die wirklich gut auf das aktuell benutzte OS abgestimmt/optimiert ist.
Das sollte klar sein. Der SlabAllocator wurde ja auch in den UserSpace "übertragen", auf der einen Seite ist das bestimmt von Vorteil, aber auf der anderen Seite, wird wohl kaum ein normales Programm den SlabAllocator direkt nutzen, sondern nur über den Umweg von malloc() und da bringt er (meiner Meinung nach) nicht mehr so viel.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 12. November 2010, 09:44
Hallo,

Diese Einstellung unterliegt folgendem Gedanken: Wenn ich der Meinung bin, dass ich es besser weiß, dann soll sich nichts in meinen Weg stellen. Also entweder mir ist es egal oder ich traue dem System, dann gebe ich kein Flag an und alles geht wahrscheinlich so, wie ich es möchte (die Automatik). Wenn ich es besser weiß, dann gebe ich ein Flag an und erzwinge damit ein gewünschtes Verhalten auch auf die Gefahr hin, dass es schlechter ist. Dann möchte ich bitte nicht von der Automatik in die Ecke gedrängt werden.
Sorry, aber das kann ich immer noch nicht nachvollziehen. Wenn ich mit einem Flag das OS bitte sich auf eine bestimmte Art zu verhalten (weil das für mich ideal wäre) dann kann es sein dass das OS dem nachkommt und ich hab Glück oder das OS kann dem nicht nachkommen (weil es dafür einen triftigen Grund gibt) und dann muss ich eben damit klar kommen. Solange letzteres keine funktionalen Einschränkungen nach sich zieht kann ich an diesem Konzept nichts verwerfliches finden. Eine Automatik ist IMHO von vornherein zum Scheitern verurteilt weil sie (bzw. der Programmierer) nie die realen Bedingungen zur Laufzeit vorhersehen kann.
Naja, ich seh das allgemein etwas anders. Einmal sollte sich eine Automatik durchaus zur Laufzeit konfigurieren lassen, denn für jeden möglichen Anwendungsfall kann man es nicht konfigurieren - und wenn das OS aus einem bestimmten Grund der Meinung ist, mein Flag nicht erfüllen zu können, dann sollte es mir bescheidsagen und nichts tun.

Ich frage ja auch nicht im Supermarkt nach Vollmilch und kriege nur fettfreie Milch. Da kommt dann erstmal ein "gibts nicht" und ich muss selbst abhängig davon entscheiden. Wenn ich also ein MAP_NOW möchte, dann möchte ich entweder ein MAP_NOW oder garnichts. (Ich kann es ja danach nochmal ohne Flag probieren.)

Wenn ich es besser weiß, dann gebe ich ein Flag an und erzwinge damit ein gewünschtes Verhalten auch auf die Gefahr hin, dass es schlechter ist.
Selbst wenn dieses Verhalten schlechter ist als wenn das OS Deiner Bitte nur "so gut es geht" nach kommt?
Ja.

Was hältst Du dann eigentlich von den ganzen Read-Ahead-Mechanismen die mache File-APIs anbieten? Das sind auch nur kann-Angelegenheiten denen das OS nachkommen darf wenn es denn die Möglichkeit hat aber es eben nicht muss wenn es gerade nicht geht.
Ich bin der Meinung, dass ich Dinge erzwingen können muss, d.h. wenn ich ein (synchrones/blockierendes) Readahead anfordere, dann möchte ich auch einen Fehler zurückkriegen, wenn das nicht gelungen ist. Wenn ich kein Flag angebe, dann ist das deine kann-Entscheidung (die von einer Automatik gefällt wird).

Das hab ich schon so verstanden (das es um mehrere Prozesse geht), trotzdem sollte das OS nicht einfach den Prozess killen der am meisten Speicher braucht, das ist manchmal gerade der Prozess der mir als User am wichtigsten ist (oder aber auch der Prozess der gerade Amok läuft).
Welche Kriterien hast du denn sonst noch, wenn das System gerade vollständig an der Wand steht? Einfach zu sagen "wenn root das macht, dann ist er selbst schuld" ist nicht hinreichend; was passiert, wenn ein root-Prozess amokläuft und dieser Prozess inhärent ein root-Prozess sein muss (fsck, Treiber)?

Der OOM-Killer muss primitiv sein. Und wenn der kommt, dann ist sowieso irgendwas anderes komplett im Eimer. Bei dem geht es nur darum, einen betriebsfähigen Zustand wiederherzustellen, koste es was es wolle.

Ich denke da an so semi-offene WLAN-Treiber, wo die Hauptarbeit in der Firmware gemacht wird und die Aufgabe des Treibers nur darin besteht, der Firmware eine Umgebung bereitzustellen. Wieviel Speicher die Firmware dann für die Hardware benötigt, kannst du weder abschätzen noch beeinflussen.
Auch hier sehe ich nicht warum der Speicherbedarf zur Laufzeit (also nach dem alle Vorbereitungshandlungen abgeschlossen sind) noch großartig schwanken sollte.
Du hast keinerlei Kontrolle über die Firmware. Und ich kann mir durchaus vorstellen, dass ein Grafiktreiber plötzlich mangels Grafikspeicher anfängt, die Texturen in das System-RAM auszulagern, um überhaupt eine Darstellung zu ermöglichen. Man sollte das in Erwägung ziehen und entweder bewusst verhindern oder als Möglichkeit offenlassen. Zumindest sollte man darüber nachdenken.

Naja, ich dachte da eher an OS spezifische Libraries wie unter Haiku die Kits und nicht an allgemeine wie libc oder QT und solche Konsorten. Das die API da gleichbleibt ist logisch, aber wenn ich halt so OS spezifische Sachen habe, kann ich das dann auch mit der Library meistens nicht abfangen.
Auch da sollte die Library eine relativ stabile API/ABI anbieten, damit Programme auch bei sich wandelndem Kernel weiterhin funktionieren. Im besten Konzept überhaupt machst du es wie Microsoft und erweiterst deine API um neue Funktionen, fasst deine bereits vorhandenen aber nie wieder an (außer in der Implementierung).

Ich meine vorallem das du ja nicht statisch linken wirst, sondern die libc als ne shared library nutzen wirst und da kannst du nicht wissen was für eine malloc() Implementation da gerade läuft. Deswegen nutzt du halt deine eigene und die ist direkt in deinem Programm mit drin.
Ich gehe davon aus, dass die System-Library durchaus eine performante malloc-Implementation bereitstellt. Außerdem ist die Library selbst systemspezifisch und meine OS-Weiche kann ja auch die OS-Version mit berücksichtigen.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 12. November 2010, 15:13
Hallo,


wenn du dem SlabAllocator beim Erstellen der "Slab-Beschreibung" mitteilst wieviele Objekte du mind. in einem Objekt haben willst.
Ich habe nicht vor sowas automatisch zur Laufzeit machen zu lassen, für die paar Objektgrößen die ich in meinem Micro-Kernel benötige kann ich vorher zur Design-Zeit festlegen wie die einzelnen Slabs genau aufgebaut sein sollen.

Würde ich jetzt aber deine Variante benutzen, könnte ich das mit den 8MB RAM für mein OS gleich knicken. Denn mit 512KB pro Objekt-Typ werde ich wohl schon bei über 8MB sein ;)
Ich bin mir nicht sicher was mein OS so brauchen wird um anzufangen aber wenn ich bedenke das ich bei der Lebendgeburt meines OS schon 3 Prozesse haben möchte (jeder braucht eine 512kB-LDT und natürlich seinen Code + Daten und Stack, für idle muss ich sogar mehrere Threads erstellen (für jede CPU einen) von denen jeder Stack benötigt), dazu kommen 5 oder 6 Slabs mit je 512kB und dann noch der Kernel-Code und für jede CPU einen Kernel-Mode-Stack. Ich schätze da sind bei 4 CPUs bereits 8MB weg, ohne jetzt mal Code + Daten der 3 Initial-Prozesse zu berücksichtigen (da ist z.B. eine RAM-Disk mit den weiteren Executables für Treiber usw. mit dabei). Ich denke wenn der Boot-Code nicht wenigstens 64MB vorfindet wird er gleich ablehnen müssen. Der Punkt ist aber das dieser Eigenbedarf des Kernel zur Laufzeit nicht mehr so stark ansteigen wird, außer ab und an mal einen neuen Slab wird der Kernel nicht viel benötigen. Der Task-Manager, von dem Windows-XP an dem ich gerade sitze, zeigt mir über 700 Threads von über 65 Prozessen an, ich weiß zwar nicht wie viel RAM der Windows-Kernel selber nur allein für deren Verwaltung benötigt aber das dürfte schon einiges sein. Spätestens bei einer derartigen Auslastung fallen meine großen Slabs auch nicht mehr ins Gewicht, dafür spare ich mir pro Prozess ein Paging-Directory.

Also mit vorher bekannt meine ich damit, wenn du den Thread erstellst, musst du die max Größe des Threads angeben.
Ich als Programmierer weiß aber nicht immer wie viel Stack meine Threads so brauchen werden. Gerade bei Workerthreads (die ja oft aus einem vorbestücktem Pool genommen werden) hängt die Stackbenutzung doch sicher auch mal von der konkreten Aufgabe ab die die einzelnen Threads dann tatsächlich bearbeiten.

Wäre nicht schlecht wenn mir mal jemand den Adressraumaufbau unter z.B. Linux erklären könnte, das ich das vielleicht mal verstehe ;)
Da dürfte die Suchmaschine Deines geringsten Misstrauens doch sicher behilflich sein.

Der SlabAllocator wurde ja auch in den UserSpace "übertragen", auf der einen Seite ist das bestimmt von Vorteil, aber auf der anderen Seite, wird wohl kaum ein normales Programm den SlabAllocator direkt nutzen, sondern nur über den Umweg von malloc() und da bringt er (meiner Meinung nach) nicht mehr so viel.
Wenn der SlabAllocator hinter einem generischen malloc steckt dann muss er für jede übergebene Größe erst mal ermitteln mit welchem Slab-Pool er dann arbeiten soll, wie Aufwändig das ist kann ich jetzt auch nicht so genau sagen. In meinem Kernel kenne ich die möglichen Objekt-Type bereits im voraus und kann ein switch nehmen das sich mit sehr wenigen Assemblerbefehlen umsetzen lässt, aber in einem unbekannten User-Programm dürfte da ein Baum nötig werden und der kostet einfach jedes mal etwas Zeit. Die Frage ist einfach "Kann der SlabAllocator das mit seiner hohen Performance wieder rausholen?".


Naja, ich seh das allgemein etwas anders.
Tja, Meinungen sind eben doch verschieden! ;)
Warum streiten wir uns eigentlich über ein Flag von FlushBurns OS? Soll er doch gleich 5 Varianten "MAP_IMMER_SOFORT", "MAP_MOEGLICH_SOFORT", "MAP_EGAL", "MAP_MOEGLICH_SPAETER" und "MAP_IMMER_SPAETER" machen und jeder von uns ist zufrieden.

Einmal sollte sich eine Automatik durchaus zur Laufzeit konfigurieren lassen
Und wer soll das konfigurieren? Meine Oma konfiguriert bestimmt nicht die Mapping-Optionen fürs Paging. Ich bleibe bei der Meinung das der Programmierer einer bestimmten Applikation noch am besten abschätzen kann was für sein Programm optimal wäre und was zumindest noch akzeptabel ist und dann eben das OS zwingt wenigstens das akzeptable einzuhalten und gleichzeitig bittet das Optimum zu versuchen.

und wenn das OS aus einem bestimmten Grund der Meinung ist, mein Flag nicht erfüllen zu können, dann sollte es mir bescheidsagen und nichts tun.
Und der zusätzliche Syscall kostet bald mehr als die Optimierung überhaupt hätte bringen können? Was ist wenn es um eine kleine Aktion geht die aber sehr oft ausgeführt wird?

Ich frage ja auch nicht im Supermarkt nach Vollmilch und kriege nur fettfreie Milch. Da kommt dann erstmal ein "gibts nicht"
Sicher, aber ein guter Verkäufer wird Dir noch im selben Atemzug die fettfreie Milch empfehlen.

Wenn ich also ein MAP_NOW möchte, dann möchte ich entweder ein MAP_NOW oder garnichts.
Das empfinde ich persönlich aber als Engstirnig. Dir entsteht doch kein funktionaler Nachteil wenn das MAP_NOW nicht komplett (sondern nur zum Großteil) erfüllt werden kann. Der einzigste Nachteil ist eine minimal geringere Performance.
Betrachte es mal von einer anderen Seite: normalerweise hast Du dieses Parameter nicht und musst Dich eh auf die Automatik vom OS verlassen aber bei FlashBurns OS gibt es die Möglichkeit diese Automatik wenigstens zu versuchen zu Deinen Gunsten zu beeinflussen. Egal wie Du das Flag benutzt Dir entsteht, gegenüber einem OS ohne dieses Flag, erst mal kein Nachteil. Du kannst also nur gewinnen.

Ich bin der Meinung, dass ich Dinge erzwingen können muss
Das ist aber unsozial! Gerade bei einem OS das die Wünsche von sehr vielen Programmen parallel erfüllen muss sollte man nicht alles mit Zwang machen sondern sich eher etwas kooperativer verhalten.

Wenn ich kein Flag angebe, dann ist das deine kann-Entscheidung (die von einer Automatik gefällt wird).
Automatik allein reicht aber oft nicht um eine gute Entscheidung zu treffen, oft ist es eben sinnvoll der Automatik wenigstens einen Hinweis zu geben.

Welche Kriterien hast du denn sonst noch, wenn das System gerade vollständig an der Wand steht?
Klar, keine mehr. Dann muss eben der allerletzte Notfallplan benutzt werden, egal welche Konsequenzen das hat. Wichtig ist mir das diese Situation unter "normalen" Umständen erst gar nicht eintritt.

Du hast keinerlei Kontrolle über die Firmware. Und ich kann mir durchaus vorstellen, dass ein Grafiktreiber plötzlich mangels Grafikspeicher anfängt, die Texturen in das System-RAM auszulagern, um überhaupt eine Darstellung zu ermöglichen. Man sollte das in Erwägung ziehen und entweder bewusst verhindern oder als Möglichkeit offenlassen. Zumindest sollte man darüber nachdenken.
Natürlich kann ein OS auch aufgrund eines Amok laufenden Treibers an die Wand gefahren werden und wenn dieser mit root-Rechten läuft (was wohl der Normalfall ist) dann ist das besonders kritisch weil der OOM-Killer sich wohl nicht traut den als ersten zu killen sondern sich erst mal an den normalen User-Prozessen schadlos hält. Das ist einer der Gründe warum root bei der Auswahl der Programme die er startet etwas mehr Sorgfalt walten lassen muss. Ich bin aber trotzdem der Meinung das 75% für den nichtauslagerbaren Speicher eine gute Obergrenze ist bei der dann der VMM erst mal Schluss macht weiteren Speicher an Prozesse (egal ob root oder nicht) zu vergeben sondern nur noch für den Kernel selber was hergibt. Mehr als 20% nichtauslagerbaren Speicher sollte es in einem normalen System eigentlich nicht geben.

machst du es wie Microsoft und erweiterst deine API um neue Funktionen, fasst deine bereits vorhandenen aber nie wieder an
Also es gibt in der inoffiziellen WIN-API durchaus immer wieder Änderungen, manchmal sogar von einem SP zum nächsten.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 12. November 2010, 15:56
Zitat von: erik
Ich als Programmierer weiß aber nicht immer wie viel Stack meine Threads so brauchen werden. Gerade bei Workerthreads (die ja oft aus einem vorbestücktem Pool genommen werden) hängt die Stackbenutzung doch sicher auch mal von der konkreten Aufgabe ab die die einzelnen Threads dann tatsächlich bearbeiten.
Du musst die Stacks deiner Threads so oder so beschränken. Denn wenn du mehrere hast, müssen die ja irgendwo hin in deinen Adressraum und somit gibt es dann Grenzen, nämlich da wo ein anderer Thread anfängt.
Da ich als OS nicht wissen kann wieviel Threads ein Programm wohl haben wird, gebe ich dem Programm halt die Möglichkeit die Stack-Größe selber zu wählen.
Was die maximale Größe eines Threads betrifft, die lässt sich rausbekommen. Man muss einfach nur wissen welche Funktionen werden aufgerufen und macht das für jede Funktion die aufgerufen kann wieder (also gucken welche Funktionen werden aufgerufen) und dann bekommst du auch nen maximalen Stackverbrauch.

Ich habe gerade mal geguckt, Standardgröße des Stacks ist unter Windows 1MB und unter Linux 2MB und man kann halt einfach bei der Thread-Erstellung angeben wie groß der Stack sein soll oder man gibt 0 an, dann entscheidet das OS.

Dazu dann die Frage, was ist die "richtige" Stackgröße? Spontan würde ich auch sagen, das 1MB ausreichend ist.

Zitat von: erik
Da dürfte die Suchmaschine Deines geringsten Misstrauens doch sicher behilflich sein.
Ich habe sogar ein eBook zum Speichermanagement unter Linux und auch das konnte es mir nicht näher bringen :(

Zitat von: erik
Wenn der SlabAllocator hinter einem generischen malloc steckt dann muss er für jede übergebene Größe erst mal ermitteln mit welchem Slab-Pool er dann arbeiten soll, wie Aufwändig das ist kann ich jetzt auch nicht so genau sagen.
Sollte gar nicht so schwierig und langsam sein. Man erstellt eh vorher (bei der Initialisierung von malloc()) Slabs mit einer Größe von 2er Potenzen.
Du musst dir ja nur das höchste Bit angucken und in der nächsten 2er Potenz liegt der Spass dann. Dann hast du einfach ein Array und könntest unter x86 sowas machen:
int slot= _bsr(size)
struct slab_t *slab= arrayOfSlabs[slot + 1];
Das sollte verdammt schnell sein, vorallem im Vergleich zu einem malloc() das erst in einer Liste nach einer "perfekten" Größe sucht.

Zitat von: erik
Warum streiten wir uns eigentlich über ein Flag von FlushBurns OS?
Ihr streitet euch wegen mir ;)
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 12. November 2010, 17:04
Hallo,


Du musst die Stacks deiner Threads so oder so beschränken.
Nein, ich muss das bestimmt nicht tun, Du musst das tun. Ich lege jeden Stack in ein eigenes Segment und bin glücklich das ich mich nicht dafür interessieren brauch wie stark die Stacks nun wachsen. Selbst auf einen 32Bit-System hab ich kein Problem damit wenn 1 oder 2 Threads ihren Stack auf ein ganzes GB anwachsen lassen (so lange genügen RAM verbaut ist).

Denn wenn du mehrere hast, müssen die ja irgendwo hin in deinen Adressraum und somit gibt es dann Grenzen, nämlich da wo ein anderer Thread anfängt.
Genau so ist es. Dieses Problem müssen alle Flat-Memory-OSe irgendwie lösen also schaffst Du das auch.

Da ich als OS nicht wissen kann wieviel Threads ein Programm wohl haben wird, gebe ich dem Programm halt die Möglichkeit die Stack-Größe selber zu wählen.
Das klingt erst mal recht vernünftig aber nicht immer kann der Programmierer das vorhersehen.

Man muss einfach nur wissen welche Funktionen werden aufgerufen und macht das für jede Funktion die aufgerufen kann wieder (also gucken welche Funktionen werden aufgerufen) und dann bekommst du auch nen maximalen Stackverbrauch.
Und was ist mit Rekursion? Man kann nicht immer zuverlässig vorhersagen wie oft rekursiert wird.

Dazu dann die Frage, was ist die "richtige" Stackgröße? Spontan würde ich auch sagen, das 1MB ausreichend ist.
Das ist schwer zu beantworten, auf der einen Seite ist das Allozieren von Objekten auf dem Stack extrem schnell (und auch manche Algorithmen lassen sich rekursiv leichter umsetzen) aber auf der anderen Seite wissen die Programmierer das der Stack begrenzt ist und versuchen damit sparsam umzugehen (und setzen auch Rekursion nicht so gerne ein). Ich persönlich bin mit Stack nicht sehr sparsam und hatte auch schon mal eine entsprechende Schutzverletzung unter Windows weil mein Programm doch zu oft rekursiert hat. Wenn man es nicht gewohnt ist sich bei der Stackbenutzung zu zügeln dann kann man damit zwar sicher etwas Performace gewinnen aber auf den üblichen Flat-Memory-Systemen läuft man eben auch Gefahr das einem der Stack schneller aus geht als man will.

Man erstellt eh vorher (bei der Initialisierung von malloc()) Slabs mit einer Größe von 2er Potenzen.
Du willst Deine Slabs nur auf 2er-Potenzen auslegen? Das gibt aber eine fette Speicherverschwendung! Was ist wenn ich 1048576 Objekte mit 36 Bytes brauche, hab ich dann 28MB Verschnitt?


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 12. November 2010, 18:07
Zitat von: erik
Nein, ich muss das bestimmt nicht tun, Du musst das tun.
Ahhhh, mit Du meinte ich ein OS auf einer "normalen" Architketur (also keine Segmente und eine MMU) ;)

Zitat von: erik
Und was ist mit Rekursion? Man kann nicht immer zuverlässig vorhersagen wie oft rekursiert wird.
Rekursion habe ich natürlich nicht beachtet, aber ganz ehrlich, bei nem Baum (als Bsp.) werde ich die maximale Rekursionstiefe kennen und wenn ich mal davon ausgehe, das du 4Byte Adresse und 3 Parameter mit jeweils 4Byte und 4 lokale Vriablen mit jeweils 4Byte hast, dann kannst du bei nem Stack von 512KB 16384 Rekursionen durchführen.
Bei so einer Anzahl würde ich dann schon überlegen ob ein anderer Algo nicht performanter ist.

Zitat von: erik
Du willst Deine Slabs nur auf 2er-Potenzen auslegen? Das gibt aber eine fette Speicherverschwendung! Was ist wenn ich 1048576 Objekte mit 36 Bytes brauche, hab ich dann 28MB Verschnitt?
Das ist, so weit ich schonmal Code gesehen habe, die gängige Praxis. Bei deinem Bsp wäre z.B. ein SlabAllocator besser geeignet als ein allgemeines malloc().
Denn ich behaupte mal das du bei dem Bsp. jede malloc() Implementation in den "Wahnsinn" treibst, da wird dein Speicher ordentlich fragmentiert und du wirst auch nen sehr schönen Verschnitt haben.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 12. November 2010, 19:40
Hallo,


mit Du meinte ich ein OS auf einer "normalen" Architketur
Definiere Bitte "normal"!

bei nem Baum (als Bsp.) werde ich die maximale Rekursionstiefe kennen
Es werden nicht nur Bäume per Rekusion verarbeitet, wimre gibt es auch einige mathematische Algorithmen die sich per Rekursion sehr einfach und effizient implementieren lassen.

wenn ich mal davon ausgehe, das du 4Byte Adresse und 3 Parameter mit jeweils 4Byte und 4 lokale Vriablen mit jeweils 4Byte hast
Und was ist mit den gesicherten Registern? Klar, bei ner statischen Funktion kann der Compiler da ziemlich tricksen um einiges einzusparen aber grundsätzlich sollte man sowas nicht außer acht lassen.

Bei so einer Anzahl würde ich dann schon überlegen ob ein anderer Algo nicht performanter ist.
Es soll aber wirklich Probleme geben die sich mit Rekursion am einfachsten und effizientesten lösen lassen.

Zitat von: erik
Was ist wenn ich 1048576 Objekte mit 36 Bytes brauche, hab ich dann 28MB Verschnitt?
Das ist, so weit ich schonmal Code gesehen habe, die gängige Praxis.
Krass! Ich war mir nie so ganz sicher wie viele Segmente ich für den Heap (im User-Mode) benutzen sollte. Nur die 2er-Potenzen erschien mir einfach zu extrem verschwenderisch, bis zu knapp 50% Verschnitt empfinde ich einfach als unangemessen. Doppelt so viele Segmente, die dann im Abstand von Wurzel aus 2 benutzt werden, haben auch noch bis zu knapp 25% Verschnitt. Wenn ich unter 2% Verschnitt haben will dann muss ich mindestens 32 mal so viele Segmente benutzen wie ich Bits hab und das wäre schon eine ganze Menge von dehnen wohl etliche leer bleiben würden (und damit erst gar nicht erstellt werden müssten).

Bei deinem Bsp wäre z.B. ein SlabAllocator besser geeignet als ein allgemeines malloc().
Das würde ich stark bezweifeln, ein ganz simples malloc hat fast gar keinen Verschnitt außer das es zu jedem Objekt eine feste Anzahl an Bytes für die Verwaltung benötigt.


@All: Wie viel Verschnitt (inklusive der Verwaltungsinformationen) ist den für den User-Mode-Heap akzeptabel?


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 12. November 2010, 20:06
Zitat von: erik
Definiere Bitte "normal"!
Hatte ich doch dahinter geschrieben, eine MMU und keine Segmente, halt FlatMemory mit Paging.

Zitat von: erik
Es soll aber wirklich Probleme geben die sich mit Rekursion am einfachsten und effizientesten lösen lassen.
Ich behaupte mal das man jeden Algo in einen Iterativen Umschreiben kann (ja der Aufwand wird sich bei vielen Sachen wahrscheinlich nicht lohnen) oder man Rekursion iterativ simulieren.
Der eigentliche Punkt ist doch, du weißt wenn du Rekursion nimmst in welche Richtung das geht und wenn du mit 1MB Stack nicht hinkommst dann musst du deinen Code halt anpassen (z.B. in dem du nen Thread erstellst der nen größeren Stack hat).

Zitat von: erik
Das würde ich stark bezweifeln, ein ganz simples malloc hat fast gar keinen Verschnitt außer das es zu jedem Objekt eine feste Anzahl an Bytes für die Verwaltung benötigt.
Das Problem ist, wenn du diese kleinen Anfragen (36Byte) hintereinander machst, ist es viel sinnvoller einmal viel Speicher zu allokieren, machst du die nicht hintereinander, fragmentiert dein Heap irgendwann so stark das du oft irgendwelche 36Byte Löcher hast die du nicht mehr nutzen kannst.
Denn genau das ist doch das Problem bei nem ganz einfachen malloc(), entweder der Speicher ist so fragmentiert das du dir um den Verschnitt keine Sorgen mehr machen musst, weil die Löcher genug Probleme machen oder du hast halt viel Verschnitt (aber nen schnelles malloc()) oder dein malloc() ist zu langsam und skaliert nicht.

Drehen wir den Spieß doch mal um. Hast du denn eine Idee wie man ein malloc() implementieren kann so dass man möglichst wenig Verschnitt hat, möglichst wenig Speicherverbrauch (die Verwaltung) und möglichst schnell ist und skalieren soll das ganz auch noch. Viel Spaß ;)

Edit::

Wenn wir mal deine Slabs mit 512KB nehmen, da passen ja 14563 Objekte mit einer Größe von 36Byte rein und wenn ich jetzt mal den average-case nehme (auch der ist nur einfach so gewählt) wo ich 100 Elemente brauche, dann hast du mit deinen goßen Slabs einen Verschnitt von 99,31% das nenne ich mal Verschwendung ;)

Was ich damit sagen will ist einfach nur, das man sich bei einem Allocator halt auf nen guten Mix aller Nachteile einlassen muss oder man weiß ziemlich genau was für ein Speicherverbrauch das Programm hat und kann dann optimieren.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 13. November 2010, 13:31
Einmal sollte sich eine Automatik durchaus zur Laufzeit konfigurieren lassen
Und wer soll das konfigurieren? Meine Oma konfiguriert bestimmt nicht die Mapping-Optionen fürs Paging.
Darum bringt das System eine sinnvolle Automatik mit. Die brauchst du ohnehin überall (auch die Frage, wie lange du wartest, bis du Anwendungen von einem überlasteten Core auf einen leeren Core umziehst, weil dir die Cache-Inhalte verlorengehen... wie lange du verzögertes Schreiben auf die Festplatte erlaubst, bis du die Buffer zwangsweise wegschreibst... wie groß deine Zeitscheiben sind...). Das sind alles Automatiken mit sinnvollen Standardwerten, die überall hinreichend gut, wenn auch nicht optimal performen.

Ich bleibe bei der Meinung das der Programmierer einer bestimmten Applikation noch am besten abschätzen kann was für sein Programm optimal wäre und was zumindest noch akzeptabel ist und dann eben das OS zwingt wenigstens das akzeptable einzuhalten und gleichzeitig bittet das Optimum zu versuchen.
Darum soll man die Automatik zur Laufzeit konfigurieren und mit Flags für einzelne Aktionen überschreiben können.

Und der zusätzliche Syscall kostet bald mehr als die Optimierung überhaupt hätte bringen können? Was ist wenn es um eine kleine Aktion geht die aber sehr oft ausgeführt wird?
Eine sinnvolle Automatik macht kleine, häufige Aktionen schnell und seltene, große Aktionen langsam, wenn es einen Kompromiss geben muss. Außerdem sind Prozesse, die extrem von solchen Dingen profitieren müssen, meist die Hauptkomponenten eines Systems - einen Webserver, der 99% seiner Zeit nichts tut, werde ich nicht stundenlang auf Durchsatz optimieren - und dann kann man halt das Standardverhalten der Anwendung angepasst zur Laufzeit ändern.

Ich frage ja auch nicht im Supermarkt nach Vollmilch und kriege nur fettfreie Milch. Da kommt dann erstmal ein "gibts nicht"
Sicher, aber ein guter Verkäufer wird Dir noch im selben Atemzug die fettfreie Milch empfehlen.
Und mir die Möglichkeit einer Entscheidung geben.

Wenn ich kein Flag angebe, dann ist das deine kann-Entscheidung (die von einer Automatik gefällt wird).
Automatik allein reicht aber oft nicht um eine gute Entscheidung zu treffen, oft ist es eben sinnvoll der Automatik wenigstens einen Hinweis zu geben.
Den sie entweder ignoriert (dann hätte ich den Hinweis nicht gebraucht) oder umsetzt (dann hätte ich es auch erzwingen können). Naja, ich sehe Computer als Dinge an, die tun was man ihnen sagt, nicht mehr und nicht weniger. Und wenn ich ihm sage "fahr da lang", dann möchte ich auch exakt diesen Weg haben. Wenn der Fahrlehrer mir einen ungültigen Weg vorgibt (Einbahnstraße), dann muss ich ihn ablehnen und mir einen neuen Weg geben lassen. Es ist nicht vorgesehen, dass ich mir selbstständig einen neuen Weg suche.

Das ist einer der Gründe warum root bei der Auswahl der Programme die er startet etwas mehr Sorgfalt walten lassen muss.
Durchaus richtig, allerdings ist das für ein normales OS meiner Meinung nach nicht ausreichend. Es sei denn, du hast zwei Systemadministrator-Accounts, von denen "toor" weniger Grenzen gesetzt sind (Treiber, Services) und "root" garkeine Grenzen hat.

Das umzusetzen ist aber wesentlich mehr Aufwand.

Ich bin aber trotzdem der Meinung das 75% für den nichtauslagerbaren Speicher eine gute Obergrenze ist bei der dann der VMM erst mal Schluss macht weiteren Speicher an Prozesse (egal ob root oder nicht) zu vergeben sondern nur noch für den Kernel selber was hergibt. Mehr als 20% nichtauslagerbaren Speicher sollte es in einem normalen System eigentlich nicht geben.
Ist eine gute Faustregel, sollte aber zusätzlich noch abhängig von vorhandenen RAM sein. Etwas in der Art "max(RAMSIZE*0.75 || RAMSIZE-200M)", aber das sind Details.

machst du es wie Microsoft und erweiterst deine API um neue Funktionen, fasst deine bereits vorhandenen aber nie wieder an
Also es gibt in der inoffiziellen WIN-API durchaus immer wieder Änderungen, manchmal sogar von einem SP zum nächsten.
Richtig, diese API ist aber nicht zu verwenden und wenn du es tust, bist du selbst schuld.

Die undokumentierte Windows-API entspricht den OS-Services von FlashBurn. ;-) Man kann sie als Anwendung benutzen, sollte sie aber in Libraries wegabstrahieren und dann diese Libraries benutzen.

Zitat von: erik
Es soll aber wirklich Probleme geben die sich mit Rekursion am einfachsten und effizientesten lösen lassen.
Ich behaupte mal das man jeden Algo in einen Iterativen Umschreiben kann (ja der Aufwand wird sich bei vielen Sachen wahrscheinlich nicht lohnen) oder man Rekursion iterativ simulieren.
QuickSort? Ist ein rekursives Verfahren, für so ziemlich jeden Anwendungsfall optimal. Die Rekursionstiefe hängt vom Sortierzustand der Datenbasis ab.

Der eigentliche Punkt ist doch, du weißt wenn du Rekursion nimmst in welche Richtung das geht und wenn du mit 1MB Stack nicht hinkommst dann musst du deinen Code halt anpassen (z.B. in dem du nen Thread erstellst der nen größeren Stack hat).
Richtig. Lässt sich wieder über ein Flag lösen. Das OS erstellt z.B. immer 1MB Stack, wenn deine Anwendung extensiv Stackverbrauch betreibt, muss sie einen eigenen Thread mit mehr erstellen.

Der Linux-Kernelstack hat übrigens 8 KB. In der Konfiguration kann man auch auf 4 KB umstellen und gucken, welche Treiber damit nicht auskommen, damit diese gefixt werden können.

Drehen wir den Spieß doch mal um. Hast du denn eine Idee wie man ein malloc() implementieren kann so dass man möglichst wenig Verschnitt hat, möglichst wenig Speicherverbrauch (die Verwaltung) und möglichst schnell ist und skalieren soll das ganz auch noch. Viel Spaß ;)
Da wären wir wieder bei meiner Aussage, dass Speicherverbrauch mal Rechenzeit eine Konstante ist. Du kannst eins gegen das andere eintauschen, musst aber einen Kompromiss finden.

Du optimierst extensiv für minimalen Speicherverbrauch (gcc -Os und entsprechende Algorithmen), Erik optimiert für hohe Ausführungsgeschwindigkeit (gcc -O3 und entsprechende Algorithmen). Beides zusammen geht nicht.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 13. November 2010, 14:36
Zitat von: svenska
QuickSort? Ist ein rekursives Verfahren, für so ziemlich jeden Anwendungsfall optimal. Die Rekursionstiefe hängt vom Sortierzustand der Datenbasis ab.
Ich muss zugeben das ich mich schon eine Weile nicht mehr mit QuickSort beschäftigt habe. Ich bleibe aber dabei das man das weiß und somit einen größeren Stack "beantragen" kann.

Zitat von: svenska
Der Linux-Kernelstack hat übrigens 8 KB.
Meine Kernel-Threads bekommen auch nur 4KB Stack und damit hatte ich bisher keine Problem, aber bei mir wird da ja auch weniger gemacht.

Zitat von: svenska
Da wären wir wieder bei meiner Aussage, dass Speicherverbrauch mal Rechenzeit eine Konstante ist. Du kannst eins gegen das andere eintauschen, musst aber einen Kompromiss finden.
Glaube ich nicht. Denn es wird sich bestimmt irgendwann mal ein intelligenter Algo finden lassen, der alles ganz gut miteinander verreint. Bis dahin ist das natürlich richtig.

Zitat von: svenska
Du optimierst extensiv für minimalen Speicherverbrauch (gcc -Os und entsprechende Algorithmen), Erik optimiert für hohe Ausführungsgeschwindigkeit (gcc -O3 und entsprechende Algorithmen). Beides zusammen geht nicht.
Das stimmt so auch nicht. Also ich optimiere nicht extensiv für einen minimalen Speicherverbrauch, aber ich gehe auch nicht verschwenderisch mit dem Speicher um (ich habe einige Sachen die eher Geschwindigkeit als Speicherverbrauch bevorzugen).
Es gibt sogar nicht wenige Sachen wo ein -Os schnelleren Code erzeugt als einen -O3. Denn das Problem sind die Caches und wenn der Code zu groß wird (und die Caches voll sind und viel nachgeladen werden muss) kann es passieren das kleinerer Code (der "schneller" in die Caches passt) schneller abgearbeitet ist als eigentlich hoch optimierter Code (bestes Bsp. ist das unrollen einer Schleife).
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 13. November 2010, 19:02
Hallo,

es gibt sicherlich verschiedene Algorithmen, aber es gibt für viele Problemstellungen mathematische Beweise über den minimalen Aufwand, den man treiben muss. Gedankenexperiment dazu ist, dass du ja Zwischenergebnisse entweder zwischenspeicherst (d.h. Speicher "verschwendest") oder für jeden Zwischenschritt neu ausrechnest (d.h. Rechenzeit "verschwendest") und das ist vom Algorithmus unabhängig.

Was den Unterschied zwischen "-O3" und "-Os" angeht, so findet das Loop-Unrolling auch nur begrenzt statt, z.B. 16x, ist aber auch abhängig von dem eingestellten Prozessor (und damit indirekt von der Cache-Größe der Prozessoren). Darum sind spezielle Optimierungen auch immer sehr spezifisch. Anderes Beispiel sind Intel Core2 gegen AMD Opteron, wo der Core2 ähnlich schnell ist, aber für die 64-Bit-Multiplikation die dreifache Zeit braucht. Im Endeffekt lohnt es sich dann, auf 32-Bit-Multiplikation (oder Addition, wenn das reicht) umzustellen.

Der Grundgedanke, dass RAM*CPU=const bei gegebener Optimierungarbeit gilt, bleibt.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 13. November 2010, 20:10
Zitat von: svenska
Der Grundgedanke, dass RAM*CPU=const bei gegebener Optimierungarbeit gilt, bleibt.
Das kommt halt auf den Algo an. Es gibt Algos die sind schnell, aber haben nen ganz schönen Verschnitt, es gibt Algos die sind nicht so schnell und haben nicht so viel Verschnitt und es gibt inzwischen Algos die sind schnell und haben nicht so viel Verschnitt (und die wären ja deiner Meinung nach nicht möglich).
Um es genauer zu sagen, gibt es halt inzwischen O(1) Implemenationen, die schnell sind und wenig Verschnitt haben.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 13. November 2010, 21:05
Hallo,


Zitat von: erik
Definiere Bitte "normal"!
Hatte ich doch dahinter geschrieben, eine MMU und keine Segmente, halt FlatMemory mit Paging.
Und warum ist gerade das "normal"? Und jetzt komm mir bitte nicht mit Statistik! ;)

Der eigentliche Punkt ist doch, du weißt wenn du Rekursion nimmst in welche Richtung das geht und wenn du mit 1MB Stack nicht hinkommst dann musst du deinen Code halt anpassen (z.B. in dem du nen Thread erstellst der nen größeren Stack hat).
Richtig, ich (als Programmierer) weiß wenn ich Rekursion benutze aber ich weiß nicht im voraus wie tief die zur Laufzeit wirklich geht.
Denke an:
QuickSort? Ist ein rekursives Verfahren, für so ziemlich jeden Anwendungsfall optimal. Die Rekursionstiefe hängt vom Sortierzustand der Datenbasis ab.
Und genau diesen Sortierzustand kennt man nicht im voraus! Also ist es besser wenn der Stack in seiner Größe möglichst flexibel ist und gerade das kann ein Flat-Memory-OS eben nicht bieten.
Stell Dir vor Du hast 100 Worker-Threads die immer wieder solche Aufgaben bekommen, ab und an mal ist eine dieser Aufgaben etwas komplexer und der Thread benötigt (zeitweise) deutlich mehr Stack. Für alle Worker-Threads im von vornherein den maximal benötigten Stack zu geben geht nicht weil Dir damit der virtuelle Adressraum aus geht. Also was machst Du? Du brichst bei einer bestimmten Rekursionstiefe ab und greifst auf einen langsameren Backup-Algorithmus zurück (der nicht so viel Stack benötigt).

Drehen wir den Spieß doch mal um. Hast du denn eine Idee wie man ein malloc() implementieren kann so dass man möglichst wenig Verschnitt hat, möglichst wenig Speicherverbrauch (die Verwaltung) und möglichst schnell ist und skalieren soll das ganz auch noch. Viel Spaß ;)
"Nichts leichter als das!" dachte sich der große Erik und es geschah das der genialste Code aus seinen geschmeidigen Händen sprudelte um sich über der klappernden Tastatur zu ergießen so wie das reinste Wasser aus den ewigen Quellen des geheiligten Flusses sprudelt um ins unendliche Meer zu strömen. ;)
Einige Stunden später stellte der große Erik dann entsetzt fest das sein Code nicht gegen doppeltes free (mit dem selben Pointer) gefeit ist. :cry:
Tja, es war eine wirklich gute Idee mit extremer Performance (natürlich nur wenn man unabhängige Segmente hat) aber dieser Makel beim free lässt sich leider nur umständlich und unperformant beheben, aber ich will noch mal ein paar Nächte drüber schlafen (ich könnte ja die Verwaltungsinformationen doch in ein extra Segment auslagern, damit kämme zum Verschnitt durch Aufrunden der Objektgröße noch ein fester Overhead für die Belegt/Frei-Information).

Wenn wir mal deine Slabs mit 512KB nehmen ....
Bei meinen 512kB-Slabs hab ich im Schnitt immer etwa einen halben Slab unbelegt und damit 256kB Verschnitt. Da ich nur etwa 6 Objekt-Größen in meinem Kernel hab kann ich damit recht gut Leben. Ein paar dieser Objekte (z.B. der Thread-Descriptor) sind aber schon recht groß so das ich auch bemüht bin innerhalb eines Slabs nicht zu viel Verschnitt zu haben und da ich (wegen einfacher Verwaltung) nur eine Slab-Größe haben möchte muss ich eben einen Kompromiss finden. Aber das sind alles bereits zur Design-Zeit bekannte Größen die ich mal (wenn alles einigermaßen fertig ist) gegeneinander abgleichen muss um dann einen möglichst optimalen Kompromiss zu finden.


Darum bringt das System eine sinnvolle Automatik mit.
Naja, ob eine Automatik immer ein sinnvolles Ergebnis liefert würde ich doch mal bezweifeln, aber lassen wir das.
Schade das Du ausgerechnet auf Sätze wie diesen nicht eingegangen bist:
Betrachte es mal von einer anderen Seite: normalerweise hast Du dieses Parameter nicht und musst Dich eh auf die Automatik vom OS verlassen aber bei FlashBurns OS gibt es die Möglichkeit diese Automatik wenigstens zu versuchen zu Deinen Gunsten zu beeinflussen. Egal wie Du das Flag benutzt Dir entsteht, gegenüber einem OS ohne dieses Flag, erst mal kein Nachteil. Du kannst also nur gewinnen.

Ich frage ja auch nicht im Supermarkt nach Vollmilch und kriege nur fettfreie Milch. Da kommt dann erstmal ein "gibts nicht"
Sicher, aber ein guter Verkäufer wird Dir noch im selben Atemzug die fettfreie Milch empfehlen.
Und mir die Möglichkeit einer Entscheidung geben.
Ich betrachte das Flag als Möglichkeit dem OS gleich im voraus Deine Entscheidung mitzuteilen. Du sagst also wie Du es gern hättest falls überhaupt eine Wahlmöglichkeit besteht und wenn die Wahl nicht möglich ist musst du eh mit dem auskommen was geht. Das i-Tüpfelchen wäre noch wenn Dir das OS zusätzlich mitteilt was es gemacht hat, so kannst Du Dich dem (also dem was das OS als Maximum für Dich tun konnte) so gut es geht anpassen.

Automatik allein reicht aber oft nicht um eine gute Entscheidung zu treffen, oft ist es eben sinnvoll der Automatik wenigstens einen Hinweis zu geben.
Den sie entweder ignoriert (dann hätte ich den Hinweis nicht gebraucht) oder umsetzt (dann hätte ich es auch erzwingen können).
Du weißt aber nicht im voraus welche von beiden Situationen dann zur Laufzeit tatsächlich herrscht. Eben deswegen ist es gut wenn Du dem OS für beide Fälle sagen kannst was Dir am liebsten wäre.


Der Grundgedanke, dass RAM*CPU=const bei gegebener Optimierungarbeit gilt, bleibt.
ACK! Mal abgesehen davon das doch immer mal wieder ein schlauer Mensch eine geniale Idee hat wie man ein bestimmtes Problem doch einfacher lösen kann, aber das kommt nur recht selten vor.
Ein gutes Beispiel ist da die Kryptographie, mancher Algorithmus der vor 20 Jahren noch als absolut unknackbar (selbst mit unbegrenzter Rechenleistung und unbegrenzter Speicherkapazität) angesehen wurde lässt sich heute mit nem simplen programmierbaren Taschenrechner (der auch schon vor 20 Jahren existierte) knacken.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 13. November 2010, 21:46
Zitat von: erik
Und warum ist gerade das "normal"? Und jetzt komm mir bitte nicht mit Statistik!
Normal ist für gewöhnlich das was die Mehrheit ist und das dürften FlatMemory Modelle sein. Ich weiß auch nicht warum dem so ist, aber es wird schon seinen Grund haben.

Zitat von: erik
Und genau diesen Sortierzustand kennt man nicht im voraus! Also ist es besser wenn der Stack in seiner Größe möglichst flexibel ist und gerade das kann ein Flat-Memory-OS eben nicht bieten.
Und trotzdem wird QuickSort benutzt, was mir sagt, das es irgendwie lösbar ist. Zumal müsstest du doch den worst-case anhand der Datenmenge abschätzen können bzw. spontan fällt mir ein,halt nen QuickSort zu "simulieren". Ich meine damit, dass du deine Datenmenge in mehrere Teile einteilst und die erst sortierst (im Endeffekt das was QuickSort macht) und wenn du die sortiert hast, sortierst du die ganze Datenmenge, da die Teilmengen schon sortiert sind, müsste das ganze ja jetzt mit weniger Rekursionen auskommen.
Es gibt immer einer Lösung ob die schnell und elegant ist sei mal dahin gestellt.

Zitat von: erik
(natürlich nur wenn man unabhängige Segmente hat)
Es ist schön das du uns die Vorteile deiner Architektur zeigen kannst, aber was die Diskussion betrifft ist das immer schlecht, weil wir ja eigentlich Probleme diskutieren die eine FlatMemory Architektur betrifft.
Das wäre wie, ich habe nen Algo gefunden der alle anderen auf Single CPU Systemen schlägt, aber auf SMP Systemen ist er grotten schlecht (und die Diskussion war wie man es auf SMP Systemen schnell lösen kann).

Was eure (erik und svenska) Diskussion betrifft, sehe ich es wie erik. Du sagst dem OS wie du es gerne hättest und das OS versucht es so gut es geht umzusetzen.
Kann man mit einem Navi vergleichen. Du willst die schnellste Strecke nehmen und fährst Autobahn, dass Navi weiß aber das auf der Autobahn Stau ist (was du nicht weißt) also wird es dich umleiten, obwohl du eigentlich Autobahn gefahren wärst.

Zitat von: erik
Ein gutes Beispiel ist da die Kryptographie, mancher Algorithmus der vor 20 Jahren noch als absolut unknackbar (selbst mit unbegrenzter Rechenleistung und unbegrenzter Speicherkapazität) angesehen wurde lässt sich heute mit nem simplen programmierbaren Taschenrechner (der auch schon vor 20 Jahren existierte) knacken.
Mal davon abgesehen dass das überall so ist (anderes Bsp. der Mensch wird nie fliegen können, Erde ist Zentrum des Universums, der Mensch wird nie seinen Planeten verlassen usw.), bin ich der Meinung man sollte was solche Sachen betrifft sich nie festlegen. Denn es kann immer neues Wissen hinzukommen und miteinmal ist es doch möglich oder anders.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 13. November 2010, 23:55
Hallo,

Um es genauer zu sagen, gibt es halt inzwischen O(1) Implemenationen, die schnell sind und wenig Verschnitt haben.
Zeige mir bitte einen O(1) Sortier- und einen O(1)-Suchalgorithmus.

Schade das Du ausgerechnet auf Sätze wie diesen nicht eingegangen bist:
Betrachte es mal von einer anderen Seite: normalerweise hast Du dieses Parameter nicht und musst Dich eh auf die Automatik vom OS verlassen aber bei FlashBurns OS gibt es die Möglichkeit diese Automatik wenigstens zu versuchen zu Deinen Gunsten zu beeinflussen. Egal wie Du das Flag benutzt Dir entsteht, gegenüber einem OS ohne dieses Flag, erst mal kein Nachteil. Du kannst also nur gewinnen.
Nun, das ist unbestreitbar korrekt und da ich merke, dass ich mit meiner Meinung auf verlorenem Posten stehe, wollte ich da nicht weiter argumentieren... ;-) Ich bin halt nach wie vor der Meinung, dass es in jedem Fall die Möglichkeit geben sollte, ein bestimmtes Verhalten zu erzwingen. Und die Lösung mit MAP_NOW/MAP_LAZY/MAP_AUTO/MAY_MAP_NOW/MAY_MAP_LAZY ist für uns alle akzeptabel.

Ein gutes Beispiel ist da die Kryptographie, mancher Algorithmus der vor 20 Jahren noch als absolut unknackbar (selbst mit unbegrenzter Rechenleistung und unbegrenzter Speicherkapazität) angesehen wurde lässt sich heute mit nem simplen programmierbaren Taschenrechner (der auch schon vor 20 Jahren existierte) knacken.
Das betrifft aber nur ganz wenige Aufgabenstellungen. Viele Aufgabenstellungen sind hinreichend auf ihre Optimierbarkeit bewiesen.

Normal ist für gewöhnlich das was die Mehrheit ist und das dürften FlatMemory Modelle sein. Ich weiß auch nicht warum dem so ist, aber es wird schon seinen Grund haben.
Sagen wirs mal so... Segmentierung auf x86 ist entstanden, weil 8-Bit-Rechner nur 16-Bit-Adressräume (64K) hatten und sich das aus Kompatiblitätsgründen besser mit Segmentierung simulieren lässt als mit flat memory. Später stellte sich aber heraus, dass damalige Compiler mit Segmentierung überhaupt nicht umgehen konnten, einmal aufgrund der Kosten für Segmentregisterwechsel (teilweise wurde jeder Zugriff mit Neuladen von DS/ES erledigt), andererseits die Compiler auch keine sinnvolle Einteilung in Segmente vornehmen konnten. Da 8-Bit-Anwendungen aber oft von Hand in Assembler programmiert und auch immer von Hand in Overlays geteilt wurden, spielte das bei der Einführung keine Rolle.

Also entstanden Flat-Memory-Systeme auf 32-Bit-Rechnern, als der Adressraum groß genug war und man sich den Segmentierungsaufwand sparen konnte. Der Grund liegt in den Compilern der 80er Jahre. Heutige Compiler könnten da wesentlich effizienter optimieren.

Und trotzdem wird QuickSort benutzt, was mir sagt, das es irgendwie lösbar ist. Zumal müsstest du doch den worst-case anhand der Datenmenge abschätzen können bzw. spontan fällt mir ein,halt nen QuickSort zu "simulieren". Ich meine damit, dass du deine Datenmenge in mehrere Teile einteilst und die erst sortierst (im Endeffekt das was QuickSort macht) und wenn du die sortiert hast, sortierst du die ganze Datenmenge, da die Teilmengen schon sortiert sind, müsste das ganze ja jetzt mit weniger Rekursionen auskommen.
Du wendest auf die Teilmengen z.B. Bubblesort an, wenn der Stack aufgebraucht ist.

Es gibt immer einer Lösung ob die schnell und elegant ist sei mal dahin gestellt.
Vergleiche mal die Laufzeit von Bubblesort und Quicksort für n Elemente (1k < n < 100k) und entscheide dann, ob dir solche Lösungen gefallen oder du nicht lieber doch den Stack allgemein größer baust. RAM ist heutzutage billig und in ausreichender Menge verfügbar.

Kann man mit einem Navi vergleichen. Du willst die schnellste Strecke nehmen und fährst Autobahn, dass Navi weiß aber das auf der Autobahn Stau ist (was du nicht weißt) also wird es dich umleiten, obwohl du eigentlich Autobahn gefahren wärst.
Ich möchte aber vorher davon erfahren. Außerdem muss ich beim Navi diese Funktion auch einschalten - ich habe die Möglichkeit, ein Wunschverhalten zu erzwingen.

bin ich der Meinung man sollte was solche Sachen betrifft sich nie festlegen. Denn es kann immer neues Wissen hinzukommen und miteinmal ist es doch möglich oder anders.
Es gibt Dinge, die sind mathematisch bewiesen und die Beweise sind mathematisch korrekt geführt worden. Das ist dann so und jede Implementierung dessen ist begrenzt. Sicherlich kann man jetzt argumentieren, dass die Mathematik vielleicht nicht korrekt ist, aber da gibt es ein Gegenbeispiel: Newtons Theorie ist nach wie vor gültig, aber mit Einschränkungen. Einstein hat Newtons Theorie nicht widerlegt, sondern erweitert. Genauso wird die Mathematik immer erweitert und jede noch so tolle Grundidee wird die jetzige Mathematik enthalten. Damit bleiben die Ergebnisse auch weiterhin gültig. Und wenn eine Aufgabe schon theoretisch nur in O(n^2) lösbar ist, so trifft dies auf jede noch so geniale Implementation zu (oder schlechter).

So Dinge wie Turing-Vollständigkeit sind zwar unglaublich dröge und unspannend, aber die Ergebnisse dessen sollte man auch als Praktiker nie wegerklären wollen. ;-) Es gibt nach wie vor Aufgabenstellungen, die theoretisch in O(n) lösbar sein sollten, es aber keinen Algorithmus gibt, der dies in unter O(e^n) lösen kann. Den muss man finden. Aber es gibt ihn nicht überall.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Jidder am 14. November 2010, 00:38
Zeige mir bitte einen O(1) Sortier- und einen O(1)-Suchalgorithmus.
#define popcount(x) __builtin_popcount(x)

/* Sortiert die Bits in einem Integer */
unsigned sort(unsigned x)
{
return (1 << popcount(x)) - 1;
}
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 14. November 2010, 00:40
Erklär mal.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Jidder am 14. November 2010, 00:56
Wenn das an mich gerichtet war: Ja, ich gebe ja zu, dass der Code nicht besonders lustig war ... Aber immerhin gibt es mal wieder eine kurze Antwort hier im Thread.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 14. November 2010, 08:09
Zitat von: svenska
Zeige mir bitte einen O(1) Sortier- und einen O(1)-Suchalgorithmus.
Schonmal was von nem O(1) Scheduler gehört, der macht ja im Endeffekt auch nichts anderes als sortieren und suchen. Du musst die Menge der Elemente nur weit genug einschränken und schon geht das ;)

Bestes Bsp. wäre sogar ein malloc() wo ein SlabAllocator dahinter steckt der nur Blöcke mit 2er Potenzen hat.

Und ein anderes Bsp. was ich neulich gefunden habe: http://rtportal.upv.es/rtmalloc/ (http://rtportal.upv.es/rtmalloc/)

Zitat von: svenska
Vergleiche mal die Laufzeit von Bubblesort und Quicksort für n Elemente (1k < n < 100k) und entscheide dann, ob dir solche Lösungen gefallen oder du nicht lieber doch den Stack allgemein größer baust. RAM ist heutzutage billig und in ausreichender Menge verfügbar.
Aber was ist mit meiner Idee QuickSort halt in Schritten anzuwenden?

Zumal es ja darum ging das du ab einer bestimmten Anzahl von Threads die Stacks nicht mehr vergrößern kannst und da hilft dann auch mehr RAM nicht mehr, sondern eine Lösung ist 64bit Architektur.

Die andere Lösung wären Stacks die nicht zusammenhängend sind. Ich habe sowas mal im Zusammenhang mit Linux gelesen, weiß aber nicht mehr wo das war oder wie das funktionieren soll.

Zitat von: svenska
Und wenn eine Aufgabe schon theoretisch nur in O(n^2) lösbar ist, so trifft dies auf jede noch so geniale Implementation zu (oder schlechter).
Wimre dann hatte ich in theoretischer Informatik mal, das man bei genau solchen Problemen sich einfach "bessere" Bedingungen (z.B. die Menge der Elemente einschränken o.ä.) schafft damit man die Probleme halt schneller lösen kann.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 14. November 2010, 10:45
Hallo,


Normal ist für gewöhnlich das was die Mehrheit ist
Ja, wenn auch eine seltsame Definition! Und die Mehrheit sind Herdentiere die einfach irgendeinem Leithammel hinterher trotten? Du warst es doch der hier mehrfach für neue Wege plädiert hat.

Ich weiß auch nicht warum dem so ist, aber es wird schon seinen Grund haben.
Die Gründe hat svenska ja recht gut zusammengefasst. Das DS/ES permanent neu geladen werden müssen liegt vor allem daran das es nur diese 2 frei benutzbaren Segmentregister auf x86 gibt. Schau Dir mal MOVS an, das ist das selbe Problem wie bei "Akkumulator-Zentriert", wenn man sich auf eine einzelne zentrale Ressource konzentriert dann ist die einfach überlastet und kann nicht skalieren (schade eigentlich das für die Segmentregister und deren Shadows nie Registerrenaming eingeführt wurde). Ich hab 16 Segmentregister und jeder Speicherzugriff kann jedes davon frei benutzen (nur beim letzten gibt es ein paar Einschränkungen da es für das Code-Segment reserviert ist).

Und trotzdem wird QuickSort benutzt, was mir sagt, das es irgendwie lösbar ist.
Quicksort wird nur da eingesetzt wo die Menge der Daten (und damit die maximalste Rekursionstiefe) überschaubar ist, so das der Programmierer sich sicher sein kann das der Stack reicht.

Zitat von: erik
(natürlich nur wenn man unabhängige Segmente hat)
Es ist schön das du uns die Vorteile deiner Architektur zeigen kannst, aber was die Diskussion betrifft ist das immer schlecht, weil wir ja eigentlich Probleme diskutieren die eine FlatMemory Architektur betrifft.
Sorry, aber ich entwickle eben eine Architektur mit Segmentierung (da hab ich auch nie ein Geheimnis draus gemacht!!) und daher ist das auch das worauf ich mich (oft aber nicht immer) beziehe. Ich denke das ich mich recht gut in die Bedürfnisse und Probleme einer Flat-Memory-Architektur hineinversetzen kann und auch entsprechend passende Antworten gebe, umgedreht erscheint mir das ziemlich selten (jedenfalls kann ich mich nur vereinzelt daran erinnern gut auf Segmentierung passende Antworten bekommen zu haben). Bitte nicht falsch verstehen, ich weiß das ich hier der Exot bin und erwarte auch nicht das ich von den "Normalen" exakt passende Antworten bekomme aber wenigstens etwas Toleranz wäre nicht schlecht.
Ich denke übrigens das ich meinen Heap doch mit 2 Segmenten pro Objekt-Größe ausstatten werde, das sogt nicht nur für ein zuverlässiges free sondern auch dafür das der User-Code erstmal keine Chance hat meine Verwaltungsstrukturen zu manipulieren (auch wenns nur "Security by Obscurity" ist, aber da mein Kernel die Segmentselectoren mit einem echten RNG vergibt ist das Okay).

.... (und die Diskussion war wie man es auf SMP Systemen schnell lösen kann)
Der Titel dieser Diskussion lautet "Allgemeiner Ressourcen-Allocator", insbesondere das erste Wort suggeriert mir das es hier nicht um eine bestimmte Architektur geht. ;)


Nun, das ist unbestreitbar korrekt und da ich merke, dass ich mit meiner Meinung auf verlorenem Posten stehe, wollte ich da nicht weiter argumentieren... ;-)
Okay, Thema erledigt.

Und die Lösung mit MAP_NOW/MAP_LAZY/MAP_AUTO/MAY_MAP_NOW/MAY_MAP_LAZY ist für uns alle akzeptabel.
Dann weiß FlashBurn ja jetzt wie er das umsetzen muss. ;)

Das betrifft aber nur ganz wenige Aufgabenstellungen. Viele Aufgabenstellungen sind hinreichend auf ihre Optimierbarkeit bewiesen.
Natürlich, ich wollte mit der Kryptographie auch nur mal eine der wenigen Ausnahmen zeigen.

Heutige Compiler könnten da wesentlich effizienter optimieren.
Ob das stimmt werde ich sicher bald rausbekommen, aber ich bin guter Dinge.

RAM ist heutzutage billig und in ausreichender Menge verfügbar.
Aber der eine virtuelle Adressraum ist eben begrenzt und kann auch noch fragmentieren (und es gibt keine Möglichkeit zum defragmentieren, genau das hab ich ja mit meinen Segmenten). Cool wäre es wenn man mehrere Paging-Directorys hätte (auf x86 also mehrere CR3) und man bei jedem Speicherzugriff eines davon wählen könnte, dann hätte man mehrere unabhängige Adressräume. Aber bei dem riesigen Speicherverbrauch für die vielen Paging-Directorys erscheint mir die Segmentierung dann doch die bessere Wahl zu sein (obwohl das Ergebnis schon ziemlich ähnlich ist).

Außerdem muss ich beim Navi diese Funktion auch einschalten - ich habe die Möglichkeit, ein Wunschverhalten zu erzwingen.
Natürlich, niemand will Dir das Recht auf einen guten Stau wegnehmen (außer die Grünen vielleicht). ;)


Bestes Bsp. wäre sogar ein malloc() wo ein SlabAllocator dahinter steckt der nur Blöcke mit 2er Potenzen hat.
Aber auch der kann seine Objekt-Pools nicht als beliebig großes Array (mit O(1)-Komplexität) verwalten (das ist das was ich mache) sondern muss die in mehreren Stücken (die Slabs) über den einen virtuellen Adressraum verteilen. Mich würde in diesem Zusammenhang mal interessieren wie das free von dem übergebenen Pointer auf die Objekt-Größe schließen kann um es dann auch in den richtigen Verwaltungsstrukturen als frei zu markieren.

sondern eine Lösung ist 64bit Architektur
Aber auch bei 64Bit sind die einzelnen Stack nicht unabhängig von einander und können sich gegenseitig blockieren (wenn auch auf einem größeren Niveau). Mein Angriff von http://forum.lowlevel.eu/index.php?topic=2224.msg25456#msg25456 (http://forum.lowlevel.eu/index.php?topic=2224.msg25456#msg25456) funktioniert auch bei 64 Bit.

Die andere Lösung wären Stacks die nicht zusammenhängend sind. Ich habe sowas mal im Zusammenhang mit Linux gelesen, weiß aber nicht mehr wo das war oder wie das funktionieren soll.
Hm, da wird wohl versucht eine Murks-Krücke mit einer weiteren Murks-Krücke zu stützen. Ich bin entsetzt, obwohl eigentlich ist man ja so eine Vorgehensweise gewohnt. :(


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 14. November 2010, 11:11
Zitat von: erik
Du warst es doch der hier mehrfach für neue Wege plädiert hat.
Ich sag ja auch gar nichts gegen neue Wege oder deine Architektur, es ist halt das Problem, dass deine Architektur noch nicht in Hardware existiert, das ich (oder wer auch immer) sie nicht haben kann und das ein OS welches für Segmente geschrieben (und optimiert) wurde sehr schlecht auf ne FlatMemory Architektur portierbar sein dürfte.

Zitat von: erik
umgedreht erscheint mir das ziemlich selten (jedenfalls kann ich mich nur vereinzelt daran erinnern gut auf Segmentierung passende Antworten bekommen zu haben).
Das könnte daran liegen das es nicht so einfach zu verstehen ist ;) Ich habe jedenfalls immernoch Probleme damit.

Zitat von: erik
aber wenigstens etwas Toleranz wäre nicht schlecht.
Ach ich denke Toleranz ist nicht das Problem. Es ist halt nur doof wenn einer sagt, ich habe hier folgendes Problem ... und du dann antwortest, auf meiner Architektur mit meinen Segmenten würde ich das so und so lösen. Ist zwar schön zu wissen, aber geholfen hat es nichts ;)

Für sowas wäre es am besten wenn du extra Threads aufmachst (Problem wäre wahrscheinlich das sich kaum Leute finden lassen die dann mit diskutieren können/wollen) oder die Diskussion so allgemein gehalten wird das es für beides gilt.

Hast du es eigentlich schonmal versucht deine Architektur in anderen Foren vorzustellen und die Feedback/Meinungen einzuholen (nichts gegen die Leute hier, aber es gibt Foren, wo ich mal behaupte das sich mehr und fähigere Leute rumtreiben)?

Zitat von: erik
Aber auch der kann seine Objekt-Pools nicht als beliebig großes Array (mit O(1)-Komplexität) verwalten (das ist das was ich mache) sondern muss die in mehreren Stücken (die Slabs) über den einen virtuellen Adressraum verteilen.
Das ist z.B. glaub ich ein Punkt wo sich mein OS von anderen unterscheidet. Denn bei mir macht das nicht der User sondern der Kernel. Du rufst nur den Kernel auf das du neuen Speicher brauchst und wieviel und bekommst (sofern genug virtueller Speicher frei ist) nen Pointer auf solch einen Bereich zurück.
Bei mir besteht der SlabAllocator im Endeffekt nur aus erstes Element der Liste entfernen und Element an den Anfang der Liste packen.
Was bei mir nicht O(1) ist, ist unter Umständen das Suchen nach der Liste (unter Linux geht das).

Zitat von: erik
Mich würde in diesem Zusammenhang mal interessieren wie das free von dem übergebenen Pointer auf die Objekt-Größe schließen kann um es dann auch in den richtigen Verwaltungsstrukturen als frei zu markieren.
Dafür gibt es mehrere Möglichkeiten, einmal hat man BoundaryTags (also vor dem Pointer steht wie groß der Bereich ist und eventuell mehr) und einmal speicherst du die Info welcher Slab-Struktur verwendet wird in der Struktur für die physische Page (unter Linux wird das so gemacht, aber das verbraucht mir zu viel Speicher).

Um mal die Möglichkeit von Linux zu diskutieren. Die haben ja ne page_t Struktur, wo die so einige Infos speicher, z.B. wie oft ne Page gemappt ist, sowas ähnliches habe ich ja auch (ist ein großes Array) und wenn ich wollte könnte ich dieses Array bzw. die Elemente darin ja um 4Byte größer machen damit ich darin nen Pointer auf ne Slab-Struktur speichern könnte.
Dazu hätte ich dann mal 2 Fragen. Ich sehe es halt so das der Speicherverbrauch eigentlich zu hoch dafür ist (die paar Pages die in meinem Kernel dann gemappt werden, max 1GB) oder wie seht ihr das?
Das nächste ist, was könnte man in so einem Pointer/Wert noch speichern, damit es sich doch lohnen würde das zu nutzen?

Problem was ich sehe, ist das es nur Infos sein dürfen die ich im Kernel brauchen könnte. Denn extra nen Syscall zu machen um die Info aus diesem Pointer zu bekommen, kostet mehr Performance als sie bringen würde und ist ein Sicherheits-Problem.

Zitat von: erik
Mein Angriff von http://forum.lowlevel.eu/index.php?topic=2224.msg25456#msg25456 funktioniert auch bei 64 Bit.
Ich habe mir jetzt nicht alles durchgelesen, aber es läuft ja darauf hinaus, das ne Guard-Page von 4KB unzureichend erscheint, da man mit nem Index von nem Array leicht über diese Grenze hinauskommt.
Ich sehe es eigentlich so, na und! Ist ein Fehler des Programms und entweder es schmiert ab (weil keine Zugriffsrechte) oder es wird was überschrieben und schmiert dann ab oder es kann fremder Code ausgeführt werden. Bei letzterer Variante ist halt das Programm schuld, sicher sollte man als OS versuchen so gut es geht sich auch vor solchen Problemen zu schützen, aber ich weiß nicht ob das Verhältnis von Aufwand Nutzen da so toll ist.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 14. November 2010, 17:39
Hallo,


und das ein OS welches für Segmente geschrieben (und optimiert) wurde sehr schlecht auf ne FlatMemory Architektur portierbar sein dürfte.
Ein OS für Segmentierung ist überhaupt nicht auf Flat-Memory portierbar.

Das könnte daran liegen das es nicht so einfach zu verstehen ist
Naja, es gibt einfach nicht viele Leute die sich damit überhaupt schon mal (wenn auch nur gedanklich) beschäftigt haben. :(
Aber wenn ich mal so weit bin wird sich das sicher ändern. :-D

Es ist halt nur doof wenn einer sagt, ich habe hier folgendes Problem ... und du dann antwortest, auf meiner Architektur mit meinen Segmenten würde ich das so und so lösen. Ist zwar schön zu wissen, aber geholfen hat es nichts ;)
Vor allem weil meine Antworten ab und an mal nach dem Prinzip "Ich hab Segmente, ich hab Dein Problem prinzipiell nicht, ätsch bätsch!" sind. Ich bin aber wirklich der Meinung das mir die Segmente mehr Probleme lösen als schaffen, aber ob das auch in der Realität so ist muss ich noch beweisen. Ich gebe aber auch manchmal Antworten die sehr gut (auf Flat-Memory) passen.

Hast du es eigentlich schonmal versucht deine Architektur in anderen Foren vorzustellen und die Feedback/Meinungen einzuholen (nichts gegen die Leute hier, aber es gibt Foren, wo ich mal behaupte das sich mehr und fähigere Leute rumtreiben)?
Nein, bis jetzt noch nicht. Hast Du da ein paar Vorschläge, was die Leute hier von meinen Plänen halten weiß ich nun ziemlich genau aber an neuem Feedback wäre ich schon sehr interessiert.
Mein Problem ist das wenn ich das Wort "Segmentierung" benutze dann bekomme ich entweder Schulterzucken als Antwort oder manche Leute (die alt genug sind) erinnern sich an den x86-Real-Mode und erklären das Segmentierung voll Sch.... ist. Mit wirklicher dynamischer Segmentierung (wie sie auch der 386 im PM bietet, zumindest in brauchbaren Ansätzen) haben sich einfach nie wirklich Leute beschäftigt.

Denn bei mir macht das nicht der User sondern der Kernel. Du rufst nur den Kernel auf das du neuen Speicher brauchst und wieviel und bekommst (sofern genug virtueller Speicher frei ist) nen Pointer auf solch einen Bereich zurück.
Du machst den Heap für den User-Mode im Kernel? Und dann noch Page-basiert? Gibt es dann jedes mal 4032 Bytes Verlust wenn ich ein Objekt mit 64 Bytes allozieren will?

Zitat von: erik
Mich würde in diesem Zusammenhang mal interessieren wie das free von dem übergebenen Pointer auf die Objekt-Größe schließen kann um es dann auch in den richtigen Verwaltungsstrukturen als frei zu markieren.
Dafür gibt es mehrere Möglichkeiten, einmal hat man BoundaryTags (also vor dem Pointer steht wie groß der Bereich ist und eventuell mehr)
Das klingt nicht sehr vertrauenerweckend, das kann doch die Applikation ganz schnell mal kaputt machen und dann ist die Hölle los.

und einmal speicherst du die Info welcher Slab-Struktur verwendet wird in der Struktur für die physische Page (unter Linux wird das so gemacht, aber das verbraucht mir zu viel Speicher).
Das ist aber IMHO nichts für den User-Mode-Heap, für den Kernel selber sieht das aber schon recht interessant aus. Es geht mir aber auch um den SlabAllocator für den User-Mode-Heap und da scheint es nicht viel besseres als suchen zu geben weil man dem Pointer die Größe des Objekts nicht einfach so ansieht (und wieder ein Vorteil meiner Segmente, über den Selector kann ich genau sagen in welchem Segment das Objekt liegt und dann kurz nachschauen welche Objektgröße darin untergebracht ist).

Zitat von: erik
Mein Angriff von http://forum.lowlevel.eu/index.php?topic=2224.msg25456#msg25456 funktioniert auch bei 64 Bit.
Ich habe mir jetzt nicht alles durchgelesen, aber es läuft ja darauf hinaus, das ne Guard-Page von 4KB unzureichend erscheint, da man mit nem Index von nem Array leicht über diese Grenze hinauskommt.
Ich sehe es eigentlich so, na und! Ist ein Fehler des Programms und entweder es schmiert ab (weil keine Zugriffsrechte) oder es wird was überschrieben und schmiert dann ab oder es kann fremder Code ausgeführt werden. Bei letzterer Variante ist halt das Programm schuld
Die Schuld für solche Probleme auf das Programm zu schieben ist IMHO der falsche Weg. Wir leben in einer Zeit wo Browser (und auch etliche andere Programme) Plug-Ins haben, die wie eine Shared-Library in den Adressraum des Browsers eingeblendet werden und dann dort alles tun können. Das viele dieser Plug-Ins auch noch Closed-Source sind macht die Situation erst recht nicht einfacher. Ein paar mehr Restriktionen damit sich eventuelle Lücken nicht zu leicht ausnutzen lassen wären schon nicht verkehrt, denn absolut fehlerfreie Software ist absolut unmöglich.

sicher sollte man als OS versuchen so gut es geht sich auch vor solchen Problemen zu schützen, aber ich weiß nicht ob das Verhältnis von Aufwand Nutzen da so toll ist.
Ich will ja nicht schon wieder die Segmente erwähnen aber mit denen ist dieses Problem recht einfach zu lösen.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 14. November 2010, 18:14
Zitat von: erik
Hast Du da ein paar Vorschläge
Naja, das Standardboard schlecht hin was osdev betrifft (behaupte ich mal) http://forum.osdev.org/index.php (http://forum.osdev.org/index.php). Ob die mit deiner Idee klarkommen, weiß ich aber nicht.

Zitat von: erik
Du machst den Heap für den User-Mode im Kernel? Und dann noch Page-basiert? Gibt es dann jedes mal 4032 Bytes Verlust wenn ich ein Objekt mit 64 Bytes allozieren will?
Genau deswegen wäre es toll wenn mir mal jemand erklärt was der Heap ist und was er macht. Denn was du mit Heap meinst, ist doch einfach nur malloc() und malloc() ruft entweder sbrk() oder mmap() auf um neuen Speicher zu bekommen.
Und genau letzteres meine ich! Was und wie malloc() dann den Speicher organisiert weiß ich nicht bzw. lege ich nicht fest.
Für mich ist der Heap einfach nur ein Speicherbereich der von malloc() genutzt wird und Page-basiert ist. Bei mir ist dieser Speicherbereich nur nicht zwingend zusammenhängend.

Zitat von: erik
Das klingt nicht sehr vertrauenerweckend, das kann doch die Applikation ganz schnell mal kaputt machen und dann ist die Hölle los.
Sorry, aber jetzt kommt bei mir wieder Unverständnis! Ihr (du und svenska) sagt, dass ein "Verbraucher" seine Ressourcen so wieder zurück gibt wie er sie bekommen hat (also nicht 3 IDs miteinmal), aber hier sagst du jetzt dass das ja doof ist und das Programm amok laufen kann.
Ist bei nem SlabAllocator nicht anders, da musst du auch darauf vertrauen das die Adresse die freigegeben werden soll auch die richtige ist.
Alles andere wäre aber auch entweder Speicherverschwenderisch und/oder langsam.

Zitat von: erik
Das ist aber IMHO nichts für den User-Mode-Heap, für den Kernel selber sieht das aber schon recht interessant aus.
Das weiß ich ja, aber ich finde es halt auch nicht so toll, wenn man für max 1GB (von 4 möglichen) Speicher verbraucht das aber für den Rest nicht nutzen kann.
Deswegen, nochmals die Frage was man in so einem Pointer/Wert noch speichern könnte, was für den Kernel vllt ganz interessant sein könnte?

Zitat von: erik
Ich will ja nicht schon wieder die Segmente erwähnen aber mit denen ist dieses Problem recht einfach zu lösen.
Wenn man den Stack wirklich nur für lokale Variablen nutzen würde und nicht die Adressen von diesen Variablen nutzen/brauchen würde, dann könnte man ganz einfach auch unter PM im 32bit Modus Segmente für den Stack nutzen. Damit wäre das Problem auch gegessen und der Compiler müsste es nicht mal wissen.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 15. November 2010, 10:07
Hallo,


Naja, das Standardboard schlecht hin was osdev betrifft (behaupte ich mal) http://forum.osdev.org/index.php (http://forum.osdev.org/index.php). Ob die mit deiner Idee klarkommen, weiß ich aber nicht.
Da bin ich mir nicht sicher ob dafür mein Englisch reicht. Aber wenn meine OpCode-Spec fertig ist sollte ich vielleicht wirklich noch mal ein bisschen mehr Feedback einholen.

Genau deswegen wäre es toll wenn mir mal jemand erklärt was der Heap ist und was er macht.
Sorry, aber so wie Du über SlabAllocator & Co geschrieben hast dachte ich eigentlich Du wüsstest das bereits. Der Heap ist der Mechanismus der dem User-Code Speicher zur Verfügung stellt, der entscheidende Punkt ist das der User-Code beliebig große Speicherstückchen anfordern darf und der Heap-Mechanismus dafür sorgen muss das da trotzdem möglichst wenig Verschnitt entsteht. Darüber hinaus ist es Aufgabe des Heap-Mechanismus ein einheitliches API (malloc/free) zur Verfügung zu stellen und die konkreten OS-Funktionen dahinter zu verstecken (wegabstrahieren).

Denn was du mit Heap meinst, ist doch einfach nur malloc() und malloc() ruft entweder sbrk() oder mmap() auf um neuen Speicher zu bekommen.
Exakt.

Was und wie malloc() dann den Speicher organisiert weiß ich nicht bzw. lege ich nicht fest.
Doch, gerade Du als OS-Entwickler musst festlegen wie die Heap-Implementierung von der zu Deinem OS gehörenden libc arbeiten soll. Da hast Du im Prinzip 2 Möglichkeiten: entweder Du nimmst ne fertige libc und musst in Deinem OS genau die Syscalls anbieten die diese libc erwartet oder Du programmierst ne eigene libc und kannst dann die speziellen Fähigkeiten Deines OS nach belieben nutzen (solange die malloc/free-API erhalten bleibt). Ich denke die meisten hier sind den zweiten Weg gegangen und ich will das auch tun.

Für mich ist der Heap einfach nur ein Speicherbereich der von malloc() genutzt wird und Page-basiert ist.
Richtig, solange die Heap-Implementierung in der Lage ist den Speicher auch in anderen Größen als in Pages dem User-Code zur Verfügung zu stellen.

Bei mir ist dieser Speicherbereich nur nicht zwingend zusammenhängend.
Das muss er auch nicht, das ist ganz allein Deine freie Entscheidung.

Zitat von: erik
Das klingt nicht sehr vertrauenerweckend, das kann doch die Applikation ganz schnell mal kaputt machen und dann ist die Hölle los.
Sorry, aber jetzt kommt bei mir wieder Unverständnis! Ihr (du und svenska) sagt, dass ein "Verbraucher" seine Ressourcen so wieder zurück gibt wie er sie bekommen hat (also nicht 3 IDs miteinmal), aber hier sagst du jetzt dass das ja doof ist und das Programm amok laufen kann.
Sorry, aber da hab ich mich wohl undeutlich ausgedrückt. Wenn die Verwaltungsstrukturen direkt neben den verwalteten Speicherblöcken liegen dann kann der User-Code diese Verwaltungsinformationen ganz leicht mal (aus versehen) überschreiben. Ich persönlich halte diese Vorgehensweise für ein erhebliches Risiko, gerade in Zeiten von allgegenwärtigen Buffer-Overflows. Ich weiß das viele Heap-Implementierungen das mit den Verwaltungsinformationen genau so machen und das dort nicht viel schlimmes passiert liegt sicher auch daran das dann oft einfache Prüfsummen u.ä. eingesetzt werden um Manipulationen zu erkennen. Trotzdem bin ich der Meinung das man die Verwaltungsinformationen von den Nutzdaten trennen sollte.

Ist bei nem SlabAllocator nicht anders, da musst du auch darauf vertrauen das die Adresse die freigegeben werden soll auch die richtige ist.
Also Vertrauen hab ich in sowas nicht all zu viel, dafür sind schon viel zu viele "fehlerfreie" Applikationen zum Problemfall geworden. Ich bevorzuge es wenn free den übergeben Pointer zu 100% auf Gültigkeit prüfen kann. So hab ich das jetzt auch in meiner User-Mode-Heap-Implementierung umgesetzt und auch das doppelte freigeben ist kein Problem mehr. Im Kernel würde ich das mit der Pointer-Prüfbarkeit wieder anders sehen, das ist ein geschlossenes System wo nur Du selber Fehler machen kannst und dort kann man z.B. für jeden Objekt-Typ/Größe ein eigenes free implementieren (im Kernel musst Du Dich nicht ans normale malloc/free-API halten).

Das weiß ich ja, aber ich finde es halt auch nicht so toll, wenn man für max 1GB (von 4 möglichen) Speicher verbraucht das aber für den Rest nicht nutzen kann.
Für den User-Space könntest Du dort den Page-Mapping-Counter (für Pages die in mehreren Adressräumen gemappt sind) unterbringen, im Kernel wirst Du doch hoffentlich kein Shared-Memory machen.

Wenn man den Stack wirklich nur für lokale Variablen nutzen würde und nicht die Adressen von diesen Variablen nutzen/brauchen würde, dann könnte man ganz einfach auch unter PM im 32bit Modus Segmente für den Stack nutzen. Damit wäre das Problem auch gegessen und der Compiler müsste es nicht mal wissen.
Das Problem ist das Du eben doch von Stack-Variablen eine Adresse generieren können musst. Im Prinzip sind die Modifikationen an einem Compiler recht überschaubar um ihm FAR-Pointer bei zu bringen. Als erstes muss der Compiler Pointer nicht auf Integer o.ä. abbilden sondern als einen eigenständigen Basis-Datentyp betrachten (das ist beim LLVM gegeben aber beim gcc nicht). Dann muss der Compiler in der Lage sein für bestimmte Datentypen mehrere Register als Einheit zu betrachten (also Segment-Register für den Selector + normales Register für das Offset) aber auch das sollte jeder Compiler können der z.B. auf einer 32Bit-CPU mit 64Bit-Integern umgehen kann. Die kritischen Stellen sind das Benutzen von Pointern (also bei jedem Speicherzugriff das richtige Segment-Register auswählen) und das Erzeugen von Pointern (also auch jedes mal das richtige Segment-Register als Quelle für den Selector benutzen). Ich denke das ich das beim LLVM alles hinbekommen werde aber wie schwierig es wirklich wird kann ich erst sagen wenn ich das auch tatsächlich geschafft hab.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 15. November 2010, 11:16
Hallo,

Schonmal was von nem O(1) Scheduler gehört, der macht ja im Endeffekt auch nichts anderes als sortieren und suchen. Du musst die Menge der Elemente nur weit genug einschränken und schon geht das ;)
Der sortiert nicht (er nimmt vorne weg und hängt hinten an) und er sucht nicht (er nimmt immer das erste Element). Definiere "sortieren" und "suchen". Wenn du n natürlich auf 1 beschränkst, weil es nur ein Element gibt, dann hat ein O(n)-Algorithmus eine O(1)-Laufzeit. Deswegen ist es aber trotzdem ein O(n)-Algorithmus.

Zitat von: svenska
Vergleiche mal die Laufzeit von Bubblesort und Quicksort für n Elemente (1k < n < 100k) und entscheide dann, ob dir solche Lösungen gefallen oder du nicht lieber doch den Stack allgemein größer baust. RAM ist heutzutage billig und in ausreichender Menge verfügbar.
Aber was ist mit meiner Idee QuickSort halt in Schritten anzuwenden?
Die ist Unsinn.

Wenn dein Quicksort feststellt, dass der Stackspace voll ist, dann müsstest du in jedem Fall einen weiteren Aufruf machen, um den Bubblesort auszuführen, und genau das geht zu dem Zeitpunkt nicht mehr. Mal abgesehen davon ist der Aufwand es nicht wert und bei Quicksort verhält sich die Rekursionstiefe auch logarithmisch zur Anzahl der Elemente.

Zumal es ja darum ging das du ab einer bestimmten Anzahl von Threads die Stacks nicht mehr vergrößern kannst und da hilft dann auch mehr RAM nicht mehr, sondern eine Lösung ist 64bit Architektur.
Dann musst du die Anzahl der Threads beschränken (macht sich im Hinblick auf Scheduler und andere Dinge im System eh besser), asynchrones I/O machen (ein Hauptgrund für Workerthreads) oder den Stack im Voraus hinreichend groß machen. Die Stackanforderungen zwischen "ls" und einer MySQL-Datenbank werden sich schon hinreichend stark unterscheiden, dass man einen kleinen Stack (z.B. 256 KB) für alles macht und Prozessen, die damit nicht klarkommen, gleich 4 MB gibt...

Zitat von: svenska
Und wenn eine Aufgabe schon theoretisch nur in O(n^2) lösbar ist, so trifft dies auf jede noch so geniale Implementation zu (oder schlechter).
Wimre dann hatte ich in theoretischer Informatik mal, das man bei genau solchen Problemen sich einfach "bessere" Bedingungen (z.B. die Menge der Elemente einschränken o.ä.) schafft damit man die Probleme halt schneller lösen kann.
Damit löst du aber die Aufgabe nicht mehr. Wenn du ein konkretes Problem hast, kannst du die Aufgabenstellung nicht immer "mal eben so" einfach ändern, nur damit man es eleganter lösen kann. Und wenn du sortieren musst, dann hängt die Sortierdauer nunmal (a) von der Anzahl der zu sortierenden Elemente und (b) vom Sortierzustand ab. Für "Anzahl=const" ist das sicherlich in O(1) machbar, aber das geht eben nicht überall.

Außerdem muss ich beim Navi diese Funktion auch einschalten - ich habe die Möglichkeit, ein Wunschverhalten zu erzwingen.
Natürlich, niemand will Dir das Recht auf einen guten Stau wegnehmen (außer die Grünen vielleicht). ;)
Bewusst in einen Stau reinfahren hatte ich auch schon. Das kommt nämlich dann zustande, wenn (bei kurzem Stau) die Umleitungsstrecke ewig viel Umweg ist und länger dauert als der Stauaufenthalt oder wenn die Ausweichstrecken ebenfalls überlastet sind. In letzterem Fall ist das oft schlimmer als der Stau selbst, weil die Strecken nicht für Massentransit gebaut sind. Es gibt schon Gründe, unter bestimmten Umständen einen Ratschlag zu ignorieren und trotzdem besser zu fahren... allerdings nicht immer.

Was bei mir nicht O(1) ist, ist unter Umständen das Suchen nach der Liste (unter Linux geht das).
Aua. Ich möchte auf deinem OS einen Benchmark machen und dir zeigen, was O(1) eigentlich bedeutet. Das Erstellen von einem Thread dauert exakt genausolange wie das Erzeugen von einer Million Threads? Ein malloc(1) dauert exakt genausolange wie ein malloc(1024*1024*1024) und ist unabhängig vom derzeitigen Zustand des physischen Speichers? Dein Mapping von physischem Speicher zu virtuellem Adressraum geschieht unabhängig von der Größe der Blöcke, unabhängig von der Fragmentierung aller Adressräume und unabhängig vom Füllstand des physischen Speichers? Ein free() dauert genausolange wie jedes andere free(), egal wo der Pointer hinzeigt?

Man kann für irgendeinen Teil ein O(1) haben, aber für das Gesamtsystem ist das wesentlich schwieriger. Mal abgesehen davon ist O(1) auch nicht immer erstrebenswert, es gibt Algorithmen mit O(1), die nicht verwendet werden, da ein O(e^n)-Algorithmus absolut gesehen schneller ist, solange man vernünftige Größen verwendet.

Um mal die Möglichkeit von Linux zu diskutieren. Die haben ja ne page_t Struktur, wo die so einige Infos speicher, z.B. wie oft ne Page gemappt ist, sowas ähnliches habe ich ja auch (ist ein großes Array) und wenn ich wollte könnte ich dieses Array bzw. die Elemente darin ja um 4Byte größer machen damit ich darin nen Pointer auf ne Slab-Struktur speichern könnte.
Ein Array ist bei bekanntem Index O(1), bei unbekanntem Index (=Suche) O(n), maximal O(log n).

Ist ein Fehler des Programms und entweder es schmiert ab (weil keine Zugriffsrechte) oder es wird was überschrieben und schmiert dann ab oder es kann fremder Code ausgeführt werden. Bei letzterer Variante ist halt das Programm schuld, sicher sollte man als OS versuchen so gut es geht sich auch vor solchen Problemen zu schützen, aber ich weiß nicht ob das Verhältnis von Aufwand Nutzen da so toll ist.
In der heutigen Zeit sollte man für Sicherheit durchaus viel Aufwand treiben... Spammer und Botnetze sind eine Folge dieses Schulterzuckens.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 15. November 2010, 12:02
Zitat von: erik
Der Heap ist der Mechanismus der dem User-Code Speicher zur Verfügung stellt, der entscheidende Punkt ist das der User-Code beliebig große Speicherstückchen anfordern darf und der Heap-Mechanismus dafür sorgen muss das da trotzdem möglichst wenig Verschnitt entsteht. Darüber hinaus ist es Aufgabe des Heap-Mechanismus ein einheitliches API (malloc/free) zur Verfügung zu stellen und die konkreten OS-Funktionen dahinter zu verstecken (wegabstrahieren).
Also können wir uns darauf einigen das der Heap == malloc()/free() ist!?

Mein Prof hat mich heute auch wieder "geärgert". Denn ich sehe es immernoch soch das malloc()/free() nichts mit dem OS zu tun zu haben. Die stehen in irgendeiner Library drin und das OS hat keine Kontrolle darüber. Genauso kann man malloc()/free() oder die Library einfach austauschen ohne dass das OS was an der Speicherverwaltung ändert.

Was mich halt dann immernoch irretiert, ist wenn Leute sagen das die UserSpace Speicherverwaltung auch komplett vom UserSpace gemacht wird (eigentlich meinen die den Heap, aber dann sollen sie es auch sagen!) und das ist halt nicht so. Der Heap kann sich bei mir halt nicht aussuchen wo neuer Speicher hingemappt wird, sondern er hat das zu nehmen was er vom OS bekommt.

Zitat von: erik
Doch, gerade Du als OS-Entwickler musst festlegen wie die Heap-Implementierung von der zu Deinem OS gehörenden libc arbeiten soll.
Siehe oben. Was ist wenn ein Programm aber ne andere libc verwendet. Alles was im UserSpace stattfindet hat mit dem OS nichts mehr zu tun (in dem Sinne das du es bestimmen kannst, mal von Apple abgesehen ;) ).

Zitat von: erik
Trotzdem bin ich der Meinung das man die Verwaltungsinformationen von den Nutzdaten trennen sollte.
Ich sage mal das du dadurch mehr Verschnitt haben wirst und wahrscheinlich sogar langsamer sein wirst (wie reden von FlatMemory ;) ).

Zitat von: erik
Für den User-Space könntest Du dort den Page-Mapping-Counter (für Pages die in mehreren Adressräumen gemappt sind) unterbringen, im Kernel wirst Du doch hoffentlich kein Shared-Memory machen.
Doch im Kernel habe ich auch SharedMemory und ich will der Struktur (die in einem Array organisiert ist) ja noch ein Element von 4Byte Größe hinzufügen, aber nicht nur weil es den SlabAllocator im Kernel schneller macht (und Speicher spart).
Damit der Speicher den ich sparen würde sich auch rentiert, müsste ich mind. 209715 Slabs in meinem Kernel haben und ich denke das sollte nicht so einfach passieren.

Zitat von: svenska
Der sortiert nicht (er nimmt vorne weg und hängt hinten an) und er sucht nicht (er nimmt immer das erste Element). Definiere "sortieren" und "suchen".
Ich füge neue Threads sortiert ein, also sortiere ich und ich suche immer den Thread mit der größten Priorität, also suche ich!
Das selbe mit dem geposteten Allocator, der sucht und sortiert auch, er macht halt nur ein paar Annahmen, naund!

Zitat von: svenska
Wenn dein Quicksort feststellt, dass der Stackspace voll ist, dann müsstest du in jedem Fall einen weiteren Aufruf machen
Ich habe gesagt, dass man das vorher entscheiden muss! Nicht erst mittendrin.

Zitat von: svenska
Dann musst du die Anzahl der Threads beschränken (macht sich im Hinblick auf Scheduler und andere Dinge im System eh besser),
Das hieße aber das ich bei vielen Sachen gar nicht die Performance bekomme die ich haben könnte und das die Anwendung nicht skaliert und das geht heutzutag gar nicht!

Zitat von: svenska
Damit löst du aber die Aufgabe nicht mehr.
Das stimmt ja so nicht. Wenn du in Physik (Schule) die Aufgabe bekommst, irgendetwas zu berechnen, machst du auch viele Annahmen, damit du die ganze Rechnung vereinfachen kannst.
Genauso macht man das bei vielen Problemen in der Informatik (frag mich bitte nicht nach einem Bsp.).

Zitat von: svenska
was O(1) eigentlich bedeutet
Die Anzahl der Schritte ist immer gleich.

Zitat von: svenska
Ein malloc(1) dauert exakt genausolange wie ein malloc(1024*1024*1024) und ist unabhängig vom derzeitigen Zustand des physischen Speichers?
Was das malloc() betrifft ist dem so. Ich kann bei so einer Betrachtung nicht noch das OS mit einbeziehen. Denn es kann ja sein das ich gar nicht weiß wie das OS es macht oder auf welchen OS mein Code läuft. Trotzdem kann malloc() O(1) sein.

[quote="svenska"
Ein Array ist bei bekanntem Index O(1), bei unbekanntem Index (=Suche) O(n), maximal O(log n).
[/quote]
Der Index dieses speziellen Arrays ist bekannt (ist einfach die Nummer der physischen Page).
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 15. November 2010, 15:50
Hallo,


In der heutigen Zeit sollte man für Sicherheit durchaus viel Aufwand treiben... Spammer und Botnetze sind eine Folge dieses Schulterzuckens.
Full ACK!


Also können wir uns darauf einigen das der Heap == malloc()/free() ist!?
Ja, im wesentlichen schon.

Denn ich sehe es immernoch soch das malloc()/free() nichts mit dem OS zu tun zu haben.
Die User-Mode-Heap-Verwaltung ist existenziell abhängig von der Verwaltung des virtuellen Adressraums die das OS zur Verfügung stellt. Also die libc (oder eben die Heap-Library) muss immer zum OS passen.

Die stehen in irgendeiner Library drin und das OS hat keine Kontrolle darüber.
Grundsätzlich hast Du recht, das OS kann die User-Mode-Heap-Verwaltung nicht direkt beeinflussen.

Genauso kann man malloc()/free() oder die Library einfach austauschen ohne dass das OS was an der Speicherverwaltung ändert.
Nur so lange die neue Library auch wieder zum OS passt. Es ist ja gerade die Aufgabe der libc dem User-Code ein paar Basis-Funktionen zur Verfügung zu stellen und eben die OS-Funktionen die das dann tatsächlich bewerkstelligen zu verstecken(wegabstrahieren), dazu ist es natürlich erforderlich das die libc zum OS passt also das OS auch so benutzen kann wie das OS eben funktioniert. Deine libc wird auf tyndur garantiert nicht laufen es sei denn Dein OS ist mit tyndur auf Syscall-Ebene exakt 100% kompatibel.

Was mich halt dann immernoch irretiert, ist wenn Leute sagen das die UserSpace Speicherverwaltung auch komplett vom UserSpace gemacht wird (eigentlich meinen die den Heap, aber dann sollen sie es auch sagen!) und das ist halt nicht so.
Doch das ist so, die User-Space-Heap-Verwaltung wird komplett im User-Space gemacht, das OS stellt nur den virtuellen Speicher zur Verfügung. Das Straßenbauamt kontrolliert ja auch nicht Deine fahrweise, es stellt nur die Straßen zur Verfügung von denen Du abhängig bist (wenn wir jetzt mal von einem nicht geländefähigem Auto ausgehen) und Deine fahrweise bestimmst Du selber (eben im Rahmen dessen was die Straßen hergeben).
Das OS macht zwar eventuell ein paar Einschränkungen weil es den virtuellen Speicher auf eine bestimmte Art verwaltet/anbietet aber so lange die libc (die ja eben zum OS passen muss) damit gut umgehen kann ist aus Sicht des User-Codes der Heap immer der selbe und völlig unabhängig vom OS.
Man kann aber auch in ein Programm eine zusätzliche Speicherverwaltung für spezielle Dinge parallel zum Heap implementieren, aber diese Speicherverwaltung ist dann auch wieder existenziell vom OS abhängig (also nicht wirklich protierbar).

Der Heap kann sich bei mir halt nicht aussuchen wo neuer Speicher hingemappt wird, sondern er hat das zu nehmen was er vom OS bekommt.
Das ist eine Designentscheidung die Du als OS-Entwickler getroffen hast, wenn die Heap-Verwaltung die Du als libc-Entwickler schreibst damit gut umgehen kann ist das auch völlig in Ordnung.
Ich persönlich würde es aber bevorzugen wenn der User-Mode-Code (also die Heap-Verwaltung) selber bestimmen kann wie sein virtueller Adressraum aufgebaut ist (in gewissen Grenzen natürlich), ich stelle mir vor (ohne es zu wissen) dass das die Heap-Verwaltung einfacher macht.

Was ist wenn ein Programm aber ne andere libc verwendet.
Dann sollte es eine libc sein die ebenfalls auf Deinem OS funktioniert ansonsten funktioniert das Programm einfach nicht (wahrscheinlich kannst Du eine fremde/unpassende libc nicht mal für Dein OS kompilieren weil in Deinen OS-spezifischen Headerfiles ja andere Syscalls deklariert sind).

Zitat von: erik
Trotzdem bin ich der Meinung das man die Verwaltungsinformationen von den Nutzdaten trennen sollte.
Ich sage mal das du dadurch mehr Verschnitt haben wirst und wahrscheinlich sogar langsamer sein wirst (wie reden von FlatMemory ;) ).
Ja, natürlich erzeugt das etwas mehr Verschnitt bzw. in diesem Fall konstanten Overhead pro Objekt, das ist auch bei mir so, denn ich wollte eigentlich die freien Objekte mit dem eigenen Speicher dieser Objekte verwalten und da gäbe es eben Probleme wenn ein Objekt doppelt dealloziert werden soll (in meinem Kernel werde ich das aber so machen weil ich da ja nicht mit fehlerhaften User-Code rechnen muss). Signifikant langsamer dürfte das aber IMHO nicht werden (nur ein ganz klein Wenig weil ein paar mehr Speicherzugriffe pro malloc/free erforderlich sind).

Doch im Kernel habe ich auch SharedMemory
Hm, na wenn das mal gut geht.

und ich will der Struktur (die in einem Array organisiert ist) ja noch ein Element von 4Byte Größe hinzufügen, aber nicht nur weil es den SlabAllocator im Kernel schneller macht (und Speicher spart).
Ist den ein schnellerer SlabAllocator kein erstrebenswertes Ziel? Ich persönlich finde schon und in meinem OS-Kernel lasse ich mir das auch gerne etwas Speicher kosten.

Zitat von: svenska
Wenn dein Quicksort feststellt, dass der Stackspace voll ist, dann müsstest du in jedem Fall einen weiteren Aufruf machen
Ich habe gesagt, dass man das vorher entscheiden muss! Nicht erst mittendrin.
Und wie willst Du vorher bestimmen wie tief QuickSort rekursieren muss? Das einzigste Kriterium was Du hast ist die Größe der zu sortierenden Daten aber damit verschenkst Du eventuell sehr viel wenn die Daten schon einigermaßen gut sortiert sind und kaum Rekursion nötig wäre.

Das stimmt ja so nicht. Wenn du in Physik (Schule) die Aufgabe bekommst, irgendetwas zu berechnen, machst du auch viele Annahmen, damit du die ganze Rechnung vereinfachen kannst.
Genauso macht man das bei vielen Problemen in der Informatik (frag mich bitte nicht nach einem Bsp.).
Dann frage ich Dich nach einem Beispiel! ;)
Wenn Du bestimmte Annahmen im Vorfeld machst dann must Du auch garantieren das diese zur Laufzeit noch gültig sind. Kannst Du das in jedem Fall?


Grüße
Rik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 15. November 2010, 16:22
Zitat von: svenska
In der heutigen Zeit sollte man für Sicherheit durchaus viel Aufwand treiben... Spammer und Botnetze sind eine Folge dieses Schulterzuckens.
Sorry, aber das ist für mich eine typisch deutsche herangehensweise (wie die es in die IT geschafft hat, ist mir ein Rätsel ;) ).
Denn was hier gemacht wird ist Symptom Bekämpfung und nicht die Ursachen beseitigen!

Warum sollte sich denn ein Programmierer um solche Sachen wie nen Bufferoverflow kümmern, wenn das OS es doch macht und es so auch noch performanter ist? Ich will nicht die Bequemlichkeit der Programmierer unterstützen.

Zitat von: erik
Doch das ist so, die User-Space-Heap-Verwaltung wird komplett im User-Space gemacht, das OS stellt nur den virtuellen Speicher zur Verfügung.
Aber welchen Teil des virtuellen Speichers der Heap belegt, das wird halt nicht (unbedingt) im UserSpace gemacht!
Ich rede da von solchen Sachen, das man es ja auch so machen könnte das der ganze virtuelle Adressraum auch selbst vom UserSpace verwaltet wird, sprich der Code entscheidet wo ne Library hingemappt wird oder nen Stack oder halt der Heap und genau das seh ich halt nicht so (und ist sei ALR - oder wie das heißt - auch nicht mehr so und ihr wollt ja Sicherheit).

Zitat von: erik
ich stelle mir vor (ohne es zu wissen) dass das die Heap-Verwaltung einfacher macht.
Wieso macht es die Heap-Verwaltung einfacher? Du musst jedes Mal auch den Fall einplanen das du den virtuellen Bereich den du haben willst nicht bekommst und wenn du den Fall eh einplanen musst, dann mach es doch gleich nur so das du den Bereich nimmst den du vom OS bekommst.

Zitat von: erik
Dann sollte es eine libc sein die ebenfalls auf Deinem OS funktioniert ansonsten funktioniert das Programm einfach nicht
Das sollte eigentlich klar sein. Ich meine sowas wie ne libc von GNU und ne libc von LLVM und ne dietlibc usw. Alle können andere Algos implementieren, aber laufen auf deinem OS.

Zitat von: erik
Signifikant langsamer dürfte das aber IMHO nicht werden (nur ein ganz klein Wenig weil ein paar mehr Speicherzugriffe pro malloc/free erforderlich sind).
Anders gefragt, was für eine Datenstruktur willst du denn einsetzen, dass es nicht signifikant langsamer wird (schreib aber bitte ob das nur mit deinen Segmenten funktioniert)?

Zitat von: erik
Hm, na wenn das mal gut geht.
Gegenfrage, was soll denn schief gehen?

Zitat von: erik
Ist den ein schnellerer SlabAllocator kein erstrebenswertes Ziel?
Du weißt doch noch die magischen 8MB ;)

Aber ich habe gerade mal nachgerechnet, bei 4Byte pro Wert, macht das zusätzliche 1KB pro 1MB RAM pro Wert, ich denke das kann man verschmerzen ;)

Jetzt mal was zu diesem Thema. Ich habe im Moment eine Struktur die so aussieht:
struct vmmSharedPageEntry_t {
struct spinlock_t lock;
uint32t refCount;
struct list_t *tasks;
};

Ich führe also nen refCount und ich trage die Prozesse ein, die diese Page nutzen. Das Lock ist halt zum Prozess eintragen da.
Meine Frage wäre jetzt, muss/sollte ich wirklich Buch führen wo (im Sinne von in welchem Prozess) eine physische Page überall gemappt ist?
Weil wenn nicht dann könnte ich das Lock und die Liste rausnehmen und beschleunige meinen SlabAllocator etwas :D

Zitat von: erik
Und wie willst Du vorher bestimmen wie tief QuickSort rekursieren muss? Das einzigste Kriterium was Du hast ist die Größe der zu sortierenden Daten aber damit verschenkst Du eventuell sehr viel wenn die Daten schon einigermaßen gut sortiert sind und kaum Rekursion nötig wäre.
Ich würde abwegen, was ist langsamer/schlechter, wenn ich irgendwann feststelle das mein Stack nicht mehr reicht und ich nen iterativen Sortier-Algo einsetzen muss oder das ich genau den von dir genannten Fall in kauf nehmen und dafür das Problem mit dem Stack gelöst habe und der worst-case wesentlich schneller ist.
Die Rekursionstiefe, würde ich anhand der Datenmenge festmachen. Was würde ich denn verschenken?

Zitat von: erik
Dann frage ich Dich nach einem Beispiel!
Hättest du dich nicht bei dem "du" mit einschließen können ;)

Zitat von: erik
Wenn Du bestimmte Annahmen im Vorfeld machst dann must Du auch garantieren das diese zur Laufzeit noch gültig sind. Kannst Du das in jedem Fall?
Da ich das nicht mathematisch beweisen kann, sage ich mal nein.

Als Bsp. würde ich jetzt malloc() nehmen. Wenn du alle Größen die auftreten können/werden sortieren willst wirst du wohl schonmal ne Weile brauche, machst du jetzt aber die Annahme das auch Blöcke mit den Größen der 2er Potenzen reichen, kannst du schon viel schneller und einfacher sortien, nämlich in O(1).
Du hast damit zwar unter Umständen einen tierischen Verschnitt, aber was solls ;)
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 15. November 2010, 17:39
Hallo,

Zitat von: svenska
In der heutigen Zeit sollte man für Sicherheit durchaus viel Aufwand treiben... Spammer und Botnetze sind eine Folge dieses Schulterzuckens.
Sorry, aber das ist für mich eine typisch deutsche herangehensweise (wie die es in die IT geschafft hat, ist mir ein Rätsel ;) ).
Denn was hier gemacht wird ist Symptom Bekämpfung und nicht die Ursachen beseitigen!

Warum sollte sich denn ein Programmierer um solche Sachen wie nen Bufferoverflow kümmern, wenn das OS es doch macht und es so auch noch performanter ist? Ich will nicht die Bequemlichkeit der Programmierer unterstützen.
Das OS kann also noch performanter Bufferoverflows verursachen? Prima...

Programmierer sind faul und bequem (andere Interpretation: unter enormem Zeitdruck, enormem Featuredruck und nur wenig Qualitätssicherung). Die Folgen sind Bufferoverflows in Betriebssystemen (inzwischen nicht mehr so häufig, solche Lücken haben enormen Marktwert), Bufferoverflows in Anwendungen (Flash, Acrobat Reader und alle Webbrowser sind derzeit bevorzugter Angriffsvektor) und Sicherheitsprobleme/falsche Annahmen in den Standardkonfigurationen (Botnetze auf Plasteroutern sind richtig prima).

Wozu Passwörter übers Netz mit MD5 hashen und auch noch verschlüsselt auf der Festplatte abspeichern?

Das sollte eigentlich klar sein. Ich meine sowas wie ne libc von GNU und ne libc von LLVM und ne dietlibc usw. Alle können andere Algos implementieren, aber laufen auf deinem OS.
Alle sind auf dein OS angepasst. Im Falle von glibc/dietlibc/newlib benutzen die Bibliotheken genau die Services, die laut POSIX-Standard vom Kernel bereitgestellt werden müssen, eventuell noch zusätzliche. Darum laufen diese auch nicht auf jedem OS...

Zitat von: erik
Und wie willst Du vorher bestimmen wie tief QuickSort rekursieren muss? Das einzigste Kriterium was Du hast ist die Größe der zu sortierenden Daten aber damit verschenkst Du eventuell sehr viel wenn die Daten schon einigermaßen gut sortiert sind und kaum Rekursion nötig wäre.
Ich würde abwegen, was ist langsamer/schlechter, wenn ich irgendwann feststelle das mein Stack nicht mehr reicht und ich nen iterativen Sortier-Algo einsetzen muss oder das ich genau den von dir genannten Fall in kauf nehmen und dafür das Problem mit dem Stack gelöst habe und der worst-case wesentlich schneller ist.
Vergleiche bitte nochmals die Geschwindigkeitsunterschiede zwischen einem Bubblesort und einem Quicksort.

Die Rekursionstiefe, würde ich anhand der Datenmenge festmachen. Was würde ich denn verschenken?
Falsche Grundannahme, denn die Rekursionstiefe wird neben der Datenmenge auch entscheidend vom Sortierzustand bestimmt. Aber ich finde es interessant, dass du von hocheffizienten Algorithmen redest und einen ebensolchen für RAM einschränkst.

Zitat von: erik
Wenn Du bestimmte Annahmen im Vorfeld machst dann must Du auch garantieren das diese zur Laufzeit noch gültig sind. Kannst Du das in jedem Fall?
Da ich das nicht mathematisch beweisen kann, sage ich mal nein.
Dann darfst du diese Annahmen nicht treffen.

Als Bsp. würde ich jetzt malloc() nehmen. Wenn du alle Größen die auftreten können/werden sortieren willst wirst du wohl schonmal ne Weile brauche, machst du jetzt aber die Annahme das auch Blöcke mit den Größen der 2er Potenzen reichen, kannst du schon viel schneller und einfacher sortien, nämlich in O(1).
Das glaube ich dir nicht. O(1) ist eine sehr heftige Einschränkung. Es wird trotzdem O(n), n=Anzahl der Blockgrößen, 1<n<20, sein. Dort spielt die Sortierzeit keine Rolle.

Mein Prof hat mich heute auch wieder "geärgert". Denn ich sehe es immernoch soch das malloc()/free() nichts mit dem OS zu tun zu haben. Die stehen in irgendeiner Library drin und das OS hat keine Kontrolle darüber. Genauso kann man malloc()/free() oder die Library einfach austauschen ohne dass das OS was an der Speicherverwaltung ändert.
Sie haben aber gefälligst direkt an das OS angepasst zu sein. Scheißegal, ob sie vom Kernel oder von der libc bereitgestellt werden: Sie sind extrem OS-spezifisch. Stellst du sie in der libc bereit, darfst du auch gern die Algorithmen ändern, aber es bleibt OS-spezifischer Code.

Zitat von: svenska
Der sortiert nicht (er nimmt vorne weg und hängt hinten an) und er sucht nicht (er nimmt immer das erste Element). Definiere "sortieren" und "suchen".
Ich füge neue Threads sortiert ein, also sortiere ich und ich suche immer den Thread mit der größten Priorität, also suche ich!
Das selbe mit dem geposteten Allocator, der sucht und sortiert auch, er macht halt nur ein paar Annahmen, naund!
Sortiert einfügen ist kein O(1), es sei denn, du hast für jede Priorität eine eigene Liste und fügst dort nicht ein, sondern hängst an. Wie du das suchen in O(1) machst, weiß ich nicht. Das ist O(n), n Anzahl der Prioritäten, also z.B. 1<n<10, damit relativ vernachlässigbar. Aber nicht O(1).

Zitat von: svenska
Wenn dein Quicksort feststellt, dass der Stackspace voll ist, dann müsstest du in jedem Fall einen weiteren Aufruf machen
Ich habe gesagt, dass man das vorher entscheiden muss! Nicht erst mittendrin.
Also konkret kein allgemeines Quicksort, sondern ein allgemeines Bubblesort und Quicksort nur unter bestimmten Randbedingungen (d.h. "genug" Stackspace).

Zitat von: svenska
Dann musst du die Anzahl der Threads beschränken (macht sich im Hinblick auf Scheduler und andere Dinge im System eh besser),
Das hieße aber das ich bei vielen Sachen gar nicht die Performance bekomme die ich haben könnte und das die Anwendung nicht skaliert und das geht heutzutag gar nicht!
Sowas reicht nicht:
if (num_cores > 1) { worker_threads = num_cores*2; }
else { worker_threads = 4; }

Du kannst natürlich einen 486er mit 8MB RAM auch mit 16384 Threads nur für den httpd vollmüllen, ob der das mag, sei jetzt mal dahingestellt... Auf dem System dürften 2-3 Threads wesentlich effizienter sein, da das System weniger arbeiten muss und vor allem mehr RAM verfügbar ist, da weniger Threadverwaltung nötig ist.

Was du da erzählst, ist unreal. "Mehr Threads = Mehr Performance" ist schlichtweg falsch.

Zitat von: svenska
Damit löst du aber die Aufgabe nicht mehr.
Das stimmt ja so nicht. Wenn du in Physik (Schule) die Aufgabe bekommst, irgendetwas zu berechnen, machst du auch viele Annahmen, damit du die ganze Rechnung vereinfachen kannst.
Damit löse ich aber nicht die Aufgabe, sondern eine angepasste/vereinfachte Aufgabe. Und ich weiß, dass mein Ergebnis nur begrenzt gebrauchbar ist.

Genauso macht man das bei vielen Problemen in der Informatik (frag mich bitte nicht nach einem Bsp.).
Äh, doch?

Zitat von: svenska
Ein malloc(1) dauert exakt genausolange wie ein malloc(1024*1024*1024) und ist unabhängig vom derzeitigen Zustand des physischen Speichers?
Was das malloc() betrifft ist dem so. Ich kann bei so einer Betrachtung nicht noch das OS mit einbeziehen. Denn es kann ja sein das ich gar nicht weiß wie das OS es macht oder auf welchen OS mein Code läuft. Trotzdem kann malloc() O(1) sein.
Unzulässige Vereinfachung: Dein malloc() fragt beim VMM nach und es hängt von dem - also genau dem OS - ab, ob du nun konstante Laufzeit hast oder nicht. Du kannst gern sagen, dass ein Teil deiner malloc()-Implementation (und zwar genau der systemunabhängige Teil) von der Komplexität O(1) ist, aber für das gesamte malloc() von Aufruf bis Return kannst du nicht sprechen.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 15. November 2010, 18:03
Zitat von: svenska
Vergleiche bitte nochmals die Geschwindigkeitsunterschiede zwischen einem Bubblesort und einem Quicksort.
Ich kann dir hier irgendwie nicht folgen. Das nen BubbleSort langsam ist und nen QuickSort schell, weiß ich, aber was du genau von mir willst ist mir nicht klar.

Ich sage nur, das man ein großes zu sortierendes Array (als Bsp.), welches aufgrund der Größe nicht sicher mit QuickSort sortiert werden kann, in mehrere Teile eingeteilt wird und die jeweils mit QuickSort (am besten noch jeder Teil in einem Thread) sortiert werden und wenn man dann das ganze Array sortiert sollte doch auch QuickSort ohne Probleme laufen (da die Teilstücke ja schon sortiert sind). Ich kann mich hier natürlich auch irren.

Zitat von: svenska
Das glaube ich dir nicht. O(1) ist eine sehr heftige Einschränkung. Es wird trotzdem O(n), n=Anzahl der Blockgrößen, 1<n<20, sein. Dort spielt die Sortierzeit keine Rolle.
Ich habe dir doch schon einen solchen Algo aufgeschrieben genannt. Indem du einfach das höchste gesetze Bit nimmst (und damit weißt in welche 2er Potenz die Größe fällt) und du dann in einem Array das erste Element aus einer Liste entfernst. Das ist O(1) denn die Schritte sind bei jeder Größe gleich (das höchste Bit bekommst du unter x86 mit bsr).

Zitat von: svenska
Sie haben aber gefälligst direkt an das OS angepasst zu sein. Scheißegal, ob sie vom Kernel oder von der libc bereitgestellt werden: Sie sind extrem OS-spezifisch. Stellst du sie in der libc bereit, darfst du auch gern die Algorithmen ändern, aber es bleibt OS-spezifischer Code.
Das sehe ich eben nicht so. Ansonsten müsstest du ja für jedes OS nen neuen Allocator schreiben, du kannst aber einfach einen Allocator nehmen (FlatMemory!) und den Algo verwenden!

Zitat von: svenska
Sortiert einfügen ist kein O(1), es sei denn, du hast für jede Priorität eine eigene Liste und fügst dort nicht ein, sondern hängst an.
Ja ich habe für jede Priorität eine Liste (das ist eben das was den Algo O(1) macht) und ob ich nun an eine Liste anfüge oder einfüge ist doch eigentlich das selbe.

Zitat von: svenska
Was du da erzählst, ist unreal. "Mehr Threads = Mehr Performance" ist schlichtweg falsch.
Es geht darum das du die Anzahl der Threads beschänken willst auch wenn sie mehr Performance bringen können.

Zitat von: svenska
Wie du das suchen in O(1) machst, weiß ich nicht.
Das selbe wie mit den 2er Potenzen. Ich habe ne Bitmap und in der suche ich das höchste gesetzte Bit, fertig!

Zitat von: svenska
Unzulässige Vereinfachung: Dein malloc() fragt beim VMM nach und es hängt von dem - also genau dem OS - ab, ob du nun konstante Laufzeit hast oder nicht. Du kannst gern sagen, dass ein Teil deiner malloc()-Implementation (und zwar genau der systemunabhängige Teil) von der Komplexität O(1) ist, aber für das gesamte malloc() von Aufruf bis Return kannst du nicht sprechen.
Wenn wir schon so die Haare spalten (zumal es um den malloc() Algo und nicht um den VMM Algo ging) dann kannst du auf einem modernen Mikroprozessor mit Cache und Out-Of-Order Ausführung nie O(1) garantieren, da nämlich der Eintrag nicht im Cache stehen kann und in die nächst höhere Cachestufe gewechselt wird und dann irgendwann in den RAM.
Auch kannst du ja nicht wissen ob eine Page nicht ausgelagert ist und dann müsste die auch erst nachgeladen werden (ist wie den VMM aufrufen) usw.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 15. November 2010, 23:08
Ich sage nur, das man ein großes zu sortierendes Array (als Bsp.), welches aufgrund der Größe nicht sicher mit QuickSort sortiert werden kann, in mehrere Teile eingeteilt wird und die jeweils mit QuickSort (am besten noch jeder Teil in einem Thread) sortiert werden und wenn man dann das ganze Array sortiert sollte doch auch QuickSort ohne Probleme laufen (da die Teilstücke ja schon sortiert sind). Ich kann mich hier natürlich auch irren.
Wie gesagt, ich halte das für nicht zielführend. Dann gib lieber im Voraus "genug Stack". Im Übrigen kostet ein Thread ebenfalls ein bisschen was an Speicher für Verwaltungsstrukturen und eine Sortierroutine mit stückweisem Sortieren und Multithreading macht den Code größer. Ich denke mal bei dem Speicheraufwand, den du da treiben würdest, kannst du es einfach dem Stack zuschlagen.

Außerdem, und dabei bleibe ich, sind die Anforderungen an ein System immer auch von den Fähigkeiten des Systems bestimmt. Das heißt, dass du ein System mit einstelligem Arbeitsspeicher sicherlich nicht so triezen wirst, wie ein System mit vierstelligem... und dass du den Standard-Stack somit auch abhängig vom vorhandenem RAM machen kannst. Mal abgesehen davon... der Stack für die Sortierung wächst mit der Datenmenge; auch die musst du erstmal in den Speicher stopfen. Ich will irgendwie nicht wissen, wie groß das Array sein muss, damit Quicksort 1 MB an Rekursions-Stack braucht...

Ich habe dir doch schon einen solchen Algo aufgeschrieben genannt. Indem du einfach das höchste gesetze Bit nimmst (und damit weißt in welche 2er Potenz die Größe fällt) und du dann in einem Array das erste Element aus einer Liste entfernst.
OK.

Zitat von: svenska
Sie haben aber gefälligst direkt an das OS angepasst zu sein. Scheißegal, ob sie vom Kernel oder von der libc bereitgestellt werden: Sie sind extrem OS-spezifisch. Stellst du sie in der libc bereit, darfst du auch gern die Algorithmen ändern, aber es bleibt OS-spezifischer Code.
Das sehe ich eben nicht so. Ansonsten müsstest du ja für jedes OS nen neuen Allocator schreiben, du kannst aber einfach einen Allocator nehmen (FlatMemory!) und den Algo verwenden!
Korrekt, solange dein beliebiger Allocator auf eine bekannte API des Kernels zurückgreift. Du kannst mir nicht erzählen, dass dein beliebiges malloc() in einer libc auf jedem x-beliebigen Betriebssystem läuft - auch mit Einschränkung auf FlatMemory nicht.

Eine identische Implementation würde ja sonst unverändert (und das ist der Punkt!) unter Windows NT, Windows 3.1 (erw.Mod.), DOS (mit Extender), Linux, Minix-386 und deinem OS laufen. Das geht nicht, weil die darunterliegende API eben nicht identisch ist.

Wenn natürlich dein Kernel eine Standard-POSIX-API zum VMM anbietet, wird jede POSIX-kompatible libc darauf ein malloc() aufsetzen können. Das ist prinzipiell vom Algorithmus unabhängig, denn es kommt auf die Interaktion mit dem OS an.

Zitat von: svenska
Was du da erzählst, ist unreal. "Mehr Threads = Mehr Performance" ist schlichtweg falsch.
Es geht darum das du die Anzahl der Threads beschänken willst auch wenn sie mehr Performance bringen können.
Kennst du den break-even-Punkt der Threadanzahl? Ist verschieden, als Faustregel gilt für CPU-bound-Prozesse die Prozessoranzahl (evtl. plus geringe Sicherheit), für I/O-bound-Prozesse etwa das doppelte; einen "immer-optimal"-Fall gibt es nicht. Also lege ich Limits im Voraus fest, ehe ich mir mit einer massiven Threaderstellung einer Anwendung einen DoS ins Haus hole.

Wenn ein Programm der Meinung ist, dass es 10 Millionen Threads für mein 10-Megapixel-Bild braucht, dann möchte ich das vorher explizit erlauben. Eleganter ist es, wenn das Programm dieses Limit vorher erfragen kann ("Dieses Programm kann mit mehr Threads mehr Leistung erzeugen.")

Wenn wir schon so die Haare spalten (zumal es um den malloc() Algo und nicht um den VMM Algo ging) dann kannst du auf einem modernen Mikroprozessor mit Cache und Out-Of-Order Ausführung nie O(1) garantieren, da nämlich der Eintrag nicht im Cache stehen kann und in die nächst höhere Cachestufe gewechselt wird und dann irgendwann in den RAM.
Du meinst hier harte Echtzeitanforderung, die geht mit modernen Prozessoren und Betriebssystemen wirklich nicht. Wenn du das Problem wirklich auf den malloc()-Algorithmus beschränkst und die darunterliegenden Ebenen vernachlässigst, hast du recht.

In meiner Welt, und offensichtlich auch in eriks, sind malloc() und VMM aber sehr eng miteinander verzahnt, sodaß eine getrennte Betrachtung nur wenig bringt. Für mich sollte ein naher malloc-Verwandter bereits eine Funktion des VMM sein, die man in der libc nur noch weitergeben muss. Damit können Kernelthreads (mal abgesehen vom VMM) es auch benutzen, ohne auf eine libc-Implementation, welche eventuell Kernelthreads erfordert, angewiesen zu sein. Sonst hast du nämlich wieder eine zyklische Abhängigkeit...

Auch kannst du ja nicht wissen ob eine Page nicht ausgelagert ist und dann müsste die auch erst nachgeladen werden (ist wie den VMM aufrufen) usw.
Richtig. Das kann ich allerdings im Voraus ändern, indem ich die Pages vorher benutze und davon ausgehe, dass genug RAM da ist.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 16. November 2010, 08:58
Hallo,


Denn was hier gemacht wird ist Symptom Bekämpfung und nicht die Ursachen beseitigen!
Die Ursache ist das der Mensch (als Programmierer) nicht perfekt ist! Daran kann man prinzipiell nichts ändern oder willst Du die Programmierer abschaffen und SW-Design nur noch den Computern selber überlassen?

Warum sollte sich denn ein Programmierer um solche Sachen wie nen Bufferoverflow kümmern, wenn das OS es doch macht und es so auch noch performanter ist? Ich will nicht die Bequemlichkeit der Programmierer unterstützen.
Menschen machen nun mal Fehler und wenn Du trotz dessen eine hohe Sicherheit erreichen willst dann funktioniert das nur wenn Du auf jeder Ebene versuchst ein Maximum an Sicherheit zu erreichen. Es bringt nichts einfach alles auf die letzte Ebene zu schieben und zu sagen "der hätte eben besser programmieren müssen"!

Wenn wir beide mit unseren OSen fertig sind können wir ja gegenseitig versuchen unsere Systeme zu knacken, mal sehen wer damit reichhaltigeren Erfolg hat. ;)

Aber welchen Teil des virtuellen Speichers der Heap belegt, das wird halt nicht (unbedingt) im UserSpace gemacht!
Nein, normalerweise nicht, aber laut POSIX legt das OS einen Bereich im virtuellen Adressraum fest und der User-Code (die Heap-Verwaltung) kann diesen Bereich vergrößern und verkleinern (der Bereich bleibt also in einem Stück).

Das sollte eigentlich klar sein. Ich meine sowas wie ne libc von GNU und ne libc von LLVM und ne dietlibc usw. Alle können andere Algos implementieren, aber laufen auf deinem OS.
Keine derzeit existierende libc wird auf meinem OS laufen, dafür gehe ich einfach an zu vielen Stellen zu sehr andere Wege.

Anders gefragt, was für eine Datenstruktur willst du denn einsetzen, dass es nicht signifikant langsamer wird (schreib aber bitte ob das nur mit deinen Segmenten funktioniert)?
Ich werde meine Segmente als Array benutzen da ich die einfach vergrößern und verkleinern kann (um das defragmentieren im linearen Speicher kümmert sich das OS) und für jede Objekt-Größe ein eigenes Segment. Aber das funktioniert natürlich nur mit unabhängigen dynamischen Segmenten, sorry.

machst du jetzt aber die Annahme das auch Blöcke mit den Größen der 2er Potenzen reichen, kannst du schon viel schneller und einfacher sortien, nämlich in O(1).
Also ich will das schon etwas feiner unterteilen, muss aber auch dafür nicht suchen sondern nur rechnen (ein bisschen Bitschieberei usw.), bleibt also bei O(1).


Programmierer sind faul und bequem (andere Interpretation: unter enormem Zeitdruck, enormem Featuredruck und nur wenig Qualitätssicherung).
Exakt so ist es. Ich musste auch schon Abstriche machen nur um Terminvorgaben halten zu können. Ich möchte nicht wissen was bei MS die Abteilung die den Kernel entwickelt sagt wenn das Management den Auslieferungstermin des nächsten Windows festgelegt hat oder andersrum was die Manager sagen wenn das Kernel-Entwicklerteam kurz vor der Auslieferung doch noch einen Bug gefunden hat oder einen zusätzlichen Feature-Request erhält bzw. wegen Zeitmangel ablehnt.

Wozu Passwörter übers Netz mit MD5 hashen und auch noch verschlüsselt auf der Festplatte abspeichern?
Wozu überhaupt Passwörter, ich hab doch ne Wohnungstür? Oder andersrum: Warum ne Wohnungstür, ich doch nen Passwort?
Das Passwort und die Wohnungstür sind eben nur 2 verschiedene Ebenen an Sicherheit. Kein normaler Mensch würde auf die Idee kommen auch nur eine dieser beiden Ebenen zu vernachlässigen. Sicherheit entsteht auch immer zu einem guten Anteil durch Redundanz!

Zitat von: svenska
Ein malloc(1) dauert exakt genausolange wie ein malloc(1024*1024*1024) und ist unabhängig vom derzeitigen Zustand des physischen Speichers?
Was das malloc() betrifft ist dem so. Ich kann bei so einer Betrachtung nicht noch das OS mit einbeziehen. Denn es kann ja sein das ich gar nicht weiß wie das OS es macht oder auf welchen OS mein Code läuft. Trotzdem kann malloc() O(1) sein.
Unzulässige Vereinfachung: Dein malloc() fragt beim VMM nach und es hängt von dem - also genau dem OS - ab, ob du nun konstante Laufzeit hast oder nicht. Du kannst gern sagen, dass ein Teil deiner malloc()-Implementation (und zwar genau der systemunabhängige Teil) von der Komplexität O(1) ist, aber für das gesamte malloc() von Aufruf bis Return kannst du nicht sprechen.
Also bei meiner Heap-Verwaltung würde ich schon sagen das, unter gewissen Einschränkungen wie Objekt-Größe im Rahmen 1Byte bis 1MByte und es muss nicht das OS gebeten werden mehr Speicher zur Verfügung zu stellen, mein malloc und free mit O(1) arbeiten. Aber wenn ich das Gesamtsystem betrachte (und das muss man einfach tun) dann dürfte es wohl absolut unmöglich sein eine Heap-Verwaltung mit O(1) zu bauen.


Ich will irgendwie nicht wissen, wie groß das Array sein muss, damit Quicksort 1 MB an Rekursions-Stack braucht...
Auf jeden Fall ziemlich Groß, aber es hängt auch davon ab wie groß der Stackframe pro Funktionsaufruf ist und da kann der Compiler sicher auch etwas zu beitragen.

In meiner Welt, und offensichtlich auch in eriks, sind malloc() und VMM aber sehr eng miteinander verzahnt, sodaß eine getrennte Betrachtung nur wenig bringt.
Exakt.

Für mich sollte ein naher malloc-Verwandter bereits eine Funktion des VMM sein, die man in der libc nur noch weitergeben muss. Damit können Kernelthreads (mal abgesehen vom VMM) es auch benutzen, ohne auf eine libc-Implementation, welche eventuell Kernelthreads erfordert, angewiesen zu sein. Sonst hast du nämlich wieder eine zyklische Abhängigkeit...
Das würde aber bedeuten das malloc fast immer einen Syscall machen muss und das würde ich persönlich nicht wollen, trotzdem ist Dein Gedanke durchaus nachvollziehbar richtig.

Das kann ich allerdings im Voraus ändern, indem ich die Pages vorher benutze und davon ausgehe, dass genug RAM da ist.
Wobei Deine letzten 8 Worte auch wieder eine Annahme sind die nicht zwangsläufig zutreffen muss. ;)


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 16. November 2010, 09:46
Hallo,

Für mich sollte ein naher malloc-Verwandter bereits eine Funktion des VMM sein, die man in der libc nur noch weitergeben muss. Damit können Kernelthreads (mal abgesehen vom VMM) es auch benutzen, ohne auf eine libc-Implementation, welche eventuell Kernelthreads erfordert, angewiesen zu sein. Sonst hast du nämlich wieder eine zyklische Abhängigkeit...
Das würde aber bedeuten das malloc fast immer einen Syscall machen muss und das würde ich persönlich nicht wollen, trotzdem ist Dein Gedanke durchaus nachvollziehbar richtig.
Die libc würde in der naiven Implementation immer den malloc-Syscall machen, richtig. Wenn ich jetzt allerdings bereits weiß, wie groß der Standard-Stack ist (in der libc sollte das bekannt sein, denn sie ist OS-abhängig), kann ich Kleinkram auch auf dem Stack alloziieren und diesen Fall direkt in der libc lösen. Worauf ich hinaus möchte, ist, dass ein kmalloc() aber im Kernel/VMM implementiert sein sollte (eben damit der Kernel eine full-featured-malloc-Umgebung nutzen kann) und wenn ich diese bereits im Kernel implementiert habe, kann ich sie in der libc auch einfach naiv weitergeben. Das vermeidet eine zyklische Abhängigkeit und lässt FlashBurns Wünsche nach einer austauschbaren libc-Implementation offen...

Das kann ich allerdings im Voraus ändern, indem ich die Pages vorher benutze und davon ausgehe, dass genug RAM da ist.
Wobei Deine letzten 8 Worte auch wieder eine Annahme sind die nicht zwangsläufig zutreffen muss. ;)
Der Ausgangspunkt hatte aber eher was mit Benchmarks zu tun als mit realem Workload. ;) Aber ich denke, eine nicht immer, aber häufig zutreffende Grundannahme kann man schon machen: Dass die Workload im Regelfall den Fähigkeiten des Systems angepasst ist (oder diese untersteigt). Schließlich werde ich auf einem Pentium 133 mit 32 MB RAM keinen Windows Server 2008 als Active Directory Server für 20000 Nutzer einsetzen...

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 16. November 2010, 10:46
Zitat von: svenska
Im Übrigen kostet ein Thread ebenfalls ein bisschen was an Speicher für Verwaltungsstrukturen und eine Sortierroutine mit stückweisem Sortieren und Multithreading macht den Code größer.
Du hast ja schon mehrmals gesagt das du es nicht so mit Threads hast, aber das was du da gesagt hast, heißt du würdest lieber einen Thread QuickSort durchführen lassen, als mehrere Threads QuickSort durchführen zu lassen?!
Sorry aber mit der Einstellung sollte man keine Programme mehr entwickeln.

Zitat von: svenska
Ich will irgendwie nicht wissen, wie groß das Array sein muss, damit Quicksort 1 MB an Rekursions-Stack braucht...
Warum diskutieren wir dann ;)

Zitat von: svenska
Du kannst mir nicht erzählen, dass dein beliebiges malloc() in einer libc auf jedem x-beliebigen Betriebssystem läuft - auch mit Einschränkung auf FlatMemory nicht.
So lange es mit Speicher arbeitet den es vom OS bekommt (und nicht mit Speicher den es gerne vom OS haben möchte, ich meine also nen zusammenhängenden Heap) sollte das auf jedes OS portierbar sein.

Zitat von: svenska
Eine identische Implementation würde ja sonst unverändert (und das ist der Punkt!) unter Windows NT, Windows 3.1 (erw.Mod.), DOS (mit Extender), Linux, Minix-386 und deinem OS laufen. Das geht nicht, weil die darunterliegende API eben nicht identisch ist.
Das ist auch nicht das was ich meine, sondern das jedes OS ne Möglichkeit bietet Speicher vom OS anzufordern und das sollte wirklich jedes OS bieten und dann sollte eigentlich jeder malloc() Algo portierbar sein.

Zitat von: svenska
In meiner Welt, und offensichtlich auch in eriks, sind malloc() und VMM aber sehr eng miteinander verzahnt, sodaß eine getrennte Betrachtung nur wenig bringt.
Gerade zwecks Portierbarkeit oder der Tatsache das sich am VMM mal was ändern kann, würde ich malloc() schon für sich alleine betrachten.

Zitat von: svenska
Für mich sollte ein naher malloc-Verwandter bereits eine Funktion des VMM sein, die man in der libc nur noch weitergeben muss.
Sehe ich 2 Probleme, wo speicherst du die Verwaltungsdaten und damit legst du den malloc() Algo ja fest und das ist "scheiße" ;)
Wenn du dann noch Syscalls anbietest das man auch ne eigene malloc() Implementation nutzen kann, kannst du dir deins auch wieder sparen, weil warum doppelt?
Zumal ich komme im Kernel komplett ohne malloc() aus, ich nutze nur den SlabAllocator (und ja ist für mich was anderes).

Zitat von: svenska
Damit können Kernelthreads (mal abgesehen vom VMM) es auch benutzen, ohne auf eine libc-Implementation, welche eventuell Kernelthreads erfordert, angewiesen zu sein. Sonst hast du nämlich wieder eine zyklische Abhängigkeit...
An welche zyklische Abhängigkeit denkst du da, mir fällt jetzt keine ein?

Zitat von: erik
Die Ursache ist das der Mensch (als Programmierer) nicht perfekt ist! Daran kann man prinzipiell nichts ändern oder willst Du die Programmierer abschaffen und SW-Design nur noch den Computern selber überlassen?
Wenn der PC irgendwann mal intelligent genug dafür ist, warum nicht ;)

Zitat von: erik
Wenn wir beide mit unseren OSen fertig sind können wir ja gegenseitig versuchen unsere Systeme zu knacken, mal sehen wer damit reichhaltigeren Erfolg hat. Wink
Sehr gerne, ich hoffe nur das wir dann noch in der Lage dazu sind ;)

Zitat von: erik
Nein, normalerweise nicht, aber laut POSIX legt das OS einen Bereich im virtuellen Adressraum fest und der User-Code (die Heap-Verwaltung) kann diesen Bereich vergrößern und verkleinern (der Bereich bleibt also in einem Stück).
Ich weiß nicht ob Linux sowas wie ALR hat, aber wenn ja, dann gilt das eigentlich auch nicht mehr.

Zitat von: erik
Ich möchte nicht wissen was bei MS die Abteilung die den Kernel entwickelt sagt wenn das Management den Auslieferungstermin des nächsten Windows festgelegt hat oder andersrum was die Manager sagen wenn das Kernel-Entwicklerteam kurz vor der Auslieferung doch noch einen Bug gefunden hat oder einen zusätzlichen Feature-Request erhält bzw. wegen Zeitmangel ablehnt.
Als vernünftiger Mensch würde man sagen, wenn da ein Bug im Kernel ist und wir das OS ausliefern obwohl wir das wussten, dann machen wir uns keine Freunde, aber verschieben wir das lieber (kommt aber auch auf den Bug drauf an).
Das die meisten Manager wahrscheinlich nicht so denken ist mir klar, aber man sollte auch mal weiter denken und nicht immer nur bis zur nächsten Version.

Zitat von: svenska
Wenn ich jetzt allerdings bereits weiß, wie groß der Standard-Stack ist (in der libc sollte das bekannt sein, denn sie ist OS-abhängig), kann ich Kleinkram auch auf dem Stack alloziieren und diesen Fall direkt in der libc lösen.
Das würde mich mal interessieren, wie du das lösen möchtest. Denn dieser Bereich könnte ja irgendwann mal überschrieben werden.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 16. November 2010, 15:27
Hallo,

ein Thread, der Quicksort macht ist in jedem Fall wesentlich einfacher zu implementieren als eine Menge von Threads, die Quicksort machen. Wohlgemerkt: Auf einer Datenbasis!! Das erfordert sicherlich einiges an Locking/Synchronisation, außerdem ist für mich die Rekursion hinreichend schwierig zu begreifen, als dass ich das noch zusätzlich durch parallel arbeitende Prozesse fehleranfällig (weil schwer zu verstehen) machen muss. Zumal du äußerst selten einen solchen Algorithmus brauchen solltest, insbesondere nicht für interne OS-Strukturen. Da hast du normalerweise nur selten mehr als 10k Elemente.

Wenn du mir, nur weil ich es gerne in erster Instanz einfach, simpel und dumm baue, unterstellst, ich solle das Programmieren sein lassen, dann solltest du im Gegenzug mal deine Konzepte überdenken... denn dann ist irgendetwas grob falsch. Erweitern lassen sich bestimmte Dinge immer.

Über malloc() denken wir verschieden, also lassen wir die Diskussion lieber sein. Mach, wie du es für richtig hältst. Die zyklische Abhängigkeit kommt, wenn du malloc in der libc implementierst, im Kernel aber gerne ein malloc/kmalloc benutzen möchtest. Da du das nicht tust, sondern deinen Slab-Allocator benutzt, ist das für dich nicht relevant.

Zitat
Wenn der PC irgendwann mal intelligent genug dafür ist, warum nicht
Denk mal nach, wer intelligent ist... der PC oder derjenige, der ihn entwickelte? Ich hoffe, dass die Singularität noch ein Weilchen weg ist. Bevorzugt bis nach meinem Tod.

Variablen, die du nur in einer Funktion benutzt, kannst du auf den Stack legen. Rufst du eine Funktion auf, wird der Rücksprungpointer dahinter gelegt, ist also egal (die Zeiger bleiben gültig), bei einem Rücksprung würdest du an die falsche Adresse (nämlich den Inhalt der Variablen) springen. Um das zu vermeiden, nutzt man Magic Cookies, die Variablen von Rücksprungadressen trennen und ein Stack-Unwinding, was vom Compiler erledigt wird.

Wenn man die Cookies zufällig erzeugt (sowohl Wert als auch Byteanzahl!), verringert sich die Angriffsfläche enorm. Inkorrektes Stack-Unwinding führt dann zu einer Exception und dem Abbruch des Programms weil das Cookie nich stimmt. Im Gegenzug für diese Komplexität brauchst du für Kleinkram keinen VMM, keinen Slab-Allocator und weil der Stack ohnehin ständig benutzt wird, ist er auch im CPU-Cache für den jeweiligen Thread vorhanden. Das bringt Performance. Speicherkosten: Codegröße, Magic Cookies.

Gruß,
ein offensichtlich inkompetenter Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 16. November 2010, 15:51
Zitat von: svenska
ein Thread, der Quicksort macht ist in jedem Fall wesentlich einfacher zu implementieren als eine Menge von Threads, die Quicksort machen.
Kann sein das ich es mir zu einfach mache, aber wenn ich nen QuadCore habe und nen Array was ausreichend groß genug ist, dann teile ich das Array einfach in 4 halbwegs gleichgroße Stücke und jage QuickSort über jedes Stück (natürlich wird jeder Teil von einem anderen Thread sortiert). Wenn man dann jetzt die 4 sortierten Teilstücke durch ein QuickSort endgültig sortiert sollte das ja sehr schnell gehen. Zumal ich in Erinnerung habe, das gerade QuickSort sehr einfach zu parallelisieren ist.

Zitat von: svenska
Zumal du äußerst selten einen solchen Algorithmus brauchen solltest, insbesondere nicht für interne OS-Strukturen. Da hast du normalerweise nur selten mehr als 10k Elemente.
Wer redet hier von internen OS-Strukturen, es ging im den UserSpace und das der Stack eventuell zu klein ist um z.B. ein QuickSort auf einer größeren Datenmenge durchzuführen.

Interne OS-Strukturen würde ich in dem Sinne nie sortien, sondern immer sortiert einfügen.

Zitat von: svenska
Wenn du mir, nur weil ich es gerne in erster Instanz einfach, simpel und dumm baue, unterstellst, ich solle das Programmieren sein lassen, dann solltest du im Gegenzug mal deine Konzepte überdenken... denn dann ist irgendetwas grob falsch.
Sorry, aber du hast ja selbst gesagt, dass du es mit Threading nicht so hast, weil dir das zu kompliziert ist und du zeigst in deinen Antworten oft das du lieber alte Wege gehst als neue auszuprobieren.
Ich sage nicht das du dumm bist, aber was Software betrifft die irgendwo produktiv im Einsatz ist sollte die nicht so programmiert sein, das sie "alt" ist.
Denn das ist wieder genau das was ich schonmal gesagt habe, wir haben immer schnellere CPUs, immer mehr CPUs in einem System, immer mehr RAM, aber die Programme werden immer langsamer!

Zitat von: svenska
Die zyklische Abhängigkeit kommt, wenn du malloc in der libc implementierst, im Kernel aber gerne ein malloc/kmalloc benutzen möchtest. Da du das nicht tust, sondern deinen Slab-Allocator benutzt, ist das für dich nicht relevant.
Ich habe das mit der zyklischen Abhängigkeit immernoch nicht verstanden. Denn darunter verstehe ich das die untereinander abhängig sind, aber das sehe ich halt nicht bzw. nicht das Problem.
Ja ich nutze nur meinen SlabAllocator, aber was außer des Algos für die Verwaltung des Speichers unterscheidet den denn von einem normalen malloc()?

Zitat von: svenska
Variablen, die du nur in einer Funktion benutzt, kannst du auf den Stack legen. Rufst du eine Funktion auf, wird der Rücksprungpointer dahinter gelegt, ist also egal (die Zeiger bleiben gültig), bei einem Rücksprung würdest du an die falsche Adresse (nämlich den Inhalt der Variablen) springen. Um das zu vermeiden, nutzt man Magic Cookies, die Variablen von Rücksprungadressen trennen und ein Stack-Unwinding, was vom Compiler erledigt wird.
Was du beschreibst braucht also Support vom Compiler und ist ohne diesen nicht möglich. Was passiert wenn ein solches Programm eine Library benutzt die sowas nicht unterstützt?

Zitat von: svenska
Wenn man die Cookies zufällig erzeugt (sowohl Wert als auch Byteanzahl!), verringert sich die Angriffsfläche enorm. Inkorrektes Stack-Unwinding führt dann zu einer Exception und dem Abbruch des Programms weil das Cookie nich stimmt. Im Gegenzug für diese Komplexität brauchst du für Kleinkram keinen VMM, keinen Slab-Allocator und weil der Stack ohnehin ständig benutzt wird, ist er auch im CPU-Cache für den jeweiligen Thread vorhanden. Das bringt Performance. Speicherkosten: Codegröße, Magic Cookies.
Also Stack-Unwinding halte ich schonmal für Performance schädlich und den VMM brauchst du trotzdem, denn wer soll denn den Stack verwalten (Multi-Threading)?

Edit::

Ich habe mir mal bei Wikipedia den Artikel zu QuickSort angeguckt und da wird auch meine Idee genannt um zu verhindern das der Stack überläuft und einen "künstlichen" Stack zu nutzen (was die ganzen Parameter und die Funktionsaufrufe spart), was auch schneller sein sollte, da iterativ ja keine Funktionen aufruft und somit schonmal einiges gespart wird.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 16. November 2010, 21:23
Hallo,


Wenn ich jetzt allerdings bereits weiß, wie groß der Standard-Stack ist (in der libc sollte das bekannt sein, denn sie ist OS-abhängig), kann ich Kleinkram auch auf dem Stack alloziieren und diesen Fall direkt in der libc lösen.
Hä, ich verstehe absolut nicht worauf Du hinaus möchtest. Willst Du den Stack als Heap benutzen? Das geht IMHO definitiv in die Hose, da helfen auch keine "Magic Cookies".

Worauf ich hinaus möchte, ist, dass ein kmalloc() aber im Kernel/VMM implementiert sein sollte (eben damit der Kernel eine full-featured-malloc-Umgebung nutzen kann) und wenn ich diese bereits im Kernel implementiert habe, kann ich sie in der libc auch einfach naiv weitergeben.
Und wenn ich im Kernel keine "full-featured-malloc-Umgebung" will/brauche? Gerade bei einem Micro-Kernel besteht danach absolut kein Bedarf. Ich will auch mit einem speziell für meinen Kernel optimierten SlabAllocator auskommen und sonst nichts, ich hab ja auch eine gute "full-featured-malloc-Umgebung" für den User-Space.

Aber ich denke, eine nicht immer, aber häufig zutreffende Grundannahme kann man schon machen
So lange die Verletzung dieser Annahme nicht zum Totalausfall des Systems führt (als keine erhebliche funktionale Einschränkung ist) sondern nur z.B. die Performance beeinflusst ist das auch völlig in Ordnung.

Dass die Workload im Regelfall den Fähigkeiten des Systems angepasst ist (oder diese untersteigt). Schließlich werde ich auf einem Pentium 133 mit 32 MB RAM keinen Windows Server 2008 als Active Directory Server für 20000 Nutzer einsetzen...
Ja, da hast Du sicher recht.


Das ist auch nicht das was ich meine, sondern das jedes OS ne Möglichkeit bietet Speicher vom OS anzufordern und das sollte wirklich jedes OS bieten und dann sollte eigentlich jeder malloc() Algo portierbar sein.
Mein OS muss auch die Möglichkeit bieten den Programm Speicher zur Verfügung zu stellen (geht ja wohl schlecht ohne) und trotzdem würdest nicht mal Du auf die Idee kommen auch nur den Algorithmus einer existierenden Heap-Implementierung auf mein OS zu portieren. Eine konkrete Heap-Implementierung ist von viel mehr abhängig als nur vom OS "irgendwie" Speicher zu bekommen. Viele Dinge davon sind nicht konkret durch die Syscall-API-Spezifikation geregelt sondern entstehen eher indirekt, z.B. weil der Programmierer des Heap weiß wie der VMM vom OS-Kernel arbeitet und versucht sich dem anzupassen um eine hohe Performance zu erzielen.

Gerade zwecks Portierbarkeit oder der Tatsache das sich am VMM mal was ändern kann, würde ich malloc() schon für sich alleine betrachten.
Portierbarkeit ist für eine Heap-Verwaltung nicht relevant da eh ziemlich unrealistisch. Wenn der VMM sich nur intern ändert aber die Syscall-API noch gleich bleibt dann muss die Heap-Verwaltung nicht unbedingt geändert werden aber es kann passieren das sie dann langsamer wird weil sie eben nicht mehr so gut dem VMM angepasst ist. Wenn der VMM sich so sehr ändert das die Syscall-API mit geändert werden muss dann muss auch zwangsläufig die Heap-Verwaltung mit geändert werden.

Zitat von: erik
Wenn wir beide mit unseren OSen fertig sind können wir ja gegenseitig versuchen unsere Systeme zu knacken, mal sehen wer damit reichhaltigeren Erfolg hat.
Sehr gerne
Okay, abgemacht!

ich hoffe nur das wir dann noch in der Lage dazu sind ;)
Was soll das heißen? So lange wollte ich nun auch wieder nicht brauchen. Außerdem hoffe ich das mein Verstand als letztes versagt, mein Gehirn ist IMHO das am besten trainierte Organ in meinem Körper.

Zitat von: erik
Nein, normalerweise nicht, aber laut POSIX legt das OS einen Bereich im virtuellen Adressraum fest und der User-Code (die Heap-Verwaltung) kann diesen Bereich vergrößern und verkleinern (der Bereich bleibt also in einem Stück).
Ich weiß nicht ob Linux sowas wie ALR hat, aber wenn ja, dann gilt das eigentlich auch nicht mehr.
Ja, Linux kann ALR (in meinem OS möchte ich das später auch unterstützen). Und "am Stück" bedeutet nicht "an einer bestimmte Stelle".


Ich hoffe, dass die Singularität noch ein Weilchen weg ist. Bevorzugt bis nach meinem Tod.
Full ACK!
Aber ich hoffe dabei sich nicht mein Tod nach diesem Ereignis richtet sondern andersherum. ;)


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 16. November 2010, 21:34
Zitat von: erik
und trotzdem würdest nicht mal Du auf die Idee kommen auch nur den Algorithmus einer existierenden Heap-Implementierung auf mein OS zu portieren.
Das ist jetzt wieder eine deiner Antworten ;) Ich habe deswegen ganz oft immer das Wort FlatMemory gebraucht.

Zitat von: erik
Eine konkrete Heap-Implementierung ist von viel mehr abhängig als nur vom OS "irgendwie" Speicher zu bekommen. Viele Dinge davon sind nicht konkret durch die Syscall-API-Spezifikation geregelt sondern entstehen eher indirekt, z.B. weil der Programmierer des Heap weiß wie der VMM vom OS-Kernel arbeitet und versucht sich dem anzupassen um eine hohe Performance zu erzielen.
Wovon ist die Implementierung denn noch abhängig? Und was stellst du dir darunter vor das man eine Heap-Implementierung an den VMM anpasst?

Eine Heap-Implementierung lässt sich doch nur in so fern optimieren, dass das alloc() und free() schnellst möglich und mit möglichst wenig Speicherverbauch/Verschnitt passiert.

Zitat von: erik
mein Gehirn ist IMHO das am besten trainierte Organ in meinem Körper.
Bei mir inzwischen leider auch ;)

Zitat von: erik
Und "am Stück" bedeutet nicht "an einer bestimmte Stelle".
Richtig, aber wenn der Adressraum von vielen benutzten Bereichen durchsetzt ist, sinkt die Wahrscheinlichkeit das man einen vorhanden Block immer weiter erweitern kann, doch sehr rapide und das war was ich damit meinte.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 17. November 2010, 10:41
Hallo,

Zitat von: svenska
Zumal du äußerst selten einen solchen Algorithmus brauchen solltest, insbesondere nicht für interne OS-Strukturen. Da hast du normalerweise nur selten mehr als 10k Elemente.
Wer redet hier von internen OS-Strukturen, es ging im den UserSpace und das der Stack eventuell zu klein ist um z.B. ein QuickSort auf einer größeren Datenmenge durchzuführen.
Richtig, ich hab das versehentlich auf eine universelle Sortierroutine verallgemeinert. Trotzdem glaube ich, dass du bei Anwendungen, die eventuell in so ein Limit laufen könnte, dies vorher weißt und den Stack im Voraus hinreichend groß machen kannst. Möglicherweise kann das sogar die Anwendung selbst tun.

Zitat von: svenska
Wenn du mir, nur weil ich es gerne in erster Instanz einfach, simpel und dumm baue, unterstellst, ich solle das Programmieren sein lassen, dann solltest du im Gegenzug mal deine Konzepte überdenken... denn dann ist irgendetwas grob falsch.
Sorry, aber du hast ja selbst gesagt, dass du es mit Threading nicht so hast, weil dir das zu kompliziert ist und du zeigst in deinen Antworten oft das du lieber alte Wege gehst als neue auszuprobieren.
Ich sehe halt wenig Notwendigkeit, gute, bekannte Konzepte wegzuwerfen. Wenn etwas allerdings nicht optimal funktioniert, dann suche ich auch nach neuen Konzepten. Das mag sich mit deinem Wesen beißen, aber bisher bin ich damit gut gefahren. Never touch a running system, solange es die Aufgabe vollständig erfüllt.

Ich sage nicht das du dumm bist, aber was Software betrifft die irgendwo produktiv im Einsatz ist sollte die nicht so programmiert sein, das sie "alt" ist.
Erklär das mal deiner normalen Qualitätssicherung. Und ich habe auch wenig Interesse, dass in einem Flugzeug der Autopilot neue Wege "ausprobiert". Das ist eine Frage der Stabilität. Apache geht z.B. immernoch auf 1994 zurück, wenn sich die Codebasis zwischendurch auch mehrfach verändert hat, aber nach außen ist es dasselbe.

Denn das ist wieder genau das was ich schonmal gesagt habe, wir haben immer schnellere CPUs, immer mehr CPUs in einem System, immer mehr RAM, aber die Programme werden immer langsamer!
Das liegt definitiv nicht an den ausgetretenen Konzepten, von SMP mal abgesehen. Du kannst mit simplem C und einem hinreichend antiken Vorgehen mit hinreichend antiken Werkzeugen auf moderner Hardware einen hochgradig performanten Webserver bauen. Solange der Ruf aber eben nicht nach Effizienz, sondern nach Features geht, wirst du Bloat produzieren. Das sind Dinge wie KDE, Gnome oder auch Firefox - Features ohne Ende auf Kosten der Performance.

Und solange man Features vor Optimierung stellt, wächst Software auch immer schneller als die dazugehörige Hardware. Heutige PC-Spiele sind so gebaut, dass sie mit Maximaleinstellungen auf Rechnern laufen, die erst in 1-2 Jahren existieren werden! Das ist dann auch eine Wirtschaftsfrage. "Was lange hält, bringt kein Geld." Effiziente Bürosoftware (Office 2000) reicht für 90% aller Anwendungsfälle hin, würde aber keinen neuen Rechner erforderlich machen. Das ist Absicht!

Solange es immer kleine, schnelle Alternativen gibt, habe ich damit auch kein Problem.

Zitat von: svenska
Die zyklische Abhängigkeit kommt, wenn du malloc in der libc implementierst, im Kernel aber gerne ein malloc/kmalloc benutzen möchtest. Da du das nicht tust, sondern deinen Slab-Allocator benutzt, ist das für dich nicht relevant.
Ich habe das mit der zyklischen Abhängigkeit immernoch nicht verstanden. Denn darunter verstehe ich das die untereinander abhängig sind, aber das sehe ich halt nicht bzw. nicht das Problem.
Der Kernel kann keine im Userspace implementierten Funktionen nutzen. Implementierst du malloc im Userspace (libc), kann der Kernel das nicht nutzen. Tut er es doch, hast du zyklische Abhängigkeiten.

Ja ich nutze nur meinen SlabAllocator, aber was außer des Algos für die Verwaltung des Speichers unterscheidet den denn von einem normalen malloc()?
Eben genau der. Es ist ein anderer.

Zitat von: svenska
Variablen, die du nur in einer Funktion benutzt, kannst du auf den Stack legen. Rufst du eine Funktion auf, wird der Rücksprungpointer dahinter gelegt, ist also egal (die Zeiger bleiben gültig), bei einem Rücksprung würdest du an die falsche Adresse (nämlich den Inhalt der Variablen) springen. Um das zu vermeiden, nutzt man Magic Cookies, die Variablen von Rücksprungadressen trennen und ein Stack-Unwinding, was vom Compiler erledigt wird.
Was du beschreibst braucht also Support vom Compiler und ist ohne diesen nicht möglich. Was passiert wenn ein solches Programm eine Library benutzt die sowas nicht unterstützt?
Dann findet es nicht statt. Entweder, der Compiler macht ein Stack-Unwinding (sollte er eigentlich sowieso) oder er tut es nicht. Danach richtet sich, ob dein malloc() bestimmte Dinge auf dem Stack alloziieren kann oder eben nicht.

Also Stack-Unwinding halte ich schonmal für Performance schädlich und den VMM brauchst du trotzdem, denn wer soll denn den Stack verwalten (Multi-Threading)?
Stack-Unwinding ist eigentlich nur, dass du den Stack-Pointer nicht verschiebst, ohne die Daten vorher wieder vom Stack runterzunehmen. Das heißt, dass du zusätzliche (OS-)Daten, die malloc() da hingelegt hat, auf dem Stack haben darfst und diese auch nutzen kannst - und du vor einem Rücksprung aus einer Funktion brav alle Daten wieder abräumen musst.

Willst Du den Stack als Heap benutzen? Das geht IMHO definitiv in die Hose, da helfen auch keine "Magic Cookies".
Bringt aber Performance, wenn du Variablen nur innerhalb einer Funktion und darunterliegenden Funktionen benutzt und der Stack ausreichend groß ist. Grund ist vor allem Cache-Lokalität und, dass du keinen Syscall für Speicher brauchst, dieser Speicher nicht extra vom System verwaltet werden muss (die Verwaltung dessen findet im Userspace statt, der Stack als Ganzes gehört ohnehin zum Thread).

Und wenn ich im Kernel keine "full-featured-malloc-Umgebung" will/brauche? Gerade bei einem Micro-Kernel besteht danach absolut kein Bedarf. Ich will auch mit einem speziell für meinen Kernel optimierten SlabAllocator auskommen und sonst nichts, ich hab ja auch eine gute "full-featured-malloc-Umgebung" für den User-Space.
Dann fällt natürlich alles, was ich gesagt habe, weg - wenn du etwas nicht brauchst/willst, musst du es nicht implementieren.

Eine Heap-Implementierung lässt sich doch nur in so fern optimieren, dass das alloc() und free() schnellst möglich und mit möglichst wenig Speicherverbauch/Verschnitt passiert.
Das hängt vom Anwendungsfall ab. Eine Rekursion, die in jedem Rekursionsschritt eine Integer-Hilfsvariable benutzt, optimiert sich anders als ein Videoschnittprogramm, welches für jedes Bild mit 9 MB (1920x1200x4) arbeitet und hunderte Bilder gleichzeitig im Speicher hält. Zumal "schnell" und "wenig Verschnitt" sich gegenseitig ausschließen, du also einen Kompromiss finden musst und genau der beste Kompromiss wieder vom Anwendungsfall abhängt. (Du siehst das anders.)

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 17. November 2010, 13:14
Zitat von: svenska
Ich sehe halt wenig Notwendigkeit, gute, bekannte Konzepte wegzuwerfen. Wenn etwas allerdings nicht optimal funktioniert, dann suche ich auch nach neuen Konzepten.
Definiere optimal? Ich rede davon das du einen Algo hast der nur eine CPU auslastet, du aber nen anderen Algo nutzen könntest der alle vorhandenen CPUs auslastet, dann ist das für mich nicht optimal.

Zitat von: svenska
Das liegt definitiv nicht an den ausgetretenen Konzepten, von SMP mal abgesehen.
Es geht mir in diesem Fall aber genau um SMP und Multithreading.

Zitat von: svenska
Implementierst du malloc im Userspace (libc), kann der Kernel das nicht nutzen. Tut er es doch, hast du zyklische Abhängigkeiten.
Entweder malloc() ruft eine Funktion auf die es im Kernel gar nicht gibt (weil sie nur im UserSpace als Syscall existiert) oder die Funktion ist die selbe, dann funktioniert das ganze oder ich habe dich nicht verstanden ;)

Du meinst doch aber nicht die libc, die eventuell im UserSpace geladen ist, im Kernel zu nutzen oder?

Zitat von: svenska
Eben genau der. Es ist ein anderer.
Du kannst nicht von malloc() auf den Algo der dahinter steckt schließen. Schließlich kann hinter einem malloc() auch ein SlabAllocator stecken.

Zitat von: svenska
Stack-Unwinding ist eigentlich nur, dass du den Stack-Pointer nicht verschiebst, ohne die Daten vorher wieder vom Stack runterzunehmen. Das heißt, dass du zusätzliche (OS-)Daten, die malloc() da hingelegt hat, auf dem Stack haben darfst und diese auch nutzen kannst - und du vor einem Rücksprung aus einer Funktion brav alle Daten wieder abräumen musst.
Was du da beschreibst sind doch aber lokale Variablen die auf den Stack gepackt werden. malloc() ruft man für gewöhnlich für Sachen auf, die länger als nur eine Funktion existieren.

Zitat von: svenska
Zumal "schnell" und "wenig Verschnitt" sich gegenseitig ausschließen, du also einen Kompromiss finden musst und genau der beste Kompromiss wieder vom Anwendungsfall abhängt. (Du siehst das anders.)
Also ersten sehe ich letzteres genauso (hängt vom Anwendungsfall ab) und zweitens heißt nur das es noch keinen Algo gibt der beides vereinen kann, dass es den niemals gibt (das sehe ich halt anders).

Erik mit seinem Segmenten (und damit mit vergrößerbaren Arrays) hat mich nun wahrscheinlich doch dazu gebracht, das ich auch für einiges Arrays einsetzen werde.

Ich rede jetzt nur von IDs wie man sie für Threads usw. benutzt. Die Threads werden dann anhand ihrer ID in einen Baum eingetragen.
Das ganze ist zwar schön flexibel, aber weder O(1) noch Speichereffizient.
Würde ich ein großes Array nehmen, könnte ich genau anhand der ID den Pointer auf den Thread holen. Das wäre O(1) und Speichereffizient. Problem wäre halt nur das ich einen zusammenhängenden Bereich im virtuellen Adressraum belege und die Anzahl der IDs künstlich beschränken würde.

Ich habe jetzt gerade nur folgendes Problem. Ich würde die FreeList in dem Array speichern, sprich ich speichere nur die erste freie ID und in dem Arrayeintrag steht die nächste freie ID. Soweit so einfach.

Wie kann man da jetzt sortiert in diese FreeList einfügen ohne das man die gesamte Liste durchgehen müsste?
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 17. November 2010, 14:08
Zitat von: svenska
Ich sehe halt wenig Notwendigkeit, gute, bekannte Konzepte wegzuwerfen. Wenn etwas allerdings nicht optimal funktioniert, dann suche ich auch nach neuen Konzepten.
Definiere optimal? Ich rede davon das du einen Algo hast der nur eine CPU auslastet, du aber nen anderen Algo nutzen könntest der alle vorhandenen CPUs auslastet, dann ist das für mich nicht optimal.
Das ist optimal, wenn nur eine CPU vorhanden/frei ist. Außerdem bringt dir Parallelität nur begrenzte Beschleunigung. Dieser Punkt (break-even) hängt von den Randbedingungen des Systems ab (z.B. HT sind zwei voneinander abhängige Kerne, du kannst nicht schnell genug parallel von der Festplatte lesen), aber auch von der inneren Struktur des Algorithmus. Jeder parallelisierbare Algorithmus lässt sich in drei Abschnitte zerlegen: (a) das Zerlegen des Problems in Teilschritte, (n) die parallele Abarbeitung dieser Teilschritte, (c) das Verbinden der Teilergebnisse zu einem einzelnen Ergebnis des Algorithmus.

Nur (b) wird parallel abgearbeitet. Ist (b) verglichen mit der Gesamtzeit von (a) und (c) klein, lohnt sich Parallelisierung nicht. Du lastest dann zwar alle CPUs aus (dein Optimalitätskriterium), erreichst aber keine Geschwindigkeitsvorteile mehr davon. Andere Optimalitätskriterien sind Einfachheit, Geschwindigkeit der Implementierung, Sicherheit, aber auch Ergebniskorrektheit.

Derzeitige 3D-Spiele, die hochgradig parallelisiert werden müssen (z.B. für Cell-Prozessoren), beschleunigt man auf ganz anderen Ebenen: Sämtliche Aktionen (Objektrendering, Physikberechnung, Eingabeverarbeitung, Housekeeping) werden in einen großen Aufgabenpuffer geworfen und auf jeder PPE (Recheneinheit) läuft ein Thread, der selbstständig eine Aufgabe aus diesem Puffer erledigt. Einzelne Algorithmen werden seltener parallelisiert, eben weil sich das nur bis zu einer bestimmten Menge an Threads/CPUs lohnt.

Im Großen und Ganzen ist das für mich aber eine Einzelfallentscheidung, die von der Gesamtlaufzeit des Algorithmus abhängt. Wird er nur einmal für die Initialisierung aufgerufen, ist mir die Geschwindigkeit egal; ist es Teil einer "hier für alles bitte nutzen"-Bibliothek, dann eher nicht.

Zitat von: svenska
Das liegt definitiv nicht an den ausgetretenen Konzepten, von SMP mal abgesehen.
Es geht mir in diesem Fall aber genau um SMP und Multithreading.
Du solltest aber nicht auf Teufel komm raus parallelisieren.

Du meinst doch aber nicht die libc, die eventuell im UserSpace geladen ist, im Kernel zu nutzen oder?
Denn das ist nicht möglich. ;-) Und die Userspace-libc ist auch nicht die Kernelspace-libc.

Du kannst nicht von malloc() auf den Algo der dahinter steckt schließen. Schließlich kann hinter einem malloc() auch ein SlabAllocator stecken.
Muss ich auch nicht.

Zitat von: svenska
Stack-Unwinding ist eigentlich nur, dass du den Stack-Pointer nicht verschiebst, ohne die Daten vorher wieder vom Stack runterzunehmen. Das heißt, dass du zusätzliche (OS-)Daten, die malloc() da hingelegt hat, auf dem Stack haben darfst und diese auch nutzen kannst - und du vor einem Rücksprung aus einer Funktion brav alle Daten wieder abräumen musst.
Was du da beschreibst sind doch aber lokale Variablen die auf den Stack gepackt werden. malloc() ruft man für gewöhnlich für Sachen auf, die länger als nur eine Funktion existieren.
Nicht nur. Es gibt genug Randfälle, wo du lokale, kurzlebige Variablen dynamisch erzeugen möchtest (z.B. Puffer, um strcpy() benutzen zu können, wenn du die Datenmenge erst zur Laufzeit bestimmen möchtest), oder wenn du temporär solche Strukturen benutzen möchtest, um sie in Unterfunktionen zu bearbeiten. Sowas kann man aus Performancegründen auch auf den Stack alloziieren.

Ich rede jetzt nur von IDs wie man sie für Threads usw. benutzt. Die Threads werden dann anhand ihrer ID in einen Baum eingetragen.
Das ganze ist zwar schön flexibel, aber weder O(1) noch Speichereffizient.
Würde ich ein großes Array nehmen, könnte ich genau anhand der ID den Pointer auf den Thread holen. Das wäre O(1) und Speichereffizient. Problem wäre halt nur das ich einen zusammenhängenden Bereich im virtuellen Adressraum belege und die Anzahl der IDs künstlich beschränken würde.
Dynamische Datenstrukturen sind nicht O(1), da du nie im Voraus weißt, wieviele Elemente du hast.

Wie kann man da jetzt sortiert in diese FreeList einfügen ohne das man die gesamte Liste durchgehen müsste?
Listen sortiert halten ist nicht möglich, ohne dir Elemente anzuschauen; dafür gibt es Bäume. Eine Alternative wären Bitfelder, aber die kann man nicht beliebig vergrößern/verkleinern und Löcher müssten halt abgebildet werden... eine andere Möglichkeit gibt es, wenn du irgendwoher einen Zeiger auf das vorherige Element besorgen kannst (gibst du einen Block frei, gehört zu diesem Block ein Zeiger in die Liste, dort fügst du ein), setzt aber die Speicherung der belegten Blöcke voraus...

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 17. November 2010, 14:27
Zitat von: svenska
Du solltest aber nicht auf Teufel komm raus parallelisieren.
Das ist klar, wenn der Aufwand für das Parallelisieren so klein ist, das er immer nicht spürbar ist, dann kann man sich den sparen, aber um beim Bsp. sortieren zu bleiben. Wenn du eine ausreichend große Datenmenge sortierst und das am besten noch oft, wird sich das schon lohnen.

Zitat von: svenska
Und die Userspace-libc ist auch nicht die Kernelspace-libc.
Jetzt verwirrst du mich komplett  :? Denn wenn du sowieso eine KernelSpace-libc und eine UserSpace-libc hast, wo ist da die Abhängigkeit?

Zitat von: svenska
Nicht nur. Es gibt genug Randfälle, wo du lokale, kurzlebige Variablen dynamisch erzeugen möchtest (z.B. Puffer, um strcpy() benutzen zu können, wenn du die Datenmenge erst zur Laufzeit bestimmen möchtest), oder wenn du temporär solche Strukturen benutzen möchtest, um sie in Unterfunktionen zu bearbeiten. Sowas kann man aus Performancegründen auch auf den Stack alloziieren.
Also für Strukturen gibt es lokale Variablen und für dein Bsp. mit strcpy gibt es dynamische Arrays (und die können seit C99) auch bei erst zur Laufzeit bekannten Größe auf dem Stack gemacht werden.
Der Punkt ist doch, das du hier von lokalen Sachen sprichst und ein malloc() aber eher für Sachen genutzt wird, welche langlebig sind.

Zitat von: svenska
Dynamische Datenstrukturen sind nicht O(1), da du nie im Voraus weißt, wieviele Elemente du hast.
Ich rede doch von einem Array und dem Zugriff und der ist nunmal O(1). Auch das bekommen einer neuen ID wäre in meinem Fall O(1) (einfach die erste freie nehmen).
Das einzige was nicht O(1) ist, ist das einfügen einer vorher benutzten ID in die FreeList.

Zitat von: svenska
Listen sortiert halten ist nicht möglich, ohne dir Elemente anzuschauen; dafür gibt es Bäume.
Der Satz macht für mich mal überhaupt keinen Sinn. Bei Bäumen musst du dir auch Elemente angucken um dort einfügen zu können.

Was ich noch vergessen habe zu erwähnen. Ich rede von einem Array das genau 1024 Einträge hat und bei dem die Anzahl der freien Elemente bekannt ist.
Ich möchte halt das sortierte Einfügen optimieren. 2 Fälle wären schonmal:

Das Element ist kleiner als das erste Element der Liste -> neuer Listenanfang

Das Element ist größer als das letzte Elemente der Liste -> neues Listenende (das würde halt nur vorraussetzen das ich das Listenende auch speichere)

Ich dachte daran halt daran einfach von dem Element was freigegeben wird (der Index) nach links und rechts nach einem freien Index zu suchen (kann ich machen, weil freie Elemente den Wert < 1024 und besetzte Elemente einen Wert > 0xC0000000 haben).
Es wäre halt schön die Schleifendurchläufe ein wenig zu drücken.

Um mal ein Bsp Array zu zeigen (FreeList= 0; und das Ende der FreeList hätte den Eintrag sizeof(Array)):
[2][0xC0001234][4][0xC000DEAD][5]
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 17. November 2010, 17:44
Hallo,

Zitat von: svenska
Du solltest aber nicht auf Teufel komm raus parallelisieren.
Das ist klar, wenn der Aufwand für das Parallelisieren so klein ist, das er immer nicht spürbar ist, dann kann man sich den sparen, aber um beim Bsp. sortieren zu bleiben. Wenn du eine ausreichend große Datenmenge sortierst und das am besten noch oft, wird sich das schon lohnen.
Definiere "ausreichend groß". Alle Felder, die ich bisher sortiert habe (gut, das waren nicht viel), ließen sich mit Quicksort rekursiv-seriell in wenigen ms (bis in Delphi nicht messbar kurzer Zeit) sortieren. Eine Datenbank, die ohnehin riesige Datenmengen verwaltet, wird ohnehin auf sie zugeschnittene Algorithmen verwenden.

Zitat von: svenska
Und die Userspace-libc ist auch nicht die Kernelspace-libc.
Jetzt verwirrst du mich komplett  :? Denn wenn du sowieso eine KernelSpace-libc und eine UserSpace-libc hast, wo ist da die Abhängigkeit?
Dann gibt es keine. :-P Ich hab den Faden verloren.

Also für Strukturen gibt es lokale Variablen und für dein Bsp. mit strcpy gibt es dynamische Arrays (und die können seit C99) auch bei erst zur Laufzeit bekannten Größe auf dem Stack gemacht werden.
OK, ich habe mich mit C-Standards nur am Rande befasst (für den Grundlagenkram, den ich kann und brauche, ist das nicht nötig). Im Endeffekt schiebst du die ganze Problematik also auf den Compiler und dessen Stackverwaltung, was so ziemlich genau das ist, was ich auch schrieb.

Der Punkt ist doch, das du hier von lokalen Sachen sprichst und ein malloc() aber eher für Sachen genutzt wird, welche langlebig sind.
Wenn man für kurzzeitige Sachen auf nifty features vom Compiler setzen kann, dann braucht man das in der libc nicht implementieren, richtig. Wusste ich nicht, hab ich mich nie befasst.

Ich rede doch von einem Array und dem Zugriff und der ist nunmal O(1). Auch das bekommen einer neuen ID wäre in meinem Fall O(1) (einfach die erste freie nehmen).
Das einzige was nicht O(1) ist, ist das einfügen einer vorher benutzten ID in die FreeList.
Eben. Ich sehe damit aber nur begrenzt ein Problem, es sei denn, es handelt sich um extrem viele IDs, die da jede Sekunde freigegeben werden. Du wirst nicht alles auf O(1) zurückführen können. :-)

Zitat von: svenska
Listen sortiert halten ist nicht möglich, ohne dir Elemente anzuschauen; dafür gibt es Bäume.
Der Satz macht für mich mal überhaupt keinen Sinn. Bei Bäumen musst du dir auch Elemente angucken um dort einfügen zu können.
Stimmt. Aber nur O(logn) statt O(n/2), sofern ich nicht irre.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 17. November 2010, 18:53
Zitat von: svenska
OK, ich habe mich mit C-Standards nur am Rande befasst (für den Grundlagenkram, den ich kann und brauche, ist das nicht nötig). Im Endeffekt schiebst du die ganze Problematik also auf den Compiler und dessen Stackverwaltung, was so ziemlich genau das ist, was ich auch schrieb.
Nur das was du beschrieben hattest, hörte sich im ersten Moment so an als wenn du langlebige Sachen auf den Stack packen wolltest und als du dann genauer wurdest klang es für mich nach lokalen Variablen die irgendwie komisch durch Magic Cookies (sind das nicht Drogenkekse ;) ) realisiert wurden.

Zitat von: svenska
Eben. Ich sehe damit aber nur begrenzt ein Problem, es sei denn, es handelt sich um extrem viele IDs, die da jede Sekunde freigegeben werden. Du wirst nicht alles auf O(1) zurückführen können. smiley
Leider nicht ;) Ich werd erstmal Code schreiben, der zur Not 1022 Elemente anfassen muss und dann später sehen ob mir nicht noch was einfällt (ne Bitmap wäre ne Lösung, na mal sehen).

Zitat von: svenska
Stimmt. Aber nur O(logn) statt O(n/2), sofern ich nicht irre.
Richtig, aber was mir noch zum Array eingefallen ist, auch vom Caching her ist ein Array um Welten besser als ein Baum, denn beim Baum wird immer genau eine Page (da wo der Wert abgespeichert ist) angefasst und beim Baum kann es im worst-case eine Page pro Element sein und da steigt natürlich die Wahrscheinlichkeit das die Pages nicht im Cache ist bzw. diesen sogar noch zumüllen.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 18. November 2010, 13:40
Hallo,

Zitat von: svenska
OK, ich habe mich mit C-Standards nur am Rande befasst (für den Grundlagenkram, den ich kann und brauche, ist das nicht nötig). Im Endeffekt schiebst du die ganze Problematik also auf den Compiler und dessen Stackverwaltung, was so ziemlich genau das ist, was ich auch schrieb.
Nur das was du beschrieben hattest, hörte sich im ersten Moment so an als wenn du langlebige Sachen auf den Stack packen wolltest und als du dann genauer wurdest klang es für mich nach lokalen Variablen die irgendwie komisch durch Magic Cookies (sind das nicht Drogenkekse ;) ) realisiert wurden.
Von mir aus darfst du es auch Magic Numbers nennen. ;-) Mir ging es an der Stelle darum, Stack-Korruption zu erkennen; das wird nämlich besonders interessant, wenn man zusätzlich zu den Rücksprungadressen auch noch Variablen/Felder auf den Stack legt. Du trennst also den Rücksprungteil von den Variablen durch ein Magic Cookie; wenn es kaputt geht, ist der Stack zerstört worden (wildgewordener Pointer oder so). Position und Wert des Cookies müssen natürlich zufällig erzeugt werden, sonst bringt es nichts.

Das implementiert man aber in der libc (und im Compiler), nicht im Kernel. Wenn der Compiler das schon intelligent tut, ist mein Gedanke hinfällig.

Zitat von: svenska
Stimmt. Aber nur O(logn) statt O(n/2), sofern ich nicht irre.
Richtig, aber was mir noch zum Array eingefallen ist, auch vom Caching her ist ein Array um Welten besser als ein Baum, denn beim Baum wird immer genau eine Page (da wo der Wert abgespeichert ist) angefasst und beim Baum kann es im worst-case eine Page pro Element sein und da steigt natürlich die Wahrscheinlichkeit das die Pages nicht im Cache ist bzw. diesen sogar noch zumüllen.
Das stimmt. Wenn du aber die Elemente deines Baumes durch einen Slab-Allocator erzeugst, dann liegen sie im Speicher auch relativ nah beieinander, was den Nachteil etwas abfedern dürfte.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 18. November 2010, 13:55
Zitat von: svenska
Mir ging es an der Stelle darum, Stack-Korruption zu erkennen; das wird nämlich besonders interessant, wenn man zusätzlich zu den Rücksprungadressen auch noch Variablen/Felder auf den Stack legt.
Jetzt hast du mich wieder soweit das ich verwirrt bin ;)

Ich dachte es ging darum kleine Anfragen an malloc() auf den Stack zu legen?

Jetzt verstehe ich aber endlich was du mit deinen Magic Cookies eigentlich erreichen wolltest, nur hat das mit dem diskutierten Problem nichts zu tun.
Die Idee ist aber trotzdem nicht schlecht, kostet halt aber auch Performance. Könnte man das nicht irgendwie performanter machen?

Obwohl wenn man beim Start des Programms einen solchen Wert (Magic Cookie) "erstellt" (wobei hier wieder das Problem der zufälligen Zahlen wäre) und an einer festen Adresse speichert. Dann muss der Compiler nur noch seinen Code so erstellen, das er, wenn Arrays verwendet werden (die auf dem Stack liegen), einfach den letzten Wert vor der Rücksprungadresse mit diesem Wert vergleicht und beim Funktions-Prolog wird dieser Magic Cookie genau nach EBP (denn das ist für den Debugger wichtig) gelegt.

Das kostet dann nur noch in den Funktionen Performance wo man auch Arrays (wie oben beschrieben) verwendet. 100% sicher ist das leider auch nicht, ersten bekommt man leider keine echten zufälligen Zahlen und es gibt die Möglichkeit, wenn auch verdammt gering, dass das Magic Cookie in Ordnung ist, aber trotzdem die Rücksprungadresse überschrieben wurde.

Zitat von: svenska
Wenn du aber die Elemente deines Baumes durch einen Slab-Allocator erzeugst, dann liegen sie im Speicher auch relativ nah beieinander, was den Nachteil etwas abfedern dürfte.
Normalerweise schon ;) In meinem Fall verwende ich den AVL-Baum, aber für viele Sachen und so kommt es das die Nodes aus einem Slab für verschiedene Bäume genutzt werden und somit wieder auf viele Pages verteilt liegen.

Das mit den Caches ist mir auch erst eingefallen, an sowas denkt man normalerweise nicht, aber wenn man sich das mal durch den Kopf gehen lässt, wieviel Zeit/Takte es auf einer modernen CPU kostet wenn etwas nicht im Cache liegt. Dann kann es miteinmal sein das entweder ein spezieller Allocator besser ist oder aber eine Datenstruktur die eigentlich langsamer (oder auch speicherverschwenderischer) ist, aufgrund von Datenlokalität besser ist.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 19. November 2010, 09:51
Ich dachte es ging darum kleine Anfragen an malloc() auf den Stack zu legen?
Unter anderem, ja.

Jetzt verstehe ich aber endlich was du mit deinen Magic Cookies eigentlich erreichen wolltest, nur hat das mit dem diskutierten Problem nichts zu tun.
Die Idee ist aber trotzdem nicht schlecht, kostet halt aber auch Performance. Könnte man das nicht irgendwie performanter machen?
Das ist eine Frage der gewünschten Sicherheit... du tauschst Performance (zusätzliche Prüfungen) gegen Sicherheit (Erkennung von Stack-Korruption) ein.

Obwohl wenn man beim Start des Programms einen solchen Wert (Magic Cookie) "erstellt" (wobei hier wieder das Problem der zufälligen Zahlen wäre) und an einer festen Adresse speichert. Dann muss der Compiler nur noch seinen Code so erstellen, das er, wenn Arrays verwendet werden (die auf dem Stack liegen), einfach den letzten Wert vor der Rücksprungadresse mit diesem Wert vergleicht und beim Funktions-Prolog wird dieser Magic Cookie genau nach EBP (denn das ist für den Debugger wichtig) gelegt.
Pseudorandom mit zufälligem Seed sollte hinreichend sein, aber der Abstand zwischen Cookie und Daten muss ebenfalls zufällig sein (d.h. auf dem Stack gibt es eine zufällige Größe an Leerraum, da reichen vielleicht 1..100 Bytes). Sonst kannst du das Cookie einfach überspringen und damit Rücksprungadressen gezielt manipulieren.

Das kostet dann nur noch in den Funktionen Performance wo man auch Arrays (wie oben beschrieben) verwendet. 100% sicher ist das leider auch nicht, ersten bekommt man leider keine echten zufälligen Zahlen und es gibt die Möglichkeit, wenn auch verdammt gering, dass das Magic Cookie in Ordnung ist, aber trotzdem die Rücksprungadresse überschrieben wurde.
Richtig. Wenn die Anwendung aber weder weiß, wo das Cookie ist, noch wie es aussieht, dann ist das schon relativ zuverlässig. Wenn man dann noch einen Pseudozufallszahlengenerator nimmt und dessen Seed regelmäßig (z.B. alle paar Sekunden) mit echtem Zufall setzt, dann reicht das eigentlich hin. Zumal ein Angriff über ein Netzwerk immer mit der Latenz zu kämpfen hat.

Schwierig ist es halt, genug echten Zufall zu bekommen. Linux nutzt dafür in diversen Subsystemen eingebaute Routinen, die immer ein paar Bit in einen großen, allgemeinen Random-Pool (exportiert als /dev/random) einzahlen. Ich weiß es sicher vom Input-Subsystem (Maus- & Tastaturbewegungen sind zufällig) und vom alten IDE-Subsystem (Suchzeiten des Festplattenkopfes schwanken auch bei gleichen Suchmustern) und das sind immer nur ein paar Bits, die da auflaufen.

Das mit den Caches ist mir auch erst eingefallen, an sowas denkt man normalerweise nicht, aber wenn man sich das mal durch den Kopf gehen lässt, wieviel Zeit/Takte es auf einer modernen CPU kostet wenn etwas nicht im Cache liegt. Dann kann es miteinmal sein das entweder ein spezieller Allocator besser ist oder aber eine Datenstruktur die eigentlich langsamer (oder auch speicherverschwenderischer) ist, aufgrund von Datenlokalität besser ist.
Richtig, deswegen mache ich mir im Allgemeinen auch nur wenig Gedanken um Optimierung. Denn entweder man kennt sich mit den Details der Materie richtig gut aus und investiert eine Menge Arbeit oder Performance-Tracing, oder man baut erstmal etwas funktionierendes zusammen (wo man nur die gröbsten Optimierungen einbaut, z.B. Quicksort statt langsamer Algorithmen) und verlässt sich beim Optimieren auf den Compiler. Wenn es nicht "hinreichend schnell" ist, dann wird halt nochmal extra geschaut. Das ist eine Aufwandsfrage, für dich eher in Richtung Performance, für mich eher in Richtung einfach.

Wobei bei heutigen Rechengeschwindigkeiten eigentlich immer gilt: Fast alles, was klein ist, ist auch schnell. Langsam ist nur Bloat. ;-)

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 19. November 2010, 14:11
Hallo,


Das ist jetzt wieder eine deiner Antworten ;)
Genau dafür mögt Ihr mich doch alle so sehr. :evil:

Ich habe deswegen ganz oft immer das Wort FlatMemory gebraucht.
Wo?
Das ist auch nicht das was ich meine, sondern das jedes OS ne Möglichkeit bietet Speicher vom OS anzufordern und das sollte wirklich jedes OS bieten und dann sollte eigentlich jeder malloc() Algo portierbar sein.
Mein OS muss auch die Möglichkeit bieten den Programm Speicher zur Verfügung zu stellen .....
Der Satz von Dir den ich zitiert habe enthält 2 mal "jedes OS" und dazu noch "jeder malloc() Algo", also 3 mal "jede[s/r]". Sorry, aber ich kann da keine Einschränkung auf Flat-Memory erkennen. ;) Mir ist schon klar das Du normalerweise von Flat-Memory ausgehst aber Du versuchst da trotzdem Verallgemeinerungen auf zu stellen die eigentlich nicht funktionieren können (nur das wollte ich Dir verdeutlichen). Die Heap-Implementierung ist existenziell von der Verwaltung des virtuellen Adressraums abhängig (daraus wird ja der Heap generiert) und die macht eben meistens das OS (oder zumindest eine OS-spezifische Library). Daraus folgt nun mal das die Heap-Verwaltung vom OS abhängig ist. Klar könnte man auch für mein OS eine ganz simple Heap-Verwaltung portieren die einfach ein eigenes Segment bekommt und dort drin dann ein brk() macht das dann einfach zu einem SetSegmentLimit() umgesetzt wird, es ließe sich theoretisch 99% vom malloc-Code und damit auch der eigentliche Algorithmus unverändert übernehmen aber als Sinnvoll würde ich das nicht betrachten. Wenn Du mehr als so eine Primitiv-Lösung möchtest (und ich denke das wollen wir alle) dann kommst Du nicht drumherum Dich mit der Verwaltung des virtuellen Adressraum genauer zu befassen und da das von jedem OS anders gehandhabt werden kann ist die Heap-Implementierung immer sehr OS-spezifisch. Es gibt natürlich recht genersiche Librarys die dann aber eben auch bestimmte Syscalls erwarten und es gibt Librarys die es zwar auf vielen OSen gibt die aber immer etwas angepasst wurden (da ist zwar ein großer Teil der Algorithmen und des Quell-Codes identisch aber es sind auch viele Details sehr unterschiedlich).

Eine Heap-Implementierung lässt sich doch nur in so fern optimieren, dass das alloc() und free() schnellst möglich und mit möglichst wenig Speicherverbauch/Verschnitt passiert.
Ja, ganz recht, aber dazu muss man schon wissen wie das System arbeitet. Wenn man sich nicht für das OS interessiert dann kann man eben auch nicht optimieren und eine nicht-optimierte Heap-Implementierung will doch keiner mehr.

Zitat von: erik
mein Gehirn ist IMHO das am besten trainierte Organ in meinem Körper.
Bei mir inzwischen leider auch ;)
Wieso leider? Ich bin recht stolz darauf das mein Gehirn in einem so guten Zustand ist und gebe mir auch alle Mühe dass das so bleibt.

aber wenn der Adressraum von vielen benutzten Bereichen durchsetzt ist, sinkt die Wahrscheinlichkeit das man einen vorhanden Block immer weiter erweitern kann, doch sehr rapide und das war was ich damit meinte.
Das ist aber ein generelles Problem von Flat-Memory-Systemen, jeder Prozess hat eben nur einen einzigen und eindimensionalen Adressraum. Flat-Memory funktioniert eben nur dann bequem wenn der virtuelle Adressraum nur dünn benutzt ist bzw. wenn der virtuelle Adressraum um ein vielfaches größer ist als der tatsächliche Speicher-Bedarf so das sich immer eine passende Lücke finden lässt.


@svenska:
Objekte auf den Stack zu legen anstatt dafür den Heap zu benutzen ist eigentlich eine Entscheidung das Programmierers. Sicher kann man versuchen das der Compiler da zur compilezeit etwas optimiert oder man könnte Code einfügen lassen der zur Laufzeit ermittelt wie viel Speicher da eigentlich geholt werden soll und wenn das nicht zu viel ist (<= z.B. 64kBytes) dann wird einfach Stack benutzt. Ich denke schon das man da einiges vom Compiler optimieren lassen könnte aber ich glaube nicht dass das heutige Compiler bereits tun.
Was die "Magic Cookies" angeht so ist das einfach ein Problem der Stack-Implementierung, das der Stack nach unten wächst aber alle anderen Datenstrukturen nach oben ist einfach eine Fehlkonstruktion (zumindest aus heutiger Sicht wo der Stack einfach ein beliebiger dynamischer Bereich des Speichers ist und nicht das obere Ende eines fixen Real-Mode-Segments). Deswegen wächst mein Stack ja auch nach oben und schon ist dieses Problem nur noch halb so schlimm, trotzdem werde ich noch etwas Denkarbeit in die Realisierung von VLAs usw. investieren müssen.
Echten Zufall müsste eigentlich auch die CPU selber zur Verfügung stellen, etwa so wie VIA das macht, und schon wäre noch ein Angriffspunkt weg. Wenn der Referenzwert für die Cookies irgendwo im Speicher liegt dann ist er dort auch lesbar und vor allem auch manipulierbar. Welches OS markiert schon die Pages in denen die .const-Section liegt wirklich als Read-Only?


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 19. November 2010, 16:11
Zitat von: svenska
Pseudorandom mit zufälligem Seed sollte hinreichend sein, aber der Abstand zwischen Cookie und Daten muss ebenfalls zufällig sein (d.h. auf dem Stack gibt es eine zufällige Größe an Leerraum, da reichen vielleicht 1..100 Bytes). Sonst kannst du das Cookie einfach überspringen und damit Rücksprungadressen gezielt manipulieren.
Wie stellst du dir das vor, das der Cookie übersprungen werden könnte? Ich meine wir reden hier doch im Endeffekt darüber einen Buffer-Overflow zu erkennen, oder?

Zitat von: svenska
Wenn die Anwendung aber weder weiß, wo das Cookie ist, noch wie es aussieht, dann ist das schon relativ zuverlässig.
Irgendwo muss es doch aber gespeichert sein, damit zu einen Wert zum überprüfen hast und diese Adresse dieses Wertes muss auch bekannt sein.

@erik

Ich will jetzt nicht deinen halben Beitrag zitieren. Also ich verstehe malloc()/free() immernoch so, dass es "nur" darum geht irgendwelche Bereiche vernünftig zu verwalten. Da ist es mir egal ob der Bereich den ich vom OS bekomme zusammenhängend ist oder ob ich viele Bereiche verwalte die über den Adressraum verteilt sind (obwohl mir eine Sache einfällt, die man doch beachten muss, wenn man guckt ob Bereiche zusammengefasst werden können und man Bounday Tags benutzt, muss man aufpassen das man nicht auf einen Bereich zugreift der nicht gemappt ist).

Zitat von: erik
Wieso leider? Ich bin recht stolz darauf das mein Gehirn in einem so guten Zustand ist und gebe mir auch alle Mühe dass das so bleibt.
Weil es nur noch das Gehirn ist und es "früher" (wenn ich das Wort überhaupt schon benutzten darf ;) ) anders war.

Zitat von: erik
Flat-Memory funktioniert eben nur dann bequem wenn der virtuelle Adressraum nur dünn benutzt ist bzw. wenn der virtuelle Adressraum um ein vielfaches größer ist als der tatsächliche Speicher-Bedarf so das sich immer eine passende Lücke finden lässt.
Das war ja auch zu Zeiten von Single-Threaded und static Linking noch der Fall.

Zitat von: erik
Was die "Magic Cookies" angeht so ist das einfach ein Problem der Stack-Implementierung, das der Stack nach unten wächst aber alle anderen Datenstrukturen nach oben ist einfach eine Fehlkonstruktion (zumindest aus heutiger Sicht wo der Stack einfach ein beliebiger dynamischer Bereich des Speichers ist und nicht das obere Ende eines fixen Real-Mode-Segments). Deswegen wächst mein Stack ja auch nach oben und schon ist dieses Problem nur noch halb so schlimm, trotzdem werde ich noch etwas Denkarbeit in die Realisierung von VLAs usw. investieren müssen.
Theoretisch ist es auch auf x86 kein Problem den Stack nach oben wachsen zu lassen (sollte eigentlich auch kein Problem für die Compiler sein, dreht sich halt das Vorzeichen beim Zugriff auf den Stack um), wie es auf anderen Architekturen aussieht weiß ich nicht. In dem Hinblick ist die x86 Architektur eigentlich recht flexibel.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 19. November 2010, 17:06
Hallo,


Wie stellst du dir das vor, das der Cookie übersprungen werden könnte? Ich meine wir reden hier doch im Endeffekt darüber einen Buffer-Overflow zu erkennen, oder?
Man muss ein Array nicht zwangsläufig von Index 0 bis Index X linear durchlaufen, man kann da auch Lücken lassen die dann eben übersprungen werden. Ist das selbe Problem wie mit den Guard-Pages, wenn Du weißt wo die sind und wie groß die sind dann ist es kein Problem da drüber zu kommen.

Irgendwo muss es doch aber gespeichert sein, damit zu einen Wert zum überprüfen hast und diese Adresse dieses Wertes muss auch bekannt sein.
Das ist eines der Probleme dieser Stack-Protectoren. Die schützen zwar gegen "aus versehen" fehlerhaften Code oder simple Buffer-Overflows aber nicht gegen einen gezielten und mit Köpfchen vorbereiteten Angriff.

Also ich verstehe malloc()/free() immernoch so, dass es "nur" darum geht irgendwelche Bereiche vernünftig zu verwalten.
Das stimmt auch so ungefähr aber malloc muss den Heap in flexiblen Größen verwalten und nicht in Pages ansonsten könntest Du ja einfach anstatt malloc direkt einen Syscall benutzen um Speicher zu bekommen. Die Kernaufgabe einer Heap-Verwaltung ist die das Du Speicher der vom OS in Pages kommt in beliebigen Größen weitergeben kannst und das mit möglichst wenig Verschnitt und natürlich mit bestmöglicher Performance. Dazu muss die Heap-Verwaltung den virtuellen Speicher der vom OS in Pages kommt verwalten und auch die Speicherbereiche die an den User-Code weitergegeben wurden verwalten, das erste von beiden ist natürlich einfacher wenn der virtuelle Speicher für den Heap insgesamt nur ein Stück ist.

Weil es nur noch das Gehirn ist und es "früher" (wenn ich das Wort überhaupt schon benutzten darf ;) ) anders war.
Das heißt Du vernachlässigst den Rest Deines Körpers? Das ist auf Dauer nicht gesund!
Als Mindestausstattung an qualitativ hochwertigen Sportgeräten sollte eine Freundin vorhanden sein, da gibt es dann schon viele flexible Möglichkeiten zur körperlichen Bewegung, auch an frischer Luft. Ich meine so Dinge wie eine Fahrradtour usw. ;)

Zitat von: erik
Flat-Memory funktioniert eben nur dann bequem wenn der virtuelle Adressraum nur dünn benutzt ist bzw. wenn der virtuelle Adressraum um ein vielfaches größer ist als der tatsächliche Speicher-Bedarf so das sich immer eine passende Lücke finden lässt.
Das war ja auch zu Zeiten von Single-Threaded und static Linking noch der Fall.
Genau so ist es!
Und weil sich das bis heute grundlegend geändert hat müssen eben neue Konzepte her und da ist meine Segmentierung ganz klar der Favorit.

Theoretisch ist es auch auf x86 kein Problem den Stack nach oben wachsen zu lassen ....
Dann schau Dir mal an wie CALL/RET oder INT/IRET oder PUSH/POP oder ENTER/LEAVE funktionieren (und wie die zugehörigen OpCodes aufgebaut sind). Das der Stack auf x86 nach unten wächst ist unumstößlich fest eingebaut! Gerade die anderen Architekturen sind da viel flexibler, viele von denen haben keine spezifischen Stack-Befehle sondern benutzen einfach normale Speicherzugriffe mit autoinkrement/autodekrement und deklarieren einfach ein beliebiges Register als Stack-Pointer und auch beim Modus-Wechsel (also von User-Mode in den System-Mode und zurück) erfolgt normalerweise kein Speicherzugriff und es wird auch kein Register modifiziert sondern es werden einfach ein paar der Register gegen andere (Schattenregister) ausgetauscht. Schau Dir mal ARM an, dort sind z.B. im GAS PUSH und POP nur vordefinierte Macros.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 19. November 2010, 17:20
Zitat von: erik
Man muss ein Array nicht zwangsläufig von Index 0 bis Index X linear durchlaufen, man kann da auch Lücken lassen die dann eben übersprungen werden. Ist das selbe Problem wie mit den Guard-Pages, wenn Du weißt wo die sind und wie groß die sind dann ist es kein Problem da drüber zu kommen.
Wir reden von Buffer-Overflows und das Problem ist ja das du etwas (linear) in den Buffer schreibst, aber davon ausgehst das sich jeder an gewisse Regeln hält was die Größe betrifft und wenn er das nicht macht, wird mehr in den Buffer geschrieben, als der Buffer groß ist. Das geschiet doch aber alles, wie gesagt, linear oder?

Zitat von: erik
Dann schau Dir mal an wie CALL/RET oder INT/IRET oder PUSH/POP oder ENTER/LEAVE funktionieren (und wie die zugehörigen OpCodes aufgebaut sind). Das der Stack auf x86 nach unten wächst ist unumstößlich fest eingebaut!
Ich behaupte das dem nicht so ist. Die von dir genannten Opcodes packen nur etwas auf den Stack, in welche Richtung das geschiet, wird meines Wissens nach, durch den Segment Diskriptor des Stack Segments festgelegt und da kann man auch sagen das der Stack nach oben wachsen kann.

@OffTopic

Zitat von: erik
Als Mindestausstattung an qualitativ hochwertigen Sportgeräten sollte eine Freundin vorhanden sein, da gibt es dann schon viele flexible Möglichkeiten zur körperlichen Bewegung, auch an frischer Luft. Ich meine so Dinge wie eine Fahrradtour usw. Wink
Die ist vorhanden, aber leider sind immer irgendwelche Faktoren vorhanden die etwas wie eine Fahrradtour schwer bis gar nicht möglich machen vorhanden. Die andere Sportart ist leider nicht so effektiv wie gutes Kraft-/Ausdauertraining ;) (macht aber mehr Spaß)
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 19. November 2010, 17:31
Hallo,

Objekte auf den Stack zu legen anstatt dafür den Heap zu benutzen ist eigentlich eine Entscheidung das Programmierers. Sicher kann man versuchen das der Compiler da zur compilezeit etwas optimiert oder man könnte Code einfügen lassen der zur Laufzeit ermittelt wie viel Speicher da eigentlich geholt werden soll und wenn das nicht zu viel ist (<= z.B. 64kBytes) dann wird einfach Stack benutzt. Ich denke schon das man da einiges vom Compiler optimieren lassen könnte aber ich glaube nicht dass das heutige Compiler bereits tun.
Würde mich nicht wundern, wenn sie das tun - das bringt eine Menge Performance, wenn eben dieser Kleinkram nicht über die Heap-Verwaltung geht und wenn der Stack ausreichend groß ist, kann man das auch mit größeren kurzlebigen Objekten tun.

Was die "Magic Cookies" angeht so ist das einfach ein Problem der Stack-Implementierung, das der Stack nach unten wächst aber alle anderen Datenstrukturen nach oben ist einfach eine Fehlkonstruktion (zumindest aus heutiger Sicht wo der Stack einfach ein beliebiger dynamischer Bereich des Speichers ist und nicht das obere Ende eines fixen Real-Mode-Segments). Deswegen wächst mein Stack ja auch nach oben und schon ist dieses Problem nur noch halb so schlimm, trotzdem werde ich noch etwas Denkarbeit in die Realisierung von VLAs usw. investieren müssen.
Naja, du kannst als Userspace-Programm deinen Stack auch per Hand adressieren und wenn du dann in der Lage bist, die Rücksprungadressen zu verändern (unter DOS nur ein "pop" zuviel), dann fliegt dir dein Programm um die Ohren. Viel schlimmer ist es, dass du Argumentübergabe auch über den Stack machst und die Syscalls daraus unter Umständen irgendetwas berechnen. Das ist ein wunderbarer Angriffsvektor, wenn die Syscalls nicht sicher implementiert sind und das ist genau unabhängig von der Richtung, in die der Stack wächst... man denke auch an Return Based Programming.

Echten Zufall müsste eigentlich auch die CPU selber zur Verfügung stellen, etwa so wie VIA das macht, und schon wäre noch ein Angriffspunkt weg. Wenn der Referenzwert für die Cookies irgendwo im Speicher liegt dann ist er dort auch lesbar und vor allem auch manipulierbar. Welches OS markiert schon die Pages in denen die .const-Section liegt wirklich als Read-Only?
Sollte jedes moderne OS eigentlich tun, bei Linux bin ich mir relativ sicher und bei Windows auch (weil dort viel an Sicherheit gedacht wurde). Wenn man das NX-Bit vollständig unterstützt, dann ist das RO-Bit doch nur noch eine Nebensache, weil die Infrastruktur (Page-Permissions) schon da ist... Wahrer Zufall wird auch von den CPUs nur begrenzt (OpenSSL unterstützt den RNG von VIA-CPUs übrigens nicht, weil er bestimmte Anforderungen nicht erfüllt), außerdem kann das nicht jede CPU. Wenn das vorhanden ist, ist das allerdings eine sehr gute (weitere) Quelle für Zufallszahlen.

Irgendwo muss es doch aber gespeichert sein, damit zu einen Wert zum überprüfen hast und diese Adresse dieses Wertes muss auch bekannt sein.
Das ist eines der Probleme dieser Stack-Protectoren. Die schützen zwar gegen "aus versehen" fehlerhaften Code oder simple Buffer-Overflows aber nicht gegen einen gezielten und mit Köpfchen vorbereiteten Angriff.
Wenn dieses Magic Cookie für jeden Stack extra abgespeichert wird, und zwar in nicht-Userspace-lesbaren Kernel-Strukturen (!), dann kann die Applikation das nicht wissen und damit auch nicht dagegen vorgehen. Aber sicher ist nur, dass es keine absolute Sicherheit gibt - aber selbst eine simple Buffer-Overflow-Erkennung ist schon etwas richtig feines.

Als Mindestausstattung an qualitativ hochwertigen Sportgeräten sollte eine Freundin vorhanden sein, da gibt es dann schon viele flexible Möglichkeiten zur körperlichen Bewegung, auch an frischer Luft. Ich meine so Dinge wie eine Fahrradtour usw. ;)
In Ermangelung einer solchen gehe ich jeden Morgen/Abend insgesamt ein paar km zu Fuß zur derzeitigen Arbeitsstelle. Das muss an Bewegung ausreichen.

Zitat von: erik
Man muss ein Array nicht zwangsläufig von Index 0 bis Index X linear durchlaufen, man kann da auch Lücken lassen die dann eben übersprungen werden. Ist das selbe Problem wie mit den Guard-Pages, wenn Du weißt wo die sind und wie groß die sind dann ist es kein Problem da drüber zu kommen.
Wir reden von Buffer-Overflows und das Problem ist ja das du etwas (linear) in den Buffer schreibst, aber davon ausgehst das sich jeder an gewisse Regeln hält was die Größe betrifft und wenn er das nicht macht, wird mehr in den Buffer geschrieben, als der Buffer groß ist. Das geschiet doch aber alles, wie gesagt, linear oder?
Ich kann mir ein Array int ar[1] deklarieren und dann mit Indizierung ( ar[1024*1024*1024] ) durchaus gezielt auf Speicher vor/hinter dem Array zugreifen. Wenn ich weiß, wo mein Array liegt (kann ich über den Pointer rauskriegen, in einem linaren Adressraum) und ich weiß, wie groß meine Daten sind (ich kenne mein Programm), dann weiß ich im Normalfall auch, wo die Rücksprungadressen sind und kann diese gezielt überschreiben. Da der Stack mein eigener ist, kannst du dich davor auch nicht schützen: Stack muss les- & schreibbar sein. Stack ausführen ist auch so ne nette Technik, die für Exploits gerne benutzt wird - denn die Übergabeparameter in die Syscalls liegen auch auf dem Stack und so bekommt man Code in den Kernel, ohne dass dieser es merkt.

Magic Cookies sind mehr als nur eine simple Buffer-Overflow-Erkennung, sie können (wenn die zugrundeliegenden Informationen Position/Wert des Cookies sicher versteckt sind) auch Stack-Angriffe vermeiden. Kostet natürlich Performance.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 19. November 2010, 17:40
Zitat von: svenska
Würde mich nicht wundern, wenn sie das tun - das bringt eine Menge Performance, wenn eben dieser Kleinkram nicht über die Heap-Verwaltung geht und wenn der Stack ausreichend groß ist, kann man das auch mit größeren kurzlebigen Objekten tun.
Erwischt ;)

Woher weiß malloc() wie lange du den angeforderten Speicher brauchst? Was ich nämlich immernoch so sehe, ist dass du keine langlebigen Objekte auf den Stack packen kannst, weil dieser beim Funktions-Return wieder abgebaut und dann von einer neuen Funktion überschrieben wird.

Zitat von: svenska
Wenn dieses Magic Cookie für jeden Stack extra abgespeichert wird, und zwar in nicht-Userspace-lesbaren Kernel-Strukturen (!), dann kann die Applikation das nicht wissen und damit auch nicht dagegen vorgehen.
Sorry, aber das ist mit Kanonen auf Spatzen schießen. Denn das würde jedes Mal heißen, das ich nen Syscall machen müsste um diese Überprüfung durchzuführen und das ist es nicht Wert bzw. meiner Meinung nach vollkommen übertrieben (geht mit viel Fantasie auch in die Richtung Sozialismus ;) ).

Zitat von: svenska
dann weiß ich im Normalfall auch, wo die Rücksprungadressen sind und kann diese gezielt überschreiben.
Wir sind immernoch bei nem Buffer-Overflow und das jemand von Außen versucht durch einen solchen seinen eigenen Code auszuführen.
Wenn der Stack nach Oben anstatt nach Unten zu wachsen, dann ist der Buffer-Overflow kein Problem mehr, weil er nicht mehr die Rücksprungadresse oder irgendetwas vor dem Buffer überschreibt.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 19. November 2010, 20:58
Hallo,


.... Das geschiet doch aber alles, wie gesagt, linear oder?
Meistens sicher schon. Ein Overflow der z.B. in einem sprintf (ohne 'n') ensteht dürfte wohl keine Lücken haben. Aber kannst Du das für alle Situationen garantieren? Wenn Du von linear ausgehst machst Du wieder so eine Annahme die zwar oft aber eben nicht immer zutrifft, böse Hacker werden sich da sicher nicht dran halten.

Zitat von: erik
Das der Stack auf x86 nach unten wächst ist unumstößlich fest eingebaut!
Ich behaupte das dem nicht so ist.
Dann beweise mal auch!

in welche Richtung das geschiet, wird meines Wissens nach, durch den Segment Diskriptor des Stack Segments festgelegt und da kann man auch sagen das der Stack nach oben wachsen kann.
Das Bit musst Du mir erst mal zeigen, das kenne ich noch nicht.

@OffTopic

aber leider sind immer irgendwelche Faktoren vorhanden die etwas wie eine Fahrradtour schwer bis gar nicht möglich machen vorhanden.
Was kann es denn da für Ausreden geben, deswegen empfehle ich ja extra ne Freundin weil die viele (männliche) Ausreden schon ganz gut wegdiskutieren können und eben auch für eine erhöhte Motivation sorgen können. ;)

(macht aber mehr Spaß)
Das ist hier völlig irrelevant, entscheidend ist das Dein Körper in guter Verfassung ist. Du kennst doch sicher "in einem gesunden Körper wohnt ein gesunder Geist"?


Würde mich nicht wundern, wenn sie das tun - das bringt eine Menge Performance, wenn eben dieser Kleinkram nicht über die Heap-Verwaltung geht ...
Das glaube ich nicht, denn da gibt es ne Menge Einschränkungen zu berücksichtigen. Wenn für das Objekt ein Pointer gebildet wird und dieser Pointer in eine statische Variable gelegt wird dann ist das schon wieder alles erledigt, auch muss geprüft werden ob das Objekt wirklich in jedem Fall vor dem Ende der Funktion wieder gelöscht wird (es also keine Wege gibt aus der Funktion raus zu kommen ohne das Objekt zu löschen, Exceptions wären da z.B. eine Möglichkeit). Aber prinzipiell ist das eine gute Möglichkeit etwas Performance zu gewinnen, es wäre auf jeden Fall sehr schön wenn die Compiler das können.

Naja, du kannst als Userspace-Programm deinen Stack auch per Hand adressieren und wenn du dann in der Lage bist, die Rücksprungadressen zu verändern (unter DOS nur ein "pop" zuviel), dann fliegt dir dein Programm um die Ohren.
Klar muss der Compiler aufpassen das er keinen Mist baut aber das ist denke ich heute kein ernstes Problem mehr. Der böse Hacker kann das natürlich trotzdem machen wenn er denn einen Weg findet eigenen Code einzuschleusen.

Viel schlimmer ist es, dass du Argumentübergabe auch über den Stack machst und die Syscalls daraus unter Umständen irgendetwas berechnen. Das ist ein wunderbarer Angriffsvektor, wenn die Syscalls nicht sicher implementiert sind und das ist genau unabhängig von der Richtung, in die der Stack wächst
Auf meiner Plattform wird es keine Syscalls mit Parametern/Rückgabewerten auf dem Stack geben, ich hab ja auch genügend Register, außerdem wird der Stack beim Modus-Wechsel automatisch mit gewechselt und sowas wie das Kopieren von den ersten x Stack-Bytes beim Ring-Wechsel auf x86 gibt es bei mir auch nicht. (hat das auf x86 überhaupt schon mal jemand benutzt?) Ein ordentlich programmierter Syscall prüft alle Parameter auf Plausibilität bevor er sie benutzt.

man denke auch an Return Based Programming.
Da lese ich gleich mal genauer nach. Ich dachte immer das vor allem x86, wegen seinen Befehlen mit flexibler Länge und dem Umstand das man jede beliebige Byte-Kette als Befehle interpretieren kann, da ein Problem ist aber es gibt da wohl einen Angriff auf SPARC und das ist dann doch schon etwas ernster. Trotzdem muss der Angreifer mindestens ein mal den Stack-Pointer nach seinen Wünschen manipulieren können bevor so ein Angriff funktioniert und da bieten einfach Stacks die nach oben wachsen eine deutlich kleinere Angriffsfläche.

(OpenSSL unterstützt den RNG von VIA-CPUs übrigens nicht, weil er bestimmte Anforderungen nicht erfüllt)
Das der RNG in den VIA-CPUs von nur bescheidener Qualität ist hab ich auch schon mal gelesen, aber das würde ich eher auf schlechte Implementierung bei VIA schieben, das kann man ganz sicher besser machen. Ein erstes Indiz für die schlechte RNG-Qualität bei VIA ist ja schon das dieser RNG konfigurierbar ist. Was soll man bei einem richtigen TRNG konfigurieren können? Aber prinzipiell gehört sowas direkt in den Befehlssatz einer jeden anständigen CPU.

Wenn dieses Magic Cookie für jeden Stack extra abgespeichert wird, und zwar in nicht-Userspace-lesbaren Kernel-Strukturen (!)
Da schließe ich mich FlashBurns Meinung an und sage dass das deutlich viel zu viel Aufwand ist. Vor allem in Relation dazu das damit trotzdem keine 100%-Sicherheit (nicht mal 99%) gegen die Manipulation der Rücksprungadressen gewährleistet werden kann. Was hinderten den eingeschleusten Code daran diesen Syscall zu benutzen um dieses Geheimnis auszuspähen?

aber selbst eine simple Buffer-Overflow-Erkennung ist schon etwas richtig feines.
Wenn man sprintf und all die anderen Kollegen ohne 'n' aus dem C-Standard verbannen tät und jedem Programmierer erklärt warum man sowas nicht macht wären die wichtigsten Einfallstore für "einfache" Buffer-Overflows schon mal gebannt. Was dann noch übrig bleibt sind die komplexeren Angriffsszenarien und gegen die hilft eine simple Stack-Protection auch nicht wirklich. Ist eben eine Frage der Verhältnismäßigkeit.

In Ermangelung einer solchen gehe ich jeden Morgen/Abend insgesamt ein paar km zu Fuß zur derzeitigen Arbeitsstelle. Das muss an Bewegung ausreichen.
Das ist sehr löblich fordert aber leider nicht den gesamten Körper, ich finde da muss noch mehr getan werden. ;)

Ich kann mir ein Array int ar[1] deklarieren und dann mit Indizierung ( ar[1024*1024*1024] ) durchaus gezielt auf Speicher vor/hinter dem Array zugreifen.
Man beachte das "vor", ist es nicht eigentlich idiotisch das man bei der Speicheradressierung bewusst mit Überläufen arbeiten kann? Ich finde es deutlich besser wenn man im OpCode des Befehls zwischen + und - wählen kann und die CPU alle Überläufe mit ner Exception quittiert, da sind dann gleich wieder eine ganze Reihe von potentiellen Einfallstoren zugegangen.

denn die Übergabeparameter in die Syscalls liegen auch auf dem Stack und so bekommt man Code in den Kernel, ohne dass dieser es merkt.
Sind heutige Kernels für sowas wirklich anfällig? Das hätte ich ehrlich gesagt nicht vermutet, da müsste man sich doch als Kernel-Programmierer IMHO schon Mühe geben damit sowas auch wirklich klappt.

Magic Cookies sind mehr als nur eine simple Buffer-Overflow-Erkennung, sie können (wenn die zugrundeliegenden Informationen Position/Wert des Cookies sicher versteckt sind) auch Stack-Angriffe vermeiden. Kostet natürlich Performance.
Aber gerade das Versteck ist das Problem, wenn der vom Compiler generierte Code da ran kommt dann kann das auch der eingeschleuste Code.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 19. November 2010, 21:16
Zitat von: erik
Aber kannst Du das für alle Situationen garantieren? Wenn Du von linear ausgehst machst Du wieder so eine Annahme die zwar oft aber eben nicht immer zutrifft, böse Hacker werden sich da sicher nicht dran halten.
Ok, wenn dem so ist, dann nutzt einem aber auch so ein Magic Cookie nichts. Denn der Hacker kann das ja einfach "ignorieren". Wo ich dann wieder da wäre, das die ganzen Schutzmaßnahmen eh nichts bringen, da jemand der wirklich will auch kann ;)

Zitat von: erik
Das Bit musst Du mir erst mal zeigen, das kenne ich noch nicht.
In meiner Revision der Intel Manuals ist das "3.4.5 Segment Discriptors". Da kann man sagen, ob ein Datensegment nach oben oder nach unten wächst.

Zitat von: erik
Was kann es denn da für Ausreden geben, deswegen empfehle ich ja extra ne Freundin weil die viele (männliche) Ausreden schon ganz gut wegdiskutieren können und eben auch für eine erhöhte Motivation sorgen können. Wink
Das liegt einmal am Wetter (hier regnet es wirklich oft, das sind wir einfach nicht gewöhnt) und sich mit mir zu bewegen ist nicht so einfach ;)

Zitat von: erik
Das ist hier völlig irrelevant, entscheidend ist das Dein Körper in guter Verfassung ist. Du kennst doch sicher "in einem gesunden Körper wohnt ein gesunder Geist"?
Ich hätte wahrscheinlich dazu sagen sollen, dass ich meiner Meinung nach nicht Fit bin, aber wenn ich mir so den durchschnitts Deutschen angucke, sieht es gar nicht so schlecht aus ;)

Zitat von: erik
auch muss geprüft werden ob das Objekt wirklich in jedem Fall vor dem Ende der Funktion wieder gelöscht wird
Aber wie willst du das als Compiler 100%ig wissen und zur Laufzeit möchte ich das nicht rausbekommen müssen (das wäre ja schon fast nen Garbage Collector)?

Zitat von: erik
Sind heutige Kernels für sowas wirklich anfällig? Das hätte ich ehrlich gesagt nicht vermutet, da müsste man sich doch als Kernel-Programmierer IMHO schon Mühe geben damit sowas auch wirklich klappt.
Mich würde jetzt eher mal interessieren, wie man es dann schafft das der Code auch ausgeführt wird?
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 19. November 2010, 21:36
Hallo,


Ok, wenn dem so ist, dann nutzt einem aber auch so ein Magic Cookie nichts. Denn der Hacker kann das ja einfach "ignorieren". Wo ich dann wieder da wäre, das die ganzen Schutzmaßnahmen eh nichts bringen, da jemand der wirklich will auch kann ;)
Diese Magic Cookies sind ganz gut wenn man Buffer-Overflows bei sprintf usw. vermeiden will aber gegen echte Angriffe eines Profis sind die IMHO nicht viel wert außer das sie eben die Latte ein klein wenig höher legen.

In meiner Revision der Intel Manuals ist das "3.4.5 Segment Discriptors". Da kann man sagen, ob ein Datensegment nach oben oder nach unten wächst.
Steht da auch das man damit die Wirkungsweise der Stack-Befehle beeinflussen kann?

hier regnet es wirklich oft, das sind wir einfach nicht gewöhnt
Muss ich jetzt noch den Spruch mit der unpassenden Kleidung ablassen? Alles nur ausreden!

aber wenn ich mir so den durchschnitts Deutschen angucke, sieht es gar nicht so schlecht aus ;)
Du sollst Dich doch nicht an irgendwelchen Leuten orientieren, ich dachte Du bist Profi also suche Dir passende Vergleiche. ;)

Zitat von: erik
auch muss geprüft werden ob das Objekt wirklich in jedem Fall vor dem Ende der Funktion wieder gelöscht wird
Aber wie willst du das als Compiler 100%ig wissen
Einfach eine Control-Flow-Graph-Analyse (oder wie das noch mal genau heißt), die Kunst ist einfach nur das man keinen möglichen Weg vergisst/übersieht. Sowas gibt es auch schon, in Java kann man z.B. eine Variable als const (heißt dort final) deklarieren ohne sie gleich zu initialisieren und der Compiler prüft ob die in jedem Fall vor dem ersten Lesezugriff genau ein mal beschrieben wird.

Mich würde jetzt eher mal interessieren, wie man es dann schafft das der Code auch ausgeführt wird?
Eben das meinte ich mit "schon Mühe geben".


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 19. November 2010, 21:55
Zitat von: erik
Steht da auch das man damit die Wirkungsweise der Stack-Befehle beeinflussen kann?
Das ist das interessante, dort steht halt das man damit die Richtung festlegen kann in welche ein Datensegment wächst.
Liest man sich aber die Erklärung zu POP durch dann liest man dort, dass es egal ist (also es steht nicht so da, sondern das bei einem POP immer inkrementiert wird).
Was soll man daraus also jetzt schließen? Wenn ich morgen mal die Zeit finde, kann ich es ja mal auf realer Hardware testen (oder ich gucke nachher noch mal schnell in Bochs nach, dem Source Code).

Zitat von: erik
Einfach eine Control-Flow-Graph-Analyse (oder wie das noch mal genau heißt), die Kunst ist einfach nur das man keinen möglichen Weg vergisst/übersieht. Sowas gibt es auch schon, in Java kann man z.B. eine Variable als const (heißt dort final) deklarieren ohne sie gleich zu initialisieren und der Compiler prüft ob die in jedem Fall vor dem ersten Lesezugriff genau ein mal beschrieben wird.
Bei Java möge das ja gehen (zumal du von einer lokalen Variable sprichst), aber wie willst du sicherstellen, das wenn du einen Pointer auf diesen Speicher an eine Funktion übergibst, die in einem anderen Source-File liegt (und der Compiler keinen Zugriff drauf hat) oder meinetwegen in einer Library, das diese nicht den Pointer irgendwo speichert um später damit zu arbeiten?
Oder anders, man muss den Speicher ja nicht in der erstellenden Funktion wieder freigeben, sondern könnte das auch in einer anderen machen (selbes Problem).
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 19. November 2010, 23:16
Hallo,

Woher weiß malloc() wie lange du den angeforderten Speicher brauchst? Was ich nämlich immernoch so sehe, ist dass du keine langlebigen Objekte auf den Stack packen kannst, weil dieser beim Funktions-Return wieder abgebaut und dann von einer neuen Funktion überschrieben wird.
Die kannst du nicht auf den Stack legen und das sicherzustellen ist Aufgabe des Compilers. Außerdem geht es nicht mit großen Strukturen, da der Stackspace begrenzt ist. Das sind Randbedingungen, die im Voraus bekannt sind. Allerdings sind kleine Strukturen meist nur kurzlebig oder lokal, wobei lokal immer zu einer Unterfunktion relativ ist. Im Zweifelsfall ist es relativ zu main(), wie z.B. ein pollfds-Array für poll(). Da ein realloc() nicht garantiert, dass der Zeiger gültig bleibt, kann man so dynamische Arrays auf dem Stack bauen, die für seltsame Anwendungsfälle in den Heap verschoben werden.

Das sehe ich nicht als gefangen an. :-P

Zitat von: svenska
Wenn dieses Magic Cookie für jeden Stack extra abgespeichert wird, und zwar in nicht-Userspace-lesbaren Kernel-Strukturen (!), dann kann die Applikation das nicht wissen und damit auch nicht dagegen vorgehen.
Sorry, aber das ist mit Kanonen auf Spatzen schießen. Denn das würde jedes Mal heißen, das ich nen Syscall machen müsste um diese Überprüfung durchzuführen und das ist es nicht Wert bzw. meiner Meinung nach vollkommen übertrieben (geht mit viel Fantasie auch in die Richtung Sozialismus ;) ).
Man muss es ja nicht in der Form implementieren. Ein simpler Mechanismus (statisches Magic Cookie direkt zwischen Rücksprung und Daten) erkennt Stack-Overflows und ist zumindest ein sinnvoller Debug-Helfer.

Zitat von: svenska
dann weiß ich im Normalfall auch, wo die Rücksprungadressen sind und kann diese gezielt überschreiben.
Wir sind immernoch bei nem Buffer-Overflow und das jemand von Außen versucht durch einen solchen seinen eigenen Code auszuführen.
Wenn der Stack nach Oben anstatt nach Unten zu wachsen, dann ist der Buffer-Overflow kein Problem mehr, weil er nicht mehr die Rücksprungadresse oder irgendetwas vor dem Buffer überschreibt.
Korrekt. Wenn es um Code ausführen geht, dann kannst du den Stack trotzdem adressieren, egal ob er nach oben oder nach unten wächst. Ein ungewollter Bufferoverflow wird nicht gezielt Code ausführen (er wird in der Regel das Programm zum Absturz bringen), daher sind das zwei Paar Schuhe.

Aber prinzipiell ist das eine gute Möglichkeit etwas Performance zu gewinnen, es wäre auf jeden Fall sehr schön wenn die Compiler das können.
Ich behaupte grundlos, dass Compiler genau diese Überprüfungen ohnehin im Rahmen ihrer Optimierungen schon tun und daher kostenlos (höchstens dauert das Kompilieren länger) die richtige Entscheidung treffen können.

Ein ordentlich programmierter Syscall prüft alle Parameter auf Plausibilität bevor er sie benutzt.
Ich behaupte, dass das nicht überall möglich (oder performant) ist. Eine grundlegende Optimierung ist doch, überflüssige Prüfungen einzusparen... darum sind Datenbanken auf virtuellen Maschinen auch schneller auf realen - die Wartezeit für "wurde auf Platte geschrieben?" ist in der VM null, in der realen Maschinen von der Festplatte abhängig. ;-)

man denke auch an Return Based Programming.
Da lese ich gleich mal genauer nach. Ich dachte immer das vor allem x86, wegen seinen Befehlen mit flexibler Länge und dem Umstand das man jede beliebige Byte-Kette als Befehle interpretieren kann, da ein Problem ist aber es gibt da wohl einen Angriff auf SPARC und das ist dann doch schon etwas ernster. Trotzdem muss der Angreifer mindestens ein mal den Stack-Pointer nach seinen Wünschen manipulieren können bevor so ein Angriff funktioniert und da bieten einfach Stacks die nach oben wachsen eine deutlich kleinere Angriffsfläche.
Damit wurde eine Wahlmaschine manipuliert. Die Technik dahinter hätte ich schon als sicher betrachtet, das war ein Z80 mit einem ROM als Codebereich und einem RAM als Datenbereich. Wenn eine Execute-Adresse auf dem Bus in das RAM zeigte, schlug ein Komparator an und hat RESET aktiviert - also kein Code im RAM möglich. Darum hat man sich auf Return Based Programming zurückgezogen und das kann man, den Original-Code vorausgesetzt, auf jeder Architektur machen.

(OpenSSL unterstützt den RNG von VIA-CPUs übrigens nicht, weil er bestimmte Anforderungen nicht erfüllt)
Das der RNG in den VIA-CPUs von nur bescheidener Qualität ist hab ich auch schon mal gelesen, aber das würde ich eher auf schlechte Implementierung bei VIA schieben, das kann man ganz sicher besser machen. Ein erstes Indiz für die schlechte RNG-Qualität bei VIA ist ja schon das dieser RNG konfigurierbar ist. Was soll man bei einem richtigen TRNG konfigurieren können? Aber prinzipiell gehört sowas direkt in den Befehlssatz einer jeden anständigen CPU.
Davon kannst du aber nicht ausgehen. Sicherheit ist noch nicht so lange wichtig, Microsoft hat damit in Gründlichkeit erst mit Vista begonnen. Andererseits bin ich ja der Meinung, dass du, sofern du einigermaßen zufällige Ereignisse verschiedener Quellen benutzt, gar keine Garantie über den absoluten Zufall mehr brauchst. Eine Anwendung kann ja nicht jede Hardware gleichermaßen unter Kontrolle haben, um sowas vorherzusagen.

Wenn dieses Magic Cookie für jeden Stack extra abgespeichert wird, und zwar in nicht-Userspace-lesbaren Kernel-Strukturen (!)
Da schließe ich mich FlashBurns Meinung an und sage dass das deutlich viel zu viel Aufwand ist. Vor allem in Relation dazu das damit trotzdem keine 100%-Sicherheit (nicht mal 99%) gegen die Manipulation der Rücksprungadressen gewährleistet werden kann. Was hinderten den eingeschleusten Code daran diesen Syscall zu benutzen um dieses Geheimnis auszuspähen?
Der geht an mir vorbei. Jeder Syscall, der vom Programm aufgerufen wird, prüft im Vorbeigehen die Konsistenz des Stacks. Da jedes sinnvolle Programm irgendwann einmal Syscalls aufrufen muss, ist das für mich ein Weg. Mal abgesehen davon, welchen Syscall meinst du denn?

aber selbst eine simple Buffer-Overflow-Erkennung ist schon etwas richtig feines.
Wenn man sprintf und all die anderen Kollegen ohne 'n' aus dem C-Standard verbannen tät und jedem Programmierer erklärt warum man sowas nicht macht wären die wichtigsten Einfallstore für "einfache" Buffer-Overflows schon mal gebannt. Was dann noch übrig bleibt sind die komplexeren Angriffsszenarien und gegen die hilft eine simple Stack-Protection auch nicht wirklich. Ist eben eine Frage der Verhältnismäßigkeit.
Soweit ich informiert bin, wirft das MSVC einen Fehler aus, wenn man diese Funktionen benutzt und dem GCC kann man das auch beibringen. Microsoft verbietet inhärent unsichere Funktionen (bzw. sie werden als obsolet markiert und kompilieren später nicht) und in den Developer Guidelines von Apple wird auch explizit auf Buffer-Overflows eingegangen. Das Umdenken ist nicht mehr das Problem; mal abgesehen davon können Buffer-Overflows auch außerhalb des Stacks geschehen, dann bringt das sowieso nichts weil andere Baustelle.

Man beachte das "vor", ist es nicht eigentlich idiotisch das man bei der Speicheradressierung bewusst mit Überläufen arbeiten kann? Ich finde es deutlich besser wenn man im OpCode des Befehls zwischen + und - wählen kann und die CPU alle Überläufe mit ner Exception quittiert, da sind dann gleich wieder eine ganze Reihe von potentiellen Einfallstoren zugegangen.
Guck dir im x86 mal den Opcode-Space an. Der ist voll. Da nochmal jeden potentiellen Befehl in zwei Varianten haben wird eher nichts. Zumal ich die Möglichkeit an sich nicht so schlecht finde - wobei ein Vergleich mit dem i860 doch "etwas" hinkt.

Wenn du schnelle, stromsparende CPUs möchtest, musst du entweder zwischen Performance und Anzahl der Transistoren einen Kompromiss finden. Die Möglichkeiten gehen von einfacheren Architekturen (Atom/VIA) und Emulatoren (Transmeta) zu neuen Architekturen (ARM, MIPS). Für Dinge, die man im Compiler beheben könnte noch Unmengen an Opcodes zu reservieren, ist beiden Varianten nicht förderlich... Das ist auch eine Frage der Verhältnismäßigkeit (und Kompatiblität).

Sind heutige Kernels für sowas wirklich anfällig? Das hätte ich ehrlich gesagt nicht vermutet, da müsste man sich doch als Kernel-Programmierer IMHO schon Mühe geben damit sowas auch wirklich klappt.
Naja, wird dein Kernel den Sicherheitsanforderungen von "heutigen Kernels" genügen? Ich traue mir sichere Programmierung jedenfalls nicht zu, geringfügig abgesicherte Designs schon.

Magic Cookies sind mehr als nur eine simple Buffer-Overflow-Erkennung, sie können (wenn die zugrundeliegenden Informationen Position/Wert des Cookies sicher versteckt sind) auch Stack-Angriffe vermeiden. Kostet natürlich Performance.
Aber gerade das Versteck ist das Problem, wenn der vom Compiler generierte Code da ran kommt dann kann das auch der eingeschleuste Code.
Eben das muss man verhindern... der Compiler generierte Userspace-Code darf keine Möglichkeit haben, der vom Compiler generierte und davon unabhängige Kernel-Code muss diese Möglichkeit haben und nutzen. Ist für mich eher ne Designsache, wobei ich es wahrscheinlich auch nicht konkret implementieren kann. ;-)

Ok, wenn dem so ist, dann nutzt einem aber auch so ein Magic Cookie nichts. Denn der Hacker kann das ja einfach "ignorieren". Wo ich dann wieder da wäre, das die ganzen Schutzmaßnahmen eh nichts bringen, da jemand der wirklich will auch kann ;)
Mein Vorschlag steht und fällt mit dem Versteck. Du kannst nichts ignorieren, was du nicht kennst. Und dass jemand, der will, auch kann, sieht man eindeutig wirklich gut an der Differential Power Analysis (ich errechne aus dem Stromverbrauch eines Mikrocontrollers den privaten Schlüssel, wenn ich den Algorithmus kenne). Sowas ist krank.

Zitat von: erik
auch muss geprüft werden ob das Objekt wirklich in jedem Fall vor dem Ende der Funktion wieder gelöscht wird
Aber wie willst du das als Compiler 100%ig wissen und zur Laufzeit möchte ich das nicht rausbekommen müssen (das wäre ja schon fast nen Garbage Collector)?
Ich behaupte, das fällt bei den ganzen Optimierungsschritten für den Compiler hinten mit raus.

Mich würde jetzt eher mal interessieren, wie man es dann schafft das der Code auch ausgeführt wird?
Standardprozedere: Ich überschreibe irgendeinen Kernelspeicher und weiß, zu welcher Funktion der gehört und an welcher Stelle in der Funktion ich deren Code gerade überschreibe. Danach brauche ich die Funktion nur noch so aufrufen, dass sie an diese Stelle springt. Beide Phasen des Exploits sind vollkommen unabhängig voneinander und können auch verschiedene Anwendungen betreffen (PHP überschreibt Kernelspeicher, Apache löst überschriebene Funktion aus). Klingt einfach, ist inzwischen aber sehr schwierig (u.a. durch so nette Dinger wie Heap-Randomization oder Magic Cookies im Stack).

Diese Magic Cookies sind ganz gut wenn man Buffer-Overflows bei sprintf usw. vermeiden will aber gegen echte Angriffe eines Profis sind die IMHO nicht viel wert außer das sie eben die Latte ein klein wenig höher legen.
Hundertprozentige Sicherheit gibt es nicht. Ziel ist es immer, die Latte so hoch zu legen, dass der Angriff unattraktiv oder unzuverlässig wird, dann werden nämlich andere Angriffsvektoren gesucht. Heutzutage greift man nur noch selten einen PC an, sondern eher den Anwender; OS/2 oder Linux sind nahezu virenfrei, weil sie auf so gut wie keinem PC installiert sind. Und selbst Windows wird nicht mehr angegriffen, sondern Dinge wie Acrobat Reader oder Adobe Flash oder Internet Explorer oder Firefox.

Hängst du die Messlatte hinreichend hoch, bleibst du von Breitbandangriffen verschont. Gegen gezielte Angriffe wie Styxnet kannst du dich nicht schützen.

Oder anders, man muss den Speicher ja nicht in der erstellenden Funktion wieder freigeben, sondern könnte das auch in einer anderen machen (selbes Problem).
Das Problem fällt auf die Frage zurück, ob derselbe Stack für sowohl malloc()ende und free()ende Funktion gültig ist. Handelt es sich um eine direkt oder indirekt von der Überfunktion (malloc) aufgerufene Funktion (free), so gibt es kein Problem.

Handelt es sich um etwas Globales, musst du zwangsweise den Heap benutzen.

So, genug geschrieben, ich geh schlafen.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 19. November 2010, 23:29
Hallo,


Das ist das interessante, dort steht halt das man damit die Richtung festlegen kann in welche ein Datensegment wächst.
Das ist eine eher verwaltungstechnische Information, damit kann man nicht die Richtung des Stacks beeinflussen. Als ich dieses Flag vor vielen Jahren entdeckt habe dachte ich auch erst das man damit den Stack beeinflussen könnte aber dem ist nicht so. Ich sag es nur ungern (besser gesagt es gibt hier Leute die das nur ungern lesen) aber x86 ist einfach total vermurkst! Es ist ein klein wenig schade das der Itanium nichts geworden ist, da waren eine Reihe interessante Ideen drin aber auch wieder neuer Murks.

Liest man sich aber die Erklärung zu POP durch dann liest man dort, dass es egal ist (also es steht nicht so da, sondern das bei einem POP immer inkrementiert wird).
Wenn es nicht da steht dann gibt es das auch nicht, die Intel-Manuals sind in dieser Hinsicht sehr gründlich.

Bei Java möge das ja gehen (zumal du von einer lokalen Variable sprichst) ....
Das hat nichts mit Java zu tun, das sollte mit jeder ordentlich strukturierten Programmiersprache gehen. Das Ergebnis eines malloc landet ja auch in einer localen Variable wenn der so allozierte Speicher nur innerhalb einer Funktion benutzt werden soll. Sobald dieser Pointer in eine statische Variable kommt geht der Trick eh nicht mehr. Auch wenn man diesen Pointer an eine andere Funktion weiter gibt ist dieser Trick nicht mehr möglich (es sei den der Compiler hat die Möglichkeit die aufgerufene Funktion vollständig zu analysieren, z.B. bei statischen Funktionen in der selben .c-Datei). Dieser Trick wäre ein nettes Feature das aber nur in eng begrenzten Situationen funktioniert, aber davon gibt es sicher einige und wenn man sich da das malloc sparen könnte wäre es eine wünschenswerte Beschleunigung. Das Problem das bleibt ist das der Programmierer das eben auch tun kann und die meisten Programmierer überlegen sicher ob sie den Speicher den sie mal kurz brauchen vom Stack oder von malloc holen, diese Entscheidung dem Compiler zu überlassen lohnt sich nur dann wenn man davon aus gehen kann das der Compiler da den besseren Überblick (Analyse der aufgerufenen Funktionen mit beliebiger Tiefe) hat und das ist gerade erst am werden, der LLVM mit seiner Fähigkeit das er nach(während) dem Linken noch mal gründlich optimiert könnte dieses Feature eventuell in ein paar Jahren tatsächlich erfolgreich umsetzen.


@svenska:
Ich hab mir das mit dem Return Oriented Programming auf SPARC mal genauer durchgelesen und muss ehrlich sagen das ich das noch nicht ganz kapiert habe. Das Prinzip hab ich, glaube ich zumindest, verstanden aber das es wirklich so viele brauchbare Code-Schnippsel gibt (vor allem bei einer CPU-Architektur wie SPARC, die Bezeichnung als anti-x86 ist schon ziemlich zutreffend) erscheint mir irgendwie unlogisch. Trotzdem ist auch dieser Angriff eben davon anhängig das dieser Angriffs-String irgendwie auf den Stack kommt und dort den Stack-Pointer überschreibt, gerade das ließe sich bei nach oben wachsenden Stack nicht mit einer einzelnen Aktion erledigen. Nebst dessen das gutes ALR und auch das Randomisieren des Initial-Stack-Pointers (wenn das OS die Threads erstellt, beides soll mein OS können, gerade das mit dem Stack ist ja simpel zu implementieren) diesen Angriff nahezu unmöglich machen würden (selbst wenn der Stack nach unten wächst).


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 19. November 2010, 23:51
Ich hab mir das mit dem Return Oriented Programming auf SPARC mal genauer durchgelesen und muss ehrlich sagen das ich das noch nicht ganz kapiert habe. Das Prinzip hab ich, glaube ich zumindest, verstanden aber das es wirklich so viele brauchbare Code-Schnippsel gibt (vor allem bei einer CPU-Architektur wie SPARC, die Bezeichnung als anti-x86 ist schon ziemlich zutreffend) erscheint mir irgendwie unlogisch.
Du brauchst nur den vorhandenen Code an jeder Bytestelle disassemblieren, bis du auf ein RETURN triffst. Dann analysierst du, ob der Code, den du gefunden hast, irgendwas sinnvolles tut. Wenn du genug Stellen gefunden hast, um dir eine turingvollständige Umgebung zu bauen, brauchst du nur noch die Daten auf den Stack schieben und einen Stackpointer überschreiben (oder in einen vorhandenen Stack injizieren).

Zu SPARC kann ich nichts sagen, aber es funktioniert garantiert mit Architekturen, deren Opcode-Space hinreichend ausgefüllt ist, besonders gut mit dem Z80 (jedes Byte ist ein gültiger Opcode) oder mit ARM (durch die Condition Codes im Opcode), aber auch x86 ist sehr gut geeignet.

Trotzdem ist auch dieser Angriff eben davon anhängig das dieser Angriffs-String irgendwie auf den Stack kommt und dort den Stack-Pointer überschreibt, gerade das ließe sich bei nach oben wachsenden Stack nicht mit einer einzelnen Aktion erledigen.
Korrekt. Der Vorteil dieser Technik ist aber (a) du legst keine unspezifizierten Daten auf den Stack, sondern valide Rücksprungadressen und (b) du führst niemals Daten aus, sondern immer vorhandenen validen Code.

Damit führst du das NX-Bit sehr elegant ad absurdum.

Nebst dessen das gutes ALR und auch das Randomisieren des Initial-Stack-Pointers (wenn das OS die Threads erstellt, beides soll mein OS können, gerade das mit dem Stack ist ja simpel zu implementieren) diesen Angriff nahezu unmöglich machen würden (selbst wenn der Stack nach unten wächst).
Bei dir funktioniert das Überschreiben des Stackpointers durch Dereferenzierung im Code sowieso nicht, da dein Stack in einem eigenen Adressraum (Segment) lebt. Außerdem brauchst du dir den Ärger mit "kurzlebige Daten auf den Stack" auch nicht machen, da bei dir Stack und Heap zwar nicht identisch, aber mit identischen Techniken gebaut sind (je ein eigenes Segment).

Du hängst die Latte für Angriffe schon allein dadurch sehr hoch, dass du "komisches Zeug" machst. Wenn ich NetBSD auf ner VAX im Keller installiere, wird die allein dadurch keiner angreifen, weil normale binäre Exploits einfach nicht funktionieren und eh sich der Angreifer heutzutage dafür eine Toolchain baut... gut, ne VAX wär cool aber nicht praktisch.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 20. November 2010, 11:24
Hallo,


Ich hab mir das mit dem Return Oriented Programming auf SPARC mal genauer durchgelesen und muss ehrlich sagen das ich das noch nicht ganz kapiert habe. Das Prinzip hab ich, glaube ich zumindest, verstanden aber das es wirklich so viele brauchbare Code-Schnippsel gibt (vor allem bei einer CPU-Architektur wie SPARC, die Bezeichnung als anti-x86 ist schon ziemlich zutreffend) erscheint mir irgendwie unlogisch.
Du brauchst nur den vorhandenen Code an jeder Bytestelle disassemblieren, bis du auf ein RETURN triffst....
Das ist mir alles klar, und es funktioniert auf x86 auch wunderbar weil es eben in den Bytes ne Menge Befehle gibt die der Compiler gar nicht bewusst erzeugt hat aber auf den typischen RISC-Architekturen mit fester Befehlsgröße und immer ausgerichteten Befehlen gibt es eigentlich keine Befehle die der Compiler nicht absichtlich generiert hat. Was ich mich eben frage ist: warum findet man dann trotzdem genug dieser Code-Schnippsel um damit alle wichtigen Operationen für eine Turing-Maschine zusammen zu bekommen? Könnte man da nicht versuchen dafür zu sorgen das der Compiler da etwas vorsichtiger ist? Außerdem benutzen die für ihren Angriff auf SPARC ja immer nur das Ende der Funktionen aber sollte in diesem Ende nicht irgendeine Form von "Stack-Aufräum-Code" sein der eben ein Chaos hinterlässt wenn nicht auch der zugehörige "Stack-Einrichtungs-Code" am Funktionsanfang durchlaufen wurde? Klar der Stack wird in diesen Angriffs-Daten passend vorbereitet aber dass das immer funktioniert wundert mich sehr.

Als Gegenmaßname sind mir noch 2 Dinge eingefallen: zum einen könnte man einfach ein Byte der Magic-Cookies auf 0x00 prüfen weil die in ihren Angriffs-Daten ja keine 0x00-Bytes einbauen können und zum anderen müsste die CPU da etwas mehr in die Pflicht genommen werden den Stack (oder zumindest die Rücksprungadresse) auf Gültigkeit zu prüfen.

In meiner CPU hab ich auch ENTER und LEAVE die jeweils die callee-save Register sichern/wiederherstellen, das Link-Register und das Flag-Register ebenfalls optional sichern/wiederherstellen und eben den Platz für die lokalen Variablen auf den Stack schaffen/freigeben. Das sind eh recht komplexe Befehle die viele Dinge tun und da wäre es IMHO doch gut möglich das diese Befehle noch zusätzlich/optional ein Magic-Cookie zu den Daten auf dem Stack ablegen bzw. es auch wieder prüfen. Dieses Magic-Cookie könnte von den Daten selber, der Adresse auf dem Stack (Stackpointer) und von einem Geheimnis das nur die CPU selber lesen kann abhängen. All diese 3 Dinge sind für den Angreifer absolut zufällig und unvorhersehbar (das Linkregister ist wegen der ALR zufällig, der Stackpointer ebenfalls weil das OS einen zufälligen Startwert benutzt und das Geheimnis erst recht weil es für jeden Thread neu aus einem ordentlichen TRNG kommt), damit wäre die Manipulation des Stack quasi aussichtslos. Ich habe in den OpCodes dieser beiden Befehle noch 2 oder 3 Bits frei und ich denke das ich die für so einen Trick benutze, das Geheimnis würde ich vor dem Anfang des Stack ablegen, die CPU hat ja den Stack-Segment-Descriptor eingelesen und weiß daher wo sie nachschauen muss und für den User-Code ist das absolut unerreichbar weil dieses Geheimnis ja außerhalb des gültigen Offset-Bereichs im Stack-Segment liegt.

Trotzdem ist auch dieser Angriff eben davon anhängig das dieser Angriffs-String irgendwie auf den Stack kommt und dort den Stack-Pointer überschreibt, gerade das ließe sich bei nach oben wachsenden Stack nicht mit einer einzelnen Aktion erledigen.
Korrekt. Der Vorteil dieser Technik ist aber (a) du legst keine unspezifizierten Daten auf den Stack, sondern valide Rücksprungadressen und (b) du führst niemals Daten aus, sondern immer vorhandenen validen Code.
Punkt (a) verstehe ich nicht, um so einen Angriffs-Daten-String zu bauen musst Du doch genau wissen wo auf dem Stack er dann liegen wird und Du musst auch wissen wo genau der Code liegt, also mit ein klein wenig Zufall in beiden Dingen (ein paar wenige Bits würden schon reichen) ist dieser Angriff doch nur noch ein reiner Zufallstreffer, selbst ganz ohne irgendeine Art von Stack-Protector (damit ist meine Idee oben eigentlich fast wieder überflüssig).

Damit führst du das NX-Bit sehr elegant ad absurdum.
So haben sich AMD und Intel das sicher nicht vorgestellt. ;)

Bei dir funktioniert das Überschreiben des Stackpointers durch Dereferenzierung im Code sowieso nicht, da dein Stack in einem eigenen Adressraum (Segment) lebt.
Ich verstehe nicht was Du da meinst, ein Stack-Overflow mit Manipulation der Rücksprungadresse ist auch bei nach oben wachsenden Stack möglich. Nur eben überschreibt die aufgerufene Funktion ihre eigene Rücksprungadresse indem sie in ein Array, das auf dem Stack der aufrufenden Funktion (oder noch davor) liegt, etwas schreibt. Das ist etwas mehr tricky und sicher nicht ganz so häufig anzutreffen aber prinzipiell genau so gut möglich.

Außerdem brauchst du dir den Ärger mit "kurzlebige Daten auf den Stack" auch nicht machen, da bei dir Stack und Heap zwar nicht identisch, aber mit identischen Techniken gebaut sind (je ein eigenes Segment).
Ein malloc ist auch bei mir deutlich aufwendiger als das Stack-Frame zu vergrößern, auch wenn beides in O(1) läuft. Okay, das malloc geht nicht immer aber oft in O(1), ab und an wird auch mal neuer Speicher vom OS benötigt also das Segment vergrößert aber das trifft auf dem Stack ja auch zu da dort eben mal ne Exception wegen einem Stack-Segment-Overflow auftreten kann und das OS dann eben mal wieder das Stack-Segment vergrößern muss, so lange beides nur recht selten ist geht die Betrachtung mit O(1) als grobe Näherung gerade so durch.

Du hängst die Latte für Angriffe schon allein dadurch sehr hoch, dass du "komisches Zeug" machst.
Mit Ausnahme des Wortes "komisch" gebe ich Dir absolut Recht. Ich mache das alles eben weil ich mir davon einen enormen Gewinn an Sicherheit (und Performance) verspreche.

...und eh sich der Angreifer heutzutage dafür eine Toolchain baut...
Also ich möchte das mein System sicher ist auch wenn der Angreifer sich ne Tool-Chain baut. Es soll auch mit extrem hohem Aufwand nahezu unmöglich sein mein System zu knacken!


Ein simpler Mechanismus (statisches Magic Cookie direkt zwischen Rücksprung und Daten) erkennt Stack-Overflows und ist zumindest ein sinnvoller Debug-Helfer.
Als simples (Zweit-)Cookie würde ich ein 0-Wort empfehlen, einfach weil das nicht über die üblichen String-Befehle kommen kann. Das schließt schon mal die simplen Lücken (was wohl auch die Mehrheit der Lücken sind) aus.

Ein ordentlich programmierter Syscall prüft alle Parameter auf Plausibilität bevor er sie benutzt.
Ich behaupte, dass das nicht überall möglich (oder performant) ist. Eine grundlegende Optimierung ist doch, überflüssige Prüfungen einzusparen...
Dann ist das eine Form der Optimierung die für einen OS-Kernel definitiv nicht akzeptabel ist.

Damit wurde eine Wahlmaschine manipuliert....
Ja ja, erstens kommt es anders und zweitens als man denkt. ;)
Ich frage mich ob mir das auch passiert, heute prahle ich noch rum wie super sicher mein System doch werden soll aber wenn es übermorgen mit einem simplen Trick überlistet wird stehe ich da wie der Depp vom Dienst. :(

Sicherheit ist noch nicht so lange wichtig
Es ist gut das da jetzt endlich mal ein umdenken stattfindet. Ich muss mir mal genau durchlesen was die OpenSSL-Leute eigentlich am RNG von VIA auszusetzen haben damit ich nicht den selben Fehler mache.

Man beachte das "vor", ist es nicht eigentlich idiotisch das man bei der Speicheradressierung bewusst mit Überläufen arbeiten kann? Ich finde es deutlich besser wenn man im OpCode des Befehls zwischen + und - wählen kann und die CPU alle Überläufe mit ner Exception quittiert, da sind dann gleich wieder eine ganze Reihe von potentiellen Einfallstoren zugegangen.
Guck dir im x86 mal den Opcode-Space an. Der ist voll. Da nochmal jeden potentiellen Befehl in zwei Varianten haben wird eher nichts. Zumal ich die Möglichkeit an sich nicht so schlecht finde - wobei ein Vergleich mit dem i860 doch "etwas" hinkt.
Das war auch kein Vorschlag für x86 (da ist eh alles zu spät) sondern einfach nur so eine Idee (die ich in meiner CPU auch konsequent umsetze).

Wenn du schnelle, stromsparende CPUs möchtest, musst du entweder zwischen Performance und Anzahl der Transistoren einen Kompromiss finden. Die Möglichkeiten gehen von einfacheren Architekturen (Atom/VIA) und Emulatoren (Transmeta) zu neuen Architekturen (ARM, MIPS). Für Dinge, die man im Compiler beheben könnte noch Unmengen an Opcodes zu reservieren, ist beiden Varianten nicht förderlich... Das ist auch eine Frage der Verhältnismäßigkeit (und Kompatiblität).
Bei ARM haben die meisten Speicherzugriffe so ein Bit im OpCode (ist bei einer reinen Load-Modify-Store-Architektur ja auch nicht schwer) nur der Overflow wird nicht abgefangen, es gibt also oft 2 Möglichkeiten ein bestimmtes Byte zu adressieren.

Naja, wird dein Kernel den Sicherheitsanforderungen von "heutigen Kernels" genügen?
Ich hoffe, ich werde mir auf jeden Fall große Mühe geben alle (mir) bekannten Angriffsszenarien auszuschließen. Ob das reicht werde ich dann sehen.

Magic Cookies sind mehr als nur eine simple Buffer-Overflow-Erkennung, sie können (wenn die zugrundeliegenden Informationen Position/Wert des Cookies sicher versteckt sind) auch Stack-Angriffe vermeiden. Kostet natürlich Performance.
Aber gerade das Versteck ist das Problem, wenn der vom Compiler generierte Code da ran kommt dann kann das auch der eingeschleuste Code.
Eben das muss man verhindern...
Das muss man dann aber in Hardware machen ansonsten wird das einfach nur ein absoluter Performance-Killer.

Mich würde jetzt eher mal interessieren, wie man es dann schafft das der Code auch ausgeführt wird?
Standardprozedere: Ich überschreibe irgendeinen Kernelspeicher und weiß, zu welcher Funktion der gehört und an welcher Stelle in der Funktion ich deren Code gerade überschreibe. Danach brauche ich die Funktion nur noch so aufrufen, dass sie an diese Stelle springt. Beide Phasen des Exploits sind vollkommen unabhängig voneinander und können auch verschiedene Anwendungen betreffen (PHP überschreibt Kernelspeicher, Apache löst überschriebene Funktion aus). Klingt einfach, ist inzwischen aber sehr schwierig (u.a. durch so nette Dinger wie Heap-Randomization oder Magic Cookies im Stack).
Wie kann man den eigentlich Code im Kernel überschreiben? Gibt es da das NX-Bit nicht mehr? Oder wie wäre es mit ALR für den Kernel-Code?

Gegen gezielte Angriffe wie Styxnet kannst du dich nicht schützen.
Das klingt nach aufgeben, soweit bin ich noch nicht. ;)


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 20. November 2010, 14:01
Hallo,

Was ich mich eben frage ist: warum findet man dann trotzdem genug dieser Code-Schnippsel um damit alle wichtigen Operationen für eine Turing-Maschine zusammen zu bekommen? Könnte man da nicht versuchen dafür zu sorgen das der Compiler da etwas vorsichtiger ist?
Halte ich nicht für machbar. Der Code, den Compiler da hinten rauswerfen, ist in der Regel hochgradig optimiert und hat mit dem ursprünglich im Source vorhandenem Code nicht unbedingt viel zu tun. Die Anforderungen für Turingvollständigkeit sind außerdem relativ gering, alles, was man zusätzlich findet, ist Bonus.

Außerdem benutzen die für ihren Angriff auf SPARC ja immer nur das Ende der Funktionen aber sollte in diesem Ende nicht irgendeine Form von "Stack-Aufräum-Code" sein der eben ein Chaos hinterlässt wenn nicht auch der zugehörige "Stack-Einrichtungs-Code" am Funktionsanfang durchlaufen wurde?
Kann man tun, riskiert dann aber totalen Stackverlust. Zumal die Programmiertechnik an sich noch ziemlich neu ist, da sind Gegenmaßnahmen noch nicht in die Programme eingezogen.

Bedenke, dass jede Gegenmaßnahme irgendwo Performance kostet. Die optimale Gegenmaßnahme wäre, alle Befehle in einem Simulator auszuführen und diesen Simulator in einen Sandkasten zu stopfen - das Prinzip von Java oder .NET. Verbindet man das mit exakt notwendig vielen Berechtigungen und verbietet alles andere, landet man bei Dingen wie SELinux.

Allerdings ist SELinux nicht allgemein realisierbar, weil viel zu komplex - feingeschnittene Berechtigungsschnipsel sind wesentlich komplizierter zu konfigurieren - und Java zeichnet sich vor allem durch Langsamkeit aus. Wenn man "sicher" möchte, kann man auch in Ada programmieren, wie z.B. Autopiloten im Flugzeug. Aber Endkundensoftware wird dann doch in C geschrieben... und warum, kann man hier (http://blog.fefe.de/?ts=ba7cf6d2) am Beispiel von Scriptsprachen sehen. Ist kein guter Vergleich, aber als Illustration taugt er.

Als Gegenmaßname sind mir noch 2 Dinge eingefallen: zum einen könnte man einfach ein Byte der Magic-Cookies auf 0x00 prüfen weil die in ihren Angriffs-Daten ja keine 0x00-Bytes einbauen können und zum anderen müsste die CPU da etwas mehr in die Pflicht genommen werden den Stack (oder zumindest die Rücksprungadresse) auf Gültigkeit zu prüfen.
Die Rücksprungadresse auf Gültigkeit zu prüfen ist nicht möglich, das ist ja doch Sinn vons Janze: Du springst immer an korrekte, gültige Adressen. Was du meinst, ist eine Whitelist über gültige Sprungadressen für Userspace - und die zu durchsuchen ist nicht O(1), schlecht in Hardware lösbar und auch nur ein Workaround für ein anderes Problem. Mit dem Nullbyte würdest du aus Angriffen NULL-Pointer-Angriffe machen, aber auch die hat man bisher erfolgreich ausgenutzt. (Darum werden im virtuellen Adressraum die ersten 4K (ARM) / 64K (x86) von Linux nicht genutzt, die BSDs machen das ähnlich.)

Dieses Magic-Cookie könnte von den Daten selber [...] abhängen.
Von den Daten selbst geht nicht, weil sich diese ja zwischenzeitlich ändern können. Auch in der Größe: realloc() darf in den Heap verschieben, schon existiert das Objekt im Stack nicht mehr oder ist veraltet. Der Rest ja.

Korrekt. Der Vorteil dieser Technik ist aber (a) du legst keine unspezifizierten Daten auf den Stack, sondern valide Rücksprungadressen
Punkt (a) verstehe ich nicht, um so einen Angriffs-Daten-String zu bauen musst Du doch genau wissen wo auf dem Stack er dann liegen wird und Du musst auch wissen wo genau der Code liegt, also mit ein klein wenig Zufall in beiden Dingen (ein paar wenige Bits würden schon reichen) ist dieser Angriff doch nur noch ein reiner Zufallstreffer, selbst ganz ohne irgendeine Art von Stack-Protector (damit ist meine Idee oben eigentlich fast wieder überflüssig).
Voraussetzung für solche Angriffe ist, dass du den Systemcode ganz genau kennst. Bei Windows-Kerneln (wenig Variation) oder Wahlmaschinen mit ROMs (keine Variation) ist das in jedem Fall gegeben. Diese Information brauchst du auch schon, bevor du den Angriff überhaupt machen kannst, schließlich brauchst du einen Disassembler dafür.
Wo genau im Stack deine manipulierten Daten landen, ist wiederum relativ egal, solange der Stackpointer da hinzeigt; für den ersten Aufruf reicht es. Danach kannst du es ja vom System ermitteln lassen. Da gibt es sicherlich genug Fantasie bei den richtigen Leuten. ;-)

Nur eben überschreibt die aufgerufene Funktion ihre eigene Rücksprungadresse indem sie in ein Array, das auf dem Stack der aufrufenden Funktion (oder noch davor) liegt, etwas schreibt. Das ist etwas mehr tricky und sicher nicht ganz so häufig anzutreffen aber prinzipiell genau so gut möglich.
Wenn du für den Stack ein eigenes Segment aufmachst und für den Heap ein eigenes Segment aufmachst (was grundsätzlich nahe liegt), dann brauchst du Allokation auf dem Stack eigentlich nicht, weil Stack und Heap dann gleich kompliziert aufgebaut sind (im Endeffekt hängt die Geschwindigkeit davon ab, ob du die Segmente vergrößern musst oder nicht). Einen Performance-Vorteil bekommst du nur, wenn die Heap-Implementation wesentlich langsamer ist, als die Stack-Implementation.

...und eh sich der Angreifer heutzutage dafür eine Toolchain baut...
Also ich möchte das mein System sicher ist auch wenn der Angreifer sich ne Tool-Chain baut. Es soll auch mit extrem hohem Aufwand nahezu unmöglich sein mein System zu knacken!
Richtig. Aber je seltsamer das System aufgebaut ist, umso mehr Aufwand (v.a. Wissen) verlangst du vom Angreifer. Und bei steigendem Aufwand sinkt die Wahrscheinlichkeit für einen Angriff.

Wie gesagt, gegen gezielte Angriffe kann man sich nicht schützen. Es geht in erster Linie um Breitbandangriffe.

Ein simpler Mechanismus (statisches Magic Cookie direkt zwischen Rücksprung und Daten) erkennt Stack-Overflows und ist zumindest ein sinnvoller Debug-Helfer.
Als simples (Zweit-)Cookie würde ich ein 0-Wort empfehlen, einfach weil das nicht über die üblichen String-Befehle kommen kann. Das schließt schon mal die simplen Lücken (was wohl auch die Mehrheit der Lücken sind) aus.
Genau das würde ich nicht machen, wenn es um das Debugging geht. Damit werden Fehler im Programm durch das Betriebssystem entschärft und das Programm hat dann auf anderen Betriebssystemen gravierende Sicherheitslücken.

Damit wurde eine Wahlmaschine manipuliert....
Ja ja, erstens kommt es anders und zweitens als man denkt. ;)
Ich frage mich ob mir das auch passiert, heute prahle ich noch rum wie super sicher mein System doch werden soll aber wenn es übermorgen mit einem simplen Trick überlistet wird stehe ich da wie der Depp vom Dienst. :(
Eben. Die Idee, dass ein ausführender Zugriff auf den RAM hardwareseitig mit einem RESET quittiert wird, fand ich schon genial... aber die Fantasie mancher Menschen ist meiner eben weit überlegen.

Bei ARM haben die meisten Speicherzugriffe so ein Bit im OpCode (ist bei einer reinen Load-Modify-Store-Architektur ja auch nicht schwer) nur der Overflow wird nicht abgefangen, es gibt also oft 2 Möglichkeiten ein bestimmtes Byte zu adressieren.
Bei x86 hast du ganz viele Möglichkeiten. ;-)

Naja, wird dein Kernel den Sicherheitsanforderungen von "heutigen Kernels" genügen?
Ich hoffe, ich werde mir auf jeden Fall große Mühe geben alle (mir) bekannten Angriffsszenarien auszuschließen. Ob das reicht werde ich dann sehen.
Oder auch nicht, es sei denn, du engagierst einen guten Hacker. Ich bin keiner. :-P

Wie kann man den eigentlich Code im Kernel überschreiben? Gibt es da das NX-Bit nicht mehr? Oder wie wäre es mit ALR für den Kernel-Code?
NX verbietet das Ausführen, nicht das Überschreiben. Code im Kernel zu überschreiben ist auf verschiedenen Wegen möglich. Man kann als "root" /dev/mem oder /dev/kmem überschreiben, deswegen führt man Anwendungen nur im Notfall so aus. Du kannst einen Hardwaretreiber so manipulieren, dass er DMA-Zugriffe von Speicher zu Speicher erledigt, da gibt es keine Prüfung. Du kannst eine Hardware so mit gezielt kaputten Daten von außen bewerfen, dass der Treiber DMA an von dir gewünschte Adressen macht. Auf diese Weise wurde ja die PS3 gehackt (manipulierte USB-Sticks). Oder dein Kernel prüft die Zieladresse an einer Stelle nicht genau und überschreibt Teile von sich selbst.

Gegen gezielte Angriffe wie Styxnet kannst du dich nicht schützen.
Das klingt nach aufgeben, soweit bin ich noch nicht. ;)
Noch nicht. ;)

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 20. November 2010, 14:12
Also ich kann euch beiden gar nicht mehr folgen  :?

Also wie man Kernel-Code überschreiben kann würde mich auch mal interessieren. Denn so wie ich es verstanden willst du irgendeinen Kernel-Code überschreiben und dann ausführen.

Gut, jetzt wurde DMA genannt, aber dagegen kann man sich ja nur schützen in dem die Treiber gut programmiert sind.
(Gibt es sowas wie DMA auch auf anderen Architekturen?)

Jetzt mal von nem MikroKernel ausgehend, wie willst du da nen Buffer-Overflow durch Paramter die über den Stack übergeben werden, im Kernel erreichen?

Noch was zu dem Direction-Flag in den Diskriptoren. Wenn das den Stack nicht beeinflusst, wozu ist das dann gut und da?
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 20. November 2010, 14:51
Also wie man Kernel-Code überschreiben kann würde mich auch mal interessieren. Denn so wie ich es verstanden willst du irgendeinen Kernel-Code überschreiben und dann ausführen.
Das ist der Weg für Götter, ja.

Gut, jetzt wurde DMA genannt, aber dagegen kann man sich ja nur schützen in dem die Treiber gut programmiert sind.
Sind sie in der Regel nicht.

(Gibt es sowas wie DMA auch auf anderen Architekturen?)
Ja.

Jetzt mal von nem MikroKernel ausgehend, wie willst du da nen Buffer-Overflow durch Paramter die über den Stack übergeben werden, im Kernel erreichen?
Hä? Angriffe durch Buffer-Overflow sind implementierungsspezifisch. Ich kenne deine Implementation nicht, also kann ich darauf nicht antworten. Im Zweifelsfall kann man das IPC angreifen.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 20. November 2010, 15:00
Zitat von: svenska
Hä? Angriffe durch Buffer-Overflow sind implementierungsspezifisch. Ich kenne deine Implementation nicht, also kann ich darauf nicht antworten. Im Zweifelsfall kann man das IPC angreifen.
Wie greift man ein fixed-size IPC an? Also ich bin mir relativ sicher (so lange bis mir jemand das Gegenteil beweist), das mein Kernel-Code Buffer-Overflow sicher ist.

Zitat von: svenska
Sind sie in der Regel nicht.
Leider. Problem ist halt immer wieder, das man sich nicht gegen DMA schützen kann bzw. das was es anrichten kann.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 20. November 2010, 15:03
Zitat von: svenska
Hä? Angriffe durch Buffer-Overflow sind implementierungsspezifisch. Ich kenne deine Implementation nicht, also kann ich darauf nicht antworten. Im Zweifelsfall kann man das IPC angreifen.
Wie greift man ein fixed-size IPC an? Also ich bin mir relativ sicher (so lange bis mir jemand das Gegenteil beweist), das mein Kernel-Code Buffer-Overflow sicher ist.
Ich würde davon ausgehen, dass es inhärent unsicher ist. Ist sicherer.

Man kann ein Fixed-Size-IPC angreifen, indem man mehr schickt als erlaubt oder indem man den Inhalt so strickt, dass der Message-Parser amok läuft oder Dinge tut, die man als Designer so nicht vorgesehen hat.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 20. November 2010, 15:23
Zitat von: svenska
Man kann ein Fixed-Size-IPC angreifen, indem man mehr schickt als erlaubt
Du meinst, dass man 2 Msgs schickt obwohl laut Protokoll nur eine geschickt werden soll?

Zitat von: svenska
indem man den Inhalt so strickt, dass der Message-Parser amok läuft oder Dinge tut, die man als Designer so nicht vorgesehen hat.
Das wären dann wieder Programmierfehler, aber wie stellst du dir das vor das der Parser amok lufen könnte?

Ich meine, ich würde nen Message-Parser als switch-Verzweigung bauen und die anderen Paramter sind dann Parameter für die aufzurufenden Funktionen (mal sehr vereinfacht gesagt), da kann dem Parser erstmal nichts passieren.

Allerdings dachte ich auch mit IPC angeifen meinst du das man das IPC nutzt um den Kernel zu infiltrieren.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 20. November 2010, 18:59
Das meine ich.
Sicherheitsprobleme teilen sich meist in zwei Kategorien auf: (a) systematische Probleme bzw. schlechtes/kaputtes Design oder (b) zufällige Probleme bzw. Bugs.

Das Design sollte von Anfang an mit Sicherheit im Hinterkopf gestrickt sein, dein fixed-length-message-IPC sollte dazu gehören. Das ist gefühlt der leichtere Teil. Gegen Bugs kannst du nichts tun außer sauber programmieren.

Und ja, ich bezweifle, dass wir alle fehlerfrei programmieren können. ;-)
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 21. November 2010, 10:09
Hallo,


Was ich mich eben frage ist: warum findet man dann trotzdem genug dieser Code-Schnippsel um damit alle wichtigen Operationen für eine Turing-Maschine zusammen zu bekommen? Könnte man da nicht versuchen dafür zu sorgen das der Compiler da etwas vorsichtiger ist?
Halte ich nicht für machbar. Der Code, den Compiler da hinten rauswerfen, ist in der Regel hochgradig optimiert und hat mit dem ursprünglich im Source vorhandenem Code nicht unbedingt viel zu tun. Die Anforderungen für Turingvollständigkeit sind außerdem relativ gering, alles, was man zusätzlich findet, ist Bonus.
Okay, der Compiler hat natürlich in erster Linie die Ausführungsgeschwindigkeit im Auge, deswegen wäre z.B. ein Compiler-Parameter nicht schlecht das ihn zwingt den Stack sauberer zu halten (in meinem Fall z.B. ENTER und LEAVE mit Magic-Cookies zu benutzen (falls die Funktion das überhaupt erfordert, was z.B. bei einer Leaf-Funktion ja nicht unbedingt der Fall ist)).

Bedenke, dass jede Gegenmaßnahme irgendwo Performance kostet....
Deswegen ja gleich in Hardware damit der Performanceverlust so klein als möglich bleibt.

und zum anderen müsste die CPU da etwas mehr in die Pflicht genommen werden den Stack (oder zumindest die Rücksprungadresse) auf Gültigkeit zu prüfen.
Die Rücksprungadresse auf Gültigkeit zu prüfen ist nicht möglich, das ist ja doch Sinn vons Janze: Du springst immer an korrekte, gültige Adressen. Was du meinst, ist eine Whitelist über gültige Sprungadressen für Userspace - und die zu durchsuchen ist nicht O(1), schlecht in Hardware lösbar und auch nur ein Workaround für ein anderes Problem.
Da hast Du mich falsch verstanden. Ich meinte das wenn die CPU ein Magic-Cookie zu den gesicherten Registern (zu denen auch das Link-Register mit der Rücksprungadresse zählt) packt das sie dann den numerischen Wert des Magic-Cookie aus eben diesen gesicherten Daten plus dem Geheimnis errechnen soll (quasi ne Art Prüfsumme), beim Laden könnte dann die selbe Rechnung durchgeführt und das Magic-Cookie geprüft werden und es würde jede Manipulation auffallen. Das funktioniert gerade deshalb weil die gesicherten Register (eben die callee-saves) und die Rücksprungadresse ja nicht auf dem Stack manipuliert werden dürfen und der Caller sich drauf verlässt das die alle noch stimmen. Andere Daten die auch noch auf dem Stack liegen dürfen natürlich nicht mit in diese Berechnung einfließen, es geht nur um das was der ENTER-Befehl selber auf dem Stack ablegt bzw. was der LEAVE-Befehl vom Stack wieder einließt. Klar ist es theoretisch möglich das die Manipulation nicht auffällt wenn der Angreifer das Geheimnis kennt oder errät. Gegen das Kennen hilft es wenn die CPU dieses Geheimnis effektiv gegen jedweden User-Mode-Zugriff schützt (das kann ich bei meiner CPU problemlos gewährleisten) und das erraten sollte bei z.B. 64Bit als Option ausscheiden. Bleibt nur noch sicherzustellen das der Kernel für jeden neuen Thread ein neues Geheimnis aus einen guten TRNG holt. Ich denke wenn man das ordentlich umsetzt dann dürfte ein Buffer-Overflow keine Gefahr mehr sein und der Performanceverlust sollte sich bei guter HW-Implementierung auch in Grenzen halten (auch wenn er nicht 0 ist, aber für irgendetwas müssen die Computer ja immer schneller werden).

Mit dem Nullbyte würdest du aus Angriffen NULL-Pointer-Angriffe machen, aber auch die hat man bisher erfolgreich ausgenutzt. (Darum werden im virtuellen Adressraum die ersten 4K (ARM) / 64K (x86) von Linux nicht genutzt, die BSDs machen das ähnlich.)
Ich fürchte auch da hast Du mich missverstanden, mit dem 0-Word als (zusätzliches) Magic-Cookie meinte ich das wenn man erwartet das ein 0-Wort auf dem Stack liegt das dann der Angreifer ein Problem hat weil er eben bei dem meisten Buffer-Overflows, die ja auf String-Funktionen beruhen, keine Chance hat weil die String-Funktionen ja eben ein 0-Byte als Abbruchkriterium nehmen. Ließ Dir mal dort http://cseweb.ucsd.edu/~hovav/papers/brss08.html (http://cseweb.ucsd.edu/~hovav/papers/brss08.html) das full paper durch, da drin wird auch erklärt warum eben keine 0-Bytes in den Angriffs-Daten drin sein dürfen.

Wo genau im Stack deine manipulierten Daten landen, ist wiederum relativ egal, solange der Stackpointer da hinzeigt; für den ersten Aufruf reicht es. Danach kannst du es ja vom System ermitteln lassen. Da gibt es sicherlich genug Fantasie bei den richtigen Leuten. ;-)
Das kann ich mir einfach nicht vorstellen, wenn Du nicht genau weißt wo Deine Daten im Stack liegen dürfte es schwer fallen diese richtig zu adressieren oder Du musst die "nützlichen" Code-Schnipsel noch weiter einschränken indem nur zum Stack-Pointer relative Adressierung nutzbar ist. Aber spätestens bei ALR für den Code ist dann Schluss weil Du ja korrekte absolute Rücksprungadressen brauchst.

Wenn du für den Stack ein eigenes Segment aufmachst und für den Heap ein eigenes Segment aufmachst (was grundsätzlich nahe liegt), dann brauchst du Allokation auf dem Stack eigentlich nicht, weil Stack und Heap dann gleich kompliziert aufgebaut sind (im Endeffekt hängt die Geschwindigkeit davon ab, ob du die Segmente vergrößern musst oder nicht). Einen Performance-Vorteil bekommst du nur, wenn die Heap-Implementation wesentlich langsamer ist, als die Stack-Implementation.
Die Heap-Implementierung ist zwangsläufig langsamer als die Stack-Implementierung, der Heap muss erst mal ermitteln welches Segment er für die gewünschte Objekt-Größe nehmen muss und dann noch ein paar kleine Verwaltungsaufgaben erledigen (z.B. das neu benutze Objekt als belegt markieren), beim (nach oben wachsenden) Stack läuft es darauf hinaus das der aktuelle Stack-Pointer gesichert werden muss (das ist dann der Pointer auf das neue Objekt) und dann die Größe des gewünschten Objekts einfach dazu addiert werden muss (also genau 2 Assemblerbefehle). Das der Stack die Objekte auch nur in LIFO frei gibt macht es ihm auch sehr viel einfacher bzw. direkt freigeben muss er eigentlich nicht das wird ja spätestens am Ende der Funktion eh automagisch erledigt.

Wie gesagt, gegen gezielte Angriffe kann man sich nicht schützen. Es geht in erster Linie um Breitbandangriffe.
Wie gesagt, ich möchte das mein System auch bei gezielten Angriffen noch gut stand halten kann. Das 100%-tige Sicherheit unmöglich ist ist mir auch bewusst aber ich will mich zumindest bemühen (dazu hat mir diese Diskussion in den letzten Tagen auch einige Denkanstöße vermittelt).

Ein simpler Mechanismus (statisches Magic Cookie direkt zwischen Rücksprung und Daten) erkennt Stack-Overflows und ist zumindest ein sinnvoller Debug-Helfer.
Als simples (Zweit-)Cookie würde ich ein 0-Wort empfehlen, einfach weil das nicht über die üblichen String-Befehle kommen kann. Das schließt schon mal die simplen Lücken (was wohl auch die Mehrheit der Lücken sind) aus.
Genau das würde ich nicht machen, wenn es um das Debugging geht. Damit werden Fehler im Programm durch das Betriebssystem entschärft und das Programm hat dann auf anderen Betriebssystemen gravierende Sicherheitslücken.
Hä, wovon schreibst Du da eigentlich? Was hat das OS damit zu tun? Das Erzeugen und Prüfen von diesen Magic-Cookies macht doch üblicherweise der vom Compiler generierte User-Code im Funktions-Prolog/Epilog. Dadurch kann ein Programm doch keine zusätzlichen Sicherheitslücken bekommen bzw. vorhandene überdeckt werden.

Bei ARM haben die meisten Speicherzugriffe so ein Bit im OpCode (ist bei einer reinen Load-Modify-Store-Architektur ja auch nicht schwer) nur der Overflow wird nicht abgefangen, es gibt also oft 2 Möglichkeiten ein bestimmtes Byte zu adressieren.
Bei x86 hast du ganz viele Möglichkeiten. ;-)
Nein, bei x86 (im PM mit Flat-Memory, im RM ist das wieder anders) gibt es immer nur eine Möglichkeit ein bestimmtes Byte zu adressieren (weil die Offsets und Displacements ja immer dazuaddiert werden), es gibt aber extrem viele Befehle die dieses Byte erreichen können.

Code im Kernel zu überschreiben ist auf verschiedenen Wegen möglich. Man kann als "root" /dev/mem oder /dev/kmem überschreiben, deswegen führt man Anwendungen nur im Notfall so aus.
Also wenn der Admin sein Passwort eingibt um ein Programm laufen zu lassen dann helfen eh nicht mehr viele Sicherheitsmaßnahmen, egal ob die in Software oder in Hardware implementiert sind.

Du kannst einen Hardwaretreiber so manipulieren, dass er DMA-Zugriffe von Speicher zu Speicher erledigt, da gibt es keine Prüfung. Du kannst eine Hardware so mit gezielt kaputten Daten von außen bewerfen, dass der Treiber DMA an von dir gewünschte Adressen macht.
Das sind dann Bugs im Treiber oder in der zugehörigen Hardware, die müssen auf jeden Fall gefixt werden. Ich denke das auch die Hardware-Hersteller sich so langsam mit dem Thema Sicherheit beschäftigen. Das ist einer der Gründe warum ich prinzipiell kein FireWire mag, da ist es möglich das ein externes Gerät beliebig auf den physischen Speicheradressraum zugreifen kann (lesen und schreiben, im RAM aber auch in den MMIO-Bereichen aller HW-Komponenten). Aus diesem Grund hab ich auf meiner Plattform im System-Controller (der Bridge zwischen den CPUs mit dem RAM und der Peripherie) eine Möglichkeit geplant das die Peripherie nur auf bestimmte RAM-Bereiche zugreifen kann. Alle die Adressen die ein Treiber seiner HW mitteilt müssen ja in festen Pages liegen und das muss der Treiber immer explizit anfordern (und auch wieder freigeben) und genau darauf soll der Kernel dann die Peripherie-Zugriff beschränken, ob das überhaupt praktikabel ist weiß ich noch nicht aber wenn nicht dann soll es damit zumindest möglich sein das der Kernel seine eigenen Speicherbereiche für die Peripherie verbietet.

Das klingt nach aufgeben, soweit bin ich noch nicht. ;)
Noch nicht. ;)
Bis ich aufgebe dauert es für gewöhnlich eine kleine Weile. ;)


Gut, jetzt wurde DMA genannt, aber dagegen kann man sich ja nur schützen in dem die Treiber gut programmiert sind.
Selbst wenn die Treiber perfekt sind könnte immer noch ein Bug in der Hardware stecken und das kannst du als User oder Treiber-Programmierer kaum prüfen. Da müssen einfach die Hardware-Hersteller mitdenken. Noch schlimmer ist es wenn externer Zugriff in der Spezifikation klar erlaubt und beschrieben ist, wie bei FireWire.

Gibt es sowas wie DMA auch auf anderen Architekturen?
Ja und wenn nicht dann ist die Hardware busmasterfähig, kann also selbstständig (ohne einen plattform-spezifischen dedizierten DMA-Controller) auf den RAM (und oft auch auf die andere Hardware) zugreifen.

Noch was zu dem Direction-Flag in den Diskriptoren. Wenn das den Stack nicht beeinflusst, wozu ist das dann gut und da?
Das ist eben eine Verwaltungsinformation damit der Kernel weiß das neuer Speicher unten dran kommt und nicht oben. Ob dieses Flag auch die CPU in irgendeiner Weise beeinflusst weiß ich nicht (mehr) aber das sollten die Intel-Manuals ausführlich erklären.


Man kann ein Fixed-Size-IPC angreifen, indem man mehr schickt als erlaubt
Das sollte der Kernel mit geeigneten Plausibilitätschecks abfangen. Gerade deswegen sollte man bei den Syscalls an dieser Stelle nicht sparsam sein, was nützt ein schnelles System wenn es mit ner simplen Kleinigkeit zum Absturz zu bringen ist.

Wegen dem Fixed-Size-IPC: ich will Flexible-Size-IPC und sehe da auch keine zusätzlichen Probleme. Warum denkt ihr das IPC überhaupt ein guter Angriffspunkt ist?

oder indem man den Inhalt so strickt, dass der Message-Parser amok läuft oder Dinge tut, die man als Designer so nicht vorgesehen hat.
Dagegen sollte ein sauber programmierter Service einigermaßen immun sein.


Und ja, ich bezweifle, dass wir alle fehlerfrei programmieren können. ;-)
Ich möchte Dich bitten keine solchen unhaltbaren Verallgemeinerungen mehr zu machen! ;)


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 21. November 2010, 10:30
Zitat von: erik
Das ist eben eine Verwaltungsinformation damit der Kernel weiß das neuer Speicher unten dran kommt und nicht oben.
Auch dadurch erschließt sich mir der Sinn dieses Flags noch nicht (das ist doch etwas dass das OS selbst speichern kann, dafür brauch man keine Hardware Unterstützung). So ein Flag würde für mich nur Sinn ergeben wenn es Einfluss auf die Hardware hat.

Zitat von: erik
Ob dieses Flag auch die CPU in irgendeiner Weise beeinflusst weiß ich nicht (mehr) aber das sollten die Intel-Manuals ausführlich erklären.
Inzwischen habe ich sogar die Stelle gefunden, wo steht das der Stack auf x86 immer nach unten wächst. Das Flag macht irgendetwas im Zusammenhang mit den Segment-Limits, aber das sind Sachen die nur Verwendung finden, wenn man kein FlatMemory benutzt.

Zitat von: erik
Das sollte der Kernel mit geeigneten Plausibilitätschecks abfangen. Gerade deswegen sollte man bei den Syscalls an dieser Stelle nicht sparsam sein, was nützt ein schnelles System wenn es mit ner simplen Kleinigkeit zum Absturz zu bringen ist.
Wie soll der Kernel überprüfen ob die Daten in der Nachricht Sinn ergeben? Der Kernel braucht bei IPC so gut wie gar nichts zu überprüfen.

Zitat von: erik
Warum denkt ihr das IPC überhaupt ein guter Angriffspunkt ist?
Das würde mich auch interessieren!
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 21. November 2010, 11:02
Hallo,


Inzwischen habe ich sogar die Stelle gefunden, wo steht das der Stack auf x86 immer nach unten wächst.
Na also.

Das Flag macht irgendetwas im Zusammenhang mit den Segment-Limits, aber das sind Sachen die nur Verwendung finden, wenn man kein FlatMemory benutzt.
Also benutzt das heute keiner mehr. Die Segmentierung des 386 kann so viel mehr, da ist es richtig schade das die konsequent ignoriert wird und AMD sollte sich was schämen das die sowas tolles im Long-Mode abgeschafft haben.

Wie soll der Kernel überprüfen ob die Daten in der Nachricht Sinn ergeben? Der Kernel braucht bei IPC so gut wie gar nichts zu überprüfen.
Der Kernel kann die Länge der Daten prüfen und auch ob die Pointer gültig sind. Da ist einiges was der Kernel bei jedem Syscall auf Plausibilität prüfen kann/muss.
Die bei IPC übergebenen Kommandos/Antworten und Nutzdaten müssen die beteiligten Prozesse schon selber prüfen, da kann der Kernel wirklich nicht viel machen.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 21. November 2010, 11:16
Zitat von: erik
Der Kernel kann die Länge der Daten prüfen und auch ob die Pointer gültig sind.
Was genau meinst du mit die Länge der Daten? Bei mir ist die Länge festgelegt, muss also nicht überprüft werden und bei dir musst du halt soviel Daten "kopieren" wie halt gesendet werden sollen (willst du da eigentlich ne Obergrenze setzen?).
Was meinste du mit Gültigkeit der Pointer?

Man muss auf jeden Fall prüfen ob der Speicher der gesendet werden darf, überhaupt existiert und ob er vom User gelesen werden darf.

Da ist dann auch gleich ein Problem, worüber ich mir die letzten Tage den Kopf zerbrochen habe und keine vernünftige Lösung gefunden habe.

Nehmen wir mal an, das Programm besteht aus 2 Threads und jeder Thread läuft auf einer anderen CPU (und keine Segmente ;) ).
Der eine Thread will eine Nachricht versenden (oder irgendetwas anderes, was es erfordert das der Kernel auf Speicher im UserSpace zugreifen muss). Der Kernel wird also, bevor er irgendwelche Daten aus dem UserSpace liest, überprüfen ob der User überhaupt den Speicher lesen darf und ob dieser vorhanden/gemappt ist.
Die Überprüfung ist abgeschlossen und der Kernel liest seine Daten. In der Zwischenzeit will der andere Thread Speicher freigeben und zwar genau den Speicher den der Kernel zugreifen will.

Worauf ich hinaus will, wie verhindert man das ein Thread Speicher freigeben kann, wo der Kernel die Überprüfung schon abgeschlossen hat und auf diesen Speicher jetzt zurgreift?
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 21. November 2010, 13:11
Hallo,


Was genau meinst du mit die Länge der Daten? Bei mir ist die Länge festgelegt, muss also nicht überprüft werden
Okay, ich dachte Du übergibst die Länge der Daten als Parameter an den Syscall, ich bin davon aus gegangen das die Länge der Daten beim erstellen Deiner Ports festgelegt werden kann also jeder Port mit einer individuell festgelegten Message-Größe arbeitet, aber wenn es da ein globales Define gibt das immer und für alles gültig ist ist das natürlich überflüssig.

und bei dir musst du halt soviel Daten "kopieren" wie halt gesendet werden sollen (willst du da eigentlich ne Obergrenze setzen?).
Ich kopiere nicht, ich erstelle ein Alias-Segment im Prozess des Service. Beim erstellen von meinen Message-Targets darf der Service festlegen wie groß die Messages maximal sein dürfen die er empfangen möchte, getrennt für Kommando/Antwort und die Nutzdaten. Also der VFS kann sagen das die Kommandos die er empfangen möchte (und damit auch die Antworten die er zurückschicken kann) maximal z.B. 4kByte groß sind und für die Nutzdaten (die mit einem einzelnen read/write-Kommando übertragen werden) darf er z.B. 256MByte als Maximum angeben. Diese Maxima sind unabhängig vom Aligment, werden also auch bei unausgerichteten Daten exakt eingehalten.

Was meinste du mit Gültigkeit der Pointer?
Man muss auf jeden Fall prüfen ob der Speicher der gesendet werden darf, überhaupt existiert und ob er vom User gelesen werden darf.
Das wären gute Vorschläge. Ansonsten wäre noch zu nennen das der Speicher nicht ausführbar sein sollte. Bei meinen Segmenten hab ich es da recht einfach ich muss einfach prüfen ob der Pointer im Segment gültig ist und das Ende der Daten nicht über das Segment-Limit hinaus geht. Zusätzlich werde ich IPC das sich auf Code-Segmente bezieht verbieten.

....
Worauf ich hinaus will, wie verhindert man das ein Thread Speicher freigeben kann, wo der Kernel die Überprüfung schon abgeschlossen hat und auf diesen Speicher jetzt zurgreift?
Hm, da fällt mir jetzt auch nichts so spontan ein außer das der Kernel die Verwaltungsstruckturen für den Prozess immer locken muss wenn er daraus lesen oder darin modifizieren will. Ansonsten hat ein Micro-Kernel eigentlich nichts in den User-Daten verloren.


Ich meine, ich würde nen Message-Parser als switch-Verzweigung bauen und die anderen Paramter sind dann Parameter für die aufzurufenden Funktionen (mal sehr vereinfacht gesagt), da kann dem Parser erstmal nichts passieren.
So wollte ich das auch machen und kann da eigentlich auch erstmal keine grundlegenden Schwächen erkennen.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 21. November 2010, 13:40
Zitat von: erik
Okay, ich dachte Du übergibst die Länge der Daten als Parameter an den Syscall, ich bin davon aus gegangen das die Länge der Daten beim erstellen Deiner Ports festgelegt werden kann also jeder Port mit einer individuell festgelegten Message-Größe arbeitet, aber wenn es da ein globales Define gibt das immer und für alles gültig ist ist das natürlich überflüssig.
Das mit der Größe pro Port ist natürlich auch eine Möglichkeit, aber selbst da brauche ich dann keinen Parameter für die Länger vom User.

Zitat von: erik
Ich kopiere nicht
Genau deswegen habe ich ja "kopieren" geschrieben ;)

Zitat von: erik
Hm, da fällt mir jetzt auch nichts so spontan ein außer das der Kernel die Verwaltungsstruckturen für den Prozess immer locken muss wenn er daraus lesen oder darin modifizieren will.
Das ist eigentlich die Idee! Ich habe ja noch ein "paar" freie Bits bei jeder Page zur Verfügung und die könnte man dafür nutzen.

Hat den "Nachteil" (mache ich eh oft) das ich die Ints während des Kopierens ausschalten muss, damit das ganze auch auf Single CPU Systemen funktioniert und andere CPUs nicht alzu lange warten müssen.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 21. November 2010, 17:34
Hallo,


Zitat von: erik
Ich kopiere nicht
Genau deswegen habe ich ja "kopieren" geschrieben ;)
Aha, Du willst also ein bisschen stänkern oder Dich gar mit mir anlegen, ja? Wollen wir noch mal darüber diskutieren warum Dein IPC-Konzept kein Zero-Copy erlaubt und meines schon? :-D

Hat den "Nachteil" (mache ich eh oft) das ich die Ints während des Kopierens ausschalten muss
Möchtest Du vielleicht doch noch mal über einen nicht unterbrechbaren Kernel nachdenken? Wir können auch darüber gerne noch mal ausführlich diskutieren. ;)


@svenska:
Ich hab übrigens keinen richtigen Grund gefunden warum OpenSSL den VIA-RNG abgelehnt hat, außer der Aussage das es eher Aufgabe des Kernels ist die Hardware-Komponenten zu unterstützen und das man sich als User-Space-Applikation lieber auf die öffentlichen Interfaces (wie /dev/random) verlassen möchte.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 22. November 2010, 13:04
Hallo,

Mit dem Nullbyte würdest du aus Angriffen NULL-Pointer-Angriffe machen, aber auch die hat man bisher erfolgreich ausgenutzt. (Darum werden im virtuellen Adressraum die ersten 4K (ARM) / 64K (x86) von Linux nicht genutzt, die BSDs machen das ähnlich.)
Ich fürchte auch da hast Du mich missverstanden, mit dem 0-Word als (zusätzliches) Magic-Cookie meinte ich das wenn man erwartet das ein 0-Wort auf dem Stack liegt das dann der Angreifer ein Problem hat weil er eben bei dem meisten Buffer-Overflows, die ja auf String-Funktionen beruhen, keine Chance hat weil die String-Funktionen ja eben ein 0-Byte als Abbruchkriterium nehmen.
Das meinte ich und finde es unschön... du verhinderst damit Angriffe durch strcpy() und Familie im Betriebssystem. Ist schön und gut, versteckt aber Bugs in den Anwendungen, die man lieber fixen sollte. Darum sollte zu Debugzwecken, also wenn man Fehler sucht, das Programm schön brav mit Anlauf auf die Schnauze fallen, damit der Fehler auffällt und behoben werden kann. Das Nullbyte versteckt den Fehler ja nur.

Mir wäre es am liebsten, wenn jeder Bug in einer Anwendung direkt zu einem Absturz führt und sich niemals irgendwo verschleiern lässt. Ist leider unmöglich.

Das kann ich mir einfach nicht vorstellen, wenn Du nicht genau weißt wo Deine Daten im Stack liegen dürfte es schwer fallen diese richtig zu adressieren oder Du musst die "nützlichen" Code-Schnipsel noch weiter einschränken indem nur zum Stack-Pointer relative Adressierung nutzbar ist. Aber spätestens bei ALR für den Code ist dann Schluss weil Du ja korrekte absolute Rücksprungadressen brauchst.
Was ist ALR? Google sagt mir auf Anhieb nichts und Wikipedia auch nicht. Solange du hinreichend viele Code-Schnipsel hast (und das sind minimal eben nicht viele), kannst du ja auch solange einschränken, bis du deine reale Adresse rausbekommen hast und davon ausgehend dann den Angriff stricken. Konkret kann ich das nicht umsetzen, aber stelle mir da genug Phantasie vor...

Genau das würde ich nicht machen, wenn es um das Debugging geht. Damit werden Fehler im Programm durch das Betriebssystem entschärft und das Programm hat dann auf anderen Betriebssystemen gravierende Sicherheitslücken.
Hä, wovon schreibst Du da eigentlich? Was hat das OS damit zu tun? Das Erzeugen und Prüfen von diesen Magic-Cookies macht doch üblicherweise der vom Compiler generierte User-Code im Funktions-Prolog/Epilog. Dadurch kann ein Programm doch keine zusätzlichen Sicherheitslücken bekommen bzw. vorhandene überdeckt werden.[/quote]Dann sind es eben die Compiler-Routinen, die den Fehler überdecken. In jedem Fall führt das dann, wenn man die Umgebung ändert (und kein Nullbyte mehr im Magic Cookie drin ist), direkt zu Sicherheitslücken, die man auch vorher hätte beheben können/sollen/müssen. :-)

Code im Kernel zu überschreiben ist auf verschiedenen Wegen möglich. Man kann als "root" /dev/mem oder /dev/kmem überschreiben, deswegen führt man Anwendungen nur im Notfall so aus.
Also wenn der Admin sein Passwort eingibt um ein Programm laufen zu lassen dann helfen eh nicht mehr viele Sicherheitsmaßnahmen, egal ob die in Software oder in Hardware implementiert sind.
Du brauchst normalerweise Root-Zugriff, um TCP-Ports unter 1024 verwenden zu dürfen... du brauchst normalerweise Root-Zugriff, um ein vollständiges Backup (inkl. /etc/shadow) des Systems anfertigen zu können. Du brauchst (noch) Root-Zugriff, um die grafische Oberfläche (X-Server) ausführen zu können. Alle diese Programme sind hackbar und damit potentielle Überschreiber von /dev/mem.

Sicher können viele Programme das Socket erzeugen und dann ihre Rechte gezielt ablegen und sich runterstufen lassen, aber ich glaube, das geht nicht immer und grundsätzlich ist das eine nicht zu vergessende Problematik. Die kann man nicht mit "tja, root ist halt selbst schuld" abhandeln. Glaube ich.

Alle die Adressen die ein Treiber seiner HW mitteilt müssen ja in festen Pages liegen und das muss der Treiber immer explizit anfordern (und auch wieder freigeben) und genau darauf soll der Kernel dann die Peripherie-Zugriff beschränken, ob das überhaupt praktikabel ist weiß ich noch nicht aber wenn nicht dann soll es damit zumindest möglich sein das der Kernel seine eigenen Speicherbereiche für die Peripherie verbietet.
Du müsstest in dem Moment direkt vor den physischen Adressleitungen zum RAM ein paar Gatter setzen, die dem System solche Zugriffsüberschreitungen mitteilen. Ich glaube nicht, dass das praktikabel ist - theoretisch ginge es aber, wenn man die MMU aus dem Prozessor herausnimmt und physischen zwischen Prozessor/Peripherie und RAM ansetzt. Das wurde ja auch gemacht (z.B. Motorola 68010 auf Sun2-Maschinen).

In Software fällt das komplett aus und in Hardware umgesetzt, ist es viel zu langsam. Zumindest, wenn man Paging benutzen möchte. Segmentierung finde ich ja gut, aber ohne Paging geht es meiner Meinung nach nicht (auch du implementierst ein Paging, weil reine Segmentierung doof ist).

Wegen dem Fixed-Size-IPC: ich will Flexible-Size-IPC und sehe da auch keine zusätzlichen Probleme. Warum denkt ihr das IPC überhaupt ein guter Angriffspunkt ist?
Weil das IPC so wunderbar zentral im System verankert ist und auf einem Mikrokernel so ziemlich der einzige Zugang direkt in den Ring 0 ist.

oder indem man den Inhalt so strickt, dass der Message-Parser amok läuft oder Dinge tut, die man als Designer so nicht vorgesehen hat.
Dagegen sollte ein sauber programmierter Service einigermaßen immun sein.
Bugs, Bugs, Bugs. ;-)

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 22. November 2010, 13:16
Zitat von: svenska
Weil das IPC so wunderbar zentral im System verankert ist und auf einem Mikrokernel so ziemlich der einzige Zugang direkt in den Ring 0 ist.
Wo ich immernoch nicht weiß, wo das Problem liegt?
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 22. November 2010, 15:11
Hallo,


Mit dem Nullbyte würdest du aus Angriffen NULL-Pointer-Angriffe machen, aber auch die hat man bisher erfolgreich ausgenutzt. (Darum werden im virtuellen Adressraum die ersten 4K (ARM) / 64K (x86) von Linux nicht genutzt, die BSDs machen das ähnlich.)
Ich fürchte auch da hast Du mich missverstanden, mit dem 0-Word als (zusätzliches) Magic-Cookie meinte ich das wenn man erwartet das ein 0-Wort auf dem Stack liegt das dann der Angreifer ein Problem hat weil er eben bei dem meisten Buffer-Overflows, die ja auf String-Funktionen beruhen, keine Chance hat weil die String-Funktionen ja eben ein 0-Byte als Abbruchkriterium nehmen.
Das meinte ich und finde es unschön... du verhinderst damit Angriffe durch strcpy() und Familie im Betriebssystem.
Ich glaube wir schreiben da immer noch aneinander vorbei. Das (zusätzliche) 0-Word-Magic-Cookie wird doch nicht im Betriebssystem gemacht sondern es wird von Code (den der Compiler extra einfügt) generiert und geprüft, so wie alle anderen Cookies auch, da gibt es absolut keine Abhängigkeit vom OS oder der benutzten CPU-Architektur. Die Funktion dieses 0-Word-Cookies ist genau so wie die anderer Cookies auch, nur eben das der Wert nicht irgendein geheimer Zufallswert ist sondern bekannt immer 0 (deswegen empfehle ich das auch nur als Zusatz-Magic-Cookie). Die Schutzfunktion bei diesem 0-Word-Magic-Cookie liegt eben darin das man es nicht mit den klassischen String-Funktionen auf dem Stack ablegen kann was bei einem Zufallswert, der kein 0-Byte enthält, ja durchaus möglich ist (falls es dem Angreifer gelingt diesen Zufallswert zu ermitteln oder zu erraten). Das ist einfach nur eine kleine zusätzliche Hürde weil somit die ganzen klassischen String-Funktionen als Einfallstor sicher ausfallen und die restliche Angriffsfläche eben kleiner wird.

Ist schön und gut, versteckt aber Bugs in den Anwendungen, die man lieber fixen sollte. ..... Das Nullbyte versteckt den Fehler ja nur.
Wieso sollte ein Magic-Cookie mit dem festen Wert 0 irgendwelche Fehler verstecken können? Ich kann da absolut nicht nachvollziehen was Du eigentlich damit meinst.

Mir wäre es am liebsten, wenn jeder Bug in einer Anwendung direkt zu einem Absturz führt und sich niemals irgendwo verschleiern lässt. Ist leider unmöglich.
Da stimme ich Dir voll zu, aber das ist eben unrealistisch. Obwohl es sich um digitale Technik handelt sind die Auswirkungen von Fehlern doch schon ziemlich fein unterteilt (zwischen "keine sichtbaren Auswirkungen" und "Total-Crash").

Was ist ALR?
Ich meinte die ganze Zeit ASLR, sorry. Ich denke das wenn die Qualität des ASLR gut ist (und auch der benutzte Zufall hoher Qualität ist) dürfte es kaum möglich sein eine brauchbare Einsprung-Adresse zu ermitteln die dann für den Angriff als Rücksprungadresse in die Stack-Angriffsdaten eingebaut werden kann.

Du brauchst normalerweise Root-Zugriff, um TCP-Ports unter 1024 verwenden zu dürfen
Also das betrachte ich als Design-Fehler, dafür sollte man eine extra Berechtigung einführen die nur bestimmte Programme wenn sie von bestimmten Usern ausgeführt werden bekommen können. Warum soll ein Web-Server (sicher das Einfallstor Nummer 1 auf den Servern) root-Rechte brauchen nur weil sein öffentlicher Port < 1024 ist?

du brauchst normalerweise Root-Zugriff, um ein vollständiges Backup (inkl. /etc/shadow) des Systems anfertigen zu können.
Das ist IMHO okay, das Backup-Programm ist nicht ständig aktiv (also das Zeitfenster für den Angreifer begrenzt) und hat auch keine öffentlichen Interfaces wie TCP-Ports usw. (der Angreifer muss also bereits Zugriff auf das System haben um da überhaupt ran zu kommen).

Du brauchst (noch) Root-Zugriff, um die grafische Oberfläche (X-Server) ausführen zu können.
Das ist natürlich genau so Quatsch wie bei dem Web-Server. Ein spezielles Recht zum zugreifen auf den GraKa-Treiber sollte völlig reichen.

Alle diese Programme sind hackbar und damit potentielle Überschreiber von /dev/mem.
Also alle Programme die in /sbin liegen sollten mit der höchsten Sicherheitsstufe vom Compiler generiert werden (da würde ich persönlich auch gerne ein paar Prozent Performanceverlust akzeptieren) und auch die OS-Mechanismen wie ASLR und NX sollten immer voll aktiv sein. Programme die mit root-Rechten laufen sind immer ein besonders interessantes Angriffsziel. Ich würde auch gerne so weit gehen das Programme die root-Rechte bekommen sollen eine spezielle Signatur benötigen damit sich root wenigstens sicher sein kann das diese Programme von niemanden auf der Platte modifiziert wurde.

Die kann man nicht mit "tja, root ist halt selbst schuld" abhandeln.
Das sehe ich ganz genau so, auch wenn ich schon der Meinung bin das man root sicher etwas mehr an Sorgfalt abverlangen kann.

Alle die Adressen die ein Treiber seiner HW mitteilt müssen ja in festen Pages liegen und das muss der Treiber immer explizit anfordern (und auch wieder freigeben) und genau darauf soll der Kernel dann die Peripherie-Zugriff beschränken, ob das überhaupt praktikabel ist weiß ich noch nicht aber wenn nicht dann soll es damit zumindest möglich sein das der Kernel seine eigenen Speicherbereiche für die Peripherie verbietet.
Du müsstest in dem Moment direkt vor den physischen Adressleitungen zum RAM ein paar Gatter setzen, die dem System solche Zugriffsüberschreitungen mitteilen.
Ich hatte an eine White-List im PCI(e)-Root-Complex gedacht die alle reinkommenden Zugriffe filtert und bei Verletzungen einen IRQ auslöst damit der Kernel weiß welche HW (bei PCIe gibt es ja Absenderadressen in den Paketen) an welche nicht freigeschaltete Adresse ran will. Das Problem ist das wenn diese White-List mehr als ca. 16 Einträge haben muss wird sie zu einer Bremse. Eine Black-List die nur die paar Bereiche des Kernel schützt (und dafür vielleicht mit 8 Einträgen gut auskommt) ist da ein akzeptabler Kompromiss (zumindest besser als gar nichts).

Weil das IPC so wunderbar zentral im System verankert ist und auf einem Mikrokernel so ziemlich der einzige Zugang direkt in den Ring 0 ist.
Hä, jeder Syscall liefert einen potentiellen Zugang zum Ring 0 (System-Mode). Was ist da an IPC so besonders? Das einzigste wo IPC von den anderen Syscalls abweicht ist das man damit Zugriff auf andere Prozesse bekommt aber das trifft auf TCP ja auch zu (auch wenn TCP auf nen Micro-Kernel nicht direkt über Syscalls gemacht wird).

Dagegen sollte ein sauber programmierter Service einigermaßen immun sein.
Bugs, Bugs, Bugs. ;-)
Da hilft nur testen, testen, testen! ;)



Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 23. November 2010, 11:00
Hallo,

Ist schön und gut, versteckt aber Bugs in den Anwendungen, die man lieber fixen sollte. ..... Das Nullbyte versteckt den Fehler ja nur.
Wieso sollte ein Magic-Cookie mit dem festen Wert 0 irgendwelche Fehler verstecken können? Ich kann da absolut nicht nachvollziehen was Du eigentlich damit meinst.
Naja, Stringfunktionen kopieren im Zweifelsfall bis zu einem Nullbyte und damit eventuell über Puffergrenzen hinweg. Wenn du in der Quelle (Stack) jetzt ein neues, zusätzliches Nullbyte einfügst, dann wird die Stringfunktion nicht mehr über den Puffer hinauslaufen (bzw. maximal ein Byte zuviel kopieren, was meist egal ist), weil die Quelldaten ein Zwangsende verpasst kriegen. Damit verhinderst du Angriffe dieser Art konsequent.

Du brauchst normalerweise Root-Zugriff, um TCP-Ports unter 1024 verwenden zu dürfen
Also das betrachte ich als Design-Fehler, dafür sollte man eine extra Berechtigung einführen die nur bestimmte Programme wenn sie von bestimmten Usern ausgeführt werden bekommen können. Warum soll ein Web-Server (sicher das Einfallstor Nummer 1 auf den Servern) root-Rechte brauchen nur weil sein öffentlicher Port < 1024 ist?
Weil Ports unterhalb von 1024 privilegierte Ports sind und deren Funktionen von der IANA standardisiert wurden. Du kannst damit Clients auf anderen Rechnern stark verwirren, wenn du falsche Dienste anbietest. Außerdem könntest du sonst als User auf dem System einen Webserver auf Port 80 starten und so tun, als würdest du einen System-Service anbieten (z.B. ein DoS auf den Webserver mit Absturz als Folge, anschließend startest du einen eigenen Webserver auf Port 80) und dann Schadcode verbreiten - ohne, dass der Admin davon was merkt (er ist nicht immer anwesend) und ohne, dass eine Firewall davor schützen könnte.

du brauchst normalerweise Root-Zugriff, um ein vollständiges Backup (inkl. /etc/shadow) des Systems anfertigen zu können.
Das ist IMHO okay, das Backup-Programm ist nicht ständig aktiv (also das Zeitfenster für den Angreifer begrenzt) und hat auch keine öffentlichen Interfaces wie TCP-Ports usw. (der Angreifer muss also bereits Zugriff auf das System haben um da überhaupt ran zu kommen).
Das regelmäßige Backup-Programm wird aber automatisch mit Root-Rechten gestartet (z.B. von "cron"), dazu muss trotzdem ein Daemon mit Root-Rechten ununterbrochen laufen.

Du brauchst (noch) Root-Zugriff, um die grafische Oberfläche (X-Server) ausführen zu können.
Das ist natürlich genau so Quatsch wie bei dem Web-Server. Ein spezielles Recht zum zugreifen auf den GraKa-Treiber sollte völlig reichen.
Richtig, bzw. der Grafiktreiber wandert in den Kernel (Windows, Linux mit KMS) und das Problem ist beseitigt. Aber wenn du den Gedankengang weiterführst, dann erzeugst du Unmengen an Rechten, die alle brav konfiguriert werden müssen.

Schau dir SELinux an, das ist sicher, wenn es richtig konfiguriert ist. Dumm ist nur, dass es fast niemand konfigurieren kann und dadurch, dass du so enorm viele Einschränkungen machen musst, ist die Zeit zwischen "Installation des Systems" und "produktive Inbetriebnahme" sehr groß, was selbst für Firmen mit sicherheitskritischen Daten nicht mehr akzeptabel ist. Eine Woche (max. zwei) muss reichen und das kriegst du mit SELinux nicht hin, wenn du Webserver/Mailserver/LDAP-Server/... auch sicher konfigurieren möchtest.

Vorgefertigte Beispielregelungen sind immer freier als notwendig, da nicht auf den Anwendungsfall angepasst; Sicherheit ist eine Individualregelung. Je seltsamer dein System aufgebaut ist, desto schwieriger wird ein Angriff.

Alle diese Programme sind hackbar und damit potentielle Überschreiber von /dev/mem.
Also alle Programme die in /sbin liegen sollten mit der höchsten Sicherheitsstufe vom Compiler generiert werden (da würde ich persönlich auch gerne ein paar Prozent Performanceverlust akzeptieren) und auch die OS-Mechanismen wie ASLR und NX sollten immer voll aktiv sein.
Richtig, schließt auch /usr/sbin mit ein. Aber du hast setuid-root-Anwendungen auch in /bin (z.B. "ping") ...

Programme die mit root-Rechten laufen sind immer ein besonders interessantes Angriffsziel. Ich würde auch gerne so weit gehen das Programme die root-Rechte bekommen sollen eine spezielle Signatur benötigen damit sich root wenigstens sicher sein kann das diese Programme von niemanden auf der Platte modifiziert wurde.
Möglichst diese Signatur noch an das TPM des Mainboards gebunden?

Ich hatte an eine White-List im PCI(e)-Root-Complex gedacht die alle reinkommenden Zugriffe filtert und bei Verletzungen einen IRQ auslöst damit der Kernel weiß welche HW (bei PCIe gibt es ja Absenderadressen in den Paketen) an welche nicht freigeschaltete Adresse ran will. Das Problem ist das wenn diese White-List mehr als ca. 16 Einträge haben muss wird sie zu einer Bremse. Eine Black-List die nur die paar Bereiche des Kernel schützt (und dafür vielleicht mit 8 Einträgen gut auskommt) ist da ein akzeptabler Kompromiss (zumindest besser als gar nichts).
Also doch eine hardwareseitige schwarze oder weiße Liste zwischen RAM und den Busmastern (CPU, DMA, PCI/e). ;-) Das wirst du aber gefühlt nicht unbedingt in die PC-Architektur eingebaut kriegen, es ist teure und eine Bremse. Selbst die MMU ist eine Bremse - auch bei 1:1-Mapping mit den Pagetables im internen Cache. So eine Schaltung (im Prinzip ein CPLD-Gatter) bremst allein dadurch, dass die Taktraten zum RAM so hoch wie möglich sind. Wenn es dir wert ist, dann ist das natürlich eine optimale Lösung.

Weil das IPC so wunderbar zentral im System verankert ist und auf einem Mikrokernel so ziemlich der einzige Zugang direkt in den Ring 0 ist.
Hä, jeder Syscall liefert einen potentiellen Zugang zum Ring 0 (System-Mode). Was ist da an IPC so besonders? Das einzigste wo IPC von den anderen Syscalls abweicht ist das man damit Zugriff auf andere Prozesse bekommt aber das trifft auf TCP ja auch zu (auch wenn TCP auf nen Micro-Kernel nicht direkt über Syscalls gemacht wird).
Syscalls sind eine Form von IPC, nur dass das Ziel fest ist. Bei einem Mikrokernel (und die kleineren Varianten) können Syscalls auch direkt als IPC implementiert werden, wenn z.B. das Memory Management in einen eigenen Task ausgelagert wird (vgl. Minix). Wenn du TCP dort sinnvollerweise als Extra-Task implementierst, wird der Netzwerktreiber dann IPC mit dem TCP/IP-Stack machen. Greifst du das IPC an, hast du Zugriff auf beide Seiten (und damit DMA).

IPC ist nicht der einzige Angriffspunkt, aber ein sehr zentral gelegener und daher gut geeigneter. Darum muss man dort besonders aufpassen.

Dagegen sollte ein sauber programmierter Service einigermaßen immun sein.
Bugs, Bugs, Bugs. ;-)
Da hilft nur testen, testen, testen! ;)
Testen findet nur Fehler, die auch auftreten. ;-) Fuzzing-Attacken sind da schon hilfreicher, aber aufwändig (Bruteforce) und finden damit auch nicht immer alles. Auch Testsuites helfen, aber nicht universell.

Wobei die Lage nicht so schlimm ist, wie von mir beschrieben. Was geht, sind Programmiersprachen, die inhärent sicher (bzw. korrekt) sind. Aber Ada will man nicht programmieren, von der Performance rede ich lieber nicht.

Das ist jetzt aber definitiv OT gewesen.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 23. November 2010, 11:10
Zitat von: svenska
Syscalls sind eine Form von IPC, nur dass das Ziel fest ist. Bei einem Mikrokernel (und die kleineren Varianten) können Syscalls auch direkt als IPC implementiert werden, wenn z.B. das Memory Management in einen eigenen Task ausgelagert wird (vgl. Minix). Wenn du TCP dort sinnvollerweise als Extra-Task implementierst, wird der Netzwerktreiber dann IPC mit dem TCP/IP-Stack machen. Greifst du das IPC an, hast du Zugriff auf beide Seiten (und damit DMA).
Dem kann man aber (und so ungefähr mache ich es auch) ganz einfach entgegentreten, es dürfen halt nicht alle auf die entsprechenden Ports zu greifen (entweder ein Bestimmer Task/Thread oder alle).
Somit kannst du da auch nichts angreifen, du bekommst einfach nen Errorcode zurück, das du keine Rechte hast da was hinzuschicken oder auszulesen.

Ich denke du stellst dir das zu einfach vor oder ich sehe bestimmte Angriffspunkte nicht.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 23. November 2010, 13:11
Zitat von: svenska
Greifst du das IPC an, hast du Zugriff auf beide Seiten (und damit DMA).
Dem kann man aber (und so ungefähr mache ich es auch) ganz einfach entgegentreten, es dürfen halt nicht alle auf die entsprechenden Ports zu greifen (entweder ein Bestimmer Task/Thread oder alle).
Somit kannst du da auch nichts angreifen, du bekommst einfach nen Errorcode zurück, das du keine Rechte hast da was hinzuschicken oder auszulesen.
Siehst du? Du hast drüber nachgedacht und mehr wollte ich nicht erreichen. ;-)

Ich denke du stellst dir das zu einfach vor oder ich sehe bestimmte Angriffspunkte nicht.
Wenn man eine zentrale Rechtevergabe für das IPC macht (und diese hinreichend fehlerfrei ist, das mal angenommen), fällt das Problem wie ein Kartenhaus zusammen.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 23. November 2010, 15:36
Hallo,

Wieso sollte ein Magic-Cookie mit dem festen Wert 0 irgendwelche Fehler verstecken können? Ich kann da absolut nicht nachvollziehen was Du eigentlich damit meinst.
Naja, Stringfunktionen kopieren im Zweifelsfall bis zu einem Nullbyte und damit eventuell über Puffergrenzen hinweg. Wenn du in der Quelle (Stack) jetzt ein neues, zusätzliches Nullbyte einfügst, dann wird die Stringfunktion nicht mehr über den Puffer hinauslaufen (bzw. maximal ein Byte zuviel kopieren, was meist egal ist), weil die Quelldaten ein Zwangsende verpasst kriegen. Damit verhinderst du Angriffe dieser Art konsequent.
Es ging mir eigentlich nicht um den Quell-Puffer sondern darum das der Angreifer nicht absichtlich ein 0-Byte in einen normalen Angriffs-String einbauen kann um damit dieses spezielle Magic-Cookie mit dem richtigen Wert zu überschreiben (bei einem Zufalls-Cookie wo kein 0-Byte drin ist geht das ja prinzipiell falls er das erraten oder ausspähen kann), das funktioniert einfach nicht mit den normalen String-Funktionen so dass das überschreiben dieses Magic-Cookies eben immer auffallen würde (weil der Angreifer ja eben kein 0-Byte ablegen kann). Ansonsten hast Du natürlich recht.

Das regelmäßige Backup-Programm wird aber automatisch mit Root-Rechten gestartet (z.B. von "cron"), dazu muss trotzdem ein Daemon mit Root-Rechten ununterbrochen laufen.
Da hast Du recht. cron ist so ein Prozess der ständig läuft und root-Rechte hat aber cron ist klein genug das er einigermaßen fehlerfrei sein sollte und außerdem hat cron auch keine öffentlich erreichbaren Interfaces (wie TCP o.ä.).

Das ist natürlich genau so Quatsch wie bei dem Web-Server. Ein spezielles Recht zum zugreifen auf den GraKa-Treiber sollte völlig reichen.
Richtig, bzw. der Grafiktreiber wandert in den Kernel (Windows, Linux mit KMS) und das Problem ist beseitigt.
Also gerade dadurch das der Grafiktreiber in den Kernel wandert kann ich absolut keinen Sicherheitsgewinn erkennen. Grafiktreiber sind enorm kurzlebig und zeitkritisch (jeder Hersteller will das neuste Feature seiner Chips auch bestmöglich anbieten) so das dort kaum auf Sicherheit u.ä. geachtet wird. Die besseren Treiber für die professionellen Workstations sind eben extra geprüft und zertifiziert und den normalen Treiber immer mindestens 6 Monate hinterher (aber das ist der typischen Klientel dieser Treiber egal).

Aber wenn du den Gedankengang weiterführst, dann erzeugst du Unmengen an Rechten, die alle brav konfiguriert werden müssen.
Das stimmt natürlich, hier ist es wichtig mit einem guten Augenmaß ran zu gehen. Sicherheit macht eben immer etwas Arbeit. Egal ob es um den Zugriff auf Ports < 1024 oder den Grafiktreiber geht (beides muss natürlich angemessen reglementiert werden), es ist immer Konfigurationsarbeit zu leisten. Für den Endanwender läuft das auf eine (nicht immer einfache) Kosten/Nutzen-Abwägung hinaus.

Möglichst diese Signatur noch an das TPM des Mainboards gebunden?
Nein, natürlich nicht! Schon allein der Gedanke ist völlig unpassend. Das sollte mit dem privaten Schlüssel von root erledigt werden und der ist nur über das Passwort (oder eine SmartCard oder ein US-Dongle oder ...) von root zugänglich.

Also doch eine hardwareseitige schwarze oder weiße Liste
Ja. Das ist das einzigste was man nicht einfach so umgehen kann, es sei denn man schafft es Kernel-Privilegien zu bekommen und diese Liste zu ändern aber wenn man eh schon soweit ist kommt man auch so an alles ran.

zwischen RAM und den Busmastern (CPU, DMA, PCI/e).
Nein, im Chipsatz. Also in der Verbindung zwischen CPUs und RAM auf der einen Seite und der Peripherie auf der anderen Seite. Es geht nicht jeder RAM-Zugriff durch diesen Filter sondern nur das was von der Peripherie kommt. DMA hab ich auf meiner Plattform nicht, würde ich aber eventuell der CPU-Seite zurechnen wenn der Kernel aufpasst das da nichts unerlaubtes rein kommt, ansonsten doch lieber auf die Peripherie-Seite.

Das wirst du aber gefühlt nicht unbedingt in die PC-Architektur eingebaut kriegen, es ist teure und eine Bremse.
Das sehe ich anders, auch bei der PC-Architektur gibt es eine zentrale Schnittstelle zwischen den CPUs mit ihrem RAM auf der einen Seite und der Peripherie auf der anderen Seite, eben der Chipsatz (dort wo der PCI(e)-Root-Complex sitzt). Selbst bei einer Single-Chip-Lösung muss dieser Filter einfach nur an der richtigen Stelle sitzen und schon ist der Performanceverlust nicht mehr existent. Bei reinkommenden Zugriffen von der Peripherie muss doch eh geschaut werden wo die hingehen und parallel dazu kann die Filterliste befragt werden, kostet nicht einen Takt extra nur ein klein wenig Logik (zumindest eine Black-List sollte sich simpel realisieren lassen).

Wenn es dir wert ist, dann ist das natürlich eine optimale Lösung.
Ja, das ist es mir wert.

Selbst die MMU ist eine Bremse - auch bei 1:1-Mapping mit den Pagetables im internen Cache.
Das ist einer der wesentlichen Gründe für meine Segmente.

IPC ist nicht der einzige Angriffspunkt, aber ein sehr zentral gelegener und daher gut geeigneter. Darum muss man dort besonders aufpassen.
Gut, IPC ist sehr wichtig und daher muss man dort besonders gut aufpassen, auch bzw. gerade bei der Konzeptentwicklung. Die anderen Syscalls sollte man aber auch nicht vernachlässigen. Es gab schon genug erfolgreiche Angriffe durch die (unbewachte) Hintertür.

hast du Zugriff auf beide Seiten (und damit DMA).
Nehmen wir an es würde einem Angreifer auf meiner Plattform gelingen sich zwischen einem Ethernet-Treiber und dem IP-Prozess zu drängeln (mal ganz egal wie das gehen soll), dann hat der Angreifer aber noch keinen wahlfreien Zugriff auf die Busmasterfunktionen des Ethernet-Controllers sondern kann sich nur Daten in seine eigenen Segmente schreiben lassen. Auch auf die Clients vom IP-Prozess hat der Angreifer keinen freien Zugriff weil die ja nur die Datenbereiche per IPC weitergeben in die sie auch Netzwerkdaten haben wollen. Ein Angreifer könnte also einen Man-in-the-Middle-Angriff auf die Netzwerkkommunikation starten (so als würde er einen Ethernet-Switch kontrollieren) aber einen Zugriff auf das System hat er damit noch lange nicht.

Da hilft nur testen, testen, testen! ;)
Testen findet nur Fehler, die auch auftreten. ;-) Fuzzing-Attacken sind da schon hilfreicher, aber aufwändig (Bruteforce) und finden damit auch nicht immer alles. Auch Testsuites helfen, aber nicht universell.
Ja, gutes Testen will gelernt sein. Aber was, außer testen, kann ein Entwickler sonst noch tun? Er muss nur genügend Test-Szenarien finden oder finden lassen (zur Not muss eben der Endanwender die Test-Szenarien finden ;) ).

Was geht, sind Programmiersprachen, die inhärent sicher (bzw. korrekt) sind. Aber Ada will man nicht programmieren, von der Performance rede ich lieber nicht.
Soweit ich weiß ist der Performanceverlust bei Ada unterhalb der Messbarkeitsgrenze. Das einzigste was C/C++ wirklich fehlt sind die "run time array bound checks" und die muss der Programmierer dann von Hand erledigen (die Stellen wo er das effizienter erledigen kann als der Compiler dürften sehr rar sein), wenn der Programmierer das nicht tut gibt es die allseits beliebten Buffer-Overflows.

Das ist jetzt aber definitiv OT gewesen.
Wir sind hier auf "Lowlevel-Coding" und Sicherheit ist immer eine Angelegenheit von allen Ebenen, auch der niedrigen Ebene.


es dürfen halt nicht alle auf die entsprechenden Ports zu greifen (entweder ein Bestimmer Task/Thread oder alle).
Das bedeutet aber das Du wirklich für jeden Client einen neuen Port aufmachen musst, oder Du kannst für jeden Port eine richtige Liste mit den erlaubten Prozess-IDs verwalten. Zum ersten connecten neuer Clients brauchst Du aber auch Ports an die jeder ran kommt. Das ganze ist dann so wie bei den TCP-Sockets, der Server erstellt einen TCP-Server-Socket an den jeder Client ran kann und für jede zustande kommende Verbindung gibt es dann einen extra Socket der nur eine einzelne Verbindung repräsentiert und damit nur von einem einzelnen Client benutzt werden kann.

Bei meinem Konzept habe ich mich dazu entschieden das grundsätzlich immer jeder Prozess auf einen IPC-Service zugreifen darf (falls er denn die nötige ID kennt), der Kernel macht da nie irgendwelche Einschränkungen. Die Rechteverwaltung bleibt somit bei den Services hängen aber z.B. der VFS muss das eh erledigen (eine bereits geöffnete Datei kann nicht einfach so noch mal geöffnet werden) so das ich da keinen allzu großen Nachteil sehe. Seht ihr das anders?


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 23. November 2010, 16:05
Zitat von: erik
Bei meinem Konzept habe ich mich dazu entschieden das grundsätzlich immer jeder Prozess auf einen IPC-Service zugreifen darf (falls er denn die nötige ID kennt), der Kernel macht da nie irgendwelche Einschränkungen. Die Rechteverwaltung bleibt somit bei den Services hängen aber z.B. der VFS muss das eh erledigen (eine bereits geöffnete Datei kann nicht einfach so noch mal geöffnet werden) so das ich da keinen allzu großen Nachteil sehe. Seht ihr das anders?
Wenn der Client also die ID des Ports vom VFS kennt (was er ja muss), kann er also das gesamte System lahmlegen, weil er von dem Port lesen kann?

Zitat von: erik
Das bedeutet aber das Du wirklich für jeden Client einen neuen Port aufmachen musst, oder Du kannst für jeden Port eine richtige Liste mit den erlaubten Prozess-IDs verwalten. Zum ersten connecten neuer Clients brauchst Du aber auch Ports an die jeder ran kommt. Das ganze ist dann so wie bei den TCP-Sockets, der Server erstellt einen TCP-Server-Socket an den jeder Client ran kann und für jede zustande kommende Verbindung gibt es dann einen extra Socket der nur eine einzelne Verbindung repräsentiert und damit nur von einem einzelnen Client benutzt werden kann.
Jap, genauso habe ich mir das vorgestellt, ein allgemein bekannter Port um die Kommunikation zu initialisieren und dann ein "privater" Port für die eigentliche Kommunikation.

Da wir das schonmal hatten, gibt es eigentlich eine Situation wo man über die gesamte Programmlaufzeit keinen Port zum lesen benötigt, aber eine Nachricht an einen Port schicken will/muss?

Ich frage deshalb, weil man bei mir zwingend einen Port braucht um überhaupt eine Nachricht an einen anderen Port zu schreiben.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 23. November 2010, 17:18
Hallo,


Wenn der Client also die ID des Ports vom VFS kennt (was er ja muss), kann er also das gesamte System lahmlegen, weil er von dem Port lesen kann?
Hä, was meinst Du damit? Mein IPC soll etwas anders arbeiten, da kann man an so einen Port (der bei mir Message-Target heißt) einfach einen synchronen IPC-Vorgang (ein komplett atomar zusammenhängendes Spiel aus Anfrage und zugehöriger Antwort) schicken, das ist für den Sender blockierend. Wenn der Service es erlaubt können auch mehrere solcher Vorgänge parallel laufen (dann sind in dem Service-Prozess mehrere IPC-PopUp-Threads für dieses eine Message-Target aktiv), also mehrere Clients parallel Anfragen durchführen (das können mehrere unabhängige Prozesse sein aber auch mehrere Threads eines Prozess). Ein Service kann auch mehrere von diesen Message-Target aufmachen, die sind dann völlig unabhängig von einander, um darüber unterschiedliche Services anzubieten (oder auch unterschiedliche Instanzen eines Services).

Es soll auch Message-Targets für asynchrones IPC geben, da wird einfach nur eine Message geschickt und dafür ein PopUp-Thread im Service aufgemacht (der dann die Message verarbeitet), der Sender kann während dessen weiter arbeiten (also nicht blockierend da keine Antwort zurückkommt).

Als Sicherheit gibt der Kernel dem PopUp-Thread immer die Prozess-ID des Senders mit so das der Service weiß für welchen Client er eine Ressource geöffnet hat.

Jap, genauso habe ich mir das vorgestellt, ein allgemein bekannter Port um die Kommunikation zu initialisieren und dann ein "privater" Port für die eigentliche Kommunikation.
Dann wirst Du eine Menge Ports dafür aufmachen müssen.

Da wir das schonmal hatten, gibt es eigentlich eine Situation wo man über die gesamte Programmlaufzeit keinen Port zum lesen benötigt, aber eine Nachricht an einen Port schicken will/muss?
Ich frage deshalb, weil man bei mir zwingend einen Port braucht um überhaupt eine Nachricht an einen anderen Port zu schreiben.
Also bei meinem Konzept benötigt der Client kein eigenes Message-Target um an einen Service eine Anfrage zu schicken (egal ob synchrones oder asynchrones IPC), er benötigt nur die ID des gewünschten Message-Targets und macht damit die IPC-Syscalls. Ich kann mir ehrlich gesagt auch nicht vorstellen wozu das gut sein soll das der Client selber quasi zum Service wird. Bei mir wird es sicher viele simple Programme (die trotzdem deutlich über Hello-World hinausgehen, ich denke da z.B. an Compiler o.ä.) geben die kein einziges eigenes Message-Target aufmachen.
Was meist du eigentlich mit "Port zum lesen", also hat das lesen ein spezielle Bedeutung?


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 23. November 2010, 17:30
Hallo,

Das regelmäßige Backup-Programm wird aber automatisch mit Root-Rechten gestartet (z.B. von "cron"), dazu muss trotzdem ein Daemon mit Root-Rechten ununterbrochen laufen.
Da hast Du recht. cron ist so ein Prozess der ständig läuft und root-Rechte hat aber cron ist klein genug das er einigermaßen fehlerfrei sein sollte und außerdem hat cron auch keine öffentlich erreichbaren Interfaces (wie TCP o.ä.).
Was ist mit inetd? Ich weiß, ein Ausgeburt der Hölle, aber mir gefällt es. Der muss Prozesse unter bestimmten Nutzernamen ausführen können, also muss er root sein. Und er ist definitiv per TCP (UDP?) von außen erreichbar.

Wie auch immer, gewisse Services müssen root-Rechte haben, solange du nicht für jede noch so kleine Aktion eine eigene Permission baust. Wenn der Kernel endlich hinreichend sicher ist, wird man sich an genau diesen Services versuchen. ;-)

Grafiktreiber sind enorm kurzlebig und zeitkritisch (jeder Hersteller will das neuste Feature seiner Chips auch bestmöglich anbieten) so das dort kaum auf Sicherheit u.ä. geachtet wird. Die besseren Treiber für die professionellen Workstations sind eben extra geprüft und zertifiziert und den normalen Treiber immer mindestens 6 Monate hinterher (aber das ist der typischen Klientel dieser Treiber egal).
Naja, ich denke, dass es dabei hauptsächlich um Funktionskorrektheit geht, weniger um Sicherheit. Professionelle Workstations sollten ohnehin weit weg vom öffentlichen Internet sein (mindestens NAT, besser Proxy).

Der Grund für die Implementation im Kernel (bei Monolithen) ist Performance (Windows) und eine gemeinsame API als Basis für mehrere Treiber (Linux). Gleicher Grund, wie die Implementation von WLAN im Kernel, damit nicht jeder Treiber seinen eigenen WLAN-Stack mitbringen muss (wie bei Windows XP) und damit jeder Treiber, egal von wem, die gleichen Dinge unterstützen kann (WPA, HostAP).

Möglichst diese Signatur noch an das TPM des Mainboards gebunden?
Nein, natürlich nicht! Schon allein der Gedanke ist völlig unpassend. Das sollte mit dem privaten Schlüssel von root erledigt werden und der ist nur über das Passwort (oder eine SmartCard oder ein US-Dongle oder ...) von root zugänglich.
Gut, dann bindest du eben den Schlüssel an das TPM. Dafür ist es schließlich da: Als Ersatz für SmartCard-Authentifizierung, wenn du dem System trauen kannst, es also nicht einfach wegtragen kannst.

zwischen RAM und den Busmastern (CPU, DMA, PCI/e).
Nein, im Chipsatz. Also in der Verbindung zwischen CPUs und RAM auf der einen Seite und der Peripherie auf der anderen Seite. Es geht nicht jeder RAM-Zugriff durch diesen Filter sondern nur das was von der Peripherie kommt. DMA hab ich auf meiner Plattform nicht, würde ich aber eventuell der CPU-Seite zurechnen wenn der Kernel aufpasst das da nichts unerlaubtes rein kommt, ansonsten doch lieber auf die Peripherie-Seite.
Das ist aber inkonsequent. :-P Im Chipsatz liegt die RAM-Steuerung, also kannst du die Liste auch direkt vor den RAM setzen und mit mehreren Eingängen (CPU, DMA, PCI-Bus, Firewire-Bus [extra], ...) versehen, die jeweils eigene Einträge haben. Der Unterschied ist dann gering und wenn du paranoid genug bist, könntest du eine Adressraumliste für jeden einzelnen Task im System haben.

Je nachdem, wie schnell ein Neuladen der Liste relativ zur Zeitscheibe von Tasks dauert, könnte das sogar erträglich sein. Allerdings bezweifle ich, dass sowas des Rätsels Lösung ist. Das Prinzip ist ja dasselbe wie ein Watchdog und der wurde ja auch schon hinreichend ausgenutzt. (Der DoS-Exploit muss den Watchdog nur selbst streicheln, schon legt man die Services trotzdem unbegrenzt lange lahm.)

Das wirst du aber gefühlt nicht unbedingt in die PC-Architektur eingebaut kriegen, es ist teure und eine Bremse.
Das sehe ich anders, auch bei der PC-Architektur gibt es eine zentrale Schnittstelle zwischen den CPUs mit ihrem RAM auf der einen Seite und der Peripherie auf der anderen Seite, eben der Chipsatz (dort wo der PCI(e)-Root-Complex sitzt).
Du vergisst die Kompatiblität mit der Urzeit. Halte dich von der PC-Architektur fern, die ist stabil seit 1981! :-P

Prinzipiell möglich ist alles, aber umsetzbar ist es dann trotzdem nicht. Das können höchstens geschlossene Systeme a la Apple oder Spezialgeräte (embedded) machen, wo es angebracht ist.

IPC ist nicht der einzige Angriffspunkt, aber ein sehr zentral gelegener und daher gut geeigneter. Darum muss man dort besonders aufpassen.
Gut, IPC ist sehr wichtig und daher muss man dort besonders gut aufpassen, auch bzw. gerade bei der Konzeptentwicklung. Die anderen Syscalls sollte man aber auch nicht vernachlässigen. Es gab schon genug erfolgreiche Angriffe durch die (unbewachte) Hintertür.
Auf dem 27C3 (hoffentlich krieg ich noch ne Karte...) wird es einen Vortrag geben zu Angriffen durch Mischung von 32- und 64-Bit-Code in einer Anwendung und ungewöhnliches Verwenden von Syscalls in beiden Modi. Ähnlich kann man es ja auf ARM (mit ARM und Thumb-Code) oder MIPS (da gibt's jetzt auch einen kleineren Code) übertragen.

Nehmen wir an es würde einem Angreifer auf meiner Plattform gelingen sich zwischen einem Ethernet-Treiber und dem IP-Prozess zu drängeln (mal ganz egal wie das gehen soll), dann hat der Angreifer aber noch keinen wahlfreien Zugriff auf die Busmasterfunktionen des Ethernet-Controllers sondern kann sich nur Daten in seine eigenen Segmente schreiben lassen.
Richtig, weil du - und das meinte ich mit "komisches Zeug" - verschiedene Adressräume hast. Und zwar hardwareseitig, wenn du es wünschst. Bei Flatmem-Systemen gibt es ja nur einen und den kannst du aus Hardwaresicht nicht mit Paging erschlagen - eben weil DMA und so. Da braucht es dann die Fangschaltung von oben, die ist aber nicht in heutigen Systemen drin.

Was geht, sind Programmiersprachen, die inhärent sicher (bzw. korrekt) sind. Aber Ada will man nicht programmieren, von der Performance rede ich lieber nicht.
Soweit ich weiß ist der Performanceverlust bei Ada unterhalb der Messbarkeitsgrenze. Das einzigste was C/C++ wirklich fehlt sind die "run time array bound checks" und die muss der Programmierer dann von Hand erledigen (die Stellen wo er das effizienter erledigen kann als der Compiler dürften sehr rar sein), wenn der Programmierer das nicht tut gibt es die allseits beliebten Buffer-Overflows.
Njain. Bevor du in Ada anfangen kannst zu programmieren, bist du erstmal ziemlich lange damit beschäftigt, die Aufgabe und ihre Einzelteile und Abhängigkeiten zu beschreiben. Darum ist Ada ja auf Korrektheit prüfbar (im Gegensatz zu den meisten Programmiersprachen), aber der Programmierer muss dafür schon enormen Aufwand treiben und er darf nicht mit unscharfen Aufgabenstellungen beworfen werden. Run-Time-Boundary-Checks will man ja mit so Dingen wie Java (virtuellen maschinen) oder .NET (managed code) erschlagen, im Endeffekt hab ich heute ne Statistik gefunden, wonach irgendwas um die Hälfte aller Sicherheitslücken auf Buffer Overflow-Angriffe zurückgeht.

Wusstest du übrigens, dass das Template-System von C++ turing-vollständig ist?

Zitat von: erik
Bei meinem Konzept habe ich mich dazu entschieden das grundsätzlich immer jeder Prozess auf einen IPC-Service zugreifen darf (falls er denn die nötige ID kennt), der Kernel macht da nie irgendwelche Einschränkungen. Die Rechteverwaltung bleibt somit bei den Services hängen aber z.B. der VFS muss das eh erledigen (eine bereits geöffnete Datei kann nicht einfach so noch mal geöffnet werden) so das ich da keinen allzu großen Nachteil sehe. Seht ihr das anders?
Du trennst also nicht zwischen der Berechtigung des Zugriffs und der Berechtigung der Wunschaktion. Gibt es die Möglichkeit, die IPC-IDs zu bruteforcen? ;-)

Da wir das schonmal hatten, gibt es eigentlich eine Situation wo man über die gesamte Programmlaufzeit keinen Port zum lesen benötigt, aber eine Nachricht an einen Port schicken will/muss?
Ja, kann man durchaus konstruieren, z.B. wenn ich etwas in der Art eines Funkbroadcast verschicken möchte, wo es keine Bestätigung geben kann oder (aus Performancegründen) muss. Sowas wie "init", welches allen Prozessen mitteilt, dass das System jetzt herunterfährt/abschaltet oder meinetwegen auch UDP-Nachrichten.

Ich frage deshalb, weil man bei mir zwingend einen Port braucht um überhaupt eine Nachricht an einen anderen Port zu schreiben.
Damit sehe ich kein Problem. Für irgendwelche Aktionen brauchst du eigentlich immer ein Handle, das wäre bei dir der Port. Ist halt ne Designvorgabe, dass man keine "anonymen" IPC-Nachrichten schicken kann. Wenn du potentiell gefährlichen Prozessen verbietest, einen Port zu bekommen, dann verhinderst du effektiv die IPC-Kommunikation; für die Sicherheit ist das sicherlich förderlich.

Ich kann mir ehrlich gesagt auch nicht vorstellen wozu das gut sein soll das der Client selber quasi zum Service wird. Bei mir wird es sicher viele simple Programme (die trotzdem deutlich über Hello-World hinausgehen, ich denke da z.B. an Compiler o.ä.) geben die kein einziges eigenes Message-Target aufmachen.
Wenn du eine Syscall-API hast, die vollkommen asynchron und mit Callbacks arbeitet, wie z.B. das X11-Protokoll, dann ist jeder Client irgendwo ein Anbieter für so eine Callback-Struktur.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 23. November 2010, 18:11
Zitat von: erik
Hä, was meinst Du damit? Mein IPC soll etwas anders arbeiten, da kann man an so einen Port (der bei mir Message-Target heißt) einfach einen synchronen IPC-Vorgang (ein komplett atomar zusammenhängendes Spiel aus Anfrage und zugehöriger Antwort) schicken, das ist für den Sender blockierend. Wenn der Service es erlaubt können auch mehrere solcher Vorgänge parallel laufen (dann sind in dem Service-Prozess mehrere IPC-PopUp-Threads für dieses eine Message-Target aktiv), also mehrere Clients parallel Anfragen durchführen (das können mehrere unabhängige Prozesse sein aber auch mehrere Threads eines Prozess). Ein Service kann auch mehrere von diesen Message-Target aufmachen, die sind dann völlig unabhängig von einander, um darüber unterschiedliche Services anzubieten (oder auch unterschiedliche Instanzen eines Services).
Ich vergesse immer das du ja synchrones IPC hast, also auf jeden Fall auf eine Antwort "gewartet" wird. Du hast aber sehr wohl eine Art Rechteüberprüfung, denn es ist ganz genau festgelegt wer eine Nachricht bekommt, das ist bei mir nicht so. Es kann bei mir Ports geben, die von allen Prozessen gelesen werden können (keine Ahnung ob das jemals benutzt wird, aber ich habe es eben).

Zitat von: erik
Also bei meinem Konzept benötigt der Client kein eigenes Message-Target um an einen Service eine Anfrage zu schicken (egal ob synchrones oder asynchrones IPC), er benötigt nur die ID des gewünschten Message-Targets und macht damit die IPC-Syscalls. Ich kann mir ehrlich gesagt auch nicht vorstellen wozu das gut sein soll das der Client selber quasi zum Service wird. Bei mir wird es sicher viele simple Programme (die trotzdem deutlich über Hello-World hinausgehen, ich denke da z.B. an Compiler o.ä.) geben die kein einziges eigenes Message-Target aufmachen.
Was meist du eigentlich mit "Port zum lesen", also hat das lesen ein spezielle Bedeutung?
Mein IPC System funktioniert doch ein wenig anders. Bei mir werden Nachrichten grundsätzlich an Ports verschickt. Deswegen braucht auch jeder Client einen Port wo er die Antwort auf eine Anfrage abholen kann (das ist mit Port lesen gemeint).
Und genau das wäre auch ein Angriffspunkt meines IPC Systems. Denn man könnte eine DoS Attacke auf jeden Clienten machen, weil der muss ja die Nachricht auf jeden Fall erstmal abholen um sie dann verwerfen zu können. Im Endeffekt verschiebe ich damit die Kontrolle ob jemand eine Nachricht an einen Port schicken darf, auf den Clienten (dieser kann ja anhand des Senders überprüfen ob die Nachricht von einer vernünftigen Quelle kommt).
Desweiteren sollte das auch kein Problem sein (höchstens aus Speichertechnischer Sicht). Denn Standardmäßig wird bei der Porterstellung auch eine max. Anzahl an Nachrichten im Port mit angegeben und wenn die erreicht ist, bekommt der Sender nen Errorcode das die Queue des Empfängers voll ist, aber eine Nachricht auf die ein "Port" wartet (also synchrones IPC) können immer in die Queue.

Ich denke das mein System auch ausreichend sicher sein sollte. Ich würde halt nen sehr niedrigen Wert Standardwert (Nachrichten in der Port-Queue) für Clienten wählen und somit würde da auch nicht alzu viel Speicher verschwendet werden.
Ansich könnte man sogar soweit gehen und sagen, das für nen Clienten ne Port-Queue-Länge von 1 reicht.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 24. November 2010, 12:45
Hallo,


Wie auch immer, gewisse Services müssen root-Rechte haben, solange du nicht für jede noch so kleine Aktion eine eigene Permission baust. Wenn der Kernel endlich hinreichend sicher ist, wird man sich an genau diesen Services versuchen.
Ja, Du hast recht. Ein normal nutzbares System wird immer irgendwelche potentiellen Angriffsziele bieten, wenn man das nicht will darf man den Computer niemals anschalten. Wir alle wissen das es keine 100%-ige Sicherheit geben wird, alles was uns bleibt ist auf jeder Ebene unser bestes zu tun.

Der Grund für die Implementation im Kernel (bei Monolithen) ist Performance (Windows) und eine gemeinsame API als Basis für mehrere Treiber (Linux). Gleicher Grund, wie die Implementation von WLAN im Kernel, damit nicht jeder Treiber seinen eigenen WLAN-Stack mitbringen muss (wie bei Windows XP) und damit jeder Treiber, egal von wem, die gleichen Dinge unterstützen kann (WPA, HostAP).
Warum muss man für ein einheitliches Interface solche Dinge unbedingt in den Kernel verlegen? Auch unterschiedliche eigenständige Treiber sollten ein einheitliches Interface anbieten können. Sowas wie der WLAN-Stack könnte ein eigenständiger Service sein der allen WLAN-HW-Treibern zur Verfügung steht. Ich weiß ich denke zu sehr in Richtung Micro-Kernel.

Gut, dann bindest du eben den Schlüssel an das TPM. Dafür ist es schließlich da: Als Ersatz für SmartCard-Authentifizierung, wenn du dem System trauen kannst, es also nicht einfach wegtragen kannst.
Also sowas wie das TPM kommt für mich persönlich schon ganz grundsätzlich nicht in Frage, dieses TPM ist in meinen Augen absolut nicht vertrauenswürdig (jedenfalls nicht für den Endanwender). Also wenn dann würde ich auf jeden Fall eine SmartCard oder ein USB-Dongle bevorzugen, meinetwegen auch ein unter die Haut implantierter RF-ID-Chip oder sowas in der Art, aber diese Komponenten müssen für mich als Anwender absolut vertrauenswürdig sein und sollten nicht permanent mit dem System verbunden sein.

Im Chipsatz liegt die RAM-Steuerung, also kannst du die Liste auch direkt vor den RAM setzen und mit mehreren Eingängen (CPU, DMA, PCI-Bus, Firewire-Bus [extra], ...) versehen, die jeweils eigene Einträge haben.
Bei heutigen PC-Chipsätzen ist der RAM-Controller nicht mehr drin, so soll meine Plattform auch werden. Aber auch logisch sind die Peripherie-Geräte ja nicht direkt mit dem RAM verbunden, sondern gehen eigentlich immer erst über den PCI(e)-Root-Controller drüber. Ich hab sogar mal mit dem Gedanken gespielt dort ne Art MMU einzubauen die in sowas wie ner Paging-Tabelle (die aber keine Adressen sondern nur Bits für erlaubt/nicht erlaubt enthält) nachschaut ob der Zugriff genehmigt werden kann, wenn man das geschickt implementiert dürfte das ziemlich schnell und vor allem sehr flexibel sein.

wenn du paranoid genug bist, könntest du eine Adressraumliste für jeden einzelnen Task im System haben.
Das hab ich doch schon: meine Segmente. Keine (User-Mode-)Software kann da dran vorbei. Es geht mir bei der Liste im Chipsatz wirklich um die Hardware auf der anderen Seite vom Chipsatz.

auch bei der PC-Architektur gibt es eine zentrale Schnittstelle zwischen den CPUs mit ihrem RAM auf der einen Seite und der Peripherie auf der anderen Seite, eben der Chipsatz (dort wo der PCI(e)-Root-Complex sitzt).
Du vergisst die Kompatiblität mit der Urzeit. Halte dich von der PC-Architektur fern, die ist stabil seit 1981! :-P
Ich bin der Meinung das eine (default leere) Black-List im PCI(e)-Root-Controller die PC-Architektur weniger beeinflusst als die Einführung von HyperTransport oder QPI und da läuft auch noch ein uraltes MS-DOS drauf.

Auf dem 27C3 (hoffentlich krieg ich noch ne Karte...) wird es einen Vortrag geben zu Angriffen durch Mischung von 32- und 64-Bit-Code in einer Anwendung und ungewöhnliches Verwenden von Syscalls in beiden Modi. Ähnlich kann man es ja auf ARM (mit ARM und Thumb-Code) oder MIPS (da gibt's jetzt auch einen kleineren Code) übertragen.
Okay, das der 32Bit/64Bit-Mischbetrieb sicher nicht einfach zu implementieren ist im OS und dort ne Menge Fehlerpotential drin steckt ist klar aber was damit die Code-Größe zu tun hat kann ich mir echt nicht erklären. Den Syscall-Handlern kann doch völlig wurscht sein woher der Syscall kommt (das sollte auf die IRQ-Handler auch zutreffen), Hauptsache die Parameter usw. stimmen. Höchstens die Exception-Handler die den Code analysieren müssen um genau zu ermitteln was schief gelaufen ist sollten sich für den Code interessieren.

Nehmen wir an es würde einem Angreifer auf meiner Plattform gelingen sich zwischen einem Ethernet-Treiber und dem IP-Prozess zu drängeln (mal ganz egal wie das gehen soll), dann hat der Angreifer aber noch keinen wahlfreien Zugriff auf die Busmasterfunktionen des Ethernet-Controllers sondern kann sich nur Daten in seine eigenen Segmente schreiben lassen.
Richtig, weil du - und das meinte ich mit "komisches Zeug" - verschiedene Adressräume hast. Und zwar hardwareseitig, wenn du es wünschst. Bei Flatmem-Systemen gibt es ja nur einen und den kannst du aus Hardwaresicht nicht mit Paging erschlagen - eben weil DMA und so. Da braucht es dann die Fangschaltung von oben, die ist aber nicht in heutigen Systemen drin.
Auch auf einem Flat-Memory-System sollte ein Treiber nur die physischen Pages von seinem Gerät lesen/schreiben lassen können die er auch virtuell erreichen kann, der Syscall der ihm die physischen Adressen verrät (und die entsprechenden Pages im RAM festlockt) funktioniert ja nur mit gültigen virtuellen Adressen.

Bevor du in Ada anfangen kannst zu programmieren, bist du erstmal ziemlich lange damit beschäftigt, die Aufgabe und ihre Einzelteile und Abhängigkeiten zu beschreiben. Darum ist Ada ja auf Korrektheit prüfbar (im Gegensatz zu den meisten Programmiersprachen), aber der Programmierer muss dafür schon enormen Aufwand treiben und er darf nicht mit unscharfen Aufgabenstellungen beworfen werden.
In meiner idealisierten Traumwelt sollte jedes Projekt so ablaufen. ;)

Run-Time-Boundary-Checks will man ja mit so Dingen wie Java (virtuellen maschinen) oder .NET (managed code) erschlagen, im Endeffekt hab ich heute ne Statistik gefunden, wonach irgendwas um die Hälfte aller Sicherheitslücken auf Buffer Overflow-Angriffe zurückgeht.
Diese Statistik glaube ich gern, deswegen ist es IMHO ja so wichtig das da endlich mal effektive Maßnahmen ergriffen werden. Auch wenn das zum Schluss nur bedeutet das die bösen Leute sich auf etwas anderes stürzen.

Du trennst also nicht zwischen der Berechtigung des Zugriffs und der Berechtigung der Wunschaktion.
Doch. An den VFS (um mal ein gutes Beispiel zu nutzen) kann erstmal jeder seine Anfragen schicken, schließlich muss ja jeder Prozess in der Lage sein mit Dateien arbeiten zu können, aber ob die gewünschte Aktion auch tatsächlich durchgeführt wird entscheidet der VFS selber.

Gibt es die Möglichkeit, die IPC-IDs zu bruteforcen?
Ja. Siehst Du darin ein Problem? In einem klassischen Monolithen kann ja auch jeder Prozess erstmal so viele Syscalls aufrufen wie er will und prinzipiell damit alle CPU-Ressourcen belegen. Mir ist auch nicht bekannt das es ein OS gibt das da versucht zu reglementieren wie viele Syscalls ein Prozess machen darf, das erscheint mir auch nicht sehr sinnvoll. Als Strafe könnten meine Services ja bei unerlaubten/ungültigen Anfragen einfach ein delay(500) machen bevor der PopUp-Thread sich mit einem Fehler-Code für den Client beendet, auf Grund der blockierenden Natur des synchronen IPC würde das einen Prozess der eine DoS-Attake probiert schon mal ziemlich ausbremsen (ohne die guten Prozesse zu beeinträchtigen). Dieser böse Prozess könnte dann zwar ne riesige Menge an Threads erstellen um das auszugleichen aber da dürfte er dann ziemlich bald ins Visier vom OOM-Killer geraten. Da fällt mir ein das ich den Speicher den die PopUp-Threads benötigen (Stack und TLS) nicht auf das Konto des Services buchen sollte sondern auf das Konto des Client, damit auch wirklich der richtige Prozess bekanntschaft mit dem OOM-Killer macht. Meine Services sollen bestimmen können wie viele PopUp-Threads pro Message-Target (also pro angebotenem Service) maximal erstellt werden dürfen (weitere Anfragen werden gequeued) was dann doch eine gewisse Art von DoS-Attake ermöglicht, weil aber wieder der Client-Thread für jede Anfrage blockiert ist das nicht so einfach nutzbar. Ein Problem sind die asynchronen IPC-Services aber darüber läuft IMHO nichts kritisches.

Ist halt ne Designvorgabe, dass man keine "anonymen" IPC-Nachrichten schicken kann.
Diese Vorgabe gibt es bei mir auch, dem PopUp-Thread wird vom Kernel immer die Prozess-ID des Clients mitgegeben. Anonyme IPC-Kommunikation wäre ein echter Angriffspunkt und sollte daher nicht möglich sein.

Wenn du potentiell gefährlichen Prozessen verbietest, einen Port zu bekommen, dann verhinderst du effektiv die IPC-Kommunikation; für die Sicherheit ist das sicherlich förderlich.
Damit sind diese Prozesse auf einem Micro-Kernel aber von allem abgeschnitten und können eigentlich gar nichts tun, das erscheint mir irgendwie sinnlos.

Wenn du eine Syscall-API hast, die vollkommen asynchron und mit Callbacks arbeitet, wie z.B. das X11-Protokoll, dann ist jeder Client irgendwo ein Anbieter für so eine Callback-Struktur.
Das ist natürlich richtig, aber das betrifft zumindest nicht die Basics.


Ich vergesse immer das du ja synchrones IPC hast, also auf jeden Fall auf eine Antwort "gewartet" wird.
Also gerade das die Antwort immer automatisch den richtigen Client erreicht und der Client nichts spezielles dafür machen muss ist IMHO der wesentliche Vorteil von synchronem IPC. Genau deswegen möchte ich das auch unbedingt haben.

Du hast aber sehr wohl eine Art Rechteüberprüfung, denn es ist ganz genau festgelegt wer eine Nachricht bekommt, das ist bei mir nicht so.
Naja, indirekt schon. Bei mir sind Anfrage und Antwort bei einem synchronen IPC-Vorgang immer zusammenhängend, weder der Client noch der Service können da irgendetwas gegen tun. Es ist mir auch sehr wichtig das selbst der Client nicht durch den Service einfach so angegriffen werden kann, der Service darf nur mit genau dem Speicher des Client arbeiten den der Client auch explizit dafür anbietet sonst absolut nichts.

Und genau das wäre auch ein Angriffspunkt meines IPC Systems. Denn man könnte eine DoS Attacke auf jeden Clienten machen, weil der muss ja die Nachricht auf jeden Fall erstmal abholen um sie dann verwerfen zu können.
Eben. Asynchrones IPC ist IMHO eine leichte Beute für DoS-Attacken. Ich wette tyndur lahm zu legen ist extrem simpel (schon allein das LIFO-Prinzip der Signal-Handler macht sowas einfach zu einfach) und mit dem derzeitigen IPC-Konzept gibt es IMHO da absolut keine Möglichkeit was dagegen zu tun.

Im Endeffekt verschiebe ich damit die Kontrolle ob jemand eine Nachricht an einen Port schicken darf, auf den Clienten (dieser kann ja anhand des Senders überprüfen ob die Nachricht von einer vernünftigen Quelle kommt).
Mag sein der der Client diese Prüfung recht simpel erledigen kann, trotzdem bin ich wirklich nicht der Meinung das die Prüfung dort hin gehört. Wenn ich einen Service programmiere mache ich mir automatisch ein paar Gedanken über dessen Sicherheit (so sollte es zumindest sein) aber bei einem reinem Client (z.B. ein simples Hello-World) macht man sich üblicherweise keine Gedanken über sowas.

aber eine Nachricht auf die ein "Port" wartet (also synchrones IPC) können immer in die Queue.
Auch wenn diese bereits voll ist?


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: FlashBurn am 24. November 2010, 13:43
Zitat von: erik
Auch wenn diese bereits voll ist?
Deswegen das "immer"! Es wird nicht geprüft ob die Queue die Länge die max. erlaubt ist erreicht hat, sondern diese Nachricht wird immer an den Queue-Anfang gepackt und der Client wird aufgeweckt.

Was die DoS-Attacke betrifft, wenn du als Client eh immer nur Anfrage-Antwort Sachen per IPC machst, gibt es eigentlich keine Möglichkeit eine DoS-Attacke durchzuführen, weil der Client nie seine Port-Queue abfragt.

Problematisch wird es halt erst wenn du nen grafisches Progg hast. Denn der GUI-Server kann/muss dir ja Nachrichten schicken, das z.B. gerade eine Taste gedrückt wurde.
Dem würde ich so entgegenwirken, das man sagt, eine Nachricht von einem Port auf den der Client wartet darf immer in die Queue (ohne Rechteprüfung) und eine Nachricht auf die der Client nicht wartet, da werden Rechteüberprüfungen gemacht und da sage ich halt einfach, das nur Nachrichten vom GUI-Server kommen dürfen. Das sollte nicht angreifbar sein und einfach ist es auch.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 24. November 2010, 23:29
Hallo,

Der Grund für die Implementation im Kernel (bei Monolithen) ist Performance (Windows) und eine gemeinsame API als Basis für mehrere Treiber (Linux). Gleicher Grund, wie die Implementation von WLAN im Kernel, damit nicht jeder Treiber seinen eigenen WLAN-Stack mitbringen muss (wie bei Windows XP) und damit jeder Treiber, egal von wem, die gleichen Dinge unterstützen kann (WPA, HostAP).
Warum muss man für ein einheitliches Interface solche Dinge unbedingt in den Kernel verlegen? Auch unterschiedliche eigenständige Treiber sollten ein einheitliches Interface anbieten können. Sowas wie der WLAN-Stack könnte ein eigenständiger Service sein der allen WLAN-HW-Treibern zur Verfügung steht. Ich weiß ich denke zu sehr in Richtung Micro-Kernel.
Ja, ich vergleiche immer mit einem monolithischen Kernel. Und alles, was da drin ist, ist für mich "Kernel", egal ob es nun als eigenständiger Service implementiert ist oder nicht. Sieh das bitte bei mir immer als Prämisse. ;-)

Aber insbesondere bei Grafiktreibern sollte die Implementation aus Geschwindigkeitsgründen im Kernel selbst stattfinden - siehe Windows NT 3.5 gegen NT 4. Dort wurde es auch vom Userspace in den Kernel verfrachtet, weil es einfach zu langsam war.

Also sowas wie das TPM kommt für mich persönlich schon ganz grundsätzlich nicht in Frage, dieses TPM ist in meinen Augen absolut nicht vertrauenswürdig (jedenfalls nicht für den Endanwender).
TPMs sind von Grund auf als sichere Technologie implementiert. Du kannst denen schon vertrauen. Als Grundlage, mit der alles steht oder fällt, dient hierbei der Rechner selbst. Ich lehne das allerdings auch ab.

Also wenn dann würde ich auf jeden Fall eine SmartCard oder ein USB-Dongle bevorzugen, meinetwegen auch ein unter die Haut implantierter RF-ID-Chip oder sowas in der Art, aber diese Komponenten müssen für mich als Anwender absolut vertrauenswürdig sein und sollten nicht permanent mit dem System verbunden sein.
Wenn du dich gegen das System authentifizieren willst, muss der Schlüssel an das System gebunden sein. Willst du dich gegen den Menschen authentifizieren, muss der Schlüssel an den Menschen gebunden sein.

Ich bin der Meinung das eine (default leere) Black-List im PCI(e)-Root-Controller die PC-Architektur weniger beeinflusst als die Einführung von HyperTransport oder QPI und da läuft auch noch ein uraltes MS-DOS drauf.
Auch wieder wahr.

Den Syscall-Handlern kann doch völlig wurscht sein woher der Syscall kommt (das sollte auf die IRQ-Handler auch zutreffen), Hauptsache die Parameter usw. stimmen. Höchstens die Exception-Handler die den Code analysieren müssen um genau zu ermitteln was schief gelaufen ist sollten sich für den Code interessieren.
Das Problem sind "die Parameter usw. stimmen". Denn es kann (und wird) Unterschiede geben, ob der Syscall nun im 32- oder im 64-Bit-Modus aufgerufen wird. Möglicherweise sind das sogar zwei verschiedene Syscalls, die aus einem gemeinsamem Template erzeugt wurden. Allerdings hab ich dazu keine Ahnung mehr.

Auch auf einem Flat-Memory-System sollte ein Treiber nur die physischen Pages von seinem Gerät lesen/schreiben lassen können die er auch virtuell erreichen kann, der Syscall der ihm die physischen Adressen verrät (und die entsprechenden Pages im RAM festlockt) funktioniert ja nur mit gültigen virtuellen Adressen.
Das erfordert aber, dass selbst ein Busmaster keinen Direktzugriff auf den RAM bekommen kann (was ja der Sinn von DMA ist). Du brauchst halt diese hardwareseitige Fangschaltung.

Du trennst also nicht zwischen der Berechtigung des Zugriffs und der Berechtigung der Wunschaktion.
Doch. An den VFS (um mal ein gutes Beispiel zu nutzen) kann erstmal jeder seine Anfragen schicken, schließlich muss ja jeder Prozess in der Lage sein mit Dateien arbeiten zu können, aber ob die gewünschte Aktion auch tatsächlich durchgeführt wird entscheidet der VFS selber.
Achso, also trennst du nicht im IPC, sondern in den Services selbst. Woher weiß das VFS denn, ob ein Prozess eine bestimmte Aktion ausführen darf - die Information muss ja von irgendwo kommen?

Gibt es die Möglichkeit, die IPC-IDs zu bruteforcen?
Ja. Siehst Du darin ein Problem? In einem klassischen Monolithen kann ja auch jeder Prozess erstmal so viele Syscalls aufrufen wie er will und prinzipiell damit alle CPU-Ressourcen belegen.
Ganz simpel wäre die Lösung, dass ein Prozess nach jedem Syscall seine Zeitscheibe verliert. ;-)

Wenn du potentiell gefährlichen Prozessen verbietest, einen Port zu bekommen, dann verhinderst du effektiv die IPC-Kommunikation; für die Sicherheit ist das sicherlich förderlich.
Damit sind diese Prozesse auf einem Micro-Kernel aber von allem abgeschnitten und können eigentlich gar nichts tun, das erscheint mir irgendwie sinnlos.
Du kannst den Port ja zeitweise sperren, das kann die Applikation selbst tun ("ich weiß, dass ich hier keine Syscalls machen werde, also verbiete ich mir das selbst"). Ist genau das gleiche, wie wenn Anwendungen als root gestartet werden, ein Socket öffnen, und dann als User "nobody" weiterlaufen (Apache).

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: kevin am 25. November 2010, 09:29
Auch auf einem Flat-Memory-System sollte ein Treiber nur die physischen Pages von seinem Gerät lesen/schreiben lassen können die er auch virtuell erreichen kann, der Syscall der ihm die physischen Adressen verrät (und die entsprechenden Pages im RAM festlockt) funktioniert ja nur mit gültigen virtuellen Adressen.
Das erfordert aber, dass selbst ein Busmaster keinen Direktzugriff auf den RAM bekommen kann (was ja der Sinn von DMA ist). Du brauchst halt diese hardwareseitige Fangschaltung.
Hm, nennen wir das Kind doch beim Namen: Deine "hardwareseitige Fangschaltung" ist eine IOMMU, oder? Wenn du dich auf sehr aktuelle Rechner beschränkst, kannst du das voraussetzen. Oder wenigstens dann einschalten, wenn eine vorhanden ist.

Zitat
Gibt es die Möglichkeit, die IPC-IDs zu bruteforcen?
Ja. Siehst Du darin ein Problem? In einem klassischen Monolithen kann ja auch jeder Prozess erstmal so viele Syscalls aufrufen wie er will und prinzipiell damit alle CPU-Ressourcen belegen.
Ganz simpel wäre die Lösung, dass ein Prozess nach jedem Syscall seine Zeitscheibe verliert. ;-)
Öhm, ja. Geschwindigkeit interessiert ja keinen. ;)
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 25. November 2010, 12:08
Hallo,


Ja, ich vergleiche immer mit einem monolithischen Kernel. Und alles, was da drin ist, ist für mich "Kernel", egal ob es nun als eigenständiger Service implementiert ist oder nicht. Sieh das bitte bei mir immer als Prämisse.
Okay, auf einen Micro-Kernel-OS übertragen bedeutet das eben Kernel + Personality. Aber selbst bei einem Monolithen besteht ein nutzbares OS aus deutlich mehr als nur dem nackten Kernel. Trotzdem bin ich nicht der Meinung das man Funktionalität nur für ein "einheitliches Interface" in den Kernel verschieben muss, sowas geht doch auch anders.

Aber insbesondere bei Grafiktreibern sollte die Implementation aus Geschwindigkeitsgründen im Kernel selbst stattfinden
Warum "sollte"? Sollte man nicht lieber ordentliches und schnelles IPC implementieren? Nur weil MS das nicht hinbekommen hat heißt es nicht dass das nicht geht.

TPMs sind von Grund auf als sichere Technologie implementiert.
Ja, für den Main-Board-Hersteller und seine Geschäftspartner aus der Content-Industrie vielleicht aber für mich als Benutzer und legaler Eigentümer des PCs ist das TPM in keinster weise Vertrauenswürdig. Ließ Dir dazu mal die Spezifikation durch, das TPM ist vom Konzept her schon nicht dazu gedacht das der Benutzer seinem PC vertrauen kann.

Du kannst denen schon vertrauen.
Dem würde ich nicht mal meine Sammlung an Kuchenrezepten anvertrauen.

... Ich lehne das allerdings auch ab.
Dann brauchen wir ja nicht weiter über dieses dämliche TPM diskutieren.

Das Problem sind "die Parameter usw. stimmen". Denn es kann (und wird) Unterschiede geben, ob der Syscall nun im 32- oder im 64-Bit-Modus aufgerufen wird. Möglicherweise sind das sogar zwei verschiedene Syscalls, die aus einem gemeinsamem Template erzeugt wurden. Allerdings hab ich dazu keine Ahnung mehr.
Über was schreiben wir da eigentlich? Über die Unterschiede zwischen dem 32Bit-Mode und dem 64Bit-Mode bei x86 oder über die unterschiedlichen OpCode-Größen bei ARM und MIPS (und etlichen anderen CPUs auch)? Für erstes sollte man meiner Meinung nach alle Syscalls doppelt haben, auch wenn die 32Bit-Versionen wohl nur eine Umsetzung auf 64Bit vornehmen (mal außer acht gelassen wie komplex so ne Umsetzung manchmal sein kann). Für zweites ist das für die Syscalls völlig egal weil die CPU ja immer noch im selben Modus ist (oder zumindest noch in einem Modus mit der selben Bit-Breite). Zumindest der ARM-Mode und der Thumb-Mode machen da keine Unterschiede, das ist dort eine Sache die eigentlich nur den Befehlsdecoder betrifft.

Auch auf einem Flat-Memory-System sollte ein Treiber nur die physischen Pages von seinem Gerät lesen/schreiben lassen können die er auch virtuell erreichen kann, der Syscall der ihm die physischen Adressen verrät (und die entsprechenden Pages im RAM festlockt) funktioniert ja nur mit gültigen virtuellen Adressen.
Das erfordert aber, dass selbst ein Busmaster keinen Direktzugriff auf den RAM bekommen kann (was ja der Sinn von DMA ist). Du brauchst halt diese hardwareseitige Fangschaltung.
Ich wollte eigentlich darauf hinaus das ein (fehlerfreier) Treiber seiner HW erstmal nur die physischen Adressen mitteilt die er selber von einem entsprechenden Syscall bekommen hat. Wenn im Treiber aber aktiver Schad-Code (oder ein Bug der sich von außen passend nutzen lässt) ist der dann anfängt den physischen Speicher (mit Hilfe seiner DMA-fähigen HW) zu analysieren dann ist eh alles zu spät, da könnte dann nur noch eine HW-Schaltung was helfen. Aus diesem Grund werde ich zumindest die Black-List-Version umsetzen damit wenigstens der Kernel selber intakt bleibt.
Eine richtige IOMMU ist natürlich auch was feines, kostet aber dann tatsächlich etwas Performance. Das Problem ist das diese IOMMU ja eigentlich sehr viele verschiedene Kontexte parallel beherrschen muss (für jedes Peripherie-Gerät einen eigenen) um auch wirklich die gewünschte Flexibilität zu erreichen und dafür ist dann wieder einiges an Speicher erforderlich (zusätzlich müssen diese Paging-Tables gepflegt werden). Wimre war die ursprüngliche Idee hinter der IOMMU die Benutzung von echten HW-Komponenten direkt aus einer VM heraus ohne dass das darin laufende OS (und auch die HW) überhaupt wissen muss das es in einer VM läuft.

Achso, also trennst du nicht im IPC, sondern in den Services selbst.
Richtig, sonst müsste ja der Kernel selber die Rechteverwaltung mitmachen und das finde ich bei einem Micro-Kernel eher unpraktisch. Mein Kernel soll nur ein paar ganz elementare Rechte kennen und benutzen, z.B. ob ein Prozess seinen Speicher im physischen RAM festpinnen darf um so die physische Adresse dieses Speichers zu erfahren (Treiber benötigen das) oder die Rechte bestimmte Syscalls zu benutzen.

Woher weiß das VFS denn, ob ein Prozess eine bestimmte Aktion ausführen darf - die Information muss ja von irgendwo kommen?
Das ist allerdings eine gute Frage, darüber hab ich mir noch nicht all zu viele Gedanken gemacht. Da ich eh für das Erstellen neuer Prozesse einen extra Service hab könnte dieser auch ne kleine Datenbank führen die zu jedem Prozess die zugehörigen Rechte + User usw. enthält, da muss ich mal in Ruhe drüber nachdenken.

Ganz simpel wäre die Lösung, dass ein Prozess nach jedem Syscall seine Zeitscheibe verliert.
Ich schließe mich hierzu taljeth's Meinung an, das wäre definitiv zu simpel.

Du kannst den Port ja zeitweise sperren, das kann die Applikation selbst tun ("ich weiß, dass ich hier keine Syscalls machen werde, also verbiete ich mir das selbst").
Also für Syscalls erscheint mir so ein (selbst auferlegtes) Verbot nicht geeignet, dann könnte das Programm ja nicht mal mehr ein Error-Log führen oder Speicher anfordern oder andere ganz banale Dinge erledigen. Wenn dann muss man sowas schon etwas feiner unterteilen (z.B. keine Netzwerk-Kommunikation also TCP/UDP/usw.).

Ist genau das gleiche, wie wenn Anwendungen als root gestartet werden, ein Socket öffnen, und dann als User "nobody" weiterlaufen (Apache).
Das funktioniert aber nur weil auch der User "nobody" immer noch verschiedene Dinge machen darf.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: kevin am 25. November 2010, 13:37
Eine richtige IOMMU ist natürlich auch was feines, kostet aber dann tatsächlich etwas Performance. Das Problem ist das diese IOMMU ja eigentlich sehr viele verschiedene Kontexte parallel beherrschen muss (für jedes Peripherie-Gerät einen eigenen) um auch wirklich die gewünschte Flexibilität zu erreichen und dafür ist dann wieder einiges an Speicher erforderlich (zusätzlich müssen diese Paging-Tables gepflegt werden). Wimre war die ursprüngliche Idee hinter der IOMMU die Benutzung von echten HW-Komponenten direkt aus einer VM heraus ohne dass das darin laufende OS (und auch die HW) überhaupt wissen muss das es in einer VM läuft.
Usermode und VM sind ja prinzipiell erstmal keine so großartig unterschiedlichen Konzepte.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 25. November 2010, 13:46
Hallo,


Usermode und VM sind ja prinzipiell erstmal keine so großartig unterschiedlichen Konzepte.
Das ist richtig (ist sogar fast das selbe), aber VMs hat man für gewöhnlich nur wenige aber User-Mode-Prozesse sind normalerweise ne ganze Menge vorhanden und wenn man ein Gerät in die VM holen will dann ist das eine recht statische Angelegenheit aber wenn man per IOMMU kontrollieren will welche HW-Komponente in welchen User-Space-Adressraum greifen darf dann ist das eine sehr dynamische Angelegenheit. Ich will damit zum Ausdruck bringen das eine IOMMU zwar prinzipiell für die Absicherung des physischen Speichers funktioniert aber das dem möglicherweise erhebliche praktische Probleme im Wege stehen (um das beurteilen zu können müsste man sich erst mal die Spezifikationen genau durchlesen).


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: kevin am 25. November 2010, 14:18
Ich glaube, wenn man nicht nur die Hardwareseite, sondern auch die Software auf die Praxis beschränkt, dass es dann wieder recht statisch ist: Es kommen und gehen ein Haufen Prozesse, die aber allesamt überhaupt keinen Zugriff auf Hardware haben. Treiber werden in der Regel genau einmal gestartet und laufen dann, bis der Rechner ausgeschaltet wird.

Aber Details habe ich mir natürlich auch noch nicht angeschaut.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 25. November 2010, 14:55
Hallo,


Es kommen und gehen ein Haufen Prozesse, die aber allesamt überhaupt keinen Zugriff auf Hardware haben.
Aber die Hardware benötigt Zugriff auf deren Adressräume, oder willst Du immer kopieren? Genau das meinte ich mit dynamisch. Jedes mal wenn irgendein Prozess eine Datei oder Socket oder was anderes lesen/schreiben will dann muss kurz dieser entsprechende Abschnitt für die jeweilige HW-Komponente freigeschaltet werden und danach wieder gesperrt werden. Besonders lustig wird es wenn in einem Prozess 2 verschiedene unausgerichtete Puffer sich eine Page teilen und der Prozess damit 2 unterschiedliche Vorgänge über unterschiedliche (oder noch schlimmer die selbe) HW-Komponenten durchführen möchte.

Ansonsten hast du natürlich Recht.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: kevin am 25. November 2010, 15:00
Hm, stimmt, bei einer VM muss man wohl nur den physischen Speicher des Gasts mappen und der bleibt über die Dauer unverändert. Ist die Frage, wie teuer es ist, wenn ein Treiber vor jedem DMA-Vorgang erstmal die passenden Rechte beantragen müsste, damit die IOMMU entsprechend aufgesetzt wird.
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 25. November 2010, 15:18
Hallo,


Ist die Frage, wie teuer es ist, wenn ein Treiber vor jedem DMA-Vorgang erstmal die passenden Rechte beantragen müsste, damit die IOMMU entsprechend aufgesetzt wird.
Ich denke es ist eh ein Syscall erforderlich damit der Treiber überhaupt die physischen Pages, zu dem virtuellem Speicher den er (oder sein Client) hat, erfährt (und diese physischen Pages gesperrt werden). In diesem Syscall könnte der Kernel auch gleich noch die IOMMU managen. Nachdem die HW fertig ist muss der Treiber auch wieder die physischen Pages entsperren und da könnte der Kernel das zugehörige IOMMU-Mapping auch wieder entfernen. Ich denke das man dafür insgesamt kaum (oder gar keine) Änderungen in den Treibern machen müsste. Die Frage ist wie Aufwändig das Management der IOMMU ist.

Wenn der Kernel den virtuellen Adressraum für die HW geschickt nutzt dann könnte er auch in diesem immer nur die aktuell benötigten Mappings eintragen und käme mit einem einzigen Kontext aus. Weil diese Mappings dann am besten jeweils an einem Stück sind hätte die HW weniger mit Scatter/Gather zu tun und die Treiber müssten nicht riesige Listen mit physischen Pages bekommen. Nachteil ist das wenn es nur einen virtuellen HW-Adressraum gibt das dann jede HW an alle aktuell erreichbaren Speicherbereiche ran kommt und nicht nur dahin was die jeweilige HW auch wirklich benötigt, aber zumindest nicht an die Bereiche die keiner freigegeben hat und damit ist das immer noch ein erheblicher Fortschritt.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 25. November 2010, 16:25
Hallo,

Trotzdem bin ich nicht der Meinung das man Funktionalität nur für ein "einheitliches Interface" in den Kernel verschieben muss, sowas geht doch auch anders.
Richtig. Wenn dieses Interface aber den Hardwaretreibern die Arbeit erledigen soll oder ohnehin universell ist (WLAN, TCP/IP), dann ist es meiner Meinung nach im Kernel gut aufgehoben. Oder in einer zum Kernel gehörenden Library. Das sind immer Einzelfallentscheidungen. Tut man es nicht, implementiert man in den Hardwaretreibern die Funktionalität mehrfach.

Aber insbesondere bei Grafiktreibern sollte die Implementation aus Geschwindigkeitsgründen im Kernel selbst stattfinden
Warum "sollte"? Sollte man nicht lieber ordentliches und schnelles IPC implementieren? Nur weil MS das nicht hinbekommen hat heißt es nicht dass das nicht geht.
Es ging bei Microsoft nicht (wobei es bei Vista wieder probiert wurde; aber die Systeme selbst sind langsamer, da fällt das evtl. nicht auf), bei Linux hat man sich dazu entschieden, die Grafiktreiber in den Kernel zu ziehen und FreeBSD hat sogar Geldmittel dafür in Aussicht gestellt.

Das sind aber alles (teil-)monolithische Designs. Bei einem Mikrokernel hast du ohnehin Leistungsverlust für Syscalls, sodaß das IPC viel mehr optimiert sein muss. Da fällt das mit der Grafikkarte nicht mehr ins Gewicht.

Ganz simpel wäre die Lösung, dass ein Prozess nach jedem Syscall seine Zeitscheibe verliert.
Ich schließe mich hierzu taljeth's Meinung an, das wäre definitiv zu simpel.
Die Zahl Eins ist an der Stelle ist willkürlich (und langsam). Du kannst da ja ein dynamisches Limit angeben, um den Missbrauch von Syscalls für DoS zu verhindern. ;-)

Du kannst den Port ja zeitweise sperren, das kann die Applikation selbst tun ("ich weiß, dass ich hier keine Syscalls machen werde, also verbiete ich mir das selbst").
Also für Syscalls erscheint mir so ein (selbst auferlegtes) Verbot nicht geeignet, dann könnte das Programm ja nicht mal mehr ein Error-Log führen oder Speicher anfordern oder andere ganz banale Dinge erledigen. Wenn dann muss man sowas schon etwas feiner unterteilen (z.B. keine Netzwerk-Kommunikation also TCP/UDP/usw.).
Auch wieder wahr. Wenn man aber für die verschiedenen Dinge verschiedene Ports hat, dann wird das wieder möglich.

Ist genau das gleiche, wie wenn Anwendungen als root gestartet werden, ein Socket öffnen, und dann als User "nobody" weiterlaufen (Apache).
Das funktioniert aber nur weil auch der User "nobody" immer noch verschiedene Dinge machen darf.
Im Optimalfall die Notwendigen und kein bisschen mehr.

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 25. November 2010, 19:33
Hallo,


Tut man es nicht, implementiert man in den Hardwaretreibern die Funktionalität mehrfach.
Das ist ganz klar Quatsch. Jede Funktionalität sollte immer nur genau ein mal Implementiert sein.

Das sind aber alles (teil-)monolithische Designs.
Ganz recht. Keines der von Dir genannten OSe würde ich als Micro-Kernel bezeichnen, noch nicht mal als etwas Micro-Kernelig.

Bei einem Mikrokernel hast du ohnehin Leistungsverlust für Syscalls
Das halte ich für eine unbewiesene Behauptung.

Du kannst da ja ein dynamisches Limit angeben, um den Missbrauch von Syscalls für DoS zu verhindern. ;-)
Wie sollte man den sowas implementieren? Stell Dir mal grep vor, wenn die Dateien überwiegend klein sind dürfte die meiste CPU-Zeit für Syscalls drauf gehen.

Wenn man aber für die verschiedenen Dinge verschiedene Ports hat, dann wird das wieder möglich.
Hm, damit lagert man aber die Zugriffskontrolle dann doch auf die einzelnen Services aus. Ich denke da reicht es schon wenn die Services sich um die Dinge kümmern die eben spezifisch für diesen Service sind, noch ganz allgemeine Dinge da mit reinnehmen zu wollen finde ich irgendwie ungeschickt.


Grüße
Erik
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: Svenska am 25. November 2010, 21:54
Hallo,

Tut man es nicht, implementiert man in den Hardwaretreibern die Funktionalität mehrfach.
Das ist ganz klar Quatsch. Jede Funktionalität sollte immer nur genau ein mal Implementiert sein.
Sollte sie. Ist sie aber im Falle von WLAN nicht. Darum gibt es nach wie vor Linux-WLAN-Treiber (z.B. "acx"), die kein WPA oder HostAP unterstützen - weil sie aus der Zeit vor dem "mac80211"-Stack sind. Unter XP ist die Situation ähnlich, dort weiß ich es von einer frühen RTL8180, die kein WPA kann.

Etwas nicht-hardwarespezifisches, was mehrere Treiber benutzen können bzw. was die Basis von Treibern ist, gehört zum Kernel dazu. Nicht unbedingt in den gleichen Adressraum, aber so ziemlich untrennbar. So zumindest meine Meinung.

Das sind aber alles (teil-)monolithische Designs.
Ganz recht. Keines der von Dir genannten OSe würde ich als Micro-Kernel bezeichnen, noch nicht mal als etwas Micro-Kernelig.
Naja, Windows NT ist als Mikrokernel entstanden, bricht aber aus Performancegründen teilweise aus diesem Konzept aus, darum Makrokernel. Das Grundprinzip ist trotzdem ein Mikrokernel.

Bei einem Mikrokernel hast du ohnehin Leistungsverlust für Syscalls
Das halte ich für eine unbewiesene Behauptung.
Wenn ein Syscall durch IPC hindurch mit Services über ein spezifiziertes Protokoll reden muss, dann ist das langsamer, als einfach eine Funktion aufzurufen. Sieh es als unbewiesen an, aber gib mir dann einen Grund an, warum sich monolithische Designs durchgesetzt haben. ;-) (Gut, inzwischen findet ein Umdenken statt. Aber die Rechner sind um viele Größenordnungen schneller und Sicherheit wandert in die Köpfe von Designern und Programmierern.)

Gruß,
Svenska
Titel: Re:Allgemeiner Ressourcen-Allocator
Beitrag von: erik.vikinger am 27. November 2010, 10:37
Hallo,


Etwas nicht-hardwarespezifisches, was mehrere Treiber benutzen können bzw. was die Basis von Treibern ist, gehört zum Kernel dazu. Nicht unbedingt in den gleichen Adressraum, aber so ziemlich untrennbar. So zumindest meine Meinung.
Dieser Meinung kann ich mich ruhigen Gewissens anschließen.

Naja, Windows NT ist als Mikrokernel entstanden, bricht aber aus Performancegründen teilweise aus diesem Konzept aus, darum Makrokernel. Das Grundprinzip ist trotzdem ein Mikrokernel.
Auf Windows 2k/XP trifft das IMHO aber nicht mehr zu. Ich hab dafür mal einen Ethernet-NIC-Treiber geschrieben und der lieft ganz eindeutig in Ring 0 im Kernel-Adressraum (oberhalb 0xC0000000). In meinen Augen ist Windows ein modularer Monolith der in der Lage ist bestimmte Executables (eigentlich ganz normale PE-EXEs nur mit nem extra Flag) zur Laufzeit in seinen Adressraum zu laden.

aber gib mir dann einen Grund an, warum sich monolithische Designs durchgesetzt haben. ;)
Die sind leichter zu programmieren, man kommt damit schneller zum Erfolg. Ja die dunkle Seite der OS-Entwicklerei ist schon ziemlich verführerisch. ;)

(Gut, inzwischen findet ein Umdenken statt. Aber die Rechner sind um viele Größenordnungen schneller und Sicherheit wandert in die Köpfe von Designern und Programmierern.)
Ja, endlich.


Ich hab nochmal ein wenig über eine IOMMU nachgedacht, sowas ist auf jeden Fall ein sehr verlockendes Feature (auch wenn es für die Peripherie-Zugriffe auf den Haupt-Speicher ein klein wenig Performance kostet was aber bei PCIe mit möglichst großen Payloads in Grenzen gehalten werden kann). Ich denke ich werde es als erstes mit ner Black-List probieren und danach (wenn ich Zeit und Lust hab) mal probieren eine IOMMU zu implementieren, wen man das geschickt macht könnte man den Treibern beim festlocken von physischen Speicher immer nur eine HW-virtuelle Adresse geben weil der Kernel den angeforderten physischen Bereich immer in einem zusammenhängenden Bereich in den HW-virtuellen Adressraum legt. Mal sehen wie viel Aufwand das für den Kernel bedeutet und wie viel Aufwand das im Treiber spart. Ein weiterer Vorteil ist das somit der Treiber niemals echte physische Adressen zu sehen bekommt.


Grüße
Erik