Damit ein Prozess in Computern ausgeführt werden kann, muss er im Speicher platziert werden. Dazu muss einem Prozess im Speicher ein Feld zugewiesen werden. Die Speicherzuweisung ist ein wichtiges Thema, das beachtet werden muss, insbesondere in Kernel- und Systemarchitekturen.

Werfen wir einen detaillierten Blick auf die Linux-Speicherzuweisung und verstehen, was hinter den Kulissen vor sich geht.

Wie erfolgt die Speicherzuweisung?

Die meisten Softwareingenieure kennen die Details dieses Prozesses nicht. Aber wenn Sie ein Kandidat für Systemprogrammierer sind, sollten Sie mehr darüber wissen. Wenn man sich den Zuteilungsprozess ansieht, muss man ein wenig ins Detail gehen auf Linux und die glibc Bücherei.

Wenn Anwendungen Speicher benötigen, müssen sie ihn vom Betriebssystem anfordern. Diese Anfrage vom Kernel erfordert natürlich einen Systemaufruf. Im Benutzermodus können Sie Speicher nicht selbst zuweisen.

Das malloc() Funktionsfamilie ist für die Speicherzuweisung in der Sprache C verantwortlich. Die hier zu stellende Frage ist, ob malloc() als glibc-Funktion einen direkten Systemaufruf durchführt.

instagram viewer

Im Linux-Kernel gibt es keinen Systemaufruf namens malloc. Es gibt jedoch zwei Systemaufrufe für Anwendungsspeicheranforderungen, nämlich brk und mmmap.

Da Sie Speicher in Ihrer Anwendung über glibc-Funktionen anfordern, fragen Sie sich vielleicht, welchen dieser Systemaufrufe glibc an dieser Stelle verwendet. Die Antwort ist beides.

Der erste Systemaufruf: brk

Jeder Prozess hat ein zusammenhängendes Datenfeld. Mit dem Systemaufruf brk wird der Programmabbruchwert, der die Grenze des Datenfeldes bestimmt, erhöht und der Allokationsprozess durchgeführt.

Obwohl die Speicherzuordnung mit dieser Methode sehr schnell ist, ist es nicht immer möglich, ungenutzten Speicherplatz an das System zurückzugeben.

Stellen Sie sich beispielsweise vor, dass Sie mit dem Systemaufruf brk über die Funktion malloc() fünf Felder mit einer Größe von jeweils 16 KB zuweisen. Wenn Sie mit Nummer zwei dieser Felder fertig sind, ist es nicht möglich, die entsprechende Ressource zurückzugeben (Deallokation), damit das System sie verwenden kann. Denn wenn Sie den Adresswert reduzieren, um die Stelle anzuzeigen, an der Ihr Feld Nummer zwei beginnt, haben Sie mit einem Aufruf von brk die Zuordnung für die Felder Nummer drei, vier und fünf aufgehoben.

Um in diesem Szenario Speicherverluste zu vermeiden, überwacht die malloc-Implementierung in der glibc die zugewiesenen Stellen im Prozessdatenfeld und gibt dann an, es mit der Funktion free() an das System zurückzugeben, damit das System den freien Speicherplatz für weiteren Speicher verwenden kann Zuweisungen.

Mit anderen Worten, nachdem fünf 16-KB-Bereiche zugewiesen wurden, wenn der zweite Bereich mit der Funktion free() und einem weiteren 16-KB-Bereich zurückgegeben wird nach einiger Zeit erneut angefordert wird, anstatt den Datenbereich durch den Systemaufruf brk zu vergrößern, wird die vorherige Adresse zurückgegeben.

Wenn der neu angeforderte Bereich jedoch größer als 16 KB ist, dann wird der Datenbereich vergrößert, indem ein neuer Bereich mit dem Systemaufruf brk zugewiesen wird, da Bereich zwei nicht verwendet werden kann. Obwohl Bereich Nummer zwei nicht verwendet wird, kann die Anwendung ihn aufgrund des Größenunterschieds nicht verwenden. Aufgrund solcher Szenarien gibt es eine Situation, die als interne Fragmentierung bezeichnet wird, und tatsächlich können Sie selten alle Teile des Speichers vollständig nutzen.

Versuchen Sie zum besseren Verständnis, die folgende Beispielanwendung zu kompilieren und auszuführen:

#enthalten <stdio.h>
#enthalten <stdlib.h>
#enthalten <unistd.h>
inthauptsächlich(int Argc, verkohlen*argv[])
{
verkohlen *ptr[7];
int n;
printf("Pid von %s: %d", argv[0], getpid());
printf("Anfängliche Programmunterbrechung: %p", sbrk (0));
für (n=0; n<5; n++) ptr[n] = malloc (16 * 1024);
printf("Nach 5 x 16kB malloc: %p", sbrk (0));
frei(ptr[1]);
printf("Nach freien zweiten 16kB: %p", sbrk (0));
ptr[5] = malloc (16 * 1024);
printf("Nach Zuweisung des 6. von 16kB: %p", sbrk (0));
frei(ptr[5]);
printf("Nach Freigabe des letzten Blocks: %p", sbrk (0));
ptr[6] = malloc (18 * 1024);
printf("Nach Zuweisung neuer 18kB: %p", sbrk (0));
getchar();
Rückkehr0;
}

Wenn Sie die Anwendung ausführen, erhalten Sie ein Ergebnis ähnlich der folgenden Ausgabe:

Pid von ./a.out: 31990
Erstes Programm brechen: 0x55ebcadf4000
Nach 5 x 16kB malloc: 0x55ebcadf4000
Nach freien zweiten 16kB: 0x55ebcadf4000
Nach Zuweisung von 6. von 16kB: 0x55ebcadf4000
Nach Freigabe des letzten Blocks: 0x55ebcadf4000
Nach Zuweisung von a Neu18KB: 0x55ebcadf4000

Die Ausgabe für brk mit strace sieht wie folgt aus:

brk(NULL) = 0x5608595b6000
brk (0x5608595d7000) = 0x5608595d7000

Wie du sehen kannst, 0x21000 an die Endadresse des Datenfeldes angefügt wurde. Sie können dies anhand des Wertes nachvollziehen 0x5608595d7000. Also ungefähr 0x21000, oder es wurden 132 KB Arbeitsspeicher zugewiesen.

Hier sind zwei wichtige Punkte zu beachten. Die erste ist die Zuweisung von mehr als dem im Beispielcode angegebenen Betrag. Eine andere ist, welche Codezeile den brk-Aufruf verursacht hat, der die Zuordnung bereitgestellt hat.

Randomisierung des Adressraum-Layouts: ASLR

Wenn Sie die obige Beispielanwendung nacheinander ausführen, sehen Sie jedes Mal unterschiedliche Adresswerte. Das zufällige Ändern des Adressraums auf diese Weise erschwert die Arbeit erheblich Sicherheitsangriffe und erhöht die Softwaresicherheit.

In 32-Bit-Architekturen werden jedoch im Allgemeinen acht Bits verwendet, um den Adressraum zu randomisieren. Eine Erhöhung der Anzahl von Bits ist nicht angemessen, da der adressierbare Bereich über die verbleibenden Bits sehr gering sein wird. Auch die Verwendung von nur 8-Bit-Kombinationen macht es dem Angreifer nicht schwer genug.

Da andererseits in 64-Bit-Architekturen zu viele Bits vorhanden sind, die für den ASLR-Betrieb zugewiesen werden können, wird eine viel größere Zufälligkeit bereitgestellt, und der Sicherheitsgrad steigt.

Linux-Kernel macht auch Android-basierte Geräte und die ASLR-Funktion ist auf Android 4.0.3 und höher vollständig aktiviert. Schon allein aus diesem Grund wäre es nicht falsch zu sagen, dass ein 64-Bit-Smartphone einen deutlichen Sicherheitsvorteil gegenüber 32-Bit-Versionen bietet.

Durch vorübergehendes Deaktivieren der ASLR-Funktion mit dem folgenden Befehl scheint es, dass die vorherige Testanwendung bei jeder Ausführung dieselben Adresswerte zurückgibt:

Echo0 | sudo tee /proc/sys/kernel/randomize_va_space

Um den vorherigen Zustand wiederherzustellen, reicht es aus, 2 anstelle von 0 in dieselbe Datei zu schreiben.

Der zweite Systemaufruf: mmap

mmap ist der zweite Systemaufruf, der für die Speicherzuweisung unter Linux verwendet wird. Beim mmap-Aufruf wird der freie Speicherplatz in einem beliebigen Bereich des Speichers auf den Adressraum des aufrufenden Prozesses abgebildet.

Wenn Sie bei einer Speicherzuweisung auf diese Weise die zweite 16-KB-Partition mit der free()-Funktion im vorherigen brk-Beispiel zurückgeben möchten, gibt es keinen Mechanismus, um diese Operation zu verhindern. Das betreffende Speichersegment wird aus dem Adressraum des Prozesses entfernt. Es wird als nicht mehr verwendet markiert und an das System zurückgegeben.

Da Speicherzuweisungen mit mmap im Vergleich zu denen mit brk sehr langsam sind, ist eine brk-Zuweisung erforderlich.

Mit mmap wird jeder freie Speicherbereich dem Adressraum des Prozesses zugeordnet, sodass der Inhalt des zugewiesenen Bereichs zurückgesetzt wird, bevor dieser Prozess abgeschlossen ist. Wenn das Zurücksetzen nicht auf diese Weise durchgeführt wurde, könnte auch der nächste unabhängige Prozess auf die Daten zugreifen, die zu dem Prozess gehören, der zuvor den betreffenden Speicherbereich verwendet hat. Damit wäre es unmöglich, von Sicherheit in Systemen zu sprechen.

Bedeutung der Speicherzuweisung in Linux

Die Speicherzuweisung ist sehr wichtig, insbesondere in Optimierungs- und Sicherheitsfragen. Wie in den obigen Beispielen zu sehen ist, kann ein nicht vollständiges Verständnis dieses Problems bedeuten, dass die Sicherheit Ihres Systems zerstört wird.

Sogar Push- und Pop-ähnliche Konzepte, die es in vielen Programmiersprachen gibt, basieren auf Speicherzuweisungsoperationen. Die Fähigkeit, den Systemspeicher gut zu nutzen und zu beherrschen, ist sowohl bei der Programmierung eingebetteter Systeme als auch bei der Entwicklung einer sicheren und optimierten Systemarchitektur von entscheidender Bedeutung.

Wenn Sie auch in die Linux-Kernel-Entwicklung eintauchen möchten, sollten Sie zuerst die Programmiersprache C beherrschen.

Eine kurze Einführung in die Programmiersprache C

Lesen Sie weiter

TeilenTwitternTeilenEmail

Verwandte Themen

  • Linux
  • Computerspeicher
  • Linux Kernel

Über den Autor

Fatih Küçükkarakurt (7 veröffentlichte Artikel)

Ein Ingenieur und Softwareentwickler, der ein Fan von Mathematik und Technik ist. Schon immer mochte er Computer, Mathematik und Physik. Er hat Spiele-Engine-Projekte sowie maschinelles Lernen, künstliche neuronale Netze und lineare Algebra-Bibliotheken entwickelt. Darüber hinaus arbeitet er weiter an maschinellem Lernen und linearen Matrizen.

Mehr von Fatih Küçükkarakurt

Abonnieren Sie unseren Newsletter

Abonnieren Sie unseren Newsletter für technische Tipps, Rezensionen, kostenlose E-Books und exklusive Angebote!

Klicken Sie hier, um sich anzumelden