Commit d2a6afca authored by Ahmed S. Darwish's avatar Ahmed S. Darwish Committed by Arun Raghavan

core: Support memfd transport; bump protocol version

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: default avatarAhmed S. Darwish <darwish.07@gmail.com>
parent b1d47d60
......@@ -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.
......
......@@ -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
......
......@@ -189,11 +189,16 @@ 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
......
......@@ -292,13 +292,22 @@ 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>
......
......@@ -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"))
;;
......
......@@ -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]' \
......
......@@ -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;
}
......
......@@ -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 },
......
......@@ -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,
......
......@@ -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;
}
......
......@@ -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;
}
}
......
......@@ -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);
......
......@@ -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;
......@@ -2631,20 +2631,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);
......@@ -2664,6 +2669,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) {
......@@ -2682,7 +2694,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;
......@@ -2777,7 +2789,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;
......@@ -2803,9 +2815,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
......@@ -2817,7 +2832,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
{
......@@ -2834,7 +2849,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) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment