blog · git · desktop · images · contact & privacy · gopher


memfd und Syscall-Wrapper

Vor einiger Zeit bin ich an memfd vorbeigekommen. Hier ist eine gute Beschreibung von einem der Autoren:

https://dvdhrm.wordpress.com/tag/memfd/

Zusammengefasst: Es gibt in Linux jetzt den Syscall memfd_create(), der einen ganz normalen File-Descriptor zurückgibt. Die Besonderheit ist, dass dahinter reiner Arbeitsspeicher steckt. Man braucht kein tmpfs und die Datei wird nie mit einer existierenden Datei kollidieren, einfach deshalb, weil sie nicht im Dateisystem sichtbar ist.

Ich wollte mit diesem Feature ein bisschen herumspielen, also habe ich damit angefangen, die Manpage zu lesen. Interessanterweise beginnt diese so:

NAME
       memfd_create - create an anonymous file

SYNOPSIS
       #include <sys/memfd.h>

       int memfd_create(const char *name, unsigned int flags);

       Note:  There  is  no  glibc  wrapper  for this system call; see
       NOTES.

Nanu? Kein Wrapper in der glibc? Das ist ungewöhnlich. memfd ist seit Version 3.17 in Linux enthalten und das wurde 2014-10-05 veröffentlicht. Heute ist der 2017-02-11. Wo hängt’s?

In 2014 wurde vom memfd-Autor ein Patch bei der glibc eingereicht, aber er scheint die Anforderungen des glibc-Projekts an Syscall-Wrapper nicht erfüllt zu haben.

– Nachtrag, 2018-06-24: Seit glibc 2.27, was etwa ein Jahr nach diesem Blogpost released wurde, gibt es dann doch einen Wrapper.

Es hat mich dann doch überrascht, dass es gar nicht so ungewöhnlich ist, wenn ein Syscall keinen Wrapper in der glibc hat. Dieses Posting sagt, dass im Mai 2015 dieser Zustand herrschte: „A de facto status of ‚syscall wrappers present for almost all syscalls added up to Linux 3.2 / glibc 2.15 but for nothing added since then‘“. Bald sind wir bei Linux 4.10, also wäre das schon eine ganz schöne Lücke, allerdings weiß ich nicht, ob es in der Zwischenzeit Updates gab.

Ich habe an der Stelle aber aufgehört, weiterzulesen. Es handelt sich hauptsächlich um Politik und Philosophie. Die interessantere technische Frage ist: Wie führt man von C aus einen Syscall aus, wenn es keinen Wrapper dafür gibt? Aus rohem Assembler habe ich das schon oft gemacht, aber bei C hatte ich da noch nie Bedarf.

Stellt sich heraus, dass es einen generischen Syscall-Wrapper gibt. Man übergibt ihm die Syscall-Nummer, die Argumente und los geht’s:

int fd = syscall(SYS_memfd_create, "foo", 0);

Diese Funktion syscall() ist offensichtlich Teil der libc, aber woher kommen die SYS_*-Makros?

Das ist ein eigenes Paket und es ist in der Tat nicht Teil der glibc, sondern wird von kernel.org heruntergeladen, wie man im PKGBUILD sehen kann. Das ist eine interessante Kombination. Das bedeutet, dass syscall() aus der glibc jeden heutigen Syscall ausführen kann, weil wir es mit aktuellen Syscall-Nummern direkt von kernel.org füttern.

Zu guter Letzt ein kleines Beispiel, das memfd benutzt:

#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int
main()
{
    int fd;
    pid_t child;
    char buf[BUFSIZ] = "";
    ssize_t br;

    fd = syscall(SYS_memfd_create, "foofile", 0);
    if (fd == -1)
    {
        perror("memfd_create");
        exit(EXIT_FAILURE);
    }

    child = fork();
    if (child == 0)
    {
        dup2(fd, 1);
        close(fd);
        execlp("/bin/date", "/bin/date", NULL);
        perror("execlp date");
        exit(EXIT_FAILURE);
    }
    else if (child == -1)
    {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    waitpid(child, NULL, 0);

    lseek(fd, 0, SEEK_SET);
    br = read(fd, buf, BUFSIZ);
    if (br == -1)
    {
        perror("read");
        exit(EXIT_FAILURE);
    }
    buf[br] = 0;

    printf("child said: '%s'\n", buf);

    exit(EXIT_SUCCESS);
}

Es wird ein memfd erstellt, ein Kind geforkt, dessen stdout umgeleitet, auf das Prozessende des Kindes gewartet und dann gelesen, was das Kind geschrieben hat. Ganz traditionell würde sowas mit einer Pipe gemacht werden.

Erwähnenswert:

Wermutstropfen: memfd scheint Linux-only zu sein.