blog · git · desktop · images · contact
2017-12-21
Wir sind in ein paar Out-Of-Memory-Situationen gelaufen. Das war ein bisschen überraschend, weil die Maschine eigentlich 16 GB RAM hat und quasi keine Daemons laufen. Wir wussten nur, dass so ziemlich aller Speicher für irgendeine Form von I/O-Cache benutzt wurde.
Sprich, es dreht sich um Speicherbelegung innerhalb des Kernels. Ganz
naiv htop
zu starten, bringt einen da nicht weiter. Und jetzt? Wie
findet man heraus, was genau da Speicher belegt?
/proc/meminfo
hat uns schnell auf „Slab“ aufmerksam gemacht.
Was ist „Slab“? Ein Mechanismus zur Speicherverwaltung. Besonders
nützlich, wenn man es mit sehr vielen gleichartigen Objekten zu tun hat,
also beispielsweise viele inode
-Structs or viele Prozessdeskriptoren.
Slab-Caches können prinzipiell beliebige Daten vorhalten, sie sind also
nicht für I/O-Caches vorbestimmt.
Man werfe einen Blick auf /proc/slabinfo
, um eine Idee dafür zu
bekommen, was mit diesem Mechanismus alles verwaltet wird.
Die Sache ist jetzt die, dass jeder dieser Caches einen gewissen
Verwaltungsoverhead hat. Sowas will man natürlich in der Regel
vermeiden, also können als Optimierung Caches für ähnliche Objekttypen
zusammengelegt werden. In der Konsequenz bedeutet das, dass
/proc/slabinfo
leider keine zuverlässige Informationsquelle ist. Das
kann verwirrend und irreführend sein, denn irgendein Cache könnte
potentiell Daten enthalten, die überhaupt nichts mit dem Namen dieses
Caches zu tun haben.
Das hier wäre slab_user.c
:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
static struct kmem_cache *my_cache_a;
struct my_data_structure {
uint64_t some_int;
char name[256];
};
static int
slab_user_init(void)
{
my_cache_a = kmem_cache_create("MY_FOO",
sizeof (struct my_data_structure),
0,
0,
NULL);
return 0;
}
static void
slab_user_exit(void)
{
kmem_cache_destroy(my_cache_a);
}
module_init(slab_user_init);
module_exit(slab_user_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("nobody-cares");
In Arch Linux kann man die Pakete base-devel
und linux-headers
installieren, um das Modul dann mit folgender Makefile zu bauen (hier
auf Einrückung mit Tabs achten):
obj-m := slab_user.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Es ist sehr zu empfehlen, das in einer virtuellen Maschine durchzuführen.
# make
# insmod slab_user.ko
Wenn das erledigt ist, sieht man den neuen Cache:
# grep MY /proc/slabinfo
MY_FOO 0 0 264 15 1 : tunables 0 0 0 : slabdata 0 0 0
Die konkreten Zahlen spielen keine Rolle. Wichtig ist, dass der Cache da ist.
Man wird ihn wieder los, indem man das Modul entlädt:
# rmmod slab_user
Man modifiziere nun den Quellcode, sodass zwei Caches angelegt werden:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
static struct kmem_cache *my_cache_a, *my_cache_b;
struct my_data_structure {
uint64_t some_int;
char name[256];
};
static int
slab_user_init(void)
{
my_cache_a = kmem_cache_create("MY_FOO",
sizeof (struct my_data_structure),
0,
0,
NULL);
my_cache_b = kmem_cache_create("MY_BAR",
sizeof (struct my_data_structure),
0,
0,
NULL);
return 0;
}
static void
slab_user_exit(void)
{
kmem_cache_destroy(my_cache_a);
kmem_cache_destroy(my_cache_b);
}
module_init(slab_user_init);
module_exit(slab_user_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("nobody-cares");
Was erwartet man jetzt im Ergebnis? Ohne etwas von Slab-Merging zu
wissen, rechnet man mit MY_FOO
und MY_BAR
in der Ausgabe von
/proc/slabinfo
. Natürlich ist das nicht der Fall:
# grep MY /proc/slabinfo
MY_FOO 0 0 264 15 1 : tunables 0 0 0 : slabdata 0 0 0
Nach wie vor nur ein Cache.
Was passiert nun, wenn man ein Objekt aus dem zweiten Cache anfordert – also aus dem, den man eigentlich gar nicht sieht?
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
static struct kmem_cache *my_cache_a, *my_cache_b;
struct my_data_structure {
uint64_t some_int;
char name[256];
};
struct my_data_structure *allocated;
static int
slab_user_init(void)
{
my_cache_a = kmem_cache_create("MY_FOO",
sizeof (struct my_data_structure),
0,
0,
NULL);
my_cache_b = kmem_cache_create("MY_BAR",
sizeof (struct my_data_structure),
0,
0,
NULL);
allocated = kmem_cache_alloc(my_cache_b, GFP_KERNEL);
return 0;
}
static void
slab_user_exit(void)
{
kmem_cache_free(my_cache_b, allocated);
kmem_cache_destroy(my_cache_a);
kmem_cache_destroy(my_cache_b);
}
module_init(slab_user_init);
module_exit(slab_user_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("nobody-cares");
Ergebnis:
# grep MY /proc/slabinfo
MY_FOO 15 15 264 15 1 : tunables 0 0 0 : slabdata 1 1 0
Der erste Cache zeigt dank Slab-Merging aktive Objekte.
Man hänge slub_nomerge
an die Kernelparameter an und reboote. Nein,
das ist kein Tippfehler:
Slub is the next-generation replacement memory allocator, which has been the default in the Linux kernel since 2.6.23. It continues to employ the basic "slab" model, but fixes several deficiencies in Slab's design, particularly around systems with large numbers of processors. Slub is simpler than Slab.
Lädt man nun dasselbe Modul von oben, sieht man endlich:
# grep MY /proc/slabinfo
MY_BAR 15 15 264 15 1 : tunables 0 0 0 : slabdata 1 1 0
MY_FOO 0 0 264 15 1 : tunables 0 0 0 : slabdata 0 0 0
Vergleicht man /proc/slabinfo
von aktivem mit inaktivem Merging, so
sieht man etwa die doppelte Anzahl an Zeilen, wenn es aus ist.