Ich möchte ein Programm erstellen, das eine OOM-Situation (Out-of-Memory) auf einem Unix-Server simuliert. Ich habe diesen supereinfachen Speicherfresser erstellt:
#include <stdio.h>
#include <stdlib.h>
unsigned long long memory_to_eat = 1024 * 50000;
size_t eaten_memory = 0;
void *memory = NULL;
int eat_kilobyte()
{
memory = realloc(memory, (eaten_memory * 1024) + 1024);
if (memory == NULL)
{
// realloc failed here - we probably can't allocate more memory for whatever reason
return 1;
}
else
{
eaten_memory++;
return 0;
}
}
int main(int argc, char **argv)
{
printf("I will try to eat %i kb of ram\n", memory_to_eat);
int megabyte = 0;
while (memory_to_eat > 0)
{
memory_to_eat--;
if (eat_kilobyte())
{
printf("Failed to allocate more memory! Stucked at %i kb :(\n", eaten_memory);
return 200;
}
if (megabyte++ >= 1024)
{
printf("Eaten 1 MB of ram\n");
megabyte = 0;
}
}
printf("Successfully eaten requested memory!\n");
free(memory);
return 0;
}
Es verbraucht so viel Speicher wie definiert, in memory_to_eat
dem jetzt genau 50 GB RAM vorhanden sind. Es ordnet Speicher um 1 MB zu und druckt genau den Punkt, an dem es nicht mehr zuordnen kann, so dass ich weiß, welchen Maximalwert es geschafft hat, zu essen.
Das Problem ist, dass es funktioniert. Auch auf einem System mit 1 GB physischem Speicher.
Wenn ich oben nachschaue, sehe ich, dass der Prozess 50 GB virtuellen Speicher und nur weniger als 1 MB residenten Speicher verbraucht. Gibt es eine Möglichkeit, einen Speicherfresser zu erstellen, der ihn wirklich verbraucht?
Systemspezifikationen: Linux-Kernel 3.16 ( Debian ) höchstwahrscheinlich mit aktiviertem Overcommit (nicht sicher, wie man es auscheckt) ohne Swap und virtualisiert.
quelle
sysctl -w vm.overcommit_memory=2
als Root arbeiten. Siehe mjmwired.net/kernel/Documentation/vm/overcommit-accounting . Beachten Sie, dass dies andere Konsequenzen haben kann. Insbesondere sehr große Programme (z. B. Ihr Webbrowser) können möglicherweise keine Hilfsprogramme (z. B. den PDF-Reader) erzeugen.Antworten:
Wenn Ihre
malloc()
Implementierung Speicher vom Systemkern anfordert (über einensbrk()
oder einenmmap()
Systemaufruf), notiert der Kernel nur, dass Sie den Speicher angefordert haben und wo er in Ihrem Adressraum abgelegt werden soll. Diese Seiten werden noch nicht zugeordnet .Wenn der Prozess anschließend auf den Speicher innerhalb der neuen Region zugreift, erkennt die Hardware einen Segmentierungsfehler und warnt den Kernel vor dem Zustand. Der Kernel sucht dann die Seite in seinen eigenen Datenstrukturen und stellt fest, dass Sie dort eine Nullseite haben sollten, sodass er einer Nullseite zugeordnet wird (möglicherweise wird zuerst eine Seite aus dem Seiten-Cache entfernt) und vom Interrupt zurückkehrt. Ihr Prozess erkennt nicht, dass dies geschehen ist. Die Kerneloperation ist vollkommen transparent (mit Ausnahme der kurzen Verzögerung, während der Kernel seine Arbeit erledigt).
Durch diese Optimierung kann der Systemaufruf sehr schnell zurückgegeben werden, und vor allem werden Ressourcen vermieden, die bei der Zuordnung für Ihren Prozess festgeschrieben werden. Dies ermöglicht es Prozessen, ziemlich große Puffer zu reservieren, die sie unter normalen Umständen niemals benötigen, ohne befürchten zu müssen, zu viel Speicher zu verschlingen.
Wenn Sie also einen Speicherfresser programmieren möchten, müssen Sie unbedingt etwas mit dem von Ihnen zugewiesenen Speicher tun. Dazu müssen Sie Ihrem Code nur eine einzige Zeile hinzufügen:
Beachten Sie, dass es vollkommen ausreichend ist, auf jeder Seite in ein einzelnes Byte zu schreiben (das auf X86 4096 Bytes enthält). Dies liegt daran, dass die gesamte Speicherzuweisung vom Kernel zu einem Prozess bei der Speicherseitengranularität erfolgt, was wiederum auf die Hardware zurückzuführen ist, die kein Paging bei kleineren Granularitäten zulässt.
quelle
mmap
und festzuschreibenMAP_POPULATE
(beachten Sie jedoch, dass auf der Manpage steht, dass " MAP_POPULATE nur für private Zuordnungen seit Linux 2.6.23 unterstützt wird ").mlockall(MCL_FUTURE)
. (Dies erfordert root, daulimit -l
bei einer Standardinstallation von Debian / Ubuntu nur 64 KB für Benutzerkonten vorhanden sind.) Ich habe es gerade unter Linux 3.19 mit dem Standard-sysctlvm/overcommit_memory = 0
versucht, und gesperrte Seiten verbrauchen Swap / physischen RAM.malloc()
Aufrufe zurückkehrtnull
. Das ist eindeutig der Nachteil dieses Ansatzes zur Speicherverwaltung. Es ist jedoch bereits das Vorhandensein von Copy-on-Write-Mappings (denken Sie an dynamische Bibliotheken undfork()
), die es dem Kernel unmöglich machen, zu wissen, wie viel Speicher tatsächlich benötigt wird. Wenn der Speicher nicht überlastet würde, würde Ihnen der abbildbare Speicher ausgehen, lange bevor Sie tatsächlich den gesamten physischen Speicher verwenden.SIGSEGV
behoben werden soll oder ob ein Signal an den Prozess übermittelt werden soll.Alle virtuellen Seiten beginnen mit dem Kopieren beim Schreiben, das derselben physischen Seite mit Null zugeordnet ist. Um physische Seiten zu verbrauchen, können Sie sie verschmutzen, indem Sie auf jede virtuelle Seite etwas schreiben.
Wenn Sie als Root ausgeführt werden, können Sie die Seiten verwenden
mlock(2)
odermlockall(2)
den Kernel verkabeln lassen, wenn sie zugewiesen sind, ohne sie verschmutzen zu müssen. (Normale Nicht-Root-Benutzer habenulimit -l
nur 64 KB.)Eine verbesserte Version des Codes, die genau das tut, was das OP wollte:
Dadurch werden auch die Nichtübereinstimmungen der Zeichenfolgen im printf-Format mit den Typen memory_to_eat und ate_memory behoben, mit denen Ganzzahlen
%zi
gedruckt werdensize_t
. Die zu essende Speichergröße in kiB kann optional als Befehlszeilenargument angegeben werden.Das chaotische Design, das globale Variablen verwendet und um 1 KB anstelle von 4 KB Seiten wächst, bleibt unverändert.
quelle
Hier wird eine sinnvolle Optimierung vorgenommen. Die Laufzeit erfasst den Speicher erst, wenn Sie ihn verwenden.
Eine einfache
memcpy
wird ausreichen, um diese Optimierung zu umgehen. (Möglicherweisecalloc
wird dadurch die Speicherzuordnung bis zum Verwendungsort noch optimiert.)quelle
malloc
, es werden Seitengrenzen zugewiesen, was mir ziemlich wahrscheinlich erscheint.calloc
nutzt mmap (MAP_ANONYMOUS), um Seiten mit Nullen zu erstellen, sodass die Arbeit des Kernels zum Nullstellen von Seiten nicht dupliziert wird.Ich bin mir nicht sicher, aber die einzige Erklärung, die ich finden kann, ist, dass Linux ein Copy-on-Write-Betriebssystem ist. Wenn man aufruft, zeigenfork
beide Prozesse auf denselben physischen Speicher. Der Speicher wird nur kopiert, wenn ein Prozess tatsächlich in den Speicher schreibt.Ich denke hier wird der tatsächliche physische Speicher nur zugewiesen, wenn man versucht, etwas darauf zu schreiben. Aufruf
sbrk
odermmap
möglicherweise nur Aktualisierung der Speicherbuchhaltung des Kernels. Der tatsächliche RAM kann nur zugewiesen werden, wenn wir tatsächlich versuchen, auf den Speicher zuzugreifen.quelle
fork
hat damit nichts zu tun. Wenn Sie Linux mit diesem Programm booten, sehen Sie dasselbe Verhalten wie/sbin/init
. (dh PID 1, der erste Prozess im Benutzermodus). Sie hatten jedoch die richtige allgemeine Idee mit Copy-on-Write: Bis Sie sie verschmutzen, werden alle neu zugewiesenen Seiten alle Copy-on-Write-Seiten derselben Seite mit Null zugeordnet.