Warum überhaupt brk in malloc verwenden? Warum nicht einfach mmap verwenden?
Lesezeit: 10 Minuten
Nate CK
Typische Implementierungen von malloc verwenden brk/sbrk als primäres Mittel, um Speicher vom Betriebssystem zu beanspruchen. Allerdings verwenden sie auch mmap um Chunks für große Allokationen zu erhalten. Gibt es einen echten Vorteil für die Verwendung brk Anstatt von mmap, oder ist es nur Tradition? Würde es nicht genauso gut funktionieren, alles damit zu machen mmap?
(Hinweis: Ich benutze sbrk und brk hier austauschbar, da es sich um Schnittstellen zum gleichen Linux-Systemaufruf handelt, brk.)
Als Referenz finden Sie hier einige Dokumente, die die glibc beschreiben malloc:
Was diese Dokumente beschreiben, ist das sbrk wird verwendet, um eine primäre Arena für kleine Zuteilungen zu beanspruchen, mmap wird verwendet, um sekundäre Arenen zu beanspruchen, und mmap wird auch verwendet, um Platz für große Objekte zu beanspruchen (“viel größer als eine Seite”).
Die Verwendung sowohl des Anwendungsheaps (beansprucht mit sbrk) und mmap führt einige zusätzliche Komplexität ein, die unnötig sein könnte:
Zugewiesene Arena – Die Hauptarena verwendet den Heap der Anwendung. Andere Arenen verwenden mmap‘d haufenweise. Um einen Chunk einem Heap zuzuordnen, müssen Sie wissen, welcher Fall zutrifft. Wenn dieses Bit 0 ist, kommt der Chunk aus der Hauptarena und dem Haupthaufen. Wenn dieses Bit 1 ist, stammt der Chunk mmap‘d-Speicher und der Ort des Heaps können aus der Adresse des Chunks berechnet werden.
[Glibc malloc is derived from ptmalloc, which was derived from dlmalloc, which was started in 1987.]
Traditionell wurden Allokatoren verwendet sbrk(2) um Speicher zu erhalten, der aus mehreren Gründen suboptimal ist, einschließlich Rennbedingungen, erhöhter Fragmentierung und künstlichen Beschränkungen des maximal nutzbaren Speichers. Wenn sbrk(2) vom Betriebssystem unterstützt wird, verwendet diese Zuweisung beide mmap(2) und sbrk(2) in dieser bevorzugten Reihenfolge; sonst nur mmap(2) wird genutzt.
Also, das sagen sie hier sogar sbrk ist suboptimal, aber sie verwenden es trotzdem, obwohl sie sich bereits die Mühe gemacht haben, ihren Code so zu schreiben, dass er ohne es funktioniert.
[Writing of jemalloc started in 2005.]
UPDATE: Wenn ich mehr darüber nachdenke, gibt mir das bisschen über “in der Reihenfolge der Präferenz” eine Zeile auf Anfrage. Warum die Präferenzordnung? Benutzen sie nur sbrk als Fallback für den Fall mmap wird nicht unterstützt (oder es fehlen die erforderlichen Funktionen), oder es ist möglich, dass der Prozess in einen Zustand gelangt, in dem er verwendet werden kann sbrk aber nicht mmap? Ich werde mir ihren Code ansehen und sehen, ob ich herausfinden kann, was er tut.
Ich frage, weil ich ein Garbage-Collection-System in C implementiere und bisher keinen Grund sehe, etwas anderes zu verwenden mmap. Ich frage mich jedoch, ob ich etwas übersehe.
(In meinem Fall habe ich einen zusätzlichen Grund zu vermeiden brkdie ich möglicherweise verwenden muss malloc irgendwann.)
Du meinst mit einem Single mmap um einen Pool für Tausende kleinerer Zuweisungen zuzuweisen, richtig? Nicht eins mmap pro Zuordnung, wie Sie es für große tun würden
– dieser andere Typ
19. April 2019 um 22:42 Uhr
Es gibt Versionen von malloc() das verwenden mmap().
– Barmar
19. April 2019 um 22:44 Uhr
Mögliches Duplikat von Was macht der Systemaufruf brk()?, Verwendet malloc() brk() oder mmap()?, Über sbrk() und malloc() usw.
– jww
19. April 2019 um 23:36 Uhr
@thatotherguy: Ich habe in der Frage einige Informationen darüber hinzugefügt, was die Zuweisungen, über die ich gelesen habe, tatsächlich tun.
– Nate CK
20. April 2019 um 12:32 Uhr
Beachten Sie, dass glibc malloc tut verwenden mmap für große Zuteilungen. Das jemalloc negative Kommentare über brk gelten am stärksten für die Verwendung alles, wie alte Malloc-Implementierungen der Unix-Geschichte. (insbesondere Fragmentierung: Unfähigkeit, Speicher an den Kernel zurückzugeben, wenn nach einer kurzfristigen großen Zuweisung eine langfristige kleine Zuweisung erfolgt.)
– Peter Cordes
4. Januar um 12:07 Uhr
Der Systemaufruf brk() hat den Vorteil, nur ein einziges Datenelement zu haben, um die Speichernutzung zu verfolgen, was glücklicherweise auch direkt mit der Gesamtgröße des Heaps zusammenhängt.
Dies ist seit Unix V6 von 1975 in genau derselben Form der Fall. Wohlgemerkt, V6 unterstützte einen Benutzeradressraum von 65.535 Bytes. Es wurde also nicht viel darüber nachgedacht, viel mehr als 64 KB zu verwalten, sicherlich nicht Terabyte.
Verwenden mmap scheint vernünftig, bis ich mich frage, wie eine geänderte oder hinzugefügte Garbage Collection verwendet werden könnte mmmap aber ohne Umschreiben des Zuordnungsalgorithmus auch.
Funktioniert das gut mit realloc(), fork()etc.?
Die Sache ist die, dass moderne Zuordner ihre Zuweisungsalgorithmen seitdem umfassend umgeschrieben haben. Einer, jemalloc, wurde bis 2005 nicht einmal geschrieben. Und moderne Allokatoren verwenden ihn mmap ausgiebig, also scheinen sie herausgefunden zu haben, wie man es zum Laufen bringt. Diejenigen, die ich mir angesehen habe, mischen es jedoch mit Anrufen sbrkwie ich es jetzt in einigen Updates zu der Frage beschrieben habe.
– Nate CK
20. April 2019 um 12:50 Uhr
mmap() gab es in den frühen Versionen von Unix nicht. brk() war damals die einzige Möglichkeit, das Datensegment des Prozesses zu vergrößern. Die erste Version von Unix mit mmap() war SunOS Mitte der 80er Jahre war die erste Open-Source-Version 1990 BSD-Reno.
Und verwendbar sein für malloc() Sie möchten keine echte Datei zum Sichern des Speichers benötigen. 1988 wurde SunOS implementiert /dev/zero zu diesem Zweck und in den 1990er Jahren implementierte HP-UX die MAP_ANONYMOUS Flagge.
Mittlerweile gibt es Versionen von mmap() die eine Vielzahl von Methoden zur Zuweisung des Heaps bieten.
Das erklärt warum mmap wurde in der Vergangenheit nicht verwendet, sondern in modernen Versionen tun verwenden, daher bin ich mir nicht sicher, ob die Geschichte erklärt, warum sie es nicht ausschließlich verwenden. Vielleicht wurden sie ursprünglich für den Gebrauch geschrieben brk erst und dann hinzugefügt mmap Anrufe später als Verbesserung? Aber jemalloc stammt erst aus dem Jahr 2005 und verwendet beides sbrk und mmap.
– Nate CK
19. April 2019 um 23:52 Uhr
Berufung mmap(2) einmal pro Speicherzuweisung ist kein praktikabler Ansatz für einen Allzweck-Speicherzuordner, da die Zuweisungsgranularität (die kleinste einzelne Einheit, die gleichzeitig zugewiesen werden kann) für mmap(2) ist PAGESIZE (normalerweise 4096 Bytes) und weil es einen langsamen und komplizierten Systemaufruf erfordert. Der Allocator Fast Path für kleine Allokationen mit geringer Fragmentierung sollte keine Syscalls erfordern.
Unabhängig davon, welche Strategie Sie verwenden, müssen Sie immer noch mehrere der von Glibc als Speicherbereiche bezeichneten Bereiche und unterstützen das GNU-Handbuch erwähnt: “Das Vorhandensein mehrerer Bereiche ermöglicht es mehreren Threads, Speicher gleichzeitig in separaten Bereichen zuzuweisen, wodurch die Leistung verbessert wird.”
Traditionell haben Zuordner sbrk(2) verwendet, um Speicher zu erhalten, der aus mehreren Gründen suboptimal ist, darunter Race Conditions, erhöhte Fragmentierung und künstliche Beschränkungen des maximal nutzbaren Speichers. Wenn sbrk(2) vom Betriebssystem unterstützt wird, verwendet dieser Zuordner sowohl mmap(2) als auch sbrk(2) in dieser bevorzugten Reihenfolge; ansonsten wird nur mmap(2) verwendet.
Ich sehe nicht, wie irgendetwas davon auf die moderne Verwendung von zutrifft sbrk(2), so wie ich es verstehe. Race-Conditions werden durch Threading-Primitive behandelt. Die Fragmentierung wird genauso gehandhabt, wie dies bei von zugewiesenen Speicherbereichen der Fall wäre mmap(2). Der maximal nutzbare Speicher ist unerheblich, weil mmap(2) sollte für jede große Zuordnung verwendet werden, um die Fragmentierung zu reduzieren und den Speicher sofort wieder für das Betriebssystem freizugeben free(3).
Die Verwendung sowohl des Anwendungsheaps (beansprucht mit sbrk) als auch von mmap führt zu einer zusätzlichen Komplexität, die möglicherweise unnötig ist:
Zugewiesene Arena – Die Hauptarena verwendet den Heap der Anwendung. Andere Arenen verwenden Mmap-Haufen. Um einen Chunk einem Heap zuzuordnen, müssen Sie wissen, welcher Fall zutrifft. Wenn dieses Bit 0 ist, kommt der Chunk aus der Hauptarena und dem Haupthaufen. Wenn dieses Bit 1 ist, kommt der Chunk aus mmap-Speicher und der Ort des Heaps kann aus der Adresse des Chunks berechnet werden.
Die Frage ist nun, ob wir bereits verwenden mmap(2)warum nicht einfach eine Arena zu Beginn des Prozesses zuweisen mmap(2) statt zu verwenden sbrk(2)? Insbesondere dann, wenn, wie zitiert, nachvollzogen werden muss, welche Allokationsart verwendet wurde. Es gibt verschiedene Gründe:
mmap(2) möglicherweise nicht unterstützt.
sbrk(2) bereits für einen Prozess initialisiert ist, wohingegen mmap(2) würde zusätzliche Anforderungen einführen.
Wie glibc-Wiki sagt, “Wenn die Anfrage groß genug ist, wird mmap() verwendet, um Speicher direkt vom Betriebssystem anzufordern […] und es kann eine Grenze dafür geben, wie viele solcher Zuordnungen gleichzeitig vorhanden sein können. “
Eine Speicherkarte mit zugeordnet mmap(2) nicht so einfach erweiterbar. Linux hat mremap(2), aber seine Verwendung beschränkt den Zuordner auf Kernel, die ihn unterstützen. Premapping vieler Seiten mit PROT_NONE access verwendet zu viel virtuellen Speicher. Verwenden MMAP_FIXED löscht jede Zuordnung, die möglicherweise zuvor ohne Warnung vorhanden war. sbrk(2) hat keines dieser Probleme und ist explizit so konzipiert, dass der Speicher sicher erweitert werden kann.
Fragmentierung wäre ein Problem für brk, wenn Sie es auch für große Zuweisungen verwenden: Sie können Speicher nur in LIFO-Reihenfolge an den Kernel zurückgeben, sodass eine langlebige kleine Zuweisung nach einer kurzlebigen großen Zuweisung uns daran hindern könnte, etwas zurückzugeben das große Stück Speicher. Natürlich können wir es auf die freie Liste setzen und es für zukünftige Zuweisungen in kleinere Blöcke zerhacken, aber wenn es ein Gigabyte schmutziger privater Speicher ist, ist das nicht das, was Sie wollen. Oder wenn es groß ist, aber nicht groß genug für die nächste große Zuweisung, auch nicht gut. Das sind im Grunde Fragmentierungsprobleme. (Was glibc mit mmap vermeidet)
– Peter Cordes
4. Januar um 12:12 Uhr
Speicherzuweisungen im Pausenbereich können nicht erweitert werden, wenn eine separate malloc Zuweisung folgt einer großen Zuweisung. Wir können die Pause gut verlängern, aber das hilft nicht, a zu befriedigen realloc Bibliothek anrufen. Die Verwendung einer dedizierten mmap für jede große Zuordnung macht es also viel wahrscheinlicher, dass Realloc noch größer wird, ohne auf eine andere Zuordnung zu treffen. (Und das Vermeiden von Kopieren ist umso wichtiger, je größer die Allokation ist). Das ist also ein weiterer guter Grund für glibc, eine Heuristik zu haben, um von einer Arena zu einer direkten mmap zu wechseln.
– Peter Cordes
4. Januar um 12:15 Uhr
Der offensichtliche Vorteil ist, dass Sie die letzte Zuteilung wachsen lassen können an Ort und Stellewas man damit nicht machen kann mmap(2) (mremap(2) ist eine Linux-Erweiterung, nicht portabel).
Für naive (und nicht so naive) Programme, die verwenden realloc(3) z.B. An einen String anzuhängen bedeutet dies einen Geschwindigkeitsschub von 1 oder 2 Größenordnungen 😉
Ich kenne die Details speziell für Linux nicht, aber unter FreeBSD wird seit einigen Jahren mmap bevorzugt und jemalloc in FreeBSDs libc hat sbrk() vollständig deaktiviert. brk()/sbrk() sind im Kernel auf den neueren Portierungen auf aarch64 und risc-v nicht implementiert.
Wenn ich die Geschichte von jemalloc richtig verstehe, war es ursprünglich der neue Allocator in FreeBSDs libc, bevor es ausgebrochen und portabel gemacht wurde. Jetzt ist FreeBSD ein nachgeschalteter Konsument von jemalloc. Es ist sehr wahrscheinlich, dass seine Präferenz für mmap() gegenüber sbrk() auf die Eigenschaften des FreeBSD-VM-Systems zurückzuführen ist, das um die Implementierung der mmap-Schnittstelle herum aufgebaut wurde.
Es ist erwähnenswert in SUS und POSIX brk/sbrk sind veraltet und sollten an dieser Stelle als nicht portierbar betrachtet werden. Wenn Sie an einer neuen Zuweisung arbeiten, möchten Sie sich wahrscheinlich nicht darauf verlassen.
13709900cookie-checkWarum überhaupt brk in malloc verwenden? Warum nicht einfach mmap verwenden?yes
Du meinst mit einem Single
mmap
um einen Pool für Tausende kleinerer Zuweisungen zuzuweisen, richtig? Nicht einsmmap
pro Zuordnung, wie Sie es für große tun würden– dieser andere Typ
19. April 2019 um 22:42 Uhr
Es gibt Versionen von
malloc()
das verwendenmmap()
.– Barmar
19. April 2019 um 22:44 Uhr
Mögliches Duplikat von Was macht der Systemaufruf brk()?, Verwendet malloc() brk() oder mmap()?, Über sbrk() und malloc() usw.
– jww
19. April 2019 um 23:36 Uhr
@thatotherguy: Ich habe in der Frage einige Informationen darüber hinzugefügt, was die Zuweisungen, über die ich gelesen habe, tatsächlich tun.
– Nate CK
20. April 2019 um 12:32 Uhr
Beachten Sie, dass glibc malloc tut verwenden
mmap
für große Zuteilungen. Dasjemalloc
negative Kommentare überbrk
gelten am stärksten für die Verwendung alles, wie alte Malloc-Implementierungen der Unix-Geschichte. (insbesondere Fragmentierung: Unfähigkeit, Speicher an den Kernel zurückzugeben, wenn nach einer kurzfristigen großen Zuweisung eine langfristige kleine Zuweisung erfolgt.)– Peter Cordes
4. Januar um 12:07 Uhr