[pulseaudio-discuss] [PATCH v4 2/2] core: Support memfd transport; bump protocol version

Ahmed S. Darwish darwish.07 at gmail.com
Fri Apr 15 21:07:36 UTC 2016


Now that all layers in the stack support memfd blocks, add memfd
support for the daemon's global core mempool. Also introduce
"enable-memfd=" daemon argument and configuration option.

For now, memfd support is an opt-in feature to be activated only
when daemon's enable-memfd= is set to yes.

Signed-off-by: Ahmed S. Darwish <darwish.07 at gmail.com>
---

Notes:
    [v4 changes]
    - better documentation in the PROTOCOL file
    - memfd disabled by default

 PROTOCOL                         | 37 ++++++++++++++++++++++++++++++
 configure.ac                     |  2 +-
 man/pulse-daemon.conf.5.xml.in   |  7 +++++-
 man/pulseaudio.1.xml.in          | 15 +++++++++---
 shell-completion/bash/pulseaudio |  4 ++--
 shell-completion/zsh/_pulseaudio |  1 +
 src/daemon/cmdline.c             | 13 ++++++++++-
 src/daemon/daemon-conf.c         |  2 ++
 src/daemon/daemon-conf.h         |  1 +
 src/daemon/main.c                |  4 +++-
 src/pulsecore/core.c             |  9 +++++---
 src/pulsecore/core.h             |  2 +-
 src/pulsecore/protocol-native.c  | 49 +++++++++++++++++++++++++++++++---------
 13 files changed, 122 insertions(+), 24 deletions(-)

diff --git a/PROTOCOL b/PROTOCOL
index 3c08fea..5191397 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -371,6 +371,43 @@ PA_COMMAND_DISABLE_SRBCHANNEL
 Tells the client to stop listening on the additional SHM ringbuffer channel.
 Acked by client by sending PA_COMMAND_DISABLE_SRBCHANNEL back.
 
+## v31, implemented by >= 9.0
+
+Memfd shared-memory support is now added to PulseAudio as an opt-in feature.
+Add 'enable-memfd=yes' to daemon's configuration to use memfds, instead of
+POSIX shm, by default.
+
+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.
+
+PulseAudio memfd support builds the necessary (but not yet sufficient)
+groundwork for a better integration with per-app containers (e.g. xdg-app)
+
+For further details on memfds in general, please check:
+
+  https://dvdhrm.wordpress.com/2014/06/10/memfd_create2/
+  Archived at: http://www.webcitation.org/6gnHTy9Kr
+
+Moreover, for both client and server, the second most-significant bit of
+the version tag is now used to flag memfd SHM support. On the way forward,
+the two most-significant _bytes_ of the version tag are now also reserved
+for flags.
+
+PA_COMMAND_REGISTER_MEMFD_SHMID
+New command that can be sent both ways, from client to server and vice versa.
+This is needed to transfer a memfd pool's blocks without passing its fd every
+time, thus minimizing overhead and avoiding fd leaks.
+
+The registration command above sends a packet with the pool's memfd fd as
+ancillary data. Such packet has an ID that uniquely identifies the pool's
+memfd memory area. Upon arrival, the other end (client or server) creates a
+permanent ID<->memfd mapping.
+
+By doing so, there's need to reference the pool's memfd file descriptor any
+further -- just its ID. Thus both endpoints can then quickly and safely
+close their memfd file descriptors.
+
 #### If you just changed the protocol, read this
 ## module-tunnel depends on the sink/source/sink-input/source-input protocol
 ## internals, so if you changed these, you might have broken module-tunnel.
diff --git a/configure.ac b/configure.ac
index ee64988..3220c84 100644
--- a/configure.ac
+++ b/configure.ac
@@ -40,7 +40,7 @@ AC_SUBST(PA_MINOR, pa_minor)
 AC_SUBST(PA_MAJORMINOR, pa_major.pa_minor)
 
 AC_SUBST(PA_API_VERSION, 12)
-AC_SUBST(PA_PROTOCOL_VERSION, 30)
+AC_SUBST(PA_PROTOCOL_VERSION, 31)
 
 # The stable ABI for client applications, for the version info x:y:z
 # always will hold y=z
diff --git a/man/pulse-daemon.conf.5.xml.in b/man/pulse-daemon.conf.5.xml.in
index 0367b1f..1abc94f 100644
--- a/man/pulse-daemon.conf.5.xml.in
+++ b/man/pulse-daemon.conf.5.xml.in
@@ -189,12 +189,17 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
 
     <option>
       <p><opt>enable-shm=</opt> Enable data transfer via POSIX
-      shared memory. Takes a boolean argument, defaults to
+      or memfd shared memory. Takes a boolean argument, defaults to
       <opt>yes</opt>. The <opt>--disable-shm</opt> command line
       argument takes precedence.</p>
     </option>
 
     <option>
+      <p><opt>enable-memfd=</opt>. Enable memfd shared memory. Takes
+      a boolean argument, defaults to <opt>no</opt>.</p>
+    </option>
+
+    <option>
       <p><opt>shm-size-bytes=</opt> Sets the shared memory segment
       size for the daemon, in bytes. If left unspecified or is set to 0
       it will default to some system-specific default, usually 64
diff --git a/man/pulseaudio.1.xml.in b/man/pulseaudio.1.xml.in
index 650b417..3187ef5 100644
--- a/man/pulseaudio.1.xml.in
+++ b/man/pulseaudio.1.xml.in
@@ -292,14 +292,23 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
       <p><opt>--disable-shm</opt><arg>[=BOOL]</arg></p>
 
       <optdesc><p>PulseAudio clients and the server can exchange audio
-      data via POSIX shared memory segments (on systems that support
-      this). If disabled PulseAudio will communicate exclusively over
-      sockets. Please note that data transfer via shared memory
+      data via POSIX or memfd shared memory segments (on systems that
+      support this). If disabled PulseAudio will communicate exclusively
+      over sockets. Please note that data transfer via shared memory
       segments is always disabled when PulseAudio is running with
       <opt>--system</opt> enabled (see above).</p></optdesc>
     </option>
 
     <option>
+      <p><opt>--enable-memfd</opt><arg>[=BOOL]</arg></p>
+
+      <optdesc><p>PulseAudio clients and the server can exchange audio
+      data via memfds - the anonymous Linux Kernel shared memory mechanism
+      (on kernels that support this). If disabled PulseAudio will
+      communicate via POSIX shared memory.</p></optdesc>
+    </option>
+
+    <option>
       <p><opt>-L | --load</opt><arg>="MODULE ARGUMENTS"</arg></p>
 
       <optdesc><p>Load the specified plugin module with the specified
diff --git a/shell-completion/bash/pulseaudio b/shell-completion/bash/pulseaudio
index cfcf7ff..e473b9c 100644
--- a/shell-completion/bash/pulseaudio
+++ b/shell-completion/bash/pulseaudio
@@ -500,13 +500,13 @@ _pulseaudio()
                 --realtime= --disallow-module-loading= --disallow-exit= --exit-idle-time=
                 --scache-idle-time= --log-level= -v --log-target= --log-meta= --log-time=
                 --log-backtrace= -p --dl-search-path= --resample-method= --use-pit-file=
-                --no-cpu-limit= --disable-shm= -L --load= -F --file= -C -n'
+                --no-cpu-limit= --disable-shm= --enable-memfd= -L --load= -F --file= -C -n'
     _init_completion -n = || return
 
     case $cur in
         --system=*|--daemonize=*|--fail=*|--high-priority=*|--realtime=*| \
             --disallow-*=*|--log-meta=*|--log-time=*|--use-pid-file=*| \
-            --no-cpu-limit=*|--disable-shm=*)
+            --no-cpu-limit=*|--disable-shm=*|--enable-memfd=*)
             cur=${cur#*=}
             COMPREPLY=($(compgen -W 'true false' -- "$cur"))
             ;;
diff --git a/shell-completion/zsh/_pulseaudio b/shell-completion/zsh/_pulseaudio
index c7d68f5..e065085 100644
--- a/shell-completion/zsh/_pulseaudio
+++ b/shell-completion/zsh/_pulseaudio
@@ -690,6 +690,7 @@ _pulseaudio_completion() {
         '--use-pid-file=[create a PID file]:bool:(true false)' \
         '--no-cpu-limit=[do not install CPU load limiter]:bool:(true false)' \
         '--disable-shm=[disable shared memory support]:bool:(true false)' \
+        '--enable-memfd=[enable memfd shared memory support]:bool:(true false)' \
         {-L,--load=}'[load the specified module]:modules:_all_modules' \
         {-F,--file=}'[run the specified script]:file:_files' \
         '-C[open a command line on the running tty]' \
diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c
index 117147d..0454b6d 100644
--- a/src/daemon/cmdline.c
+++ b/src/daemon/cmdline.c
@@ -63,6 +63,7 @@ enum {
     ARG_CHECK,
     ARG_NO_CPU_LIMIT,
     ARG_DISABLE_SHM,
+    ARG_ENABLE_MEMFD,
     ARG_DUMP_RESAMPLE_METHODS,
     ARG_SYSTEM,
     ARG_CLEANUP_SHM,
@@ -100,6 +101,7 @@ static const struct option long_options[] = {
     {"system",                      2, 0, ARG_SYSTEM},
     {"no-cpu-limit",                2, 0, ARG_NO_CPU_LIMIT},
     {"disable-shm",                 2, 0, ARG_DISABLE_SHM},
+    {"enable-memfd",                2, 0, ARG_ENABLE_MEMFD},
     {"dump-resample-methods",       2, 0, ARG_DUMP_RESAMPLE_METHODS},
     {"cleanup-shm",                 2, 0, ARG_CLEANUP_SHM},
     {NULL, 0, 0, 0}
@@ -152,7 +154,8 @@ void pa_cmdline_help(const char *argv0) {
            "      --use-pid-file[=BOOL]             Create a PID file\n"
            "      --no-cpu-limit[=BOOL]             Do not install CPU load limiter on\n"
            "                                        platforms that support it.\n"
-           "      --disable-shm[=BOOL]              Disable shared memory support.\n\n"
+           "      --disable-shm[=BOOL]              Disable shared memory support.\n"
+           "      --enable-memfd[=BOOL]             Enable memfd shared memory support.\n\n"
 
            "STARTUP SCRIPT:\n"
            "  -L, --load=\"MODULE ARGUMENTS\"         Load the specified plugin module with\n"
@@ -389,6 +392,14 @@ int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d
                 conf->disable_shm = !!b;
                 break;
 
+            case ARG_ENABLE_MEMFD:
+                if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) {
+                    pa_log(_("--enable-memfd expects boolean argument"));
+                    goto fail;
+                }
+                conf->disable_memfd = !b;
+                break;
+
             default:
                 goto fail;
         }
diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c
index 288aed2..965a5c8 100644
--- a/src/daemon/daemon-conf.c
+++ b/src/daemon/daemon-conf.c
@@ -92,6 +92,7 @@ static const pa_daemon_conf default_conf = {
 #endif
     .no_cpu_limit = true,
     .disable_shm = false,
+    .disable_memfd = true,
     .lock_memory = false,
     .deferred_volume = true,
     .default_n_fragments = 4,
@@ -526,6 +527,7 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
         { "cpu-limit",                  pa_config_parse_not_bool, &c->no_cpu_limit, NULL },
         { "disable-shm",                pa_config_parse_bool,     &c->disable_shm, NULL },
         { "enable-shm",                 pa_config_parse_not_bool, &c->disable_shm, NULL },
+        { "enable-memfd",               pa_config_parse_not_bool, &c->disable_memfd, NULL },
         { "flat-volumes",               pa_config_parse_bool,     &c->flat_volumes, NULL },
         { "lock-memory",                pa_config_parse_bool,     &c->lock_memory, NULL },
         { "enable-deferred-volume",     pa_config_parse_bool,     &c->deferred_volume, NULL },
diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h
index 458784c..82b619f 100644
--- a/src/daemon/daemon-conf.h
+++ b/src/daemon/daemon-conf.h
@@ -66,6 +66,7 @@ typedef struct pa_daemon_conf {
         system_instance,
         no_cpu_limit,
         disable_shm,
+        disable_memfd,
         disable_remixing,
         disable_lfe_remixing,
         load_default_script_file,
diff --git a/src/daemon/main.c b/src/daemon/main.c
index c2f47b6..ae1185d 100644
--- a/src/daemon/main.c
+++ b/src/daemon/main.c
@@ -1017,7 +1017,9 @@ int main(int argc, char *argv[]) {
 
     pa_assert_se(mainloop = pa_mainloop_new());
 
-    if (!(c = pa_core_new(pa_mainloop_get_api(mainloop), !conf->disable_shm, conf->shm_size))) {
+    if (!(c = pa_core_new(pa_mainloop_get_api(mainloop), !conf->disable_shm,
+                          !conf->disable_shm && !conf->disable_memfd && pa_memfd_is_locally_supported(),
+                          conf->shm_size))) {
         pa_log(_("pa_core_new() failed."));
         goto finish;
     }
diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
index 56cbe9d..6d102f5 100644
--- a/src/pulsecore/core.c
+++ b/src/pulsecore/core.c
@@ -61,16 +61,19 @@ static int core_process_msg(pa_msgobject *o, int code, void *userdata, int64_t o
 
 static void core_free(pa_object *o);
 
-pa_core* pa_core_new(pa_mainloop_api *m, bool shared, size_t shm_size) {
+pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t shm_size) {
     pa_core* c;
     pa_mempool *pool;
+    pa_mem_type_t type;
     int j;
 
     pa_assert(m);
 
     if (shared) {
-        if (!(pool = pa_mempool_new(PA_MEM_TYPE_SHARED_POSIX, shm_size, false))) {
-            pa_log_warn("Failed to allocate shared memory pool. Falling back to a normal memory pool.");
+        type = (enable_memfd) ? PA_MEM_TYPE_SHARED_MEMFD : PA_MEM_TYPE_SHARED_POSIX;
+        if (!(pool = pa_mempool_new(type, shm_size, false))) {
+            pa_log_warn("Failed to allocate %s memory pool. Falling back to a normal memory pool.",
+                        pa_mem_type_to_string(type));
             shared = false;
         }
     }
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
index 9f5c445..1a3c490 100644
--- a/src/pulsecore/core.h
+++ b/src/pulsecore/core.h
@@ -218,7 +218,7 @@ enum {
     PA_CORE_MESSAGE_MAX
 };
 
-pa_core* pa_core_new(pa_mainloop_api *m, bool shared, size_t shm_size);
+pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t shm_size);
 
 /* Check whether no one is connected to this core */
 void pa_core_check_idle(pa_core *c);
diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
index 3f39431..7b57b89 100644
--- a/src/pulsecore/protocol-native.c
+++ b/src/pulsecore/protocol-native.c
@@ -2598,7 +2598,7 @@ static void command_exit(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta
     pa_pstream_send_simple_ack(c->pstream, tag); /* nonsense */
 }
 
-static void setup_srbchannel(pa_native_connection *c) {
+static void setup_srbchannel(pa_native_connection *c, pa_mem_type_t shm_type) {
     pa_srbchannel_template srbt;
     pa_srbchannel *srb;
     pa_memchunk mc;
@@ -2626,20 +2626,25 @@ static void setup_srbchannel(pa_native_connection *c) {
         return;
     }
 
-    if (!(c->rw_mempool = pa_mempool_new(PA_MEM_TYPE_SHARED_POSIX, c->protocol->core->shm_size, true))) {
+    if (!(c->rw_mempool = pa_mempool_new(shm_type, c->protocol->core->shm_size, true))) {
         pa_log_warn("Disabling srbchannel, reason: Failed to allocate shared "
                     "writable memory pool.");
         return;
     }
+
+    if (shm_type == PA_MEM_TYPE_SHARED_MEMFD) {
+        const char *reason;
+        if (pa_pstream_register_memfd_mempool(c->pstream, c->rw_mempool, &reason)) {
+            pa_log_warn("Disabling srbchannel, reason: Failed to register memfd mempool: %s", reason);
+            goto fail;
+        }
+    }
     pa_mempool_set_is_remote_writable(c->rw_mempool, true);
 
     srb = pa_srbchannel_new(c->protocol->core->mainloop, c->rw_mempool);
     if (!srb) {
         pa_log_debug("Failed to create srbchannel");
-
-        pa_mempool_unref(c->rw_mempool);
-        c->rw_mempool = NULL;
-        return;
+        goto fail;
     }
     pa_log_debug("Enabling srbchannel...");
     pa_srbchannel_export(srb, &srbt);
@@ -2659,6 +2664,13 @@ static void setup_srbchannel(pa_native_connection *c) {
     pa_pstream_send_memblock(c->pstream, 0, 0, 0, &mc);
 
     c->srbpending = srb;
+    return;
+
+fail:
+    if (c->rw_mempool) {
+        pa_mempool_unref(c->rw_mempool);
+        c->rw_mempool = NULL;
+    }
 }
 
 static void command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
@@ -2677,7 +2689,7 @@ static void command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32
 static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
     pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
     const void*cookie;
-    bool memfd_on_remote = false;
+    bool memfd_on_remote = false, do_memfd = false;
     pa_tagstruct *reply;
     pa_mem_type_t shm_type;
     bool shm_on_remote = false, do_shm;
@@ -2772,7 +2784,7 @@ static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta
         }
     }
 
-    /* Enable shared memory support if possible */
+    /* Enable shared memory and memfd support if possible */
     do_shm =
         pa_mempool_is_shared(c->protocol->core->mempool) &&
         c->is_local;
@@ -2798,9 +2810,12 @@ static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta
     pa_log_debug("Negotiated SHM: %s", pa_yes_no(do_shm));
     pa_pstream_enable_shm(c->pstream, do_shm);
 
+    do_memfd =
+        do_shm && pa_mempool_is_memfd_backed(c->protocol->core->mempool);
+
     shm_type = PA_MEM_TYPE_PRIVATE;
     if (do_shm) {
-        if (c->version >= 31 && memfd_on_remote && pa_memfd_is_locally_supported()) {
+        if (c->version >= 31 && memfd_on_remote && do_memfd) {
             pa_pstream_enable_memfd(c->pstream);
             shm_type = PA_MEM_TYPE_SHARED_MEMFD;
         } else
@@ -2812,7 +2827,7 @@ static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta
 
     reply = reply_new(tag);
     pa_tagstruct_putu32(reply, PA_PROTOCOL_VERSION | (do_shm ? 0x80000000 : 0) |
-                        (pa_memfd_is_locally_supported() ? 0x40000000 : 0));
+                        (do_memfd ? 0x40000000 : 0));
 
 #ifdef HAVE_CREDS
 {
@@ -2829,7 +2844,19 @@ static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta
     pa_pstream_send_tagstruct(c->pstream, reply);
 #endif
 
-    setup_srbchannel(c);
+    /* The client enables memfd transport on its pstream only after
+     * inspecting our version flags to see if we support memfds too.
+     *
+     * Thus register any pools after sending the server's version
+     * flags and _never_ before it. */
+    if (shm_type == PA_MEM_TYPE_SHARED_MEMFD) {
+        const char *reason;
+
+        if (pa_pstream_register_memfd_mempool(c->pstream, c->protocol->core->mempool, &reason))
+            pa_log("Failed to register memfd mempool. Reason: %s", reason);
+    }
+
+    setup_srbchannel(c, shm_type);
 }
 
 static void command_register_memfd_shmid(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
-- 
2.8.0



More information about the pulseaudio-discuss mailing list