core: add systemd-executor binary

Currently we spawn services by forking a child process, doing a bunch
of work, and then exec'ing the service executable.

There are some advantages to this approach:

- quick: we immediately have access to all the enourmous amount of
  state simply by virtue of sharing the memory with the parent
- easy to refactor and add features
- part of the same binary, will never be out of sync

There are however significant drawbacks:

- doing work after fork and before exec is against glibc's supported
  case for several APIs we call
- copy-on-write trap: anytime any memory is touched in either parent
  or child, a copy of that page will be triggered
- memory footprint of the child process will be memory footprint of
  PID1, but using the cgroup memory limits of the unit

The last issue is especially problematic on resource constrained
systems where hard memory caps are enforced and swap is not allowed.
As soon as PID1 is under load, with no page out due to no swap, and a
service with a low MemoryMax= tries to start, hilarity ensues.

Add a new systemd-executor binary, that is able to receive all the
required state via memfd, deserialize it, prepare the appropriate
data structures and call exec_child.

Use posix_spawn which uses CLONE_VM + CLONE_VFORK, to ensure there is
no copy-on-write (same address space will be used, and parent process
will be frozen, until exec).
The sd-executor binary is pinned by FD on startup, so that we can
guarantee there will be no incompatibilities during upgrades.
This commit is contained in:
Luca Boccassi
2023-10-12 15:01:51 +01:00
parent 56df7a461f
commit bb5232b6a3
15 changed files with 497 additions and 35 deletions
+11
View File
@@ -471,6 +471,13 @@ struct ExecParameters {
#include "unit.h"
#include "dynamic-user.h"
int exec_invoke(const ExecCommand *command,
const ExecContext *context,
ExecParameters *params,
ExecRuntime *runtime,
const CGroupContext *cgroup_context,
int *exit_status);
int exec_spawn(Unit *unit,
ExecCommand *command,
const ExecContext *context,
@@ -479,6 +486,7 @@ int exec_spawn(Unit *unit,
const CGroupContext *cgroup_context,
pid_t *ret);
void exec_command_done(ExecCommand *c);
void exec_command_done_array(ExecCommand *c, size_t n);
ExecCommand* exec_command_free_list(ExecCommand *c);
void exec_command_free_array(ExecCommand **c, size_t n);
@@ -524,15 +532,18 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(ExecSharedRuntime*, exec_shared_runtime_unref);
int exec_shared_runtime_serialize(const Manager *m, FILE *f, FDSet *fds);
int exec_shared_runtime_deserialize_compat(Unit *u, const char *key, const char *value, FDSet *fds);
int exec_shared_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds);
void exec_shared_runtime_done(ExecSharedRuntime *rt);
void exec_shared_runtime_vacuum(Manager *m);
int exec_runtime_make(const Unit *unit, const ExecContext *context, ExecSharedRuntime *shared, DynamicCreds *creds, ExecRuntime **ret);
ExecRuntime* exec_runtime_free(ExecRuntime *rt);
DEFINE_TRIVIAL_CLEANUP_FUNC(ExecRuntime*, exec_runtime_free);
ExecRuntime* exec_runtime_destroy(ExecRuntime *rt);
void exec_runtime_clear(ExecRuntime *rt);
void exec_params_clear(ExecParameters *p);
void exec_params_dump(const ExecParameters *p, FILE* f, const char *prefix);
void exec_params_serialized_done(ExecParameters *p);
bool exec_context_get_cpu_affinity_from_numa(const ExecContext *c);