blog · git · desktop · images · contact
memfd
und Syscall-Wrapper2017-02-11
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?
/usr/include/sys/syscall.h
ist Teil der glibc, aber hier passiert
nichts Spannendes. Es werden nur bereits existierende Makros
umdefiniert, zum Beispiel:
#define SYS_memfd_create __NR_memfd_create
__NR_*
kommt aus /usr/include/asm/unistd_64.h
(zumindest bei
einem amd64-Linux). Diese Datei ist bei Arch Linux Teil des Pakets
linux-api-headers
und enthält die eigentlichen Definitionen:
#define __NR_memfd_create 319
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:
cat /proc/$pid/fd/3
ausführen und den Inhalt des
memfd
auslesen.foofile
sichtbar, aber sonst quasi
nirgends. Der Name spielt auch keine große Rolle und ist
hauptsächlich zum Debugging nützlich.memfd
schreiben, als wäre es eine gewöhnliche Datei. Niemand
muss die Daten je lesen. (Eine Pipe blockiert irgendwann.)Wermutstropfen: memfd
scheint Linux-only zu sein.