Memfd is a simple memory sharing mechanism, added by the systemd/kdbus developers, to share pages between processes in an anonymous, no global registry needed, no mount-point required, relatively secure, manner. We plan to use POSIX SHM only in legacy mode and exclusively use memfds on the way forward. Signed-off-by: Ahmed S. Darwish <darwish.07 at gmail.com> --- configure.ac | 19 ++++++++ src/Makefile.am | 6 +++ src/pulsecore/memblock.c | 8 ++- src/pulsecore/memfd-wrappers.h | 72 +++++++++++++++++++++++++++ src/pulsecore/memfd.c | 108 +++++++++++++++++++++++++++++++++++++++++ src/pulsecore/memfd.h | 68 ++++++++++++++++++++++++++ 6 files changed, 279 insertions(+), 2 deletions(-) create mode 100644 src/pulsecore/memfd-wrappers.h create mode 100644 src/pulsecore/memfd.c create mode 100644 src/pulsecore/memfd.h diff --git a/configure.ac b/configure.ac index 151e2b1..cee1bb4 100644 --- a/configure.ac +++ b/configure.ac @@ -595,6 +595,23 @@ AC_DEFINE(HAVE_DLADDR, [1], [Have dladdr?]) AM_ICONV +#### Linux memfd_create(2) SHM support #### + +AC_ARG_ENABLE([memfd], + AS_HELP_STRING([--disable-memfd], [Disable Linux memfd shared memory])) + +AS_IF([test "x$enable_memfd" != "xno"], + AC_CHECK_DECL(__NR_memfd_create, [HAVE_MEMFD=1], [HAVE_MEMFD=0], [#include <sys/syscall.h>]), + [HAVE_MEMFD=0]) + +AS_IF([test "x$enable_memfd" = "xyes" && test "x$HAVE_MEMFD" = "x0"], + [AC_MSG_ERROR([*** Your Linux kernel does not support memfd shared memory. + *** Use linux v3.17 or higher for such a feature.])]) + +AC_SUBST(HAVE_MEMFD) +AM_CONDITIONAL([HAVE_MEMFD], [test "x$HAVE_MEMFD" = x1]) +AS_IF([test "x$HAVE_MEMFD" = "x1"], AC_DEFINE([HAVE_MEMFD], 1, [Have memfd shared memory.])) + #### X11 (optional) #### AC_ARG_ENABLE([x11], @@ -1517,6 +1534,7 @@ AC_OUTPUT # ========================================================================== +AS_IF([test "x$HAVE_MEMFD" = "x1"], ENABLE_MEMFD=yes, ENABLE_MEMFD=no) AS_IF([test "x$HAVE_X11" = "x1"], ENABLE_X11=yes, ENABLE_X11=no) AS_IF([test "x$HAVE_OSS_OUTPUT" = "x1"], ENABLE_OSS_OUTPUT=yes, ENABLE_OSS_OUTPUT=no) AS_IF([test "x$HAVE_OSS_WRAPPER" = "x1"], ENABLE_OSS_WRAPPER=yes, ENABLE_OSS_WRAPPER=no) @@ -1578,6 +1596,7 @@ echo " CPPFLAGS: ${CPPFLAGS} LIBS: ${LIBS} + Enable memfd shared memory: ${ENABLE_MEMFD} Enable X11: ${ENABLE_X11} Enable OSS Output: ${ENABLE_OSS_OUTPUT} Enable OSS Wrapper: ${ENABLE_OSS_WRAPPER} diff --git a/src/Makefile.am b/src/Makefile.am index 0686c8a..423cd90 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -699,6 +699,7 @@ libpulsecommon_ at PA_MAJORMINOR@_la_SOURCES = \ pulsecore/sample-util.c pulsecore/sample-util.h \ pulsecore/mem.h pulsecore/mem.c \ pulsecore/shm.c pulsecore/shm.h \ + pulsecore/memfd.h \ pulsecore/privatemem.c pulsecore/privatemem.h \ pulsecore/bitset.c pulsecore/bitset.h \ pulsecore/socket-client.c pulsecore/socket-client.h \ @@ -727,6 +728,11 @@ libpulsecommon_ at PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS) $(LIBS libpulsecommon_ at PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version libpulsecommon_ at PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBJSON_LIBS) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS) +if HAVE_MEMFD +libpulsecommon_ at PA_MAJORMINOR@_la_SOURCES += \ + pulsecore/memfd.c pulsecore/memfd-wrappers.h +endif + if HAVE_X11 libpulsecommon_ at PA_MAJORMINOR@_la_SOURCES += \ pulse/client-conf-x11.c pulse/client-conf-x11.h \ diff --git a/src/pulsecore/memblock.c b/src/pulsecore/memblock.c index ab117d8..e0e4dfb 100644 --- a/src/pulsecore/memblock.c +++ b/src/pulsecore/memblock.c @@ -38,6 +38,7 @@ #include <pulsecore/mem.h> #include <pulsecore/shm.h> +#include <pulsecore/memfd.h> #include <pulsecore/privatemem.h> #include <pulsecore/log.h> #include <pulsecore/hashmap.h> @@ -152,6 +153,7 @@ struct pa_mempool { pa_mem mem; union { pa_shm shm; + pa_memfd memfd; pa_privatemem privatemem; } per_type; }; @@ -791,12 +793,14 @@ pa_mempool *pa_mempool_new(pa_mempool_type_t type, size_t size) { pa_mem_size = p->n_blocks * p->block_size; switch (type) { + case PA_MEMPOOL_SHARED_POSIX: if (pa_shm_create(&p->per_type.shm, pa_mem_size, 0700) < 0) goto fail; break; case PA_MEMPOOL_SHARED_MEMFD: - pa_assert_not_reached(); + if (pa_memfd_create(&p->per_type.memfd, pa_mem_size) < 0) + goto fail; break; case PA_MEMPOOL_PRIVATE: if (pa_privatemem_create(&p->per_type.privatemem, pa_mem_size) < 0) @@ -895,7 +899,7 @@ void pa_mempool_free(pa_mempool *p) { pa_shm_free(&p->per_type.shm); break; case PA_MEMPOOL_SHARED_MEMFD: - pa_assert_not_reached(); + pa_memfd_free(&p->per_type.memfd); break; case PA_MEMPOOL_PRIVATE: pa_privatemem_free(&p->per_type.privatemem); diff --git a/src/pulsecore/memfd-wrappers.h b/src/pulsecore/memfd-wrappers.h new file mode 100644 index 0000000..ef222ac --- /dev/null +++ b/src/pulsecore/memfd-wrappers.h @@ -0,0 +1,72 @@ +#ifndef SRC_PULSECORE_MEMFD_WRAPPERS_H_ +#define SRC_PULSECORE_MEMFD_WRAPPERS_H_ + +/*** + This file is part of PulseAudio. + + Copyright 2015 Ahmed S. Darwish <darwish.07 at gmail.com> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_MEMFD + +#include <sys/syscall.h> +#include <fcntl.h> + +/* + * No glibc wrappers exist for memfd_create(2), so provide our own. + * + * Also define memfd fcntl sealing macros. While they are already + * defined in the kernel header file <linux/fcntl.h>, that file as + * a whole conflicts with the original glibc header <fnctl.h>. + */ + +static inline int memfd_create(const char *name, unsigned int flags) { + return syscall(__NR_memfd_create, name, flags); +} + +/* memfd_create(2) flags */ + +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif + +#ifndef MFD_ALLOW_SEALING +#define MFD_ALLOW_SEALING 0x0002U +#endif + +/* fcntl() seals-related flags */ + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_ADD_SEALS +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) + +#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ +#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ +#define F_SEAL_GROW 0x0004 /* prevent file from growing */ +#define F_SEAL_WRITE 0x0008 /* prevent writes */ +#endif + +#else + +#error "Please include this file only when memfd is supported." + +#endif /* HAVE_MEMFD */ + +#endif /* SRC_PULSECORE_MEMFD_WRAPPERS_H_ */ diff --git a/src/pulsecore/memfd.c b/src/pulsecore/memfd.c new file mode 100644 index 0000000..429e82e --- /dev/null +++ b/src/pulsecore/memfd.c @@ -0,0 +1,108 @@ +/*** + This file is part of PulseAudio. + + Copyright 2015 Ahmed S. Darwish <darwish.07 at gmail.com> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/mman.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <errno.h> + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/macro.h> +#include <pulsecore/core-error.h> +#include <pulsecore/memfd-wrappers.h> +#include <pulsecore/memfd.h> + +static void memfd_reset(pa_memfd *memfd) { + memfd->ptr = NULL; + memfd->size = 0; + memfd->fd = -1; +} + +static int __pa_memfd_create(pa_memfd *memfd, int fd, size_t size, bool writable) { + int prot; + + memfd->fd = fd; + memfd->size = size; + prot = writable ? PROT_READ | PROT_WRITE : PROT_READ; + + memfd->ptr= mmap(NULL, size, prot, MAP_SHARED | MAP_NORESERVE, fd, 0); + if (memfd->ptr == MAP_FAILED) { + pa_log_error("Failed to mmap memfd fd %d: %s", fd, pa_cstrerror(errno)); + goto fail; + } + + return 0; + +fail: + memfd_reset(memfd); + return -1; +} + +int pa_memfd_create(pa_memfd *memfd, size_t size) { + int fd; + pa_assert(memfd); + pa_assert(size > 0); + pa_assert(size <= MAX_SHARED_MEM_SIZE); + + if ((fd = memfd_create("memfd", MFD_ALLOW_SEALING)) < 0) { + pa_log_error("Failed to create memfd file descriptor: %s", pa_cstrerror(errno)); + goto fail0; + } + + if (ftruncate(fd, size) < 0) { + pa_log_error("Failed to truncate memfd fd %d to %ld bytes: %s", fd, size, pa_cstrerror(errno)); + goto fail1; + } + + return __pa_memfd_create(memfd, fd, size, true); + +fail1: + close(fd); +fail0: + return -1; +} + +int pa_memfd_attach(pa_memfd *memfd, int fd, bool writable) { + size_t size; + pa_assert(memfd); + pa_assert(fd >= 0); + + if (shared_fd_get_file_size(fd, &size) < 0 || size > MAX_SHARED_MEM_SIZE) + return -1; + + return __pa_memfd_create(memfd, fd, size, writable); +} + +void pa_memfd_free(pa_memfd *memfd) { + pa_assert(memfd); + pa_assert(memfd->ptr); + pa_assert(memfd->fd >= 0); + + if (munmap(memfd->ptr, memfd->size) < 0) + pa_log_error("memfd munmap() failed: %s", pa_cstrerror(errno)); + + if (close(memfd->fd) < 0) + pa_log_error("memfd close() failed: %s", pa_cstrerror(errno)); + + memfd_reset(memfd); +} diff --git a/src/pulsecore/memfd.h b/src/pulsecore/memfd.h new file mode 100644 index 0000000..2c07d8e --- /dev/null +++ b/src/pulsecore/memfd.h @@ -0,0 +1,68 @@ +#ifndef SRC_PULSECORE_MEMFD_H_ +#define SRC_PULSECORE_MEMFD_H_ + +/*** + This file is part of PulseAudio. + + Copyright 2015 Ahmed S. Darwish <darwish.07 at gmail.com> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include "mem.h" + +/* A mempool memory allocation backend using Linux kernel memfds. + * + * For a rationale on why memfds were created (and why are we thus + * using them in PulseAudio), please check: + * + * - `memfd_create(2)', David Herrmann blog + * https://dvdhrm.wordpress.com/2014/06/10/memfd_create2/ + */ +typedef struct pa_memfd { + __PA_PARENT_MEM_STRUCT__; + int fd; +} pa_memfd; + +#ifdef HAVE_MEMFD + +static inline bool pa_memfd_is_supported() { + return true; +} + +int pa_memfd_create(pa_memfd *memfd, size_t size); +int pa_memfd_attach(pa_memfd *memfd, int fd, bool writable); +void pa_memfd_free(pa_memfd *memfd); + +#else + +#include <pulsecore/macro.h> + +static inline bool PA_UNUSED pa_memfd_is_supported() { + return false; +} + +static inline int pa_memfd_create(pa_memfd *memfd, size_t size) { + pa_assert_not_reached(); +} +static inline int pa_memfd_attach(pa_memfd *memfd, int fd, bool writable) { + pa_assert_not_reached(); +} +static inline void pa_memfd_free(pa_memfd *memfd) { + pa_assert_not_reached(); +} + +#endif + +#endif /* SRC_PULSECORE_MEMFD_H_ */ -- Darwish http://darwish.chasingpointers.com