![Tejun Heo](/assets/img/avatar_default.png)
Consider the following v2 hierarchy. P0 (+memory) --- P1 (-memory) --- A \- B P0 has memory enabled in its subtree_control while P1 doesn't. If both A and B contain processes, they would belong to the memory css of P1. Now if memory is enabled on P1's subtree_control, memory csses should be created on both A and B and A's processes should be moved to the former and B's processes the latter. IOW, enabling controllers can cause atomic migrations into different csses. The core cgroup migration logic has been updated accordingly but the controller migration methods haven't and still assume that all tasks migrate to a single target css; furthermore, the methods were fed the css in which subtree_control was updated which is the parent of the target csses. pids controller depends on the migration methods to move charges and this made the controller attribute charges to the wrong csses often triggering the following warning by driving a counter negative. WARNING: CPU: 1 PID: 1 at kernel/cgroup_pids.c:97 pids_cancel.constprop.6+0x31/0x40() Modules linked in: CPU: 1 PID: 1 Comm: systemd Not tainted 4.4.0-rc1+ #29 ... ffffffff81f65382 ffff88007c043b90 ffffffff81551ffc 0000000000000000 ffff88007c043bc8 ffffffff810de202 ffff88007a752000 ffff88007a29ab00 ffff88007c043c80 ffff88007a1d8400 0000000000000001 ffff88007c043bd8 Call Trace: [<ffffffff81551ffc>] dump_stack+0x4e/0x82 [<ffffffff810de202>] warn_slowpath_common+0x82/0xc0 [<ffffffff810de2fa>] warn_slowpath_null+0x1a/0x20 [<ffffffff8118e031>] pids_cancel.constprop.6+0x31/0x40 [<ffffffff8118e0fd>] pids_can_attach+0x6d/0xf0 [<ffffffff81188a4c>] cgroup_taskset_migrate+0x6c/0x330 [<ffffffff81188e05>] cgroup_migrate+0xf5/0x190 [<ffffffff81189016>] cgroup_attach_task+0x176/0x200 [<ffffffff8118949d>] __cgroup_procs_write+0x2ad/0x460 [<ffffffff81189684>] cgroup_procs_write+0x14/0x20 [<ffffffff811854e5>] cgroup_file_write+0x35/0x1c0 [<ffffffff812e26f1>] kernfs_fop_write+0x141/0x190 [<ffffffff81265f88>] __vfs_write+0x28/0xe0 [<ffffffff812666fc>] vfs_write+0xac/0x1a0 [<ffffffff81267019>] SyS_write+0x49/0xb0 [<ffffffff81bcef32>] entry_SYSCALL_64_fastpath+0x12/0x76 This patch fixes the bug by removing @css parameter from the three migration methods, ->can_attach, ->cancel_attach() and ->attach() and updating cgroup_taskset iteration helpers also return the destination css in addition to the task being migrated. All controllers are updated accordingly. * Controllers which don't care whether there are one or multiple target csses can be converted trivially. cpu, io, freezer, perf, netclassid and netprio fall in this category. * cpuset's current implementation assumes that there's single source and destination and thus doesn't support v2 hierarchy already. The only change made by this patchset is how that single destination css is obtained. * memory migration path already doesn't do anything on v2. How the single destination css is obtained is updated and the prep stage of mem_cgroup_can_attach() is reordered to accomodate the change. * pids is the only controller which was affected by this bug. It now correctly handles multi-destination migrations and no longer causes counter underflow from incorrect accounting. Signed-off-by: Tejun Heo <tj@kernel.org> Reported-and-tested-by: Daniel Wagner <daniel.wagner@bmw-carit.de> Cc: Aleksa Sarai <cyphar@cyphar.com>
290 lines
6.5 KiB
C
290 lines
6.5 KiB
C
/*
|
|
* net/core/netprio_cgroup.c Priority Control Group
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
* Authors: Neil Horman <nhorman@tuxdriver.com>
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/types.h>
|
|
#include <linux/string.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/cgroup.h>
|
|
#include <linux/rcupdate.h>
|
|
#include <linux/atomic.h>
|
|
#include <net/rtnetlink.h>
|
|
#include <net/pkt_cls.h>
|
|
#include <net/sock.h>
|
|
#include <net/netprio_cgroup.h>
|
|
|
|
#include <linux/fdtable.h>
|
|
|
|
#define PRIOMAP_MIN_SZ 128
|
|
|
|
/*
|
|
* Extend @dev->priomap so that it's large enough to accommodate
|
|
* @target_idx. @dev->priomap.priomap_len > @target_idx after successful
|
|
* return. Must be called under rtnl lock.
|
|
*/
|
|
static int extend_netdev_table(struct net_device *dev, u32 target_idx)
|
|
{
|
|
struct netprio_map *old, *new;
|
|
size_t new_sz, new_len;
|
|
|
|
/* is the existing priomap large enough? */
|
|
old = rtnl_dereference(dev->priomap);
|
|
if (old && old->priomap_len > target_idx)
|
|
return 0;
|
|
|
|
/*
|
|
* Determine the new size. Let's keep it power-of-two. We start
|
|
* from PRIOMAP_MIN_SZ and double it until it's large enough to
|
|
* accommodate @target_idx.
|
|
*/
|
|
new_sz = PRIOMAP_MIN_SZ;
|
|
while (true) {
|
|
new_len = (new_sz - offsetof(struct netprio_map, priomap)) /
|
|
sizeof(new->priomap[0]);
|
|
if (new_len > target_idx)
|
|
break;
|
|
new_sz *= 2;
|
|
/* overflowed? */
|
|
if (WARN_ON(new_sz < PRIOMAP_MIN_SZ))
|
|
return -ENOSPC;
|
|
}
|
|
|
|
/* allocate & copy */
|
|
new = kzalloc(new_sz, GFP_KERNEL);
|
|
if (!new)
|
|
return -ENOMEM;
|
|
|
|
if (old)
|
|
memcpy(new->priomap, old->priomap,
|
|
old->priomap_len * sizeof(old->priomap[0]));
|
|
|
|
new->priomap_len = new_len;
|
|
|
|
/* install the new priomap */
|
|
rcu_assign_pointer(dev->priomap, new);
|
|
if (old)
|
|
kfree_rcu(old, rcu);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* netprio_prio - return the effective netprio of a cgroup-net_device pair
|
|
* @css: css part of the target pair
|
|
* @dev: net_device part of the target pair
|
|
*
|
|
* Should be called under RCU read or rtnl lock.
|
|
*/
|
|
static u32 netprio_prio(struct cgroup_subsys_state *css, struct net_device *dev)
|
|
{
|
|
struct netprio_map *map = rcu_dereference_rtnl(dev->priomap);
|
|
int id = css->cgroup->id;
|
|
|
|
if (map && id < map->priomap_len)
|
|
return map->priomap[id];
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* netprio_set_prio - set netprio on a cgroup-net_device pair
|
|
* @css: css part of the target pair
|
|
* @dev: net_device part of the target pair
|
|
* @prio: prio to set
|
|
*
|
|
* Set netprio to @prio on @css-@dev pair. Should be called under rtnl
|
|
* lock and may fail under memory pressure for non-zero @prio.
|
|
*/
|
|
static int netprio_set_prio(struct cgroup_subsys_state *css,
|
|
struct net_device *dev, u32 prio)
|
|
{
|
|
struct netprio_map *map;
|
|
int id = css->cgroup->id;
|
|
int ret;
|
|
|
|
/* avoid extending priomap for zero writes */
|
|
map = rtnl_dereference(dev->priomap);
|
|
if (!prio && (!map || map->priomap_len <= id))
|
|
return 0;
|
|
|
|
ret = extend_netdev_table(dev, id);
|
|
if (ret)
|
|
return ret;
|
|
|
|
map = rtnl_dereference(dev->priomap);
|
|
map->priomap[id] = prio;
|
|
return 0;
|
|
}
|
|
|
|
static struct cgroup_subsys_state *
|
|
cgrp_css_alloc(struct cgroup_subsys_state *parent_css)
|
|
{
|
|
struct cgroup_subsys_state *css;
|
|
|
|
css = kzalloc(sizeof(*css), GFP_KERNEL);
|
|
if (!css)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
return css;
|
|
}
|
|
|
|
static int cgrp_css_online(struct cgroup_subsys_state *css)
|
|
{
|
|
struct cgroup_subsys_state *parent_css = css->parent;
|
|
struct net_device *dev;
|
|
int ret = 0;
|
|
|
|
if (!parent_css)
|
|
return 0;
|
|
|
|
rtnl_lock();
|
|
/*
|
|
* Inherit prios from the parent. As all prios are set during
|
|
* onlining, there is no need to clear them on offline.
|
|
*/
|
|
for_each_netdev(&init_net, dev) {
|
|
u32 prio = netprio_prio(parent_css, dev);
|
|
|
|
ret = netprio_set_prio(css, dev, prio);
|
|
if (ret)
|
|
break;
|
|
}
|
|
rtnl_unlock();
|
|
return ret;
|
|
}
|
|
|
|
static void cgrp_css_free(struct cgroup_subsys_state *css)
|
|
{
|
|
kfree(css);
|
|
}
|
|
|
|
static u64 read_prioidx(struct cgroup_subsys_state *css, struct cftype *cft)
|
|
{
|
|
return css->cgroup->id;
|
|
}
|
|
|
|
static int read_priomap(struct seq_file *sf, void *v)
|
|
{
|
|
struct net_device *dev;
|
|
|
|
rcu_read_lock();
|
|
for_each_netdev_rcu(&init_net, dev)
|
|
seq_printf(sf, "%s %u\n", dev->name,
|
|
netprio_prio(seq_css(sf), dev));
|
|
rcu_read_unlock();
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t write_priomap(struct kernfs_open_file *of,
|
|
char *buf, size_t nbytes, loff_t off)
|
|
{
|
|
char devname[IFNAMSIZ + 1];
|
|
struct net_device *dev;
|
|
u32 prio;
|
|
int ret;
|
|
|
|
if (sscanf(buf, "%"__stringify(IFNAMSIZ)"s %u", devname, &prio) != 2)
|
|
return -EINVAL;
|
|
|
|
dev = dev_get_by_name(&init_net, devname);
|
|
if (!dev)
|
|
return -ENODEV;
|
|
|
|
rtnl_lock();
|
|
|
|
ret = netprio_set_prio(of_css(of), dev, prio);
|
|
|
|
rtnl_unlock();
|
|
dev_put(dev);
|
|
return ret ?: nbytes;
|
|
}
|
|
|
|
static int update_netprio(const void *v, struct file *file, unsigned n)
|
|
{
|
|
int err;
|
|
struct socket *sock = sock_from_file(file, &err);
|
|
if (sock)
|
|
sock->sk->sk_cgrp_prioidx = (u32)(unsigned long)v;
|
|
return 0;
|
|
}
|
|
|
|
static void net_prio_attach(struct cgroup_taskset *tset)
|
|
{
|
|
struct task_struct *p;
|
|
struct cgroup_subsys_state *css;
|
|
|
|
cgroup_taskset_for_each(p, css, tset) {
|
|
void *v = (void *)(unsigned long)css->cgroup->id;
|
|
|
|
task_lock(p);
|
|
iterate_fd(p->files, 0, update_netprio, v);
|
|
task_unlock(p);
|
|
}
|
|
}
|
|
|
|
static struct cftype ss_files[] = {
|
|
{
|
|
.name = "prioidx",
|
|
.read_u64 = read_prioidx,
|
|
},
|
|
{
|
|
.name = "ifpriomap",
|
|
.seq_show = read_priomap,
|
|
.write = write_priomap,
|
|
},
|
|
{ } /* terminate */
|
|
};
|
|
|
|
struct cgroup_subsys net_prio_cgrp_subsys = {
|
|
.css_alloc = cgrp_css_alloc,
|
|
.css_online = cgrp_css_online,
|
|
.css_free = cgrp_css_free,
|
|
.attach = net_prio_attach,
|
|
.legacy_cftypes = ss_files,
|
|
};
|
|
|
|
static int netprio_device_event(struct notifier_block *unused,
|
|
unsigned long event, void *ptr)
|
|
{
|
|
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
|
struct netprio_map *old;
|
|
|
|
/*
|
|
* Note this is called with rtnl_lock held so we have update side
|
|
* protection on our rcu assignments
|
|
*/
|
|
|
|
switch (event) {
|
|
case NETDEV_UNREGISTER:
|
|
old = rtnl_dereference(dev->priomap);
|
|
RCU_INIT_POINTER(dev->priomap, NULL);
|
|
if (old)
|
|
kfree_rcu(old, rcu);
|
|
break;
|
|
}
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block netprio_device_notifier = {
|
|
.notifier_call = netprio_device_event
|
|
};
|
|
|
|
static int __init init_cgroup_netprio(void)
|
|
{
|
|
register_netdevice_notifier(&netprio_device_notifier);
|
|
return 0;
|
|
}
|
|
|
|
subsys_initcall(init_cgroup_netprio);
|
|
MODULE_LICENSE("GPL v2");
|