summaryrefslogtreecommitdiff
path: root/src/core/unit.c
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2015-09-01 19:22:36 +0200
committerLennart Poettering <lennart@poettering.net>2015-09-01 23:52:27 +0200
commitefdb02375beb0a940c3320865572913780b4d7de (patch)
treebffddfbb0344c1d7c2e1853f36b0acf3f1624d64 /src/core/unit.c
parent92dcf85e11d24b60f099401c1865add607d0bf4a (diff)
core: unified cgroup hierarchy support
This patch set adds full support the new unified cgroup hierarchy logic of modern kernels. A new kernel command line option "systemd.unified_cgroup_hierarchy=1" is added. If specified the unified hierarchy is mounted to /sys/fs/cgroup instead of a tmpfs. No further hierarchies are mounted. The kernel command line option defaults to off. We can turn it on by default as soon as the kernel's APIs regarding this are stabilized (but even then downstream distros might want to turn this off, as this will break any tools that access cgroupfs directly). It is possibly to choose for each boot individually whether the unified or the legacy hierarchy is used. nspawn will by default provide the legacy hierarchy to containers if the host is using it, and the unified otherwise. However it is possible to run containers with the unified hierarchy on a legacy host and vice versa, by setting the $UNIFIED_CGROUP_HIERARCHY environment variable for nspawn to 1 or 0, respectively. The unified hierarchy provides reliable cgroup empty notifications for the first time, via inotify. To make use of this we maintain one manager-wide inotify fd, and each cgroup to it. This patch also removes cg_delete() which is unused now. On kernel 4.2 only the "memory" controller is compatible with the unified hierarchy, hence that's the only controller systemd exposes when booted in unified heirarchy mode. This introduces a new enum for enumerating supported controllers, plus a related enum for the mask bits mapping to it. The core is changed to make use of this everywhere. This moves PID 1 into a new "init.scope" implicit scope unit in the root slice. This is necessary since on the unified hierarchy cgroups may either contain subgroups or processes but not both. PID 1 hence has to move out of the root cgroup (strictly speaking the root cgroup is the only one where processes and subgroups are still allowed, but in order to support containers nicey, we move PID 1 into the new scope in all cases.) This new unit is also used on legacy hierarchy setups. It's actually pretty useful on all systems, as it can then be used to filter journal messages coming from PID 1, and so on. The root slice ("-.slice") is now implicitly created and started (and does not require a unit file on disk anymore), since that's where "init.scope" is located and the slice needs to be started before the scope can. To check whether we are in unified or legacy hierarchy mode we use statfs() on /sys/fs/cgroup. If the .f_type field reports tmpfs we are in legacy mode, if it reports cgroupfs we are in unified mode. This patch set carefuly makes sure that cgls and cgtop continue to work as desired. When invoking nspawn as a service it will implicitly create two subcgroups in the cgroup it is using, one to move the nspawn process into, the other to move the actual container processes into. This is done because of the requirement that cgroups may either contain processes or other subgroups.
Diffstat (limited to 'src/core/unit.c')
-rw-r--r--src/core/unit.c168
1 files changed, 27 insertions, 141 deletions
diff --git a/src/core/unit.c b/src/core/unit.c
index 34d3adcd3b..8c07c6140d 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -91,6 +91,7 @@ Unit *unit_new(Manager *m, size_t size) {
u->unit_file_state = _UNIT_FILE_STATE_INVALID;
u->unit_file_preset = -1;
u->on_failure_job_mode = JOB_REPLACE;
+ u->cgroup_inotify_wd = -1;
RATELIMIT_INIT(u->auto_stop_ratelimit, 10 * USEC_PER_SEC, 16);
@@ -525,10 +526,7 @@ void unit_free(Unit *u) {
if (u->in_cgroup_queue)
LIST_REMOVE(cgroup_queue, u->manager->cgroup_queue, u);
- if (u->cgroup_path) {
- hashmap_remove(u->manager->cgroup_unit, u->cgroup_path);
- free(u->cgroup_path);
- }
+ unit_release_cgroup(u);
manager_update_failed_units(u->manager, u, false);
set_remove(u->manager->startup_units, u);
@@ -1801,7 +1799,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su
/* Make sure the cgroup is always removed when we become inactive */
if (UNIT_IS_INACTIVE_OR_FAILED(ns))
- unit_destroy_cgroup_if_empty(u);
+ unit_prune_cgroup(u);
/* Note that this doesn't apply to RemainAfterExit services exiting
* successfully, since there's no change of state in that case. Which is
@@ -2028,70 +2026,7 @@ void unit_unwatch_all_pids(Unit *u) {
while (!set_isempty(u->pids))
unit_unwatch_pid(u, PTR_TO_LONG(set_first(u->pids)));
- set_free(u->pids);
- u->pids = NULL;
-}
-
-static int unit_watch_pids_in_path(Unit *u, const char *path) {
- _cleanup_closedir_ DIR *d = NULL;
- _cleanup_fclose_ FILE *f = NULL;
- int ret = 0, r;
-
- assert(u);
- assert(path);
-
- /* Adds all PIDs from a specific cgroup path to the set of PIDs we watch. */
-
- r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, path, &f);
- if (r >= 0) {
- pid_t pid;
-
- while ((r = cg_read_pid(f, &pid)) > 0) {
- r = unit_watch_pid(u, pid);
- if (r < 0 && ret >= 0)
- ret = r;
- }
- if (r < 0 && ret >= 0)
- ret = r;
-
- } else if (ret >= 0)
- ret = r;
-
- r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, path, &d);
- if (r >= 0) {
- char *fn;
-
- while ((r = cg_read_subgroup(d, &fn)) > 0) {
- _cleanup_free_ char *p = NULL;
-
- p = strjoin(path, "/", fn, NULL);
- free(fn);
-
- if (!p)
- return -ENOMEM;
-
- r = unit_watch_pids_in_path(u, p);
- if (r < 0 && ret >= 0)
- ret = r;
- }
- if (r < 0 && ret >= 0)
- ret = r;
-
- } else if (ret >= 0)
- ret = r;
-
- return ret;
-}
-
-int unit_watch_all_pids(Unit *u) {
- assert(u);
-
- /* Adds all PIDs from our cgroup to the set of PIDs we watch */
-
- if (!u->cgroup_path)
- return -ENOENT;
-
- return unit_watch_pids_in_path(u, u->cgroup_path);
+ u->pids = set_free(u->pids);
}
void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2) {
@@ -2400,31 +2335,6 @@ char *unit_dbus_path(Unit *u) {
return unit_dbus_path_from_name(u->id);
}
-char *unit_default_cgroup_path(Unit *u) {
- _cleanup_free_ char *escaped = NULL, *slice = NULL;
- int r;
-
- assert(u);
-
- if (unit_has_name(u, SPECIAL_ROOT_SLICE))
- return strdup(u->manager->cgroup_root);
-
- if (UNIT_ISSET(u->slice) && !unit_has_name(UNIT_DEREF(u->slice), SPECIAL_ROOT_SLICE)) {
- r = cg_slice_to_path(UNIT_DEREF(u->slice)->id, &slice);
- if (r < 0)
- return NULL;
- }
-
- escaped = cg_escape(u->id);
- if (!escaped)
- return NULL;
-
- if (slice)
- return strjoin(u->manager->cgroup_root, "/", slice, "/", escaped, NULL);
- else
- return strjoin(u->manager->cgroup_root, "/", escaped, NULL);
-}
-
int unit_set_slice(Unit *u, Unit *slice) {
assert(u);
assert(slice);
@@ -2447,6 +2357,10 @@ int unit_set_slice(Unit *u, Unit *slice) {
if (slice->type != UNIT_SLICE)
return -EINVAL;
+ if (unit_has_name(u, SPECIAL_INIT_SCOPE) &&
+ !unit_has_name(slice, SPECIAL_ROOT_SLICE))
+ return -EPERM;
+
if (UNIT_DEREF(u->slice) == slice)
return 0;
@@ -2495,7 +2409,7 @@ int unit_set_default_slice(Unit *u) {
slice_name = b;
} else
slice_name =
- u->manager->running_as == MANAGER_SYSTEM
+ u->manager->running_as == MANAGER_SYSTEM && !unit_has_name(u, SPECIAL_INIT_SCOPE)
? SPECIAL_SYSTEM_SLICE
: SPECIAL_ROOT_SLICE;
@@ -2704,40 +2618,6 @@ void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) {
fprintf(f, "%s=%s\n", key, value);
}
-static int unit_set_cgroup_path(Unit *u, const char *path) {
- _cleanup_free_ char *p = NULL;
- int r;
-
- assert(u);
-
- if (path) {
- p = strdup(path);
- if (!p)
- return -ENOMEM;
- } else
- p = NULL;
-
- if (streq_ptr(u->cgroup_path, p))
- return 0;
-
- if (p) {
- r = hashmap_put(u->manager->cgroup_unit, p, u);
- if (r < 0)
- return r;
- }
-
- if (u->cgroup_path) {
- log_unit_debug(u, "Changing cgroup path from %s to %s.", u->cgroup_path, strna(p));
- hashmap_remove(u->manager->cgroup_unit, u->cgroup_path);
- free(u->cgroup_path);
- }
-
- u->cgroup_path = p;
- p = NULL;
-
- return 0;
-}
-
int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
ExecRuntime **rt = NULL;
size_t offset;
@@ -2868,6 +2748,8 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
if (r < 0)
log_unit_debug_errno(u, r, "Failed to set cgroup path %s, ignoring: %m", v);
+ (void) unit_watch_cgroup(u);
+
continue;
} else if (streq(l, "cgroup-realized")) {
int b;
@@ -3600,18 +3482,22 @@ int unit_kill_context(
} else if (r > 0) {
- /* FIXME: For now, we will not wait for the
- * cgroup members to die if we are running in
- * a container or if this is a delegation
- * unit, simply because cgroup notification is
- * unreliable in these cases. It doesn't work
- * at all in containers, and outside of
- * containers it can be confused easily by
- * left-over directories in the cgroup --
- * which however should not exist in
- * non-delegated units. */
-
- if (detect_container(NULL) == 0 && !unit_cgroup_delegate(u))
+ /* FIXME: For now, on the legacy hierarchy, we
+ * will not wait for the cgroup members to die
+ * if we are running in a container or if this
+ * is a delegation unit, simply because cgroup
+ * notification is unreliable in these
+ * cases. It doesn't work at all in
+ * containers, and outside of containers it
+ * can be confused easily by left-over
+ * directories in the cgroup -- which however
+ * should not exist in non-delegated units. On
+ * the unified hierarchy that's different,
+ * there we get proper events. Hence rely on
+ * them.*/
+
+ if (cg_unified() > 0 ||
+ (detect_container(NULL) == 0 && !unit_cgroup_delegate(u)))
wait_for_exit = true;
if (c->send_sighup && k != KILL_KILL) {