Laut Linux-Programmierhandbuch:
brk () und sbrk () ändern den Ort der Programmunterbrechung, wodurch das Ende des Datensegments des Prozesses definiert wird.
Was bedeutet das Datensegment hier? Ist es nur das Datensegment oder Daten, BSS und Heap kombiniert?
Laut Wiki:
Manchmal werden die Daten-, BSS- und Heap-Bereiche gemeinsam als "Datensegment" bezeichnet.
Ich sehe keinen Grund, nur die Größe des Datensegments zu ändern. Wenn es sich um Daten, BSS und Heap handelt, ist dies sinnvoll, da Heap mehr Speicherplatz erhält.
Das bringt mich zu meiner zweiten Frage. In allen Artikeln, die ich bisher gelesen habe, sagt der Autor, dass der Haufen nach oben und der Stapel nach unten wächst. Was sie jedoch nicht erklären, ist, was passiert, wenn der Heap den gesamten Raum zwischen Heap und Stack einnimmt?
brk()
Systemaufruf ist in der Assemblersprache nützlicher als in C. In Cmalloc()
sollte er anstelle vonbrk()
Datenzuweisungszwecken verwendet werden - dies macht die vorgeschlagene Frage jedoch in keiner Weise ungültig.brk()
und manipuliert wirdsbrk()
? Die Stapel werden vom Seitenzuweiser auf einer viel niedrigeren Ebene verwaltet.Antworten:
In dem von Ihnen geposteten Diagramm ist die "Unterbrechung" - die von
brk
und manipulierte Adressesbrk
- die gepunktete Linie oben auf dem Heap.Die Dokumentation, die Sie gelesen haben, beschreibt dies als das Ende des "Datensegments", da in herkömmlichen (Pre-Shared-Libraries, Pre-
mmap
) Unix das Datensegment mit dem Heap kontinuierlich war. Vor dem Programmstart lud der Kernel die Blöcke "Text" und "Daten" ab Adresse Null in den RAM (tatsächlich etwas über Adresse Null, sodass der NULL-Zeiger wirklich auf nichts zeigte) und setzte die Unterbrechungsadresse auf das Ende des Datensegments. Der erste Aufruf vonmalloc
würde dann verwendetsbrk
, um die Aufteilung zu verschieben und den Heap zwischen dem oberen Rand des Datensegments und der neuen, höheren Unterbrechungsadresse zu erstellen , wie im Diagramm gezeigt, und die anschließende Verwendung vonmalloc
würde ihn verwenden, um den Heap größer zu machen wie nötig.In der Zwischenzeit beginnt der Stapel oben im Speicher und wächst nach unten. Der Stack benötigt keine expliziten Systemaufrufe, um ihn zu vergrößern. Entweder beginnt es mit so viel RAM, wie ihm jemals zugewiesen werden kann (dies war der traditionelle Ansatz), oder es gibt einen Bereich reservierter Adressen unterhalb des Stapels, dem der Kernel automatisch RAM zuweist, wenn er einen Versuch bemerkt, dort zu schreiben (Dies ist der moderne Ansatz). In beiden Fällen kann sich am unteren Rand des Adressraums ein "Schutz" -Bereich befinden oder nicht, der für den Stapel verwendet werden kann. Wenn diese Region existiert (alle modernen Systeme tun dies), wird sie dauerhaft nicht zugeordnet. wenn auch nichtWenn der Stapel oder der Heap versucht, hineinzuwachsen, wird ein Segmentierungsfehler angezeigt. Traditionell machte der Kernel jedoch keinen Versuch, eine Grenze durchzusetzen. Der Stapel könnte in den Heap hineinwachsen, oder der Heap könnte in den Stapel hineinwachsen, und so oder so würden sie sich gegenseitig über die Daten kritzeln und das Programm würde abstürzen. Wenn Sie sehr viel Glück hatten, würde es sofort abstürzen.
Ich bin nicht sicher, woher die Nummer 512 GB in diesem Diagramm stammt. Dies impliziert einen virtuellen 64-Bit-Adressraum, der nicht mit der sehr einfachen Speicherzuordnung übereinstimmt, die Sie dort haben. Ein echter 64-Bit-Adressraum sieht eher so aus:
Dies ist nicht remote skalierbar und sollte nicht so interpretiert werden, wie es ein bestimmtes Betriebssystem macht (nachdem ich es gezeichnet habe, stellte ich fest, dass Linux die ausführbare Datei tatsächlich viel näher an die Adresse Null bringt, als ich dachte, und die gemeinsam genutzten Bibliotheken bei überraschend hohen Adressen). Die schwarzen Bereiche dieses Diagramms sind nicht zugeordnet - jeder Zugriff verursacht einen sofortigen Segfault - und sie sind im Verhältnis zu den grauen Bereichen gigantisch . Die hellgrauen Bereiche sind das Programm und seine gemeinsam genutzten Bibliotheken (es können Dutzende von gemeinsam genutzten Bibliotheken vorhanden sein). Jeder hat eine unabhängigeText- und Datensegment (und "bss" -Segment, das ebenfalls globale Daten enthält, jedoch auf All-Bit-Null initialisiert wird, anstatt Speicherplatz in der ausführbaren Datei oder Bibliothek auf der Festplatte zu belegen). Der Heap ist nicht mehr unbedingt kontinuierlich mit dem Datensegment der ausführbaren Datei - ich habe es so gezeichnet, aber es sieht so aus, als würde Linux das zumindest nicht tun. Der Stapel ist nicht mehr an den oberen Rand des virtuellen Adressraums gebunden, und der Abstand zwischen dem Heap und dem Stapel ist so groß, dass Sie sich keine Sorgen mehr machen müssen, ihn zu überqueren.
Die Pause ist immer noch die Obergrenze des Haufens. Was ich jedoch nicht gezeigt habe, ist, dass es Dutzende von unabhängigen Speicherzuordnungen geben könnte, die irgendwo schwarze Zahlen schreiben, die mit
mmap
statt gemacht wurdenbrk
. (Das Betriebssystem wird versuchen, diese von dembrk
Bereich fernzuhalten , damit sie nicht kollidieren.)quelle
malloc
noch darauf verlassenbrk
oder ob es verwendet wirdmmap
, um separate Speicherblöcke "zurückgeben" zu können?malloc
denbrk
Bereich für kleine Zuweisungen und einzelnemmap
s für große Zuweisungen (z. B.> 128 KB). Siehe zum Beispiel die Diskussion von MMAP_THRESHOLD in der Linux-malloc(3)
Manpage.mmap
; Es ist extrem betriebssystemabhängig.Minimal lauffähiges Beispiel
Bittet den Kernel, Sie lesen und in einen zusammenhängenden Speicherblock namens Heap schreiben zu lassen.
Wenn Sie nicht fragen, kann dies zu einem Segfault führen.
Ohne
brk
:Mit
brk
:GitHub stromaufwärts .
Das Obige trifft möglicherweise nicht auf eine neue Seite und auch ohne das nicht auf Segfault.
brk
Hier ist also eine aggressivere Version, die 16 MB zuweist und sehr wahrscheinlich ohne dasbrk
folgende Fehler auftritt :Getestet unter Ubuntu 18.04.
Visualisierung des virtuellen Adressraums
Vorher
brk
:Nachher
brk(p + 2)
:Nachher
brk(b)
:Um Adressräume besser zu verstehen, sollten Sie sich mit Paging vertraut machen: Wie funktioniert x86-Paging? .
Warum brauchen wir beide
brk
undsbrk
?brk
könnte natürlich mitsbrk
+ Offset-Berechnungen implementiert werden, beide existieren nur der Einfachheit halber.Im Backend verfügt der Linux-Kernel v5.0 über einen einzigen Systemaufruf
brk
, mit dem beide implementiert werden: https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64. tbl # L23Ist
brk
POSIX?brk
Früher war es POSIX, aber es wurde in POSIX 2001 entfernt, sodass_GNU_SOURCE
auf den Glibc-Wrapper zugegriffen werden muss.Das Entfernen ist wahrscheinlich auf die Einführung zurückzuführen
mmap
, bei der es sich um eine Obermenge handelt, mit der mehrere Bereiche und mehr Zuordnungsoptionen zugewiesen werden können.Ich denke, es gibt keinen gültigen Fall, in dem Sie
brk
anstelle vonmalloc
odermmap
heutzutage verwenden sollten.brk
vs.malloc
brk
ist eine alte Möglichkeit der Implementierungmalloc
.mmap
ist der neuere, streng leistungsfähigere Mechanismus, den wahrscheinlich alle POSIX-Systeme derzeit zur Implementierung verwendenmalloc
. Hier ist ein Beispiel für eine minimale lauffähigemmap
Speicherzuweisung .Kann ich mischen
brk
und malloc?Wenn Ihr
malloc
mit implementiert istbrk
, habe ich keine Ahnung, wie das möglicherweise nicht explodieren kann, dabrk
nur ein einziger Speicherbereich verwaltet wird.Ich konnte jedoch nichts darüber in den glibc-Dokumenten finden, z.
Die Dinge werden wahrscheinlich nur dort funktionieren, da sie
mmap
wahrscheinlich für verwendet werdenmalloc
.Siehe auch:
Mehr Info
Intern entscheidet der Kernel, ob der Prozess so viel Speicher haben kann, und reserviert Speicherseiten für diese Verwendung.
Dies erklärt, wie der Stapel mit dem Heap verglichen wird: Welche Funktion haben die Push / Pop-Anweisungen, die für Register in der x86-Assembly verwendet werden?
quelle
p
ein Zeiger auf Typ istint
, sollte dies nicht gewesen seinbrk(p + 2);
?*(p + i) = 1;
brk(p + 2)
anstatt es einfach um zu erhöhensbrk(2)
? Ist brk wirklich notwendig?brk
syscall).brk
Es ist etwas bequemer, zuvor zugewiesene Stapel wiederherzustellen.Sie können
brk
und sichsbrk
selbst verwenden, um den "Malloc Overhead" zu vermeiden, über den sich alle immer beschweren. Sie können diese Methode jedoch nicht einfach in Verbindung mit verwenden,malloc
sodass sie nur dann angemessen ist, wenn Siefree
nichts benötigen . Weil du nicht kannst. Außerdem sollten Sie Bibliotheksaufrufe vermeiden, die möglicherweisemalloc
intern verwendet werden. Dh.strlen
ist wahrscheinlich sicher, aberfopen
wahrscheinlich nicht.Rufen
sbrk
Sie an, wie Sie anrufen würdenmalloc
. Es gibt einen Zeiger auf die aktuelle Unterbrechung zurück und erhöht die Unterbrechung um diesen Betrag.Während Sie einzelne Zuteilungen , die nicht frei ist (weil es keine ist malloc-Overhead , erinnern), Sie können kostenlos den gesamten Raum durch den Aufruf
brk
durch den ersten Aufruf zurückgegeben mit dem Wertsbrk
, also die brk Zurückspulen .Sie können diese Regionen sogar stapeln und die neueste Region verwerfen, indem Sie die Unterbrechung zum Start der Region zurückspulen.
Eine Sache noch ...
sbrk
ist auch nützlich im Code Golf, weil es 2 Zeichen kürzer als istmalloc
.quelle
malloc
/ dem Betriebssystem mitfree
Sicherheit Speicher zurückgeben kann (und dies auch tut). Sie tun dies möglicherweise nicht immer dann, wenn Sie dies wünschen, aber dies hängt davon ab, ob die Heuristiken nicht perfekt auf Ihren Anwendungsfall abgestimmt sind. Noch wichtiger ist, dass es unsicher ist,sbrk
in einem Programm, das jemals aufgerufen wird, mit einem Argument ungleich Null aufzurufenmalloc
- und fast alle Funktionen der C-Bibliothek dürfenmalloc
intern aufgerufen werden . Die einzigen, die dies definitiv nicht tun werden, sind die asynchronen, signal-sicheren Funktionen.malloc
.sbrk
hierfür ist nur für Code-Golf nützlich, da die manuelle Verwendungmmap(MAP_ANONYMOUS)
in jeder Hinsicht besser ist, mit Ausnahme der Quellcode-Größe.Es gibt eine speziell festgelegte anonyme Zuordnung des privaten Speichers (traditionell direkt hinter den Daten / BSS, aber modernes Linux passt den Speicherort tatsächlich mit ASLR an). Im Prinzip ist es nicht besser als jedes andere Mapping, mit dem Sie erstellen könnten
mmap
, aber Linux verfügt über einige Optimierungen, die es ermöglichen, das Ende dieses Mappings (mithilfe desbrk
Systemaufrufs) nach oben zu erweitern, wobei die Sperrkosten im Vergleich zu dem, was anfallen würdemmap
oder fallenmremap
würde, reduziert werden . Dies macht es fürmalloc
Implementierungen attraktiv , wenn sie den Hauptheap implementieren.quelle
Ich kann Ihre zweite Frage beantworten. Malloc schlägt fehl und gibt einen Nullzeiger zurück. Aus diesem Grund suchen Sie beim dynamischen Zuweisen von Speicher immer nach einem Nullzeiger.
quelle
malloc()
wirdbrk()
und / odersbrk()
unter der Haube verwendet - und Sie können auch, wenn Sie Ihre eigene angepasste Version von implementieren möchtenmalloc()
.Der Heap wird zuletzt im Datensegment des Programms platziert.
brk()
wird verwendet, um die Größe des Heaps zu ändern (zu erweitern). Wenn der Heap nicht mehr wachsen kann, schlägt jedermalloc
Aufruf fehl.quelle
Das Datensegment ist der Teil des Speichers, der alle Ihre statischen Daten enthält, die beim Start aus der ausführbaren Datei eingelesen und normalerweise mit Nullen gefüllt werden.
quelle
.bss
) werden vom Betriebssystem vor dem Programmstart auf All-Bit-Null initialisiert. Dies wird tatsächlich durch den C-Standard garantiert. Einige eingebettete Systeme könnten sich nicht darum kümmern (ich habe noch nie eines gesehen, aber ich arbeite nicht sommap
, aber ich würde davon ausgehen, dass die Seite.bss
immer noch auf Null gesetzt wird. Der BSS-Raum ist wahrscheinlich der kompakteste Weg, um die Tatsache auszudrücken, dass ein Programm einige Null-Arrays wünscht..bss
und nicht Null ist,.bss
wäre daher nicht konform. Aber nichts zwingt eine C-Implementierung dazu,.bss
überhaupt etwas zu verwenden oder sogar so etwas zu haben.main
. Dieser Code könnte den.bss
Bereich auf Null setzen, anstatt ihn vom Kernel ausführen zu lassen, und das wäre immer noch konform.malloc verwendet den Systemaufruf brk, um Speicher zuzuweisen.
einschließen
Führen Sie dieses einfache Programm mit strace aus, es wird brk system aufrufen.
quelle