blog · git · desktop · images · contact


Linux: Slab-Merging

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.

Ein einfaches Kernel-Modul mit einem Cache

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

Zusammenlegung ähnlicher Caches

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.

Zusammenlegung für’s Debugging deaktivieren

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.

Comments?