a19bc6
From 7d44d0d43465892d4753ff50592588f49d56cf95 Mon Sep 17 00:00:00 2001
a19bc6
From: Lennart Poettering <lennart@poettering.net>
a19bc6
Date: Thu, 10 Sep 2015 12:32:16 +0200
a19bc6
Subject: [PATCH] core: add support for the "pids" cgroup controller
a19bc6
a19bc6
This adds support for the new "pids" cgroup controller of 4.3 kernels.
a19bc6
It allows accounting the number of tasks in a cgroup and enforcing
a19bc6
limits on it.
a19bc6
a19bc6
This adds two new setting TasksAccounting= and TasksMax= to each unit,
a19bc6
as well as a gloabl option DefaultTasksAccounting=.
a19bc6
a19bc6
This also updated "cgtop" to optionally make use of the new
a19bc6
kernel-provided accounting.
a19bc6
a19bc6
systemctl has been updated to show the number of tasks for each service
a19bc6
if it is available.
a19bc6
a19bc6
This patch also adds correct support for undoing memory limits for units
a19bc6
using a MemoryLimit=infinity syntax. We do the same for TasksMax= now
a19bc6
and hence keep things in sync here.
a19bc6
a19bc6
Cherry-picked from: 03a7b521e3ffb7f5d153d90480ba5d4bc29d1e8f
a19bc6
Resolves: #1337244
a19bc6
---
23b3cf
 man/systemd-system.conf.xml           | 22 +++++-----
23b3cf
 man/systemd.resource-control.xml      | 63 ++++++++++++++++++++++-----
23b3cf
 src/core/cgroup.c                     | 44 +++++++++++++++++++
a19bc6
 src/core/cgroup.h                     |  5 +++
23b3cf
 src/core/dbus-cgroup.c                | 41 ++++++++++++++++-
23b3cf
 src/core/dbus-unit.c                  | 25 +++++++++++
23b3cf
 src/core/load-fragment-gperf.gperf.m4 |  2 +
23b3cf
 src/core/load-fragment.c              | 37 ++++++++++++++--
a19bc6
 src/core/load-fragment.h              |  1 +
a19bc6
 src/core/main.c                       |  3 ++
a19bc6
 src/core/manager.h                    |  1 +
a19bc6
 src/core/unit.c                       |  1 +
a19bc6
 src/shared/cgroup-util.h              |  1 +
23b3cf
 src/systemctl/systemctl.c             | 17 ++++++++
a19bc6
 14 files changed, 236 insertions(+), 27 deletions(-)
a19bc6
a19bc6
diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml
181b3f
index 57b3b90be..d367ccd13 100644
a19bc6
--- a/man/systemd-system.conf.xml
a19bc6
+++ b/man/systemd-system.conf.xml
a19bc6
@@ -51,14 +51,14 @@
a19bc6
   </refnamediv>
a19bc6
 
a19bc6
   <refsynopsisdiv>
a19bc6
-    <para><filename>/etc/systemd/system.conf</filename></para>
a19bc6
-    <para><filename>/etc/systemd/system.conf.d/*.conf</filename></para>
a19bc6
-    <para><filename>/run/systemd/system.conf.d/*.conf</filename></para>
a19bc6
-    <para><filename>/usr/lib/systemd/system.conf.d/*.conf</filename></para>
a19bc6
-    <para><filename>/etc/systemd/user.conf</filename></para>
a19bc6
-    <para><filename>/etc/systemd/user.conf.d/*.conf</filename></para>
a19bc6
-    <para><filename>/run/systemd/user.conf.d/*.conf</filename></para>
a19bc6
-    <para><filename>/usr/lib/systemd/user.conf.d/*.conf</filename></para>
a19bc6
+    <para><filename>/etc/systemd/system.conf</filename>,
a19bc6
+    <filename>/etc/systemd/system.conf.d/*.conf</filename>,
a19bc6
+    <filename>/run/systemd/system.conf.d/*.conf</filename>,
a19bc6
+    <filename>/usr/lib/systemd/system.conf.d/*.conf</filename></para>
a19bc6
+    <para><filename>/etc/systemd/user.conf</filename>,
a19bc6
+    <filename>/etc/systemd/user.conf.d/*.conf</filename>,
a19bc6
+    <filename>/run/systemd/user.conf.d/*.conf</filename>,
a19bc6
+    <filename>/usr/lib/systemd/user.conf.d/*.conf</filename></para>
a19bc6
   </refsynopsisdiv>
a19bc6
 
a19bc6
   <refsect1>
a19bc6
@@ -307,12 +307,14 @@
a19bc6
         <term><varname>DefaultCPUAccounting=</varname></term>
a19bc6
         <term><varname>DefaultBlockIOAccounting=</varname></term>
a19bc6
         <term><varname>DefaultMemoryAccounting=</varname></term>
a19bc6
+        <term><varname>DefaultTasksAccounting=</varname></term>
a19bc6
 
a19bc6
         <listitem><para>Configure the default resource accounting
a19bc6
         settings, as configured per-unit by
a19bc6
         <varname>CPUAccounting=</varname>,
a19bc6
-        <varname>BlockIOAccounting=</varname> and
a19bc6
-        <varname>MemoryAccounting=</varname>. See
a19bc6
+        <varname>BlockIOAccounting=</varname>,
a19bc6
+        <varname>MemoryAccounting=</varname> and
a19bc6
+        <varname>TasksAccounting=</varname>. See
a19bc6
         <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
a19bc6
         for details on the per-unit settings.</para></listitem>
a19bc6
       </varlistentry>
a19bc6
diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml
181b3f
index 8f4e7a3f1..6b9329bbe 100644
a19bc6
--- a/man/systemd.resource-control.xml
a19bc6
+++ b/man/systemd.resource-control.xml
a19bc6
@@ -103,10 +103,10 @@
a19bc6
         <listitem>
a19bc6
           <para>Turn on CPU usage accounting for this unit. Takes a
a19bc6
           boolean argument. Note that turning on CPU accounting for
a19bc6
-          one unit might also implicitly turn it on for all units
a19bc6
+          one unit will also implicitly turn it on for all units
a19bc6
           contained in the same slice and for all its parent slices
a19bc6
           and the units contained therein. The system default for this
a19bc6
-          setting maybe controlled with
a19bc6
+          setting may be controlled with
a19bc6
           <varname>DefaultCPUAccounting=</varname> in
a19bc6
           <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
a19bc6
         </listitem>
a19bc6
@@ -134,7 +134,7 @@
a19bc6
           prioritizing specific services at boot-up differently than
a19bc6
           during normal runtime.</para>
a19bc6
 
a19bc6
-          <para>Those options imply
a19bc6
+          <para>These options imply
a19bc6
           <literal>CPUAccounting=true</literal>.</para>
a19bc6
         </listitem>
a19bc6
       </varlistentry>
a19bc6
@@ -168,9 +168,10 @@
a19bc6
         <listitem>
a19bc6
           <para>Turn on process and kernel memory accounting for this
a19bc6
           unit. Takes a boolean argument. Note that turning on memory
a19bc6
-          accounting for one unit might also implicitly turn it on for
a19bc6
-          all its parent slices. The system default for this setting
a19bc6
-          maybe controlled with
a19bc6
+          accounting for one unit will also implicitly turn it on for
a19bc6
+          all units contained in the same slice and for all its parent
a19bc6
+          slices and the units contained therein. The system default
a19bc6
+          for this setting may be controlled with
a19bc6
           <varname>DefaultMemoryAccounting=</varname> in
a19bc6
           <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
a19bc6
         </listitem>
181b3f
@@ -186,26 +187,64 @@
a19bc6
           memory size in bytes. If the value is suffixed with K, M, G
a19bc6
           or T, the specified memory size is parsed as Kilobytes,
a19bc6
           Megabytes, Gigabytes, or Terabytes (with the base 1024),
a19bc6
-          respectively. This controls the
a19bc6
-          <literal>memory.limit_in_bytes</literal> control group
a19bc6
-          attribute. For details about this control group attribute,
a19bc6
-          see 
a19bc6
+          respectively. If assigned the special value
a19bc6
+          <literal>infinity</literal> no memory limit is applied. This
a19bc6
+          controls the <literal>memory.limit_in_bytes</literal>
a19bc6
+          control group attribute. For details about this control
a19bc6
+          group attribute, see 
a19bc6
           url="https://www.kernel.org/doc/Documentation/cgroups/memory.txt">memory.txt</ulink>.</para>
a19bc6
 
a19bc6
           <para>Implies <literal>MemoryAccounting=true</literal>.</para>
181b3f
         </listitem>
a19bc6
       </varlistentry>
a19bc6
 
181b3f
+      <varlistentry>
a19bc6
+        <term><varname>TasksAccounting=</varname></term>
a19bc6
+
a19bc6
+        <listitem>
a19bc6
+          <para>Turn on task accounting for this unit. Takes a
a19bc6
+          boolean argument. If enabled, the system manager will keep
a19bc6
+          track of the number of tasks in the unit. The number of
a19bc6
+          tasks accounted this way includes both kernel threads and
a19bc6
+          userspace processes, with each thread counting
a19bc6
+          individually. Note that turning on tasks accounting for one
a19bc6
+          unit will also implicitly turn it on for all units contained
a19bc6
+          in the same slice and for all its parent slices and the
a19bc6
+          units contained therein. The system default for this setting
a19bc6
+          may be controlled with
a19bc6
+          <varname>DefaultTasksAccounting=</varname> in
a19bc6
+          <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
a19bc6
+        </listitem>
a19bc6
+      </varlistentry>
a19bc6
+
a19bc6
+      <varlistentry>
a19bc6
+        <term><varname>TasksMax=<replaceable>N</replaceable></varname></term>
a19bc6
+
a19bc6
+        <listitem>
a19bc6
+          <para>Specify the maximum number of tasks that may be
a19bc6
+          created in the unit. This ensures that the number of tasks
a19bc6
+          accounted for the unit (see above) stays below a specific
a19bc6
+          limit. If assigned the special value
a19bc6
+          <literal>infinity</literal> no tasks limit is applied. This
a19bc6
+          controls the <literal>pids.max</literal> control group
a19bc6
+          attribute. For details about this control group attribute,
a19bc6
+          see 
a19bc6
+          url="https://www.kernel.org/doc/Documentation/cgroups/pids.txt">pids.txt</ulink>.</para>
a19bc6
+
a19bc6
+          <para>Implies <literal>TasksAccounting=true</literal>.</para>
a19bc6
+        </listitem>
a19bc6
+      </varlistentry>
a19bc6
+
181b3f
       <varlistentry>
a19bc6
         <term><varname>BlockIOAccounting=</varname></term>
a19bc6
 
a19bc6
         <listitem>
a19bc6
           <para>Turn on Block IO accounting for this unit. Takes a
a19bc6
           boolean argument. Note that turning on block IO accounting
a19bc6
-          for one unit might also implicitly turn it on for all units
a19bc6
+          for one unit will also implicitly turn it on for all units
a19bc6
           contained in the same slice and all for its parent slices
a19bc6
           and the units contained therein. The system default for this
a19bc6
-          setting maybe controlled with
a19bc6
+          setting may be controlled with
a19bc6
           <varname>DefaultBlockIOAccounting=</varname> in
a19bc6
           <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
a19bc6
         </listitem>
a19bc6
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
181b3f
index b7f08fb42..d4a8f9cbe 100644
a19bc6
--- a/src/core/cgroup.c
a19bc6
+++ b/src/core/cgroup.c
a19bc6
@@ -40,6 +40,7 @@ void cgroup_context_init(CGroupContext *c) {
a19bc6
         c->memory_limit = (uint64_t) -1;
a19bc6
         c->blockio_weight = (unsigned long) -1;
a19bc6
         c->startup_blockio_weight = (unsigned long) -1;
a19bc6
+        c->tasks_max = (uint64_t) -1;
a19bc6
 
a19bc6
         c->cpu_quota_per_sec_usec = USEC_INFINITY;
a19bc6
 }
a19bc6
@@ -105,6 +106,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
a19bc6
                 "%sBlockIOWeight=%lu\n"
a19bc6
                 "%sStartupBlockIOWeight=%lu\n"
a19bc6
                 "%sMemoryLimit=%" PRIu64 "\n"
a19bc6
+                "%sTasksMax=%" PRIu64 "\n"
a19bc6
                 "%sDevicePolicy=%s\n"
a19bc6
                 "%sDelegate=%s\n",
a19bc6
                 prefix, yes_no(c->cpu_accounting),
a19bc6
@@ -116,6 +118,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
a19bc6
                 prefix, c->blockio_weight,
a19bc6
                 prefix, c->startup_blockio_weight,
a19bc6
                 prefix, c->memory_limit,
a19bc6
+                prefix, c->tasks_max,
a19bc6
                 prefix, cgroup_device_policy_to_string(c->device_policy),
a19bc6
                 prefix, yes_no(c->delegate));
a19bc6
 
a19bc6
@@ -456,6 +459,21 @@ void cgroup_context_apply(CGroupContext *c, CGroupControllerMask mask, const cha
a19bc6
                                 log_debug("Ignoring device %s while writing cgroup attribute.", a->path);
a19bc6
                 }
a19bc6
         }
a19bc6
+
a19bc6
+        if ((mask & CGROUP_PIDS) && !is_root) {
a19bc6
+
a19bc6
+                if (c->tasks_max != (uint64_t) -1) {
a19bc6
+                        char buf[DECIMAL_STR_MAX(uint64_t) + 2];
a19bc6
+
a19bc6
+                        sprintf(buf, "%" PRIu64 "\n", c->tasks_max);
a19bc6
+                        r = cg_set_attribute("pids", path, "pids.max", buf);
a19bc6
+                } else
a19bc6
+                        r = cg_set_attribute("pids", path, "pids.max", "max");
a19bc6
+
a19bc6
+                if (r < 0)
a19bc6
+                        log_full_errno(IN_SET(r, -ENOENT, -EROFS) ? LOG_DEBUG : LOG_WARNING, r,
a19bc6
+                                       "Failed to set pids.max on %s: %m", path);
a19bc6
+        }
a19bc6
 }
a19bc6
 
a19bc6
 CGroupControllerMask cgroup_context_get_mask(CGroupContext *c) {
a19bc6
@@ -484,6 +502,10 @@ CGroupControllerMask cgroup_context_get_mask(CGroupContext *c) {
a19bc6
             c->device_policy != CGROUP_AUTO)
a19bc6
                 mask |= CGROUP_DEVICE;
a19bc6
 
a19bc6
+        if (c->tasks_accounting ||
a19bc6
+            c->tasks_max != (uint64_t) -1)
a19bc6
+                mask |= CGROUP_PIDS;
a19bc6
+
a19bc6
         return mask;
a19bc6
 }
a19bc6
 
a19bc6
@@ -1044,6 +1066,28 @@ int manager_notify_cgroup_empty(Manager *m, const char *cgroup) {
a19bc6
         return 0;
a19bc6
 }
a19bc6
 
a19bc6
+int unit_get_tasks_current(Unit *u, uint64_t *ret) {
a19bc6
+        _cleanup_free_ char *v = NULL;
a19bc6
+        int r;
a19bc6
+
a19bc6
+        assert(u);
a19bc6
+       assert(ret);
a19bc6
+
a19bc6
+        if (!u->cgroup_path)
a19bc6
+                return -ENODATA;
a19bc6
+
a19bc6
+        if ((u->cgroup_realized_mask & CGROUP_PIDS) == 0)
a19bc6
+                return -ENODATA;
a19bc6
+
a19bc6
+        r = cg_get_attribute("pids", u->cgroup_path, "pids.current", &v);
a19bc6
+        if (r == -ENOENT)
a19bc6
+                return -ENODATA;
a19bc6
+        if (r < 0)
a19bc6
+                return r;
a19bc6
+
a19bc6
+        return safe_atou64(v, ret);
a19bc6
+}
a19bc6
+
a19bc6
 static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = {
a19bc6
         [CGROUP_AUTO] = "auto",
a19bc6
         [CGROUP_CLOSED] = "closed",
a19bc6
diff --git a/src/core/cgroup.h b/src/core/cgroup.h
181b3f
index 8fa851de3..8af3eaa3a 100644
a19bc6
--- a/src/core/cgroup.h
a19bc6
+++ b/src/core/cgroup.h
a19bc6
@@ -72,6 +72,7 @@ struct CGroupContext {
a19bc6
         bool cpu_accounting;
a19bc6
         bool blockio_accounting;
a19bc6
         bool memory_accounting;
a19bc6
+        bool tasks_accounting;
a19bc6
 
a19bc6
         unsigned long cpu_shares;
a19bc6
         unsigned long startup_cpu_shares;
a19bc6
@@ -88,6 +89,8 @@ struct CGroupContext {
a19bc6
         LIST_HEAD(CGroupDeviceAllow, device_allow);
a19bc6
 
a19bc6
         bool delegate;
a19bc6
+
a19bc6
+        uint64_t tasks_max;
a19bc6
 };
a19bc6
 
a19bc6
 #include "unit.h"
a19bc6
@@ -127,5 +130,7 @@ pid_t unit_search_main_pid(Unit *u);
a19bc6
 
a19bc6
 int manager_notify_cgroup_empty(Manager *m, const char *group);
a19bc6
 
a19bc6
+int unit_get_tasks_current(Unit *u, uint64_t *ret);
a19bc6
+
a19bc6
 const char* cgroup_device_policy_to_string(CGroupDevicePolicy i) _const_;
a19bc6
 CGroupDevicePolicy cgroup_device_policy_from_string(const char *s) _pure_;
a19bc6
diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c
181b3f
index 4a9df0601..a4465dc7a 100644
a19bc6
--- a/src/core/dbus-cgroup.c
a19bc6
+++ b/src/core/dbus-cgroup.c
a19bc6
@@ -168,6 +168,8 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
a19bc6
         SD_BUS_PROPERTY("MemoryLimit", "t", NULL, offsetof(CGroupContext, memory_limit), 0),
a19bc6
         SD_BUS_PROPERTY("DevicePolicy", "s", property_get_cgroup_device_policy, offsetof(CGroupContext, device_policy), 0),
a19bc6
         SD_BUS_PROPERTY("DeviceAllow", "a(ss)", property_get_device_allow, 0, 0),
a19bc6
+        SD_BUS_PROPERTY("TasksAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, tasks_accounting), 0),
a19bc6
+        SD_BUS_PROPERTY("TasksMax", "t", NULL, offsetof(CGroupContext, tasks_max), 0),
a19bc6
         SD_BUS_VTABLE_END
a19bc6
 };
a19bc6
 
a19bc6
@@ -551,7 +553,11 @@ int bus_cgroup_set_property(
a19bc6
                 if (mode != UNIT_CHECK) {
a19bc6
                         c->memory_limit = limit;
a19bc6
                         u->cgroup_realized_mask &= ~CGROUP_MEMORY;
a19bc6
-                        unit_write_drop_in_private_format(u, mode, name, "%s=%" PRIu64, name, limit);
a19bc6
+
a19bc6
+                        if (limit == (uint64_t) -1)
a19bc6
+                                unit_write_drop_in_private(u, mode, name, "MemoryLimit=infinity");
a19bc6
+                        else
a19bc6
+                                unit_write_drop_in_private_format(u, mode, name, "MemoryLimit=%" PRIu64, limit);
a19bc6
                 }
a19bc6
 
a19bc6
                 return 1;
a19bc6
@@ -667,6 +673,39 @@ int bus_cgroup_set_property(
a19bc6
 
a19bc6
                 return 1;
a19bc6
 
a19bc6
+        } else if (streq(name, "TasksAccounting")) {
a19bc6
+                int b;
a19bc6
+
a19bc6
+                r = sd_bus_message_read(message, "b", &b);
a19bc6
+                if (r < 0)
a19bc6
+                        return r;
a19bc6
+
a19bc6
+                if (mode != UNIT_CHECK) {
a19bc6
+                        c->tasks_accounting = b;
a19bc6
+                        u->cgroup_realized_mask &= ~CGROUP_PIDS;
a19bc6
+                        unit_write_drop_in_private(u, mode, name, b ? "TasksAccounting=yes" : "TasksAccounting=no");
a19bc6
+                }
a19bc6
+
a19bc6
+                return 1;
a19bc6
+
a19bc6
+        } else if (streq(name, "TasksMax")) {
a19bc6
+                uint64_t limit;
a19bc6
+
a19bc6
+                r = sd_bus_message_read(message, "t", &limit);
a19bc6
+                if (r < 0)
a19bc6
+                        return r;
a19bc6
+
a19bc6
+                if (mode != UNIT_CHECK) {
a19bc6
+                        c->tasks_max = limit;
a19bc6
+                        u->cgroup_realized_mask &= ~CGROUP_PIDS;
a19bc6
+
a19bc6
+                        if (limit == (uint64_t) -1)
a19bc6
+                                unit_write_drop_in_private(u, mode, name, "TasksMax=infinity");
a19bc6
+                        else
a19bc6
+                                unit_write_drop_in_private_format(u, mode, name, "TasksMax=%" PRIu64, limit);
a19bc6
+                }
a19bc6
+
a19bc6
+                return 1;
a19bc6
         }
a19bc6
 
a19bc6
         if (u->transient && u->load_state == UNIT_STUB) {
a19bc6
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
181b3f
index 056a17ac1..1d0d6f67c 100644
a19bc6
--- a/src/core/dbus-unit.c
a19bc6
+++ b/src/core/dbus-unit.c
a19bc6
@@ -673,11 +673,36 @@ static int property_get_current_memory(
a19bc6
         return sd_bus_message_append(reply, "t", sz);
a19bc6
 }
a19bc6
 
a19bc6
+static int property_get_current_tasks(
a19bc6
+                sd_bus *bus,
a19bc6
+                const char *path,
a19bc6
+                const char *interface,
a19bc6
+                const char *property,
a19bc6
+                sd_bus_message *reply,
a19bc6
+                void *userdata,
a19bc6
+                sd_bus_error *error) {
a19bc6
+
a19bc6
+        uint64_t cn = (uint64_t) -1;
a19bc6
+        Unit *u = userdata;
a19bc6
+        int r;
a19bc6
+
a19bc6
+        assert(bus);
a19bc6
+        assert(reply);
a19bc6
+        assert(u);
a19bc6
+
a19bc6
+        r = unit_get_tasks_current(u, &cn;;
a19bc6
+        if (r < 0 && r != -ENODATA)
a19bc6
+                log_unit_warning_errno(u->id, r, "Failed to get pids.current attribute: %m");
a19bc6
+
a19bc6
+        return sd_bus_message_append(reply, "t", cn);
a19bc6
+}
a19bc6
+
a19bc6
 const sd_bus_vtable bus_unit_cgroup_vtable[] = {
a19bc6
         SD_BUS_VTABLE_START(0),
a19bc6
         SD_BUS_PROPERTY("Slice", "s", property_get_slice, 0, 0),
a19bc6
         SD_BUS_PROPERTY("ControlGroup", "s", NULL, offsetof(Unit, cgroup_path), 0),
a19bc6
         SD_BUS_PROPERTY("MemoryCurrent", "t", property_get_current_memory, 0, 0),
a19bc6
+        SD_BUS_PROPERTY("TasksCurrent", "t", property_get_current_tasks, 0, 0),
a19bc6
         SD_BUS_VTABLE_END
a19bc6
 };
a19bc6
 
a19bc6
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
181b3f
index f996032cf..26e4c618e 100644
a19bc6
--- a/src/core/load-fragment-gperf.gperf.m4
a19bc6
+++ b/src/core/load-fragment-gperf.gperf.m4
a19bc6
@@ -125,6 +125,8 @@ $1.StartupBlockIOWeight,         config_parse_blockio_weight,        0,
a19bc6
 $1.BlockIODeviceWeight,          config_parse_blockio_device_weight, 0,                             offsetof($1, cgroup_context)
a19bc6
 $1.BlockIOReadBandwidth,         config_parse_blockio_bandwidth,     0,                             offsetof($1, cgroup_context)
a19bc6
 $1.BlockIOWriteBandwidth,        config_parse_blockio_bandwidth,     0,                             offsetof($1, cgroup_context)
a19bc6
+$1.TasksAccounting,              config_parse_bool,                  0,                             offsetof($1, cgroup_context.tasks_accounting)
a19bc6
+$1.TasksMax,                     config_parse_tasks_max,             0,                             offsetof($1, cgroup_context)
a19bc6
 $1.Delegate,                     config_parse_bool,                  0,                             offsetof($1, cgroup_context.delegate)'
a19bc6
 )m4_dnl
a19bc6
 Unit.Description,                config_parse_unit_string_printf,    0,                             offsetof(Unit, description)
a19bc6
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
181b3f
index 7d1ac6c25..7d2e737d0 100644
a19bc6
--- a/src/core/load-fragment.c
a19bc6
+++ b/src/core/load-fragment.c
a19bc6
@@ -3052,7 +3052,7 @@ int config_parse_memory_limit(
a19bc6
         off_t bytes;
a19bc6
         int r;
a19bc6
 
a19bc6
-        if (isempty(rvalue)) {
a19bc6
+        if (isempty(rvalue) || streq(rvalue, "infinity")) {
a19bc6
                 c->memory_limit = (uint64_t) -1;
a19bc6
                 return 0;
a19bc6
         }
a19bc6
@@ -3060,9 +3060,8 @@ int config_parse_memory_limit(
a19bc6
         assert_cc(sizeof(uint64_t) == sizeof(off_t));
a19bc6
 
a19bc6
         r = parse_size(rvalue, 1024, &bytes);
a19bc6
-        if (r < 0) {
a19bc6
-                log_syntax(unit, LOG_ERR, filename, line, EINVAL,
a19bc6
-                           "Memory limit '%s' invalid. Ignoring.", rvalue);
a19bc6
+        if (r < 0 || bytes < 1) {
a19bc6
+                log_syntax(unit, LOG_ERR, filename, line, EINVAL, "Memory limit '%s' invalid. Ignoring.", rvalue);
a19bc6
                 return 0;
a19bc6
         }
a19bc6
 
a19bc6
@@ -3070,6 +3069,36 @@ int config_parse_memory_limit(
a19bc6
         return 0;
a19bc6
 }
a19bc6
 
a19bc6
+int config_parse_tasks_max(
a19bc6
+                const char *unit,
a19bc6
+                const char *filename,
a19bc6
+                unsigned line,
a19bc6
+                const char *section,
a19bc6
+                unsigned section_line,
a19bc6
+                const char *lvalue,
a19bc6
+                int ltype,
a19bc6
+                const char *rvalue,
a19bc6
+                void *data,
a19bc6
+                void *userdata) {
a19bc6
+
a19bc6
+        CGroupContext *c = data;
a19bc6
+        uint64_t u;
a19bc6
+        int r;
a19bc6
+
a19bc6
+        if (isempty(rvalue) || streq(rvalue, "infinity")) {
a19bc6
+                c->tasks_max = (uint64_t) -1;
a19bc6
+                return 0;
a19bc6
+        }
a19bc6
+
a19bc6
+        r = safe_atou64(rvalue, &u);
a19bc6
+        if (r < 0 || u < 1) {
a19bc6
+                log_syntax(unit, LOG_ERR, filename, line, EINVAL, "Maximum tasks value '%s' invalid. Ignoring.", rvalue);
a19bc6
+                return 0;
a19bc6
+        }
a19bc6
+
a19bc6
+        return 0;
a19bc6
+}
a19bc6
+
a19bc6
 int config_parse_device_allow(
a19bc6
                 const char *unit,
a19bc6
                 const char *filename,
a19bc6
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
181b3f
index 2059353d3..8d334f2c8 100644
a19bc6
--- a/src/core/load-fragment.h
a19bc6
+++ b/src/core/load-fragment.h
a19bc6
@@ -89,6 +89,7 @@ int config_parse_pass_environ(const char *unit, const char *filename, unsigned l
a19bc6
 int config_parse_unit_slice(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
 int config_parse_cpu_shares(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
 int config_parse_memory_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
+int config_parse_tasks_max(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
 int config_parse_device_policy(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
 int config_parse_device_allow(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
 int config_parse_blockio_weight(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
diff --git a/src/core/main.c b/src/core/main.c
181b3f
index cba992cea..aca05a535 100644
a19bc6
--- a/src/core/main.c
a19bc6
+++ b/src/core/main.c
a19bc6
@@ -117,6 +117,7 @@ static bool arg_default_cpu_accounting = false;
a19bc6
 static bool arg_default_blockio_accounting = false;
a19bc6
 static bool arg_default_memory_accounting = false;
a19bc6
 static EmergencyAction arg_cad_burst_action = EMERGENCY_ACTION_REBOOT_FORCE;
a19bc6
+static bool arg_default_tasks_accounting = false;
a19bc6
 
a19bc6
 static void nop_handler(int sig) {}
a19bc6
 
a19bc6
@@ -676,6 +677,7 @@ static int parse_config_file(void) {
a19bc6
                 { "Manager", "DefaultBlockIOAccounting",  config_parse_bool,             0, &arg_default_blockio_accounting        },
a19bc6
                 { "Manager", "DefaultMemoryAccounting",   config_parse_bool,             0, &arg_default_memory_accounting         },
a19bc6
                 { "Manager", "CtrlAltDelBurstAction",     config_parse_emergency_action, 0, &arg_cad_burst_action                  },
a19bc6
+                { "Manager", "DefaultTasksAccounting",    config_parse_bool,             0, &arg_default_tasks_accounting          },
a19bc6
                 {}
a19bc6
         };
a19bc6
 
a19bc6
@@ -1685,6 +1687,7 @@ int main(int argc, char *argv[]) {
a19bc6
         m->default_cpu_accounting = arg_default_cpu_accounting;
a19bc6
         m->default_blockio_accounting = arg_default_blockio_accounting;
a19bc6
         m->default_memory_accounting = arg_default_memory_accounting;
a19bc6
+        m->default_tasks_accounting = arg_default_tasks_accounting;
a19bc6
         m->runtime_watchdog = arg_runtime_watchdog;
a19bc6
         m->shutdown_watchdog = arg_shutdown_watchdog;
a19bc6
 
a19bc6
diff --git a/src/core/manager.h b/src/core/manager.h
181b3f
index 231c076b1..96dcd83dc 100644
a19bc6
--- a/src/core/manager.h
a19bc6
+++ b/src/core/manager.h
a19bc6
@@ -261,6 +261,7 @@ struct Manager {
a19bc6
         bool default_cpu_accounting;
a19bc6
         bool default_memory_accounting;
a19bc6
         bool default_blockio_accounting;
a19bc6
+        bool default_tasks_accounting;
a19bc6
 
a19bc6
         usec_t default_timer_accuracy_usec;
a19bc6
 
a19bc6
diff --git a/src/core/unit.c b/src/core/unit.c
181b3f
index 103f92084..2fcb4fbf0 100644
a19bc6
--- a/src/core/unit.c
a19bc6
+++ b/src/core/unit.c
a19bc6
@@ -126,6 +126,7 @@ static void unit_init(Unit *u) {
a19bc6
                 cc->cpu_accounting = u->manager->default_cpu_accounting;
a19bc6
                 cc->blockio_accounting = u->manager->default_blockio_accounting;
a19bc6
                 cc->memory_accounting = u->manager->default_memory_accounting;
a19bc6
+                cc->tasks_accounting = u->manager->default_tasks_accounting;
a19bc6
         }
a19bc6
 
a19bc6
         ec = unit_get_exec_context(u);
a19bc6
diff --git a/src/shared/cgroup-util.h b/src/shared/cgroup-util.h
181b3f
index 96a3d3baf..31bd8d311 100644
a19bc6
--- a/src/shared/cgroup-util.h
a19bc6
+++ b/src/shared/cgroup-util.h
a19bc6
@@ -35,6 +35,7 @@ typedef enum CGroupControllerMask {
a19bc6
         CGROUP_BLKIO = 4,
a19bc6
         CGROUP_MEMORY = 8,
a19bc6
         CGROUP_DEVICE = 16,
a19bc6
+        CGROUP_PIDS = 32,
a19bc6
         _CGROUP_CONTROLLER_MASK_ALL = 31
a19bc6
 } CGroupControllerMask;
a19bc6
 
a19bc6
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
181b3f
index 0333599c8..b1862b567 100644
a19bc6
--- a/src/systemctl/systemctl.c
a19bc6
+++ b/src/systemctl/systemctl.c
a19bc6
@@ -3280,6 +3280,8 @@ typedef struct UnitStatusInfo {
a19bc6
         /* CGroup */
a19bc6
         uint64_t memory_current;
a19bc6
         uint64_t memory_limit;
a19bc6
+        uint64_t tasks_current;
a19bc6
+        uint64_t tasks_max;
a19bc6
 
a19bc6
         LIST_HEAD(ExecStatusInfo, exec);
a19bc6
 } UnitStatusInfo;
a19bc6
@@ -3539,6 +3541,15 @@ static void print_status_info(
a19bc6
         if (i->status_errno > 0)
a19bc6
                 printf("    Error: %i (%s)\n", i->status_errno, strerror(i->status_errno));
a19bc6
 
a19bc6
+        if (i->tasks_current != (uint64_t) -1) {
a19bc6
+                printf("    Tasks: %" PRIu64, i->tasks_current);
a19bc6
+
a19bc6
+                if (i->tasks_max != (uint64_t) -1)
a19bc6
+                        printf(" (limit: %" PRIi64 ")\n", i->tasks_max);
a19bc6
+                else
a19bc6
+                        printf("\n");
a19bc6
+        }
a19bc6
+
a19bc6
         if (i->memory_current != (uint64_t) -1) {
a19bc6
                 char buf[FORMAT_BYTES_MAX];
a19bc6
 
a19bc6
@@ -3768,6 +3779,10 @@ static int status_property(const char *name, sd_bus_message *m, UnitStatusInfo *
a19bc6
                         i->memory_current = u;
a19bc6
                 else if (streq(name, "MemoryLimit"))
a19bc6
                         i->memory_limit = u;
a19bc6
+                else if (streq(name, "TasksCurrent"))
a19bc6
+                        i->tasks_current = u;
a19bc6
+                else if (streq(name, "TasksMax"))
a19bc6
+                        i->tasks_max = u;
a19bc6
 
a19bc6
                 break;
a19bc6
         }
a19bc6
@@ -4248,6 +4263,8 @@ static int show_one(
a19bc6
         UnitStatusInfo info = {
a19bc6
                 .memory_current = (uint64_t) -1,
a19bc6
                 .memory_limit = (uint64_t) -1,
a19bc6
+                .tasks_current = (uint64_t) -1,
a19bc6
+                .tasks_max = (uint64_t) -1,
a19bc6
         };
a19bc6
         ExecStatusInfo *p;
a19bc6
         int r;