GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Was macht der Systemaufruf brk()?

Es gibt eine speziell ausgewiesene Zuordnung des anonymen privaten Speichers (normalerweise direkt hinter data/bss, aber modernes Linux passt den Speicherort tatsächlich mit ASLR an). Im Prinzip ist es nicht besser als jedes andere Mapping, das Sie mit mmap erstellen könnten , aber Linux hat einige Optimierungen, die es ermöglichen, das Ende dieser Zuordnung zu erweitern (unter Verwendung der brk syscall) aufwärts mit reduzierten Locking-Kosten relativ zu mmap oder mremap anfallen würden. Das macht es attraktiv für malloc Implementierungen, die beim Implementieren des Hauptheaps verwendet werden sollen.


Sie können brk verwenden und sbrk um den "Malloc-Overhead" zu vermeiden, über den sich alle immer beschweren. Aber Sie können diese Methode nicht einfach in Verbindung mit malloc verwenden daher ist es nur angemessen, wenn Sie nicht free müssen irgendetwas. Weil du es nicht kannst. Außerdem sollten Sie alle Bibliotheksaufrufe vermeiden, die malloc verwenden könnten im Inneren. Dh. strlen ist wahrscheinlich sicher, aber fopen wahrscheinlich nicht.

Rufen Sie sbrk an genauso wie Sie malloc anrufen würden . Es gibt einen Zeiger auf den aktuellen Break zurück und inkrementiert den Break um diesen Betrag.

void *myallocate(int n){
    return sbrk(n);
}

Sie können zwar keine einzelnen Zuweisungen freigeben (weil es keinen malloc-Overhead gibt , denken Sie daran), Sie können befreien Sie den gesamten Speicherplatz durch Aufruf von brk mit dem Wert, der beim ersten Aufruf von sbrk zurückgegeben wird , wodurch das brk zurückgespult wird .

void *memorypool;
void initmemorypool(void){
    memorypool = sbrk(0);
}
void resetmemorypool(void){
    brk(memorypool);
}

Sie könnten diese Regionen sogar stapeln und die letzte Region verwerfen, indem Sie die Unterbrechung zum Anfang der Region zurückspulen.

Eine Sache noch ...

sbrk ist auch beim Code-Golf nützlich, da es 2 Zeichen kürzer als malloc ist .


Minimales lauffähiges Beispiel

Was macht der Systemaufruf brk( )?

Fordert den Kernel auf, Ihnen Lese- und Schreibzugriff auf einen zusammenhängenden Teil des Speichers zu geben, der Heap genannt wird.

Wenn du nicht fragst, könnte es dir einen Segfault geben.

Ohne brk :

#define _GNU_SOURCE
#include <unistd.h>

int main(void) {
    /* Get the first address beyond the end of the heap. */
    void *b = sbrk(0);
    int *p = (int *)b;
    /* May segfault because it is outside of the heap. */
    *p = 1;
    return 0;
}

Mit brk :

#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>

int main(void) {
    void *b = sbrk(0);
    int *p = (int *)b;

    /* Move it 2 ints forward */
    brk(p + 2);

    /* Use the ints. */
    *p = 1;
    *(p + 1) = 2;
    assert(*p == 1);
    assert(*(p + 1) == 2);

    /* Deallocate back. */
    brk(b);

    return 0;
}

GitHub-Upstream.

Das Obige trifft möglicherweise keine neue Seite und segfault auch ohne brk nicht , hier ist also eine aggressivere Version, die 16 MiB zuweist und sehr wahrscheinlich ohne den brk segfault :

#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>

int main(void) {
    void *b;
    char *p, *end;

    b = sbrk(0);
    p = (char *)b;
    end = p + 0x1000000;
    brk(end);
    while (p < end) {
        *(p++) = 1;
    }
    brk(b);
    return 0;
}

Getestet auf Ubuntu 18.04.

Virtuelle Adressraumvisualisierung

Vor brk :

+------+ <-- Heap Start == Heap End

Nach brk(p + 2) :

+------+ <-- Heap Start + 2 * sizof(int) == Heap End 
|      |
| You can now write your ints
| in this memory area.
|      |
+------+ <-- Heap Start

Nach brk(b) :

+------+ <-- Heap Start == Heap End

Um Adressräume besser zu verstehen, sollten Sie sich mit Paging vertraut machen:Wie funktioniert x86-Paging?.

Warum brauchen wir beide brk und sbrk ?

brk könnte natürlich mit sbrk implementiert werden + Offset-Berechnungen, beide existieren nur der Einfachheit halber.

Im Backend hat der Linux-Kernel v5.0 einen einzigen Systemaufruf brk das verwendet wird, um beide zu implementieren:https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64.tbl#L23

12  common  brk         __x64_sys_brk

Ist brk POSIX?

brk war früher POSIX, wurde aber in POSIX 2001 entfernt, daher die Notwendigkeit für _GNU_SOURCE um auf den Glibc-Wrapper zuzugreifen.

Die Entfernung ist wahrscheinlich auf die Einführung mmap zurückzuführen , die eine Obermenge ist, die die Zuweisung mehrerer Bereiche und mehr Zuweisungsoptionen ermöglicht.

Ich denke, es gibt keinen gültigen Fall, in dem Sie brk verwenden sollten statt malloc oder mmap heutzutage.

brk vs. malloc

brk ist eine alte Möglichkeit, malloc zu implementieren .

mmap ist der neuere, wesentlich leistungsfähigere Mechanismus, den derzeit wahrscheinlich alle POSIX-Systeme verwenden, um malloc zu implementieren . Hier ist ein minimal lauffähiger mmap Beispiel für die Speicherzuweisung.

Kann ich brk mischen und malloc?

Wenn Ihr malloc wird mit brk implementiert , ich habe keine Ahnung, wie das die Dinge möglicherweise nicht in die Luft jagen kann, da brk verwaltet nur einen einzigen Speicherbereich.

Ich konnte jedoch nichts darüber in den Glibc-Dokumenten finden, z. B.:

  • https://www.gnu.org/software/libc/manual/html_mono/libc.html#Resizing-the-Data-Segment

Die Dinge werden dort wahrscheinlich seit mmap funktionieren, nehme ich an wird wahrscheinlich für malloc verwendet .

Siehe auch:

  • Was ist unsicher/legacy an brk/sbrk?
  • Warum ergibt der zweimalige Aufruf von sbrk(0) einen anderen Wert?

Weitere Informationen

Intern entscheidet der Kernel, ob der Prozess so viel Speicher haben kann, und reserviert Speicherseiten für diese Nutzung.

Dies erklärt, wie sich der Stack mit dem Heap vergleicht:Was ist die Funktion der Push/Pop-Anweisungen, die für Register in der x86-Assembly verwendet werden?


In dem von Ihnen geposteten Diagramm ist die "Unterbrechung" die Adresse, die von brk manipuliert wurde und sbrk – ist die gepunktete Linie oben auf dem Haufen.

Die Dokumentation, die Sie gelesen haben, beschreibt dies als das Ende des "Datensegments", da in traditionellen (vorinstallierten Bibliotheken, vor mmap ) Unix war das Datensegment kontinuierlich mit dem Heap; Vor dem Programmstart würde der Kernel die "Text"- und "Daten"-Blöcke beginnend bei Adresse Null (eigentlich etwas über Adresse Null, so dass der NULL-Zeiger wirklich auf nichts zeigte) in den RAM laden und die Break-Adresse auf setzen das Ende des Datensegments. Der erste Aufruf von malloc würde dann sbrk verwenden um die Trennung nach oben zu verschieben und den Haufen dazwischen zu erstellen Anfang des Datensegments und die neue, höhere Break-Adresse, wie im Diagramm gezeigt, und anschließende Verwendung von malloc würde es verwenden, um den Heap nach Bedarf zu vergrößern.

In der Zwischenzeit beginnt der Stapel oben im Speicher und wächst nach unten. Der Stapel benötigt keine expliziten Systemaufrufe, um ihn zu vergrößern; entweder beginnt es mit so viel RAM wie möglich (das war der traditionelle Ansatz) oder es gibt einen Bereich reservierter Adressen unterhalb des Stacks, dem der Kernel automatisch RAM zuweist, wenn er einen Schreibversuch bemerkt (Dies ist der moderne Ansatz). In jedem Fall kann es am unteren Ende des Adressraums einen "Schutz"-Bereich geben, der für den Stapel verwendet werden kann, oder auch nicht. Wenn diese Region existiert (alle modernen Systeme tun dies), ist sie dauerhaft nicht kartiert; wenn entweder der Stack oder Heap versucht, hineinzuwachsen, erhalten Sie einen Segmentierungsfehler. Traditionell unternahm der Kernel jedoch keinen Versuch, eine Grenze zu erzwingen; der Stack könnte in den Haufen wachsen, oder der Haufen könnte in den Stack wachsen, und so oder so würden sie die Daten des anderen überschreiben und das Programm würde abstürzen. Wenn Sie sehr viel Glück hatten, würde es sofort abstürzen.

Ich bin mir nicht sicher, woher die Zahl 512 GB in diesem Diagramm kommt. Es 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:

              Legend:  t: text, d: data, b: BSS

Dies ist nicht annähernd maßstabsgetreu und sollte nicht so interpretiert werden, wie genau ein bestimmtes Betriebssystem Dinge tut (nachdem ich es gezeichnet hatte, entdeckte ich, 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 abgebildet – jeder Zugriff verursacht einen sofortigen Segfault – und sie sind gigantisch relativ zu den Grauzonen. Die hellgrauen Bereiche sind das Programm und seine gemeinsam genutzten Bibliotheken (es kann Dutzende von gemeinsam genutzten Bibliotheken geben); jeder hat einen unabhängigen Text- und Datensegment (und "bss"-Segment, das auch globale Daten enthält, aber auf alle Bits Null initialisiert wird, anstatt Platz 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 zumindest so aus, als würde Linux das nicht tun. Der Stack ist nicht mehr an die Spitze des virtuellen Adressraums gebunden, und der Abstand zwischen dem Heap und dem Stack ist so enorm, dass Sie sich keine Sorgen machen müssen, ihn zu überschreiten.

Die Pause ist immer noch die obere Grenze des Haufens. Was ich jedoch nicht gezeigt habe, ist, dass es irgendwo in den schwarzen Zahlen Dutzende unabhängiger Speicherzuweisungen geben könnte, die mit mmap erstellt wurden statt brk . (Das Betriebssystem wird versuchen, diese weit vom brk fernzuhalten Bereich, damit sie nicht kollidieren.)


Linux
  1. Was ist der aktuelle Runlevel des Linux-Systems?

  2. Was macht ?

  3. Was macht Rackspace in der Cloud?

  4. Was bedeutet das 'rc' in `.bashrc` usw.?

  5. Was genau macht init?

Was ist der Unterschied zwischen Systemaufruf und Bibliotheksaufruf?

Was ist der Unterschied zwischen einem Bibliotheksaufruf und einem Systemaufruf in Linux?

Was macht das -e in einem Bash-Shebang?

Was hat der Systemaufruf tuxcall gemacht?

Was bedeutet die ep-Fähigkeit?

Was bedeutet das Suffix .d unter Linux?