2016-03-24 20:24:15

by Parav Pandit

[permalink] [raw]
Subject: [PATCHv10 0/3] rdmacg: IB/core: rdma controller support

Patch is generated and tested against below Doug's linux-rdma
git tree as it merges cleanly with both the git tree
(linux-rdma and linux-cgroup).

URL: git://git.kernel.org/pub/scm/linux/kernel/git/dledford/rdma.git
Branch: k.o/for-4.6

Patch 1/3, 3/3 also compiles against Tejun's cgroup tree as well
and tested with little different patch of 2/3 as both the
trees are little different.

Overview:
Currently user space applications can easily take away all the rdma
device specific resources such as AH, CQ, QP, MR etc. Due to which other
applications in other cgroup or kernel space ULPs may not even get chance
to allocate any rdma resources. This results into service unavailibility.

RDMA cgroup addresses this issue by allowing resource accounting,
limit enforcement on per cgroup, per rdma device basis.

Resources are not defined by the RDMA cgroup. Instead they are defined
by RDMA/IB stack. This allows rdma cgroup to remain constant while RDMA/IB
stack can evolve without the need of rdma cgroup update. A new
resource can be easily added by the RDMA/IB stack without touching
rdma cgroup.

RDMA uverbs layer will enforce limits on well defined RDMA verb
resources without any HCA vendor device driver involvement.

RDMA uverbs layer will not do limit enforcement of HCA hw vendor
specific resources. Instead rdma cgroup provides set of APIs
through which vendor specific drivers can do resource accounting
by making use of rdma cgroup.

Resource limit enforcement is hierarchical.

When process is migrated with active RDMA resources, rdma cgroup
continues to uncharge original cgroup for allocated resource. New resource
is charged to current process's cgroup, which means if the process is
migrated with active resources, for new resources it will be charged to
new cgroup and old resources will be correctly uncharged from old cgroup.

Changes from v9:
* (To address comments from Tejun)
1. Included clear documentation of resources.
2. Fixed issue of race condition of process migration during
charging stage.
3. Fixed comments and code to adhere to CodingStyle.
4. Simplified and removed support to charge/uncharge multiple
resource.
5. Fixed replaced refcnt with usage_num that tracks how many
resources are unused to trigger freeing the object.
6. Simplified locking scheme to use single spin lock for whole
subsystem.

Changes from v8:
* Fixed compilation error.
* Fixed warning reported by checkpatch script.

Changes from v7:
* (To address comments from Haggai)
1. Removed max_limit from query_limit function as it is
unnecessary.
2. Kept existing printk as it is to instead of replacing all
with pr_warn except newly added printk.

Changes from v6:
* (To address comments from Haggai)
1. Made functions as void wherever necessary.
2. Code cleanup related to correting few spelling mistakes
in comments, correcting comments to reflect the code.
3. Removed max_count parameter from query_limit as its not
necessary.
4. Fixed printk to pr_warn.
5. Removed dependency on pd, instead relying on ib_dev.
6. Added more documentation to reflect that IB stack honors
configured limit during query_device operation.
7. Added pr_warn and avoided system crash in case of
IB stack or rdma cgroup bug.
* (To address comments from Leon)
1. Removed #ifdef CONFIG_CGROUP_RDMA from .c files and added
necessary dummy functions in header file.
2. Removed unwanted forward declaration.
* Fixed uncharing to rdma controller after resource is released
from verb layer, instead of uncharing first. This ensures that
uncharging doesn't complete while resource is still allocated.

Changes from v5:
* (To address comments from Tejun)
1. Removed two type of resource pool, made is single type (as Tejun
described in past comment)
2. Removed match tokens and have array definition like "qp", "mr",
"cq" etc.
3. Wrote small parser and avoided match_token API as that won't work
due to different array definitions
4. Removed one-off remove API to unconfigure cgroup, instead all
resource should be set to max.
5. Removed resource pool type (user/default), instead having
max_num_cnt, when ref_cnt drops to zero and
max_num_cnt = total_rescource_cnt, pool is freed.
6. Resource definition ownership is now only with IB stack at single
header file, no longer in each low level driver.
This goes through IB maintainer and other reviewers eyes.
This continue to give flexibility to not force kernel upgrade for
few enums additions for new resource type.
7. Wherever possible pool lock is pushed out, except for hierarchical
charging/unchanging points, as it not possible to do so, due to
iterative process involves blocking allocations of rpool. Coming up
more levels up to release locks doesn't make any sense either.
This is anyway slow path where rpool is not allocated. Except for
typical first resource allocation, this is less travelled path.
8. Avoided %d manipulation due to removal of match_token and replaced
with seq_putc etc friend functions.
* Other minor cleanups.
* Fixed rdmacg_register_device to return error in case of IB stack
tries to register for than 64 resources.
* Fixed not allowing negative value on resource setting.
* Fixed cleaning up resource pools during device removal.
* Simplfied and rename table length field to use ARRAY_SIZE macro.
* Updated documentation to reflect single resource pool and shorter
file names.

Changes from v4:
* Fixed compilation errors for lockdep_assert_held reported by kbuild
test robot
* Fixed compilation warning reported by coccinelle for extra
semicolon.
* Fixed compilation error for inclusion of linux/parser.h which
cannot be included in any header file, as that triggers multiple
inclusion error. parser.h is included in C files which intent to
use it.
* Removed unused header file inclusion in cgroup_rdma.c

Changes from v3:
* (To address comments from Tejun)
1. Renamed cg_resource to rdmacg_resource
2. Merged dealloc_cg_rpool and _dealloc_cg_rpool to single function
3. Renamed _find_cg_rpool to find_cg_rpool_locked()
5. Removed RDMACG_MAX_RESOURCE_INDEX limitation
6. Fixed few alignments.
7. Improved description for RDMA cgroup configuration menu
8. Renamed cg_list_lock to rpool_list_lock to reflect the lock
is for rpools.
9. Renamed _get_cg_rpool to find_cg_rpool.
10. Made creator as int variable, instead of atomic as its not
required to be atomic.
* Fixed freeing right rpool during query_limit error path
* Added copywrite for cgroup.c
* Removed including parser.h from cgroup.c as its included by
cgroup_rdma.h
* Reduced try_charge functions to single function and removed duplicate
comments.

Changes from v2:
* Fixed compilation error reported by 0-DAY kernel test infrastructure
for m68k architecture where CONFIG_CGROUP is also not defined.
* Fixed comment in patch to refer to legacy mode of cgroup, changed to
refer to v1 and v2 version.
* Added more information in commit log for rdma controller patch.

Changes from v1:
* (To address comments from Tejun)
a. reduces 3 patches to single patch
b. removed resource word from the cgroup configuration files
c. changed cgroup configuration file names to match other cgroups
d. removed .list file and merged functionality with .max file
* Based on comment to merge to single patch for rdma controller;
IB/core patches are reduced to single patch.
* Removed pid cgroup map and simplified design -
Charge/Uncharge caller stack keeps track of the rdmacg for
given resource. This removes the need to maintain and perform
hash lookup. This also allows little more accurate resource
charging/uncharging when process moved from one to other cgroup
with active resources and continue to allocate more.
* Critical fix: Removed rdma cgroup's dependency on the kernel module
header files to avoid crashes when modules are upgraded without kernel
upgrade, which is very common due to high amount of changes in IB stack
and it is also shipped as individual kernel modules.
* uboject extended to keep track of the owner rdma cgroup, so that same
rdmacg can be used while uncharging.
* Added support functions to hide details of rdmacg device in uverbs
modules for cases of cgroup enabled/disabled at compile time. This
avoids multiple ifdefs for every API in uverbs layer.
* Removed failure counters in first patch, which will be added once
initial feature is merged.
* Fixed stale rpool access which is getting freed, while doing
configuration to rdma.verb.max file.
* Fixed rpool resource leak while querying max, current values.

Changes from v0:
(To address comments from Haggai, Doug, Liran, Tejun, Sean, Jason)
* Redesigned to support per device per cgroup limit settings by bringing
concept of resource pool.
* Redesigned to let IB stack define the resources instead of rdma
controller using resource template.
* Redesigned to support hw vendor specific limits setting
(optional to drivers).
* Created new rdma controller instead of piggyback on device cgroup.
* Fixed race conditions for multiple tasks sharing rdma resources.
* Removed dependency on the task_struct.


Parav Pandit (3):
rdmacg: Added rdma cgroup controller
IB/core: added support to use rdma cgroup controller
rdmacg: Added documentation for rdmacg

Documentation/cgroup-v1/rdma.txt | 123 ++++++
Documentation/cgroup-v2.txt | 43 ++
drivers/infiniband/core/Makefile | 1 +
drivers/infiniband/core/cgroup.c | 106 +++++
drivers/infiniband/core/core_priv.h | 40 ++
drivers/infiniband/core/device.c | 10 +
drivers/infiniband/core/uverbs_cmd.c | 135 +++++-
drivers/infiniband/core/uverbs_main.c | 19 +
include/linux/cgroup_rdma.h | 52 +++
include/linux/cgroup_subsys.h | 4 +
include/rdma/ib_verbs.h | 29 ++
init/Kconfig | 10 +
kernel/Makefile | 1 +
kernel/cgroup_rdma.c | 746 ++++++++++++++++++++++++++++++++++
14 files changed, 1304 insertions(+), 15 deletions(-)
create mode 100644 Documentation/cgroup-v1/rdma.txt
create mode 100644 drivers/infiniband/core/cgroup.c
create mode 100644 include/linux/cgroup_rdma.h
create mode 100644 kernel/cgroup_rdma.c

--
1.8.3.1


2016-03-24 20:24:38

by Parav Pandit

[permalink] [raw]
Subject: [PATCHv10 2/3] IB/core: added support to use rdma cgroup controller

Added support APIs for IB core to register/unregister every IB/RDMA
device with rdma cgroup for tracking verbs and hw resources.
IB core registers with rdma cgroup controller and also defines
resources that can be accounted.
Added support APIs for uverbs layer to make use of rdma controller.
Added uverbs layer to perform resource charge/uncharge functionality.
Added support during query_device uverb operation to ensure it
returns resource limits by honoring rdma cgroup configured limits.

Signed-off-by: Parav Pandit <[email protected]>
---
drivers/infiniband/core/Makefile | 1 +
drivers/infiniband/core/cgroup.c | 106 ++++++++++++++++++++++++++
drivers/infiniband/core/core_priv.h | 40 ++++++++++
drivers/infiniband/core/device.c | 10 +++
drivers/infiniband/core/uverbs_cmd.c | 135 ++++++++++++++++++++++++++++++----
drivers/infiniband/core/uverbs_main.c | 19 +++++
include/rdma/ib_verbs.h | 29 ++++++++
7 files changed, 325 insertions(+), 15 deletions(-)
create mode 100644 drivers/infiniband/core/cgroup.c

diff --git a/drivers/infiniband/core/Makefile b/drivers/infiniband/core/Makefile
index f818538..60d9e44 100644
--- a/drivers/infiniband/core/Makefile
+++ b/drivers/infiniband/core/Makefile
@@ -13,6 +13,7 @@ ib_core-y := packer.o ud_header.o verbs.o cq.o sysfs.o \
roce_gid_mgmt.o
ib_core-$(CONFIG_INFINIBAND_USER_MEM) += umem.o
ib_core-$(CONFIG_INFINIBAND_ON_DEMAND_PAGING) += umem_odp.o umem_rbtree.o
+ib_core-$(CONFIG_CGROUP_RDMA) += cgroup.o

ib_mad-y := mad.o smi.o agent.o mad_rmpp.o

diff --git a/drivers/infiniband/core/cgroup.c b/drivers/infiniband/core/cgroup.c
new file mode 100644
index 0000000..1d08d37
--- /dev/null
+++ b/drivers/infiniband/core/cgroup.c
@@ -0,0 +1,106 @@
+/*
+ * This software is available to you under a choice of one of two
+ * licenses. You may choose to be licensed under the terms of the GNU
+ * General Public License (GPL) Version 2, available from the file
+ * COPYING in the main directory of this source tree, or the
+ * OpenIB.org BSD license below:
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <linux/cgroup_rdma.h>
+
+#include "core_priv.h"
+
+/*
+ * resource table definition as to be seen by the user.
+ * Need to add entries to it when more resources are
+ * added/defined at IB verb/core layer.
+ */
+static char const *resource_tokens[] = {
+ [RDMA_VERB_RESOURCE_UCTX] = "uctx",
+ [RDMA_VERB_RESOURCE_AH] = "ah",
+ [RDMA_VERB_RESOURCE_PD] = "pd",
+ [RDMA_VERB_RESOURCE_CQ] = "cq",
+ [RDMA_VERB_RESOURCE_MR] = "mr",
+ [RDMA_VERB_RESOURCE_MW] = "mw",
+ [RDMA_VERB_RESOURCE_SRQ] = "srq",
+ [RDMA_VERB_RESOURCE_QP] = "qp",
+ [RDMA_VERB_RESOURCE_FLOW] = "flow",
+};
+
+/**
+ * ib_device_register_rdmacg - register with rdma cgroup.
+ * @device: device to register to participate in resource
+ * accounting by rdma cgroup.
+ *
+ * Register with the rdma cgroup. Should be called before
+ * exposing rdma device to user space applications to avoid
+ * resource accounting leak.
+ * Returns 0 on success or otherwise failure code.
+ */
+int ib_device_register_rdmacg(struct ib_device *device)
+{
+ device->cg_device.name = device->name;
+ device->cg_device.pool_info.resource_name_table = resource_tokens;
+ device->cg_device.pool_info.table_len = ARRAY_SIZE(resource_tokens);
+ return rdmacg_register_device(&device->cg_device);
+}
+
+/**
+ * ib_device_unregister_rdmacg - unregister with rdma cgroup.
+ * @device: device to unregister.
+ *
+ * Unregister with the rdma cgroup. Should be called after
+ * all the resources are deallocated, and after a stage when any
+ * other resource allocation by user application cannot be done
+ * for this device to avoid any leak in accounting.
+ */
+void ib_device_unregister_rdmacg(struct ib_device *device)
+{
+ rdmacg_unregister_device(&device->cg_device);
+}
+
+int ib_rdmacg_try_charge(struct ib_rdmacg_object *cg_obj,
+ struct ib_device *device,
+ int resource_index)
+{
+ return rdmacg_try_charge(&cg_obj->cg, &device->cg_device,
+ resource_index);
+}
+EXPORT_SYMBOL(ib_rdmacg_try_charge);
+
+void ib_rdmacg_uncharge(struct ib_rdmacg_object *cg_obj,
+ struct ib_device *device,
+ int resource_index)
+{
+ rdmacg_uncharge(cg_obj->cg, &device->cg_device,
+ resource_index);
+}
+EXPORT_SYMBOL(ib_rdmacg_uncharge);
+
+void ib_rdmacg_query_limit(struct ib_device *device, int *limits)
+{
+ rdmacg_query_limit(&device->cg_device, limits);
+}
+EXPORT_SYMBOL(ib_rdmacg_query_limit);
diff --git a/drivers/infiniband/core/core_priv.h b/drivers/infiniband/core/core_priv.h
index eab3221..c693745 100644
--- a/drivers/infiniband/core/core_priv.h
+++ b/drivers/infiniband/core/core_priv.h
@@ -124,6 +124,46 @@ int ib_cache_setup_one(struct ib_device *device);
void ib_cache_cleanup_one(struct ib_device *device);
void ib_cache_release_one(struct ib_device *device);

+#ifdef CONFIG_CGROUP_RDMA
+int ib_device_register_rdmacg(struct ib_device *device);
+void ib_device_unregister_rdmacg(struct ib_device *device);
+
+int ib_rdmacg_try_charge(struct ib_rdmacg_object *cg_obj,
+ struct ib_device *device,
+ int resource_index);
+
+void ib_rdmacg_uncharge(struct ib_rdmacg_object *cg_obj,
+ struct ib_device *device,
+ int resource_index);
+
+void ib_rdmacg_query_limit(struct ib_device *device, int *limits);
+#else
+static inline int ib_device_register_rdmacg(struct ib_device *device)
+{ return 0; }
+
+static inline void ib_device_unregister_rdmacg(struct ib_device *device)
+{ }
+
+static inline int ib_rdmacg_try_charge(struct ib_rdmacg_object *cg_obj,
+ struct ib_device *device,
+ int resource_index)
+{ return 0; }
+
+static inline void ib_rdmacg_uncharge(struct ib_rdmacg_object *cg_obj,
+ struct ib_device *device,
+ int resource_index)
+{ }
+
+static inline void ib_rdmacg_query_limit(struct ib_device *device,
+ int *limits)
+{
+ int i;
+
+ for (i = 0; i < RDMA_RESOURCE_MAX; i++)
+ limits[i] = S32_MAX;
+}
+#endif
+
static inline bool rdma_is_upper_dev_rcu(struct net_device *dev,
struct net_device *upper)
{
diff --git a/drivers/infiniband/core/device.c b/drivers/infiniband/core/device.c
index 270c7ff..defb444 100644
--- a/drivers/infiniband/core/device.c
+++ b/drivers/infiniband/core/device.c
@@ -354,10 +354,18 @@ int ib_register_device(struct ib_device *device,
goto out;
}

+ ret = ib_device_register_rdmacg(device);
+ if (ret) {
+ pr_warn("Couldn't register device with rdma cgroup\n");
+ ib_cache_cleanup_one(device);
+ goto out;
+ }
+
memset(&device->attrs, 0, sizeof(device->attrs));
ret = device->query_device(device, &device->attrs, &uhw);
if (ret) {
pr_warn("Couldn't query the device attributes\n");
+ ib_device_unregister_rdmacg(device);
ib_cache_cleanup_one(device);
goto out;
}
@@ -366,6 +374,7 @@ int ib_register_device(struct ib_device *device,
if (ret) {
pr_warn("Couldn't register device %s with driver model\n",
device->name);
+ ib_device_unregister_rdmacg(device);
ib_cache_cleanup_one(device);
goto out;
}
@@ -415,6 +424,7 @@ void ib_unregister_device(struct ib_device *device)

mutex_unlock(&device_mutex);

+ ib_device_unregister_rdmacg(device);
ib_device_unregister_sysfs(device);
ib_cache_cleanup_one(device);

diff --git a/drivers/infiniband/core/uverbs_cmd.c b/drivers/infiniband/core/uverbs_cmd.c
index 3638c78..8b67d24 100644
--- a/drivers/infiniband/core/uverbs_cmd.c
+++ b/drivers/infiniband/core/uverbs_cmd.c
@@ -293,6 +293,7 @@ ssize_t ib_uverbs_get_context(struct ib_uverbs_file *file,
struct ib_udata udata;
struct ib_ucontext *ucontext;
struct file *filp;
+ struct ib_rdmacg_object cg_obj;
int ret;

if (out_len < sizeof resp)
@@ -312,13 +313,18 @@ ssize_t ib_uverbs_get_context(struct ib_uverbs_file *file,
(unsigned long) cmd.response + sizeof resp,
in_len - sizeof cmd, out_len - sizeof resp);

+ ret = ib_rdmacg_try_charge(&cg_obj, ib_dev, RDMA_VERB_RESOURCE_UCTX);
+ if (ret)
+ goto err;
+
ucontext = ib_dev->alloc_ucontext(ib_dev, &udata);
if (IS_ERR(ucontext)) {
ret = PTR_ERR(ucontext);
- goto err;
+ goto err_alloc;
}

ucontext->device = ib_dev;
+ ucontext->cg_obj = cg_obj;
INIT_LIST_HEAD(&ucontext->pd_list);
INIT_LIST_HEAD(&ucontext->mr_list);
INIT_LIST_HEAD(&ucontext->mw_list);
@@ -382,6 +388,9 @@ err_free:
put_pid(ucontext->tgid);
ib_dev->dealloc_ucontext(ucontext);

+err_alloc:
+ ib_rdmacg_uncharge(&cg_obj, ib_dev, RDMA_VERB_RESOURCE_UCTX);
+
err:
mutex_unlock(&file->mutex);
return ret;
@@ -390,7 +399,8 @@ err:
static void copy_query_dev_fields(struct ib_uverbs_file *file,
struct ib_device *ib_dev,
struct ib_uverbs_query_device_resp *resp,
- struct ib_device_attr *attr)
+ struct ib_device_attr *attr,
+ int *limits)
{
resp->fw_ver = attr->fw_ver;
resp->node_guid = ib_dev->node_guid;
@@ -400,15 +410,19 @@ static void copy_query_dev_fields(struct ib_uverbs_file *file,
resp->vendor_id = attr->vendor_id;
resp->vendor_part_id = attr->vendor_part_id;
resp->hw_ver = attr->hw_ver;
- resp->max_qp = attr->max_qp;
+ resp->max_qp = min_t(int, attr->max_qp,
+ limits[RDMA_VERB_RESOURCE_QP]);
resp->max_qp_wr = attr->max_qp_wr;
resp->device_cap_flags = attr->device_cap_flags;
resp->max_sge = attr->max_sge;
resp->max_sge_rd = attr->max_sge_rd;
- resp->max_cq = attr->max_cq;
+ resp->max_cq = min_t(int, attr->max_cq,
+ limits[RDMA_VERB_RESOURCE_CQ]);
resp->max_cqe = attr->max_cqe;
- resp->max_mr = attr->max_mr;
- resp->max_pd = attr->max_pd;
+ resp->max_mr = min_t(int, attr->max_mr,
+ limits[RDMA_VERB_RESOURCE_MR]);
+ resp->max_pd = min_t(int, attr->max_pd,
+ limits[RDMA_VERB_RESOURCE_PD]);
resp->max_qp_rd_atom = attr->max_qp_rd_atom;
resp->max_ee_rd_atom = attr->max_ee_rd_atom;
resp->max_res_rd_atom = attr->max_res_rd_atom;
@@ -417,16 +431,19 @@ static void copy_query_dev_fields(struct ib_uverbs_file *file,
resp->atomic_cap = attr->atomic_cap;
resp->max_ee = attr->max_ee;
resp->max_rdd = attr->max_rdd;
- resp->max_mw = attr->max_mw;
+ resp->max_mw = min_t(int, attr->max_mw,
+ limits[RDMA_VERB_RESOURCE_MW]);
resp->max_raw_ipv6_qp = attr->max_raw_ipv6_qp;
resp->max_raw_ethy_qp = attr->max_raw_ethy_qp;
resp->max_mcast_grp = attr->max_mcast_grp;
resp->max_mcast_qp_attach = attr->max_mcast_qp_attach;
resp->max_total_mcast_qp_attach = attr->max_total_mcast_qp_attach;
- resp->max_ah = attr->max_ah;
+ resp->max_ah = min_t(int, attr->max_ah,
+ limits[RDMA_VERB_RESOURCE_AH]);
resp->max_fmr = attr->max_fmr;
resp->max_map_per_fmr = attr->max_map_per_fmr;
- resp->max_srq = attr->max_srq;
+ resp->max_srq = min_t(int, attr->max_srq,
+ limits[RDMA_VERB_RESOURCE_SRQ]);
resp->max_srq_wr = attr->max_srq_wr;
resp->max_srq_sge = attr->max_srq_sge;
resp->max_pkeys = attr->max_pkeys;
@@ -441,6 +458,7 @@ ssize_t ib_uverbs_query_device(struct ib_uverbs_file *file,
{
struct ib_uverbs_query_device cmd;
struct ib_uverbs_query_device_resp resp;
+ int limits[RDMA_RESOURCE_MAX];

if (out_len < sizeof resp)
return -ENOSPC;
@@ -448,8 +466,10 @@ ssize_t ib_uverbs_query_device(struct ib_uverbs_file *file,
if (copy_from_user(&cmd, buf, sizeof cmd))
return -EFAULT;

+ ib_rdmacg_query_limit(ib_dev, limits);
+
memset(&resp, 0, sizeof resp);
- copy_query_dev_fields(file, ib_dev, &resp, &ib_dev->attrs);
+ copy_query_dev_fields(file, ib_dev, &resp, &ib_dev->attrs, limits);

if (copy_to_user((void __user *) (unsigned long) cmd.response,
&resp, sizeof resp))
@@ -535,6 +555,13 @@ ssize_t ib_uverbs_alloc_pd(struct ib_uverbs_file *file,
if (!uobj)
return -ENOMEM;

+ ret = ib_rdmacg_try_charge(&uobj->cg_obj, ib_dev,
+ RDMA_VERB_RESOURCE_PD);
+ if (ret) {
+ kfree(uobj);
+ return -EPERM;
+ }
+
init_uobj(uobj, 0, file->ucontext, &pd_lock_class);
down_write(&uobj->mutex);

@@ -580,6 +607,7 @@ err_idr:
ib_dealloc_pd(pd);

err:
+ ib_rdmacg_uncharge(&uobj->cg_obj, ib_dev, RDMA_VERB_RESOURCE_PD);
put_uobj_write(uobj);
return ret;
}
@@ -612,6 +640,8 @@ ssize_t ib_uverbs_dealloc_pd(struct ib_uverbs_file *file,
if (ret)
goto err_put;

+ ib_rdmacg_uncharge(&uobj->cg_obj, ib_dev, RDMA_VERB_RESOURCE_PD);
+
uobj->live = 0;
put_uobj_write(uobj);

@@ -982,6 +1012,11 @@ ssize_t ib_uverbs_reg_mr(struct ib_uverbs_file *file,
}
}

+ ret = ib_rdmacg_try_charge(&uobj->cg_obj, ib_dev,
+ RDMA_VERB_RESOURCE_MR);
+ if (ret)
+ goto err_charge;
+
mr = pd->device->reg_user_mr(pd, cmd.start, cmd.length, cmd.hca_va,
cmd.access_flags, &udata);
if (IS_ERR(mr)) {
@@ -1029,6 +1064,9 @@ err_unreg:
ib_dereg_mr(mr);

err_put:
+ ib_rdmacg_uncharge(&uobj->cg_obj, ib_dev, RDMA_VERB_RESOURCE_MR);
+
+err_charge:
put_pd_read(pd);

err_free:
@@ -1153,6 +1191,8 @@ ssize_t ib_uverbs_dereg_mr(struct ib_uverbs_file *file,
if (ret)
return ret;

+ ib_rdmacg_uncharge(&uobj->cg_obj, ib_dev, RDMA_VERB_RESOURCE_MR);
+
idr_remove_uobj(&ib_uverbs_mr_idr, uobj);

mutex_lock(&file->mutex);
@@ -1201,6 +1241,11 @@ ssize_t ib_uverbs_alloc_mw(struct ib_uverbs_file *file,
in_len - sizeof(cmd) - sizeof(struct ib_uverbs_cmd_hdr),
out_len - sizeof(resp));

+ ret = ib_rdmacg_try_charge(&uobj->cg_obj, ib_dev,
+ RDMA_VERB_RESOURCE_MW);
+ if (ret)
+ goto err_charge;
+
mw = pd->device->alloc_mw(pd, cmd.mw_type, &udata);
if (IS_ERR(mw)) {
ret = PTR_ERR(mw);
@@ -1246,6 +1291,9 @@ err_unalloc:
uverbs_dealloc_mw(mw);

err_put:
+ ib_rdmacg_uncharge(&uobj->cg_obj, ib_dev, RDMA_VERB_RESOURCE_MW);
+
+err_charge:
put_pd_read(pd);

err_free:
@@ -1281,6 +1329,8 @@ ssize_t ib_uverbs_dealloc_mw(struct ib_uverbs_file *file,
if (ret)
return ret;

+ ib_rdmacg_uncharge(&uobj->cg_obj, ib_dev, RDMA_VERB_RESOURCE_MW);
+
idr_remove_uobj(&ib_uverbs_mw_idr, uobj);

mutex_lock(&file->mutex);
@@ -1380,6 +1430,11 @@ static struct ib_ucq_object *create_cq(struct ib_uverbs_file *file,
if (cmd_sz > offsetof(typeof(*cmd), flags) + sizeof(cmd->flags))
attr.flags = cmd->flags;

+ ret = ib_rdmacg_try_charge(&obj->uobject.cg_obj, ib_dev,
+ RDMA_VERB_RESOURCE_CQ);
+ if (ret)
+ goto err_charge;
+
cq = ib_dev->create_cq(ib_dev, &attr,
file->ucontext, uhw);
if (IS_ERR(cq)) {
@@ -1427,6 +1482,10 @@ err_free:
ib_destroy_cq(cq);

err_file:
+ ib_rdmacg_uncharge(&obj->uobject.cg_obj, ib_dev,
+ RDMA_VERB_RESOURCE_CQ);
+
+err_charge:
if (ev_file)
ib_uverbs_release_ucq(file, ev_file, obj);

@@ -1707,6 +1766,8 @@ ssize_t ib_uverbs_destroy_cq(struct ib_uverbs_file *file,
if (ret)
return ret;

+ ib_rdmacg_uncharge(&uobj->cg_obj, ib_dev, RDMA_VERB_RESOURCE_CQ);
+
idr_remove_uobj(&ib_uverbs_cq_idr, uobj);

mutex_lock(&file->mutex);
@@ -1846,6 +1907,12 @@ static int create_qp(struct ib_uverbs_file *file,
goto err_put;
}

+ ret = ib_rdmacg_try_charge(&obj->uevent.uobject.cg_obj,
+ device,
+ RDMA_VERB_RESOURCE_QP);
+ if (ret)
+ goto err_put;
+
if (cmd->qp_type == IB_QPT_XRC_TGT)
qp = ib_create_qp(pd, &attr);
else
@@ -1853,7 +1920,7 @@ static int create_qp(struct ib_uverbs_file *file,

if (IS_ERR(qp)) {
ret = PTR_ERR(qp);
- goto err_put;
+ goto err_create;
}

if (cmd->qp_type != IB_QPT_XRC_TGT) {
@@ -1928,6 +1995,10 @@ err_cb:
err_destroy:
ib_destroy_qp(qp);

+err_create:
+ ib_rdmacg_uncharge(&obj->uevent.uobject.cg_obj, device,
+ RDMA_VERB_RESOURCE_QP);
+
err_put:
if (xrcd)
put_xrcd_read(xrcd_uobj);
@@ -2396,6 +2467,8 @@ ssize_t ib_uverbs_destroy_qp(struct ib_uverbs_file *file,
if (ret)
return ret;

+ ib_rdmacg_uncharge(&uobj->cg_obj, ib_dev, RDMA_VERB_RESOURCE_QP);
+
if (obj->uxrcd)
atomic_dec(&obj->uxrcd->refcnt);

@@ -2842,10 +2915,15 @@ ssize_t ib_uverbs_create_ah(struct ib_uverbs_file *file,
memset(&attr.dmac, 0, sizeof(attr.dmac));
memcpy(attr.grh.dgid.raw, cmd.attr.grh.dgid, 16);

+ ret = ib_rdmacg_try_charge(&uobj->cg_obj, ib_dev,
+ RDMA_VERB_RESOURCE_AH);
+ if (ret)
+ goto err_put;
+
ah = ib_create_ah(pd, &attr);
if (IS_ERR(ah)) {
ret = PTR_ERR(ah);
- goto err_put;
+ goto err_create;
}

ah->uobject = uobj;
@@ -2881,6 +2959,9 @@ err_copy:
err_destroy:
ib_destroy_ah(ah);

+err_create:
+ ib_rdmacg_uncharge(&uobj->cg_obj, ib_dev, RDMA_VERB_RESOURCE_AH);
+
err_put:
put_pd_read(pd);

@@ -2915,6 +2996,8 @@ ssize_t ib_uverbs_destroy_ah(struct ib_uverbs_file *file,
if (ret)
return ret;

+ ib_rdmacg_uncharge(&uobj->cg_obj, ib_dev, RDMA_VERB_RESOURCE_AH);
+
idr_remove_uobj(&ib_uverbs_ah_idr, uobj);

mutex_lock(&file->mutex);
@@ -3175,10 +3258,16 @@ int ib_uverbs_ex_create_flow(struct ib_uverbs_file *file,
err = -EINVAL;
goto err_free;
}
+
+ err = ib_rdmacg_try_charge(&uobj->cg_obj, ib_dev,
+ RDMA_VERB_RESOURCE_FLOW);
+ if (err)
+ goto err_free;
+
flow_id = ib_create_flow(qp, flow_attr, IB_FLOW_DOMAIN_USER);
if (IS_ERR(flow_id)) {
err = PTR_ERR(flow_id);
- goto err_free;
+ goto err_create;
}
flow_id->qp = qp;
flow_id->uobject = uobj;
@@ -3212,6 +3301,8 @@ err_copy:
idr_remove_uobj(&ib_uverbs_rule_idr, uobj);
destroy_flow:
ib_destroy_flow(flow_id);
+err_create:
+ ib_rdmacg_uncharge(&uobj->cg_obj, ib_dev, RDMA_VERB_RESOURCE_FLOW);
err_free:
kfree(flow_attr);
err_put:
@@ -3251,8 +3342,11 @@ int ib_uverbs_ex_destroy_flow(struct ib_uverbs_file *file,
flow_id = uobj->object;

ret = ib_destroy_flow(flow_id);
- if (!ret)
+ if (!ret) {
uobj->live = 0;
+ ib_rdmacg_uncharge(&uobj->cg_obj, ib_dev,
+ RDMA_VERB_RESOURCE_FLOW);
+ }

put_uobj_write(uobj);

@@ -3320,6 +3414,11 @@ static int __uverbs_create_xsrq(struct ib_uverbs_file *file,
obj->uevent.events_reported = 0;
INIT_LIST_HEAD(&obj->uevent.event_list);

+ ret = ib_rdmacg_try_charge(&obj->uevent.uobject.cg_obj, ib_dev,
+ RDMA_VERB_RESOURCE_SRQ);
+ if (ret)
+ goto err_put_cq;
+
srq = pd->device->create_srq(pd, &attr, udata);
if (IS_ERR(srq)) {
ret = PTR_ERR(srq);
@@ -3384,6 +3483,8 @@ err_destroy:
ib_destroy_srq(srq);

err_put:
+ ib_rdmacg_uncharge(&obj->uevent.uobject.cg_obj, ib_dev,
+ RDMA_VERB_RESOURCE_SRQ);
put_pd_read(pd);

err_put_cq:
@@ -3570,6 +3671,8 @@ ssize_t ib_uverbs_destroy_srq(struct ib_uverbs_file *file,
if (ret)
return ret;

+ ib_rdmacg_uncharge(&uobj->cg_obj, ib_dev, RDMA_VERB_RESOURCE_SRQ);
+
if (srq_type == IB_SRQT_XRC) {
us = container_of(obj, struct ib_usrq_object, uevent);
atomic_dec(&us->uxrcd->refcnt);
@@ -3603,6 +3706,7 @@ int ib_uverbs_ex_query_device(struct ib_uverbs_file *file,
struct ib_uverbs_ex_query_device_resp resp;
struct ib_uverbs_ex_query_device cmd;
struct ib_device_attr attr;
+ int limits[RDMA_RESOURCE_MAX];
int err;

if (ucore->inlen < sizeof(cmd))
@@ -3629,7 +3733,8 @@ int ib_uverbs_ex_query_device(struct ib_uverbs_file *file,
if (err)
return err;

- copy_query_dev_fields(file, ib_dev, &resp.base, &attr);
+ ib_rdmacg_query_limit(ib_dev, limits);
+ copy_query_dev_fields(file, ib_dev, &resp.base, &attr, limits);
resp.comp_mask = 0;

if (ucore->outlen < resp.response_length + sizeof(resp.odp_caps))
diff --git a/drivers/infiniband/core/uverbs_main.c b/drivers/infiniband/core/uverbs_main.c
index 28ba2cc..3b32964 100644
--- a/drivers/infiniband/core/uverbs_main.c
+++ b/drivers/infiniband/core/uverbs_main.c
@@ -49,6 +49,7 @@
#include <asm/uaccess.h>

#include "uverbs.h"
+#include "core_priv.h"

MODULE_AUTHOR("Roland Dreier");
MODULE_DESCRIPTION("InfiniBand userspace verbs access");
@@ -227,6 +228,8 @@ static int ib_uverbs_cleanup_ucontext(struct ib_uverbs_file *file,

idr_remove_uobj(&ib_uverbs_ah_idr, uobj);
ib_destroy_ah(ah);
+ ib_rdmacg_uncharge(&uobj->cg_obj, context->device,
+ RDMA_VERB_RESOURCE_AH);
kfree(uobj);
}

@@ -236,6 +239,8 @@ static int ib_uverbs_cleanup_ucontext(struct ib_uverbs_file *file,

idr_remove_uobj(&ib_uverbs_mw_idr, uobj);
uverbs_dealloc_mw(mw);
+ ib_rdmacg_uncharge(&uobj->cg_obj, context->device,
+ RDMA_VERB_RESOURCE_MW);
kfree(uobj);
}

@@ -244,6 +249,8 @@ static int ib_uverbs_cleanup_ucontext(struct ib_uverbs_file *file,

idr_remove_uobj(&ib_uverbs_rule_idr, uobj);
ib_destroy_flow(flow_id);
+ ib_rdmacg_uncharge(&uobj->cg_obj, context->device,
+ RDMA_VERB_RESOURCE_FLOW);
kfree(uobj);
}

@@ -258,6 +265,8 @@ static int ib_uverbs_cleanup_ucontext(struct ib_uverbs_file *file,
} else {
ib_uverbs_detach_umcast(qp, uqp);
ib_destroy_qp(qp);
+ ib_rdmacg_uncharge(&uobj->cg_obj, context->device,
+ RDMA_VERB_RESOURCE_QP);
}
ib_uverbs_release_uevent(file, &uqp->uevent);
kfree(uqp);
@@ -271,6 +280,8 @@ static int ib_uverbs_cleanup_ucontext(struct ib_uverbs_file *file,
idr_remove_uobj(&ib_uverbs_srq_idr, uobj);
ib_destroy_srq(srq);
ib_uverbs_release_uevent(file, uevent);
+ ib_rdmacg_uncharge(&uobj->cg_obj, context->device,
+ RDMA_VERB_RESOURCE_SRQ);
kfree(uevent);
}

@@ -282,6 +293,8 @@ static int ib_uverbs_cleanup_ucontext(struct ib_uverbs_file *file,

idr_remove_uobj(&ib_uverbs_cq_idr, uobj);
ib_destroy_cq(cq);
+ ib_rdmacg_uncharge(&uobj->cg_obj, context->device,
+ RDMA_VERB_RESOURCE_CQ);
ib_uverbs_release_ucq(file, ev_file, ucq);
kfree(ucq);
}
@@ -291,6 +304,8 @@ static int ib_uverbs_cleanup_ucontext(struct ib_uverbs_file *file,

idr_remove_uobj(&ib_uverbs_mr_idr, uobj);
ib_dereg_mr(mr);
+ ib_rdmacg_uncharge(&uobj->cg_obj, context->device,
+ RDMA_VERB_RESOURCE_MR);
kfree(uobj);
}

@@ -311,9 +326,13 @@ static int ib_uverbs_cleanup_ucontext(struct ib_uverbs_file *file,

idr_remove_uobj(&ib_uverbs_pd_idr, uobj);
ib_dealloc_pd(pd);
+ ib_rdmacg_uncharge(&uobj->cg_obj, context->device,
+ RDMA_VERB_RESOURCE_PD);
kfree(uobj);
}

+ ib_rdmacg_uncharge(&context->cg_obj, context->device,
+ RDMA_VERB_RESOURCE_UCTX);
put_pid(context->tgid);

return context->device->dealloc_ucontext(context);
diff --git a/include/rdma/ib_verbs.h b/include/rdma/ib_verbs.h
index 3a03c1d..11cc4a0 100644
--- a/include/rdma/ib_verbs.h
+++ b/include/rdma/ib_verbs.h
@@ -59,6 +59,7 @@
#include <linux/atomic.h>
#include <linux/mmu_notifier.h>
#include <asm/uaccess.h>
+#include <linux/cgroup_rdma.h>

extern struct workqueue_struct *ib_wq;
extern struct workqueue_struct *ib_comp_wq;
@@ -111,6 +112,22 @@ enum rdma_protocol_type {
RDMA_PROTOCOL_USNIC_UDP
};

+enum rdma_resource_type {
+ RDMA_VERB_RESOURCE_UCTX,
+ RDMA_VERB_RESOURCE_AH,
+ RDMA_VERB_RESOURCE_PD,
+ RDMA_VERB_RESOURCE_CQ,
+ RDMA_VERB_RESOURCE_MR,
+ RDMA_VERB_RESOURCE_MW,
+ RDMA_VERB_RESOURCE_SRQ,
+ RDMA_VERB_RESOURCE_QP,
+ RDMA_VERB_RESOURCE_FLOW,
+ /*
+ * add any hw specific resource here as RDMA_HW_RESOURCE_NAME
+ */
+ RDMA_RESOURCE_MAX,
+};
+
__attribute_const__ enum rdma_transport_type
rdma_node_get_transport(enum rdma_node_type node_type);

@@ -1288,6 +1305,12 @@ struct ib_fmr_attr {
u8 page_shift;
};

+struct ib_rdmacg_object {
+#ifdef CONFIG_CGROUP_RDMA
+ struct rdma_cgroup *cg; /* owner rdma cgroup */
+#endif
+};
+
struct ib_umem;

struct ib_ucontext {
@@ -1320,12 +1343,14 @@ struct ib_ucontext {
struct list_head no_private_counters;
int odp_mrs_count;
#endif
+ struct ib_rdmacg_object cg_obj;
};

struct ib_uobject {
u64 user_handle; /* handle given to us by userspace */
struct ib_ucontext *context; /* associated user context */
void *object; /* containing object */
+ struct ib_rdmacg_object cg_obj; /* rdmacg object */
struct list_head list; /* link to context's list */
int id; /* index into kernel idr */
struct kref ref;
@@ -1886,6 +1911,10 @@ struct ib_device {
u8 phys_port_cnt;
struct ib_device_attr attrs;

+#ifdef CONFIG_CGROUP_RDMA
+ struct rdmacg_device cg_device;
+#endif
+
/**
* The following mandatory functions are used only at device
* registration. Keep functions such as these at the end of this
--
1.8.3.1

2016-03-24 20:24:43

by Parav Pandit

[permalink] [raw]
Subject: [PATCHv10 3/3] rdmacg: Added documentation for rdmacg

Added documentation for v1 and v2 version describing high
level design and usage examples on using rdma controller.

Signed-off-by: Parav Pandit <[email protected]>
---
Documentation/cgroup-v1/rdma.txt | 123 +++++++++++++++++++++++++++++++++++++++
Documentation/cgroup-v2.txt | 43 ++++++++++++++
2 files changed, 166 insertions(+)
create mode 100644 Documentation/cgroup-v1/rdma.txt

diff --git a/Documentation/cgroup-v1/rdma.txt b/Documentation/cgroup-v1/rdma.txt
new file mode 100644
index 0000000..f5e0a54
--- /dev/null
+++ b/Documentation/cgroup-v1/rdma.txt
@@ -0,0 +1,123 @@
+ RDMA Controller
+ ----------------
+
+Contents
+--------
+
+1. Overview
+ 1-1. What is RDMA controller?
+ 1-2. Why RDMA controller needed?
+ 1-3. How is RDMA controller implemented?
+2. Usage Examples
+
+1. Overview
+
+1-1. What is RDMA controller?
+-----------------------------
+
+RDMA controller allows user to limit RDMA/IB specific resources
+that a given set of processes can use. These processes are grouped using
+RDMA controller.
+
+RDMA controller allows operating on resources defined by the IB stack
+which are mainly IB verb resources and in future hardware specific
+well defined resources.
+
+1-2. Why RDMA controller needed?
+--------------------------------
+
+Currently user space applications can easily take away all the rdma device
+specific resources such as AH, CQ, QP, MR etc. Due to which other applications
+in other cgroup or kernel space ULPs may not even get chance to allocate any
+rdma resources. This leads to service unavailability.
+
+Therefore RDMA controller is needed through which resource consumption
+of processes can be limited. Through this controller various different rdma
+resources described by IB stack can be accounted.
+
+1-3. How is RDMA controller implemented?
+----------------------------------------
+
+RDMA cgroup allows limit configuration of resources. These resources are not
+defined by the rdma controller. Instead they are defined by the IB stack.
+This provides great flexibility to allow IB stack to define additional
+resources without any changes to rdma cgroup.
+Rdma cgroup maintains resource accounting per cgroup, per device using
+resource pool structure. Each such resource pool is limited up to
+64 resources in given resource pool by rdma cgroup, which can be extended
+later if required.
+
+This resource pool object is linked to the cgroup css. Typically there
+are 0 to 4 resource pool instances per cgroup, per device in most use cases.
+But nothing limits to have it more. At present hundreds of RDMA devices per
+single cgroup may not be handled optimally, however there is no
+known use case for such configuration either.
+
+Since RDMA resources can be allocated from any process and can be freed by any
+of the child processes which shares the address space, rdma resources are
+always owned by the creator cgroup css. This allows process migration from one
+to other cgroup without major complexity of transferring resource ownership;
+because such ownership is not really present due to shared nature of
+rdma resources. Linking resources around css also ensures that cgroups can be
+deleted after processes migrated. This allow progress migration as well with
+active resources, even though that’s not the primary use case.
+
+Whenever RDMA resource charing occurs, owner rdma cgroup is returned to
+the caller. Same rdma cgroup should be passed while uncharging the resource.
+This also allows process migrated with active RDMA resource to charge
+to new owner cgroup for new resource. It also allows to uncharge resource of
+a process from previously charged cgroup which is migrated to new cgroup,
+even though that is not a primary use case.
+
+Resource pool object is created in following situations.
+(a) User sets the limit and no previous resource pool exist for the device
+of interest for the cgroup.
+(b) No resource limits were configured, but IB/RDMA stack tries to
+charge the resource. So that it correctly uncharge them when applications are
+running without limits and later on when limits are enforced during uncharging,
+otherwise usage count will drop to negative.
+
+Resource pool is destroyed if it all the resource limits are set to max
+and it is the last resource getting deallocated.
+
+User should set all the limit to max value if it intents to remove/unconfigure
+the resource pool for a particular device.
+
+IB stack honors limits enforced by the rdma controller. When application
+query about maximum resource limits of IB device, it returns minimum of
+what is configured by user for a given cgroup and what is supported by
+IB device.
+
+Following resources can be accounted.
+ uctx Maximum number of User Contexts
+ pd Maximum number of Protection domains
+ ah Maximum number of Address handles
+ mr Maximum number of Memory Regions
+ mw Maximum number of Memory Windows
+ cq Maximum number of Completion Queues
+ srq Maximum number of Shared Receive Queues
+ qp Maximum number of Queue Pairs
+ flow Maximum number of Flows
+
+
+2. Usage Examples
+-----------------
+
+(a) Configure resource limit:
+echo mlx4_0 mr=100 qp=10 ah=2 > /sys/fs/cgroup/rdma/1/rdma.max
+echo ocrdma1 mr=120 qp=20 cq=10 > /sys/fs/cgroup/rdma/2/rdma.max
+
+(b) Query resource limit:
+cat /sys/fs/cgroup/rdma/2/rdma.max
+#Output:
+mlx4_0 uctx=max pd=max ah=2 mr=100 mw=max cq=max srq=max qp=10 flow=max
+ocrdma1 uctx=1 pd=5 ah=1 mr=10 cq=10 srq=max qp=20 flow=max flow=max
+
+(c) Query current usage:
+cat /sys/fs/cgroup/rdma/2/rdma.current
+#Output:
+mlx4_0 uctx=1 pd=2 ah=2 mr=95 mw=0 cq=2 srq=0 qp=8 flow=0
+ocrdma1 uctx=1 pd=6 ah=9 mr=20 mw=0 cq=1 srq=0 qp=2 flow=0
+
+(d) Delete resource limit:
+echo mlx4_0 mr=max qp=max ah=max > /sys/fs/cgroup/rdma/1/rdma.max
diff --git a/Documentation/cgroup-v2.txt b/Documentation/cgroup-v2.txt
index ff49cf9..b3e0712 100644
--- a/Documentation/cgroup-v2.txt
+++ b/Documentation/cgroup-v2.txt
@@ -47,6 +47,8 @@ CONTENTS
5-3. IO
5-3-1. IO Interface Files
5-3-2. Writeback
+ 5-4. RDMA
+ 5-4-1. RDMA Interface Files
P. Information on Kernel Programming
P-1. Filesystem Support for Writeback
D. Deprecated v1 Core Features
@@ -1088,6 +1090,47 @@ writeback as follows.
total available memory and applied the same way as
vm.dirty[_background]_ratio.

+5-4. RDMA
+
+The "rdma" controller regulates the distribution of RDMA resources.
+This controller implements resource accounting of resources defined
+by IB stack.
+
+5-4-1. RDMA Interface Files
+
+ rdma.max
+ A readwrite file that exists for all the cgroups except root that
+ describes current configured resource limit for a RDMA/IB device.
+
+ Lines are keyed by device name and are not ordered.
+ Each line contains space separated resource name and its configured
+ limit that can be distributed.
+
+ Following keys are defined.
+
+ uctx Maximum number of User Contexts
+ pd Maximum number of Protection domains
+ ah Maximum number of Address handles
+ mr Maximum number of Memory Regions
+ mw Maximum number of Memory Windows
+ cq Maximum number of Completion Queues
+ srq Maximum number of Shared Receive Queues
+ qp Maximum number of Queue Pairs
+ flow Maximum number of Flows
+
+ An example for mlx4 and ocrdma device follows.
+
+ mlx4_0 uctx=max pd=4 ah=2 mr=10 mw=max cq=1 srq=1 qp=10 flow=10
+ ocrdma1 uctx=2 pd=2 ah=2 mr=20 mw=max cq=1 srq=1 qp=10 flow=10
+
+ rdma.current
+ A read-only file that describes current resource usage.
+ It exists for all the cgroup except root.
+
+ An example for mlx4 and ocrdma device follows.
+
+ mlx4_1 uctx=1 ah=0 pd=1 cq=4 qp=4 mr=100 srq=0 flow=10
+ ocrdma1 uctx=2 pd=2 ah=2 mr=20 mw=max cq=1 srq=1 qp=10 flow=10

P. Information on Kernel Programming

--
1.8.3.1

2016-03-24 20:25:25

by Parav Pandit

[permalink] [raw]
Subject: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

Added rdma cgroup controller that does accounting, limit enforcement
on rdma/IB verbs and hw resources.

Added rdma cgroup header file which defines its APIs to perform
charing/uncharing functionality and device registration which will
participate in controller functions of accounting and limit
enforcements. It also define rdmacg_device structure to bind IB stack
and RDMA cgroup controller.

RDMA resources are tracked using resource pool. Resource pool is per
device, per cgroup entity which allows setting up accounting limits
on per device basis.

Resources are not defined by the RDMA cgroup, instead they are defined
by the external module IB stack. This allows extending IB stack
without changing kernel, as IB stack is going through changes
and enhancements.

Resource pool is created/destroyed dynamically whenever
charging/uncharging occurs respectively and whenever user
configuration is done. Its a tradeoff of memory vs little more code
space that creates resource pool whenever necessary,
instead of creating them during cgroup creation and device registration
time.

Signed-off-by: Parav Pandit <[email protected]>
---
include/linux/cgroup_rdma.h | 52 +++
include/linux/cgroup_subsys.h | 4 +
init/Kconfig | 10 +
kernel/Makefile | 1 +
kernel/cgroup_rdma.c | 746 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 813 insertions(+)
create mode 100644 include/linux/cgroup_rdma.h
create mode 100644 kernel/cgroup_rdma.c

diff --git a/include/linux/cgroup_rdma.h b/include/linux/cgroup_rdma.h
new file mode 100644
index 0000000..baec8f0
--- /dev/null
+++ b/include/linux/cgroup_rdma.h
@@ -0,0 +1,52 @@
+#ifndef _CGROUP_RDMA_H
+#define _CGROUP_RDMA_H
+
+#include <linux/cgroup.h>
+
+#ifdef CONFIG_CGROUP_RDMA
+
+struct rdma_cgroup {
+ struct cgroup_subsys_state css;
+
+ /* protects resource pool list */
+ spinlock_t rpool_list_lock;
+ /*
+ * head to keep track of all resource pools
+ * that belongs to this cgroup.
+ */
+ struct list_head rpool_head;
+};
+
+struct rdmacg_pool_info {
+ const char **resource_name_table;
+ int table_len;
+};
+
+struct rdmacg_device {
+ struct rdmacg_pool_info pool_info;
+ struct list_head rdmacg_list;
+ struct list_head rpool_head;
+ /* protects resource pool list */
+ spinlock_t rpool_lock;
+ char *name;
+};
+
+/*
+ * APIs for RDMA/IB stack to publish when a device wants to
+ * participate in resource accounting
+ */
+int rdmacg_register_device(struct rdmacg_device *device);
+void rdmacg_unregister_device(struct rdmacg_device *device);
+
+/* APIs for RDMA/IB stack to charge/uncharge pool specific resources */
+int rdmacg_try_charge(struct rdma_cgroup **rdmacg,
+ struct rdmacg_device *device,
+ int resource_index);
+void rdmacg_uncharge(struct rdma_cgroup *cg,
+ struct rdmacg_device *device,
+ int resource_index);
+void rdmacg_query_limit(struct rdmacg_device *device,
+ int *limits);
+
+#endif /* CONFIG_CGROUP_RDMA */
+#endif /* _CGROUP_RDMA_H */
diff --git a/include/linux/cgroup_subsys.h b/include/linux/cgroup_subsys.h
index 0df0336a..d0e597c 100644
--- a/include/linux/cgroup_subsys.h
+++ b/include/linux/cgroup_subsys.h
@@ -56,6 +56,10 @@ SUBSYS(hugetlb)
SUBSYS(pids)
#endif

+#if IS_ENABLED(CONFIG_CGROUP_RDMA)
+SUBSYS(rdma)
+#endif
+
/*
* The following subsystems are not supported on the default hierarchy.
*/
diff --git a/init/Kconfig b/init/Kconfig
index 2232080..0d3efe0 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1054,6 +1054,16 @@ config CGROUP_PIDS
since the PIDs limit only affects a process's ability to fork, not to
attach to a cgroup.

+config CGROUP_RDMA
+ bool "RDMA controller"
+ help
+ Provides enforcement of RDMA resources defined by IB stack.
+ It is fairly easy for consumers to exhaust RDMA resources, which
+ can result into resource unavailability to other consumers.
+ RDMA controller is designed to stop this from happening.
+ Attaching processes with active RDMA resources to the cgroup
+ hierarchy is allowed even if can cross the hierarchy's limit.
+
config CGROUP_FREEZER
bool "Freezer controller"
help
diff --git a/kernel/Makefile b/kernel/Makefile
index 53abf00..26e413c 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -57,6 +57,7 @@ obj-$(CONFIG_COMPAT) += compat.o
obj-$(CONFIG_CGROUPS) += cgroup.o
obj-$(CONFIG_CGROUP_FREEZER) += cgroup_freezer.o
obj-$(CONFIG_CGROUP_PIDS) += cgroup_pids.o
+obj-$(CONFIG_CGROUP_RDMA) += cgroup_rdma.o
obj-$(CONFIG_CPUSETS) += cpuset.o
obj-$(CONFIG_UTS_NS) += utsname.o
obj-$(CONFIG_USER_NS) += user_namespace.o
diff --git a/kernel/cgroup_rdma.c b/kernel/cgroup_rdma.c
new file mode 100644
index 0000000..21dd27a
--- /dev/null
+++ b/kernel/cgroup_rdma.c
@@ -0,0 +1,746 @@
+/*
+ * This file is subject to the terms and conditions of version 2 of the GNU
+ * General Public License. See the file COPYING in the main directory of the
+ * Linux distribution for more details.
+ */
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/seq_file.h>
+#include <linux/cgroup.h>
+#include <linux/parser.h>
+#include <linux/cgroup_rdma.h>
+
+#define RDMACG_MAX_STR "max"
+
+static DEFINE_MUTEX(dev_mutex);
+static LIST_HEAD(dev_list_head);
+
+/*
+ * Protects list of resource pools maintained on per cgroup basis.
+ */
+static spinlock_t rpool_list_lock = __SPIN_LOCK_UNLOCKED(&rpool_list_lock);
+
+enum rdmacg_file_type {
+ RDMACG_RESOURCE_MAX,
+ RDMACG_RESOURCE_STAT,
+};
+
+/* resource tracker for each resource for rdma cgroup */
+struct rdmacg_resource {
+ int max;
+ int usage;
+};
+
+/*
+ * resource pool object which represents per cgroup, per device
+ * resources. There are multiple instance of this object per cgroup,
+ * therefore it cannot be embedded within rdma_cgroup structure. It
+ * is maintained as list.
+ */
+struct rdmacg_resource_pool {
+ struct list_head cg_list;
+ struct list_head dev_list;
+
+ struct rdmacg_device *device;
+ struct rdmacg_resource *resources;
+
+ /* count active user tasks of this pool */
+ int usage_sum;
+ /* total number counts which are set to max */
+ int num_max_cnt;
+};
+
+static struct rdma_cgroup *css_rdmacg(struct cgroup_subsys_state *css)
+{
+ return container_of(css, struct rdma_cgroup, css);
+}
+
+static struct rdma_cgroup *parent_rdmacg(struct rdma_cgroup *cg)
+{
+ return css_rdmacg(cg->css.parent);
+}
+
+static inline struct rdma_cgroup *get_current_rdmacg(void)
+{
+ return css_rdmacg(task_get_css(current, rdma_cgrp_id));
+}
+
+static void set_resource_limit(struct rdmacg_resource_pool *rpool,
+ int index, int new_max)
+{
+ if (new_max == S32_MAX) {
+ if (rpool->resources[index].max != S32_MAX)
+ rpool->num_max_cnt++;
+ } else {
+ if (rpool->resources[index].max == S32_MAX)
+ rpool->num_max_cnt--;
+ }
+ rpool->resources[index].max = new_max;
+}
+
+static void set_all_resource_max_limit(struct rdmacg_resource_pool *rpool)
+{
+ struct rdmacg_pool_info *pool_info = &rpool->device->pool_info;
+ int i;
+
+ for (i = 0; i < pool_info->table_len; i++)
+ set_resource_limit(rpool, i, S32_MAX);
+}
+
+static void free_cg_rpool_mem(struct rdmacg_resource_pool *rpool)
+{
+ kfree(rpool->resources);
+ kfree(rpool);
+}
+
+static void free_cg_rpool(struct rdmacg_resource_pool *rpool)
+{
+ spin_lock(&rpool->device->rpool_lock);
+ list_del(&rpool->dev_list);
+ spin_unlock(&rpool->device->rpool_lock);
+
+ free_cg_rpool_mem(rpool);
+}
+
+static struct rdmacg_resource_pool *
+find_cg_rpool_locked(struct rdma_cgroup *cg,
+ struct rdmacg_device *device)
+
+{
+ struct rdmacg_resource_pool *pool;
+
+ lockdep_assert_held(&rpool_list_lock);
+
+ list_for_each_entry(pool, &cg->rpool_head, cg_list)
+ if (pool->device == device)
+ return pool;
+
+ return NULL;
+}
+
+static int
+alloc_cg_rpool(struct rdma_cgroup *cg, struct rdmacg_device *device)
+{
+ struct rdmacg_resource_pool *rpool, *other_rpool;
+ struct rdmacg_pool_info *pool_info = &device->pool_info;
+ int ret;
+
+ rpool = kzalloc(sizeof(*rpool), GFP_KERNEL);
+ if (!rpool) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ rpool->resources = kcalloc(pool_info->table_len,
+ sizeof(*rpool->resources),
+ GFP_KERNEL);
+ if (!rpool->resources) {
+ ret = -ENOMEM;
+ goto alloc_err;
+ }
+
+ rpool->device = device;
+ INIT_LIST_HEAD(&rpool->cg_list);
+ INIT_LIST_HEAD(&rpool->dev_list);
+ spin_lock_init(&device->rpool_lock);
+ set_all_resource_max_limit(rpool);
+
+ spin_lock(&rpool_list_lock);
+
+ other_rpool = find_cg_rpool_locked(cg, device);
+
+ /*
+ * if other task added resource pool for this device for this cgroup
+ * than free up which was recently created and use the one we found.
+ */
+ if (other_rpool) {
+ spin_unlock(&rpool_list_lock);
+ free_cg_rpool(rpool);
+ return 0;
+ }
+
+ list_add_tail(&rpool->cg_list, &cg->rpool_head);
+
+ spin_lock(&device->rpool_lock);
+ list_add_tail(&rpool->dev_list, &device->rpool_head);
+ spin_unlock(&device->rpool_lock);
+
+ spin_unlock(&rpool_list_lock);
+ return 0;
+
+alloc_err:
+ kfree(rpool);
+err:
+ return ret;
+}
+
+/**
+ * uncharge_cg_resource_locked - uncharge resource for rdma cgroup
+ * @cg: pointer to cg to uncharge and all parents in hierarchy
+ * @device: pointer to ib device
+ * @index: index of the resource to uncharge in cg (resource pool)
+ *
+ * It also frees the resource pool which was created as part of
+ * charging operation when there are no resources attached to
+ * resource pool.
+ */
+static void
+uncharge_cg_resource_locked(struct rdma_cgroup *cg,
+ struct rdmacg_device *device,
+ int index)
+{
+ struct rdmacg_resource_pool *rpool;
+ struct rdmacg_pool_info *pool_info = &device->pool_info;
+
+ rpool = find_cg_rpool_locked(cg, device);
+
+ /*
+ * rpool cannot be null at this stage. Let kernel operate in case
+ * if there a bug in IB stack or rdma controller, instead of crashing
+ * the system.
+ */
+ if (unlikely(!rpool)) {
+ pr_warn("Invalid device %p or rdma cgroup %p\n", cg, device);
+ return;
+ }
+
+ rpool->resources[index].usage--;
+
+ /*
+ * A negative count (or overflow) is invalid,
+ * it indicates a bug in the rdma controller.
+ */
+ WARN_ON_ONCE(rpool->resources[index].usage < 0);
+ rpool->usage_sum--;
+ if (rpool->usage_sum == 0 &&
+ rpool->num_max_cnt == pool_info->table_len) {
+ /*
+ * No user of the rpool and all entries are set to max, so
+ * safe to delete this rpool.
+ */
+ list_del(&rpool->cg_list);
+ free_cg_rpool(rpool);
+ }
+}
+
+/**
+ * rdmacg_uncharge_resource - hierarchically uncharge rdma resource count
+ * @device: pointer to rdmacg device
+ * @index: index of the resource to uncharge in cg in given resource pool
+ * @num: the number of rdma resource to uncharge
+ *
+ */
+void rdmacg_uncharge(struct rdma_cgroup *cg,
+ struct rdmacg_device *device,
+ int index)
+{
+ struct rdma_cgroup *p;
+
+ spin_lock(&rpool_list_lock);
+ for (p = cg; p; p = parent_rdmacg(p))
+ uncharge_cg_resource_locked(p, device, index);
+ spin_unlock(&rpool_list_lock);
+
+ css_put(&cg->css);
+}
+EXPORT_SYMBOL(rdmacg_uncharge);
+
+/**
+ * rdmacg_try_charge_resource - hierarchically try to charge the rdma resource
+ * @rdmacg: pointer to rdma cgroup which will own this resource
+ * @device: pointer to rdmacg device
+ * @index: index of the resource to charge in cg (resource pool)
+ *
+ * This function follows charging resource in hierarchical way.
+ * It will fail if the charge would cause the new value to exceed the
+ * hierarchical limit.
+ * Returns 0 if the charge succeded, otherwise -EAGAIN, -ENOMEM or -EINVAL.
+ * Returns pointer to rdmacg for this resource when charging is successful.
+ *
+ * Charger needs to account resources on two criteria.
+ * (a) per cgroup & (b) per device resource usage.
+ * Per cgroup resource usage ensures that tasks of cgroup doesn't cross
+ * the configured limits. Per device provides granular configuration
+ * in multi device usage. It allocates resource pool in the hierarchy
+ * for each parent it come across for first resource. Later on resource
+ * pool will be available. Therefore it will be much faster thereon
+ * to charge/uncharge.
+ */
+int rdmacg_try_charge(struct rdma_cgroup **rdmacg,
+ struct rdmacg_device *device,
+ int index)
+{
+ struct rdma_cgroup *cg, *p, *q;
+ struct rdmacg_resource_pool *rpool;
+ s64 new;
+ int ret = 0;
+
+ /*
+ * hold on to css, as cgroup can be removed but resource
+ * accounting happens on css.
+ */
+ cg = get_current_rdmacg();
+
+ spin_lock(&rpool_list_lock);
+ for (p = cg; p; p = parent_rdmacg(p)) {
+retry:
+ rpool = find_cg_rpool_locked(p, device);
+ if (rpool) {
+ new = rpool->resources[index].usage + 1;
+ if (new > rpool->resources[index].max) {
+ spin_unlock(&rpool_list_lock);
+ ret = -EAGAIN;
+ goto err;
+ } else {
+ rpool->resources[index].usage = new;
+ rpool->usage_sum++;
+ }
+ } else {
+ spin_unlock(&rpool_list_lock);
+ ret = alloc_cg_rpool(p, device);
+ if (ret) {
+ goto err;
+ } else {
+ spin_lock(&rpool_list_lock);
+ goto retry;
+ }
+ }
+ }
+ spin_unlock(&rpool_list_lock);
+
+ *rdmacg = cg;
+ return 0;
+
+err:
+ spin_lock(&rpool_list_lock);
+ for (q = cg; q != p; q = parent_rdmacg(q))
+ uncharge_cg_resource_locked(q, device, index);
+ spin_unlock(&rpool_list_lock);
+ css_put(&cg->css);
+ return ret;
+}
+EXPORT_SYMBOL(rdmacg_try_charge);
+
+/**
+ * rdmacg_register_rdmacg_device - register rdmacg device to rdma controller.
+ * @device: pointer to rdmacg device whose resources need to be accounted.
+ *
+ * If IB stack wish a device to participate in rdma cgroup resource
+ * tracking, it must invoke this API to register with rdma cgroup before
+ * any user space application can start using the RDMA resources.
+ * Returns 0 on success or EINVAL when table length given is beyond
+ * supported size.
+ */
+int rdmacg_register_device(struct rdmacg_device *device)
+{
+ if (device->pool_info.table_len > 64)
+ return -EINVAL;
+
+ INIT_LIST_HEAD(&device->rdmacg_list);
+ INIT_LIST_HEAD(&device->rpool_head);
+ spin_lock_init(&device->rpool_lock);
+
+ mutex_lock(&dev_mutex);
+ list_add_tail(&device->rdmacg_list, &dev_list_head);
+ mutex_unlock(&dev_mutex);
+ return 0;
+}
+EXPORT_SYMBOL(rdmacg_register_device);
+
+/**
+ * rdmacg_unregister_rdmacg_device - unregister the rdmacg device
+ * from rdma controller.
+ * @device: pointer to rdmacg device which was previously registered with rdma
+ * controller using rdmacg_register_device().
+ *
+ * IB stack must invoke this after all the resources of the IB device
+ * are destroyed and after ensuring that no more resources will be created
+ * when this API is invoked.
+ */
+void rdmacg_unregister_device(struct rdmacg_device *device)
+{
+ struct rdmacg_resource_pool *rpool, *tmp;
+
+ /*
+ * Synchronize with any active resource settings,
+ * usage query happening via configfs.
+ * At this stage, there should not be any active resource pools
+ * for this device, as RDMA/IB stack is expected to shutdown,
+ * tear down all the applications and free up resources.
+ */
+ mutex_lock(&dev_mutex);
+ list_del_init(&device->rdmacg_list);
+ mutex_unlock(&dev_mutex);
+
+ /*
+ * Now that this device off the cgroup list, its safe to free
+ * all the rpool resources.
+ */
+ list_for_each_entry_safe(rpool, tmp, &device->rpool_head, dev_list) {
+ list_del_init(&rpool->dev_list);
+
+ spin_lock(&rpool_list_lock);
+ list_del_init(&rpool->cg_list);
+ spin_unlock(&rpool_list_lock);
+
+ free_cg_rpool_mem(rpool);
+ }
+}
+EXPORT_SYMBOL(rdmacg_unregister_device);
+
+/**
+ * rdmacg_query_limit - query the resource limits that
+ * might have been configured by the user.
+ * @device: pointer to ib device
+ * @type: the type of resource pool to know the limits of.
+ * @limits: pointer to an array of limits where rdma cg will provide
+ * the configured limits of the cgroup.
+ *
+ * This function honors resource limit in hierarchical way.
+ * In a cgroup hirarchy, it considers the limit of a controller which has
+ * smallest limit configured.
+ */
+void rdmacg_query_limit(struct rdmacg_device *device, int *limits)
+{
+ struct rdma_cgroup *cg, *p;
+ struct rdmacg_resource_pool *rpool;
+ struct rdmacg_pool_info *pool_info;
+ int i;
+
+ pool_info = &device->pool_info;
+
+ for (i = 0; i < pool_info->table_len; i++)
+ limits[i] = S32_MAX;
+
+ cg = get_current_rdmacg();
+ /*
+ * Check in hirerchy which pool get the least amount of
+ * resource limits.
+ */
+ spin_lock(&rpool_list_lock);
+ for (p = cg; p; p = parent_rdmacg(p)) {
+ rpool = find_cg_rpool_locked(cg, device);
+ if (!rpool)
+ continue;
+
+ for (i = 0; i < pool_info->table_len; i++)
+ limits[i] = min_t(int, limits[i],
+ rpool->resources[i].max);
+ }
+ spin_unlock(&rpool_list_lock);
+ css_put(&cg->css);
+}
+EXPORT_SYMBOL(rdmacg_query_limit);
+
+static int parse_resource(char *c, struct rdmacg_pool_info *pool_info,
+ int *intval)
+{
+ substring_t argstr;
+ const char **table = pool_info->resource_name_table;
+ char *name, *value = c;
+ size_t len;
+ int ret, i = 0;
+
+ name = strsep(&value, "=");
+ if (!name || !value)
+ return -EINVAL;
+
+ len = strlen(value);
+
+ for (i = 0; i < pool_info->table_len; i++) {
+ if (strcmp(table[i], name))
+ continue;
+
+ argstr.from = value;
+ argstr.to = value + len;
+
+ ret = match_int(&argstr, intval);
+ if (ret >= 0) {
+ if (*intval < 0)
+ break;
+ return i;
+ }
+ if (strncmp(value, RDMACG_MAX_STR, len) == 0) {
+ *intval = S32_MAX;
+ return i;
+ }
+ break;
+ }
+ return -EINVAL;
+}
+
+static int rdmacg_parse_limits(char *options,
+ struct rdmacg_pool_info *pool_info,
+ int *new_limits, u64 *enables)
+{
+ char *c;
+ int err = -EINVAL;
+
+ /* parse resource options */
+ while ((c = strsep(&options, " ")) != NULL) {
+ int index, intval;
+
+ index = parse_resource(c, pool_info, &intval);
+ if (index < 0)
+ goto err;
+
+ new_limits[index] = intval;
+ *enables |= BIT(index);
+ }
+ return 0;
+
+err:
+ return err;
+}
+
+static struct rdmacg_device *rdmacg_get_device_locked(const char *name)
+{
+ struct rdmacg_device *device;
+
+ list_for_each_entry(device, &dev_list_head, rdmacg_list)
+ if (!strcmp(name, device->name))
+ return device;
+
+ return NULL;
+}
+
+static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
+ char *buf, size_t nbytes, loff_t off)
+{
+ struct rdma_cgroup *cg = css_rdmacg(of_css(of));
+ const char *dev_name;
+ struct rdmacg_resource_pool *rpool;
+ struct rdmacg_device *device;
+ char *options = strstrip(buf);
+ struct rdmacg_pool_info *pool_info;
+ u64 enables = 0;
+ int *new_limits;
+ int i = 0, ret = 0;
+
+ /* extract the device name first */
+ dev_name = strsep(&options, " ");
+ if (!dev_name) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* acquire lock to synchronize with hot plug devices */
+ mutex_lock(&dev_mutex);
+
+ device = rdmacg_get_device_locked(dev_name);
+ if (!device) {
+ ret = -ENODEV;
+ goto parse_err;
+ }
+
+ pool_info = &device->pool_info;
+
+ new_limits = kcalloc(pool_info->table_len, sizeof(int), GFP_KERNEL);
+ if (!new_limits) {
+ ret = -ENOMEM;
+ goto parse_err;
+ }
+
+ ret = rdmacg_parse_limits(options, pool_info, new_limits, &enables);
+ if (ret)
+ goto opt_err;
+
+retry:
+ spin_lock(&rpool_list_lock);
+ rpool = find_cg_rpool_locked(cg, device);
+ if (!rpool) {
+ spin_unlock(&rpool_list_lock);
+ ret = alloc_cg_rpool(cg, device);
+ if (ret)
+ goto opt_err;
+ else
+ goto retry;
+ }
+
+ /* now set the new limits of the rpool */
+ while (enables) {
+ /* if user set the limit, enables bit is set */
+ if (enables & BIT(i)) {
+ enables &= ~BIT(i);
+ set_resource_limit(rpool, i, new_limits[i]);
+ }
+ i++;
+ }
+
+ if (rpool->usage_sum == 0 &&
+ rpool->num_max_cnt == pool_info->table_len) {
+ /*
+ * No user of the rpool and all entries are set to max, so
+ * safe to delete this rpool.
+ */
+ list_del(&rpool->cg_list);
+ spin_unlock(&rpool_list_lock);
+
+ free_cg_rpool(rpool);
+ } else {
+ spin_unlock(&rpool_list_lock);
+ }
+
+opt_err:
+ kfree(new_limits);
+parse_err:
+ mutex_unlock(&dev_mutex);
+err:
+ return ret ?: nbytes;
+}
+
+static u32 *get_cg_rpool_values(struct rdma_cgroup *cg,
+ struct rdmacg_device *device,
+ enum rdmacg_file_type sf_type,
+ int count)
+{
+ struct rdmacg_resource_pool *rpool;
+ u32 *value_tbl;
+ int i, ret;
+
+ value_tbl = kcalloc(count, sizeof(u32), GFP_KERNEL);
+ if (!value_tbl) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ spin_lock(&rpool_list_lock);
+
+ rpool = find_cg_rpool_locked(cg, device);
+
+ for (i = 0; i < count; i++) {
+ if (sf_type == RDMACG_RESOURCE_MAX) {
+ if (rpool)
+ value_tbl[i] = rpool->resources[i].max;
+ else
+ value_tbl[i] = S32_MAX;
+ } else {
+ if (rpool)
+ value_tbl[i] = rpool->resources[i].usage;
+ }
+ }
+
+ spin_unlock(&rpool_list_lock);
+
+ return value_tbl;
+
+err:
+ return ERR_PTR(ret);
+}
+
+static void print_rpool_values(struct seq_file *sf,
+ struct rdmacg_pool_info *pool_info,
+ u32 *value_tbl)
+{
+ int i;
+
+ for (i = 0; i < pool_info->table_len; i++) {
+ seq_puts(sf, pool_info->resource_name_table[i]);
+ seq_putc(sf, '=');
+ if (value_tbl[i] == S32_MAX)
+ seq_puts(sf, RDMACG_MAX_STR);
+ else
+ seq_printf(sf, "%d", value_tbl[i]);
+ seq_putc(sf, ' ');
+ }
+}
+
+static int rdmacg_resource_read(struct seq_file *sf, void *v)
+{
+ struct rdmacg_device *device;
+ struct rdma_cgroup *cg = css_rdmacg(seq_css(sf));
+ struct rdmacg_pool_info *pool_info;
+ u32 *value_tbl;
+ int ret = 0;
+
+ mutex_lock(&dev_mutex);
+
+ list_for_each_entry(device, &dev_list_head, rdmacg_list) {
+ pool_info = &device->pool_info;
+
+ /* get the value from resource pool */
+ value_tbl = get_cg_rpool_values(cg, device,
+ seq_cft(sf)->private,
+ pool_info->table_len);
+ if (IS_ERR_OR_NULL(value_tbl)) {
+ ret = -ENOMEM;
+ break;
+ }
+
+ seq_printf(sf, "%s ", device->name);
+ print_rpool_values(sf, pool_info, value_tbl);
+ seq_putc(sf, '\n');
+ kfree(value_tbl);
+ }
+
+ mutex_unlock(&dev_mutex);
+ return ret;
+}
+
+static struct cftype rdmacg_files[] = {
+ {
+ .name = "max",
+ .write = rdmacg_resource_set_max,
+ .seq_show = rdmacg_resource_read,
+ .private = RDMACG_RESOURCE_MAX,
+ .flags = CFTYPE_NOT_ON_ROOT,
+ },
+ {
+ .name = "current",
+ .seq_show = rdmacg_resource_read,
+ .private = RDMACG_RESOURCE_STAT,
+ .flags = CFTYPE_NOT_ON_ROOT,
+ },
+ { } /* terminate */
+};
+
+static struct cgroup_subsys_state *
+rdmacg_css_alloc(struct cgroup_subsys_state *parent)
+{
+ struct rdma_cgroup *cg;
+
+ cg = kzalloc(sizeof(*cg), GFP_KERNEL);
+ if (!cg)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&cg->rpool_head);
+ spin_lock_init(&rpool_list_lock);
+ return &cg->css;
+}
+
+static void rdmacg_css_free(struct cgroup_subsys_state *css)
+{
+ struct rdma_cgroup *cg = css_rdmacg(css);
+
+ kfree(cg);
+}
+
+/**
+ * rdmacg_css_offline - cgroup css_offline callback
+ * @css: css of interest
+ *
+ * This function is called when @css is about to go away and responsible
+ * for shooting down all rdmacg associated with @css. As part of that it
+ * marks all the resource pool entries to max value, so that when resources are
+ * uncharged, associated resource pool can be freed as well.
+ */
+static void rdmacg_css_offline(struct cgroup_subsys_state *css)
+{
+ struct rdma_cgroup *cg = css_rdmacg(css);
+ struct rdmacg_resource_pool *rpool;
+
+ spin_lock(&rpool_list_lock);
+
+ list_for_each_entry(rpool, &cg->rpool_head, cg_list)
+ set_all_resource_max_limit(rpool);
+
+ spin_unlock(&rpool_list_lock);
+}
+
+struct cgroup_subsys rdma_cgrp_subsys = {
+ .css_alloc = rdmacg_css_alloc,
+ .css_free = rdmacg_css_free,
+ .css_offline = rdmacg_css_offline,
+ .legacy_cftypes = rdmacg_files,
+ .dfl_cftypes = rdmacg_files,
+};
--
1.8.3.1

2016-04-04 19:36:47

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

Hello, Parav.

Sorry about the delay.

> +struct rdma_cgroup {
> + struct cgroup_subsys_state css;
> +
> + /* protects resource pool list */
> + spinlock_t rpool_list_lock;

Is this lock still being used?

> + /*
> + * head to keep track of all resource pools
> + * that belongs to this cgroup.
> + */
> + struct list_head rpool_head;
> +};
> +
> +struct rdmacg_pool_info {
> + const char **resource_name_table;
> + int table_len;

It's a bit bothering that resource type defs are outside the
controller in a way the controller can't know and assumed to have the
same type and range. Architecting kernel code so that allow building
modules separately can modify behaviors usually isn't a priority and
styling things like this makes implementing specific one-off behaviors
cumbersome.

It now looks okay but let's say in the future someone wants to use a
different type and/or ranges for one of the resources, given the above
framework, that person is most likely to blow up pool_info - first
with types and min, max values and maybe later with callback handlers
and so on, when the only thing which would have been necessary is an
one-off handling of that specific setting in the interface functions.

Making things modular comes at the cost of complexity and ad-hoc
flexiblity. There are cases where such overhead is warranted but as I
mentioned before, I don't think this is one of them. Please look back
on how this patchset developed. It has been a continuous process of
cutting down complexities which didn't need to be there which is the
opposite of what we usually want to do - buliding up sophiscation and
complexity as necessary from something simple. Please cut down this
part too.

> +};
> +
> +struct rdmacg_device {
> + struct rdmacg_pool_info pool_info;
> + struct list_head rdmacg_list;
> + struct list_head rpool_head;
> + /* protects resource pool list */
> + spinlock_t rpool_lock;

Does this still need to be a separate lock?

> +#define RDMACG_MAX_STR "max"
> +
> +static DEFINE_MUTEX(dev_mutex);

Can you please prefix the above with rdmacg?

> +static LIST_HEAD(dev_list_head);

Ditto, how about rdmacg_dev_list?

> +/*
> + * resource pool object which represents per cgroup, per device
> + * resources. There are multiple instance of this object per cgroup,
^s
> + * therefore it cannot be embedded within rdma_cgroup structure. It
> + * is maintained as list.
> + */
> +struct rdmacg_resource_pool {
> + struct list_head cg_list;
> + struct list_head dev_list;

Isn't it more conventional to use OBJ_list or OBJs for the head and
_node or _link for the members?

> +static int
> +alloc_cg_rpool(struct rdma_cgroup *cg, struct rdmacg_device *device)
> +{
> + struct rdmacg_resource_pool *rpool, *other_rpool;
> + struct rdmacg_pool_info *pool_info = &device->pool_info;
> + int ret;
> +
> + rpool = kzalloc(sizeof(*rpool), GFP_KERNEL);
> + if (!rpool) {
> + ret = -ENOMEM;
> + goto err;
> + }
> + rpool->resources = kcalloc(pool_info->table_len,
> + sizeof(*rpool->resources),
> + GFP_KERNEL);
> + if (!rpool->resources) {
> + ret = -ENOMEM;
> + goto alloc_err;
> + }
> +
> + rpool->device = device;
> + INIT_LIST_HEAD(&rpool->cg_list);
> + INIT_LIST_HEAD(&rpool->dev_list);
> + spin_lock_init(&device->rpool_lock);
> + set_all_resource_max_limit(rpool);
> +
> + spin_lock(&rpool_list_lock);
> +
> + other_rpool = find_cg_rpool_locked(cg, device);
> +
> + /*
> + * if other task added resource pool for this device for this cgroup
> + * than free up which was recently created and use the one we found.
> + */

* It's usually a good idea to have hierarchical objects to be created
all the way to the root when a leaf node is requested and link
parent at each level. That way, when a descendant object is looked
up, the caller can always deref its ->parent pointer till it reaches
the top.

* Unless it's a very hot path, using a mutex which protects creation
of new nodes is usually simpler than doing alloc -> lock ->
try-insert. The concurrency in the creation path doesn't matter and
even if it mattered it isn't a good idea to bang on allocation path
concurrently anyway.

> +static void
> +uncharge_cg_resource_locked(struct rdma_cgroup *cg,
> + struct rdmacg_device *device,
> + int index)
> +{
> + struct rdmacg_resource_pool *rpool;
> + struct rdmacg_pool_info *pool_info = &device->pool_info;
> +
> + rpool = find_cg_rpool_locked(cg, device);

So, with each pool linking to its parent, instead of looking up at
each level, it can just follow ->parent recursively.

> + /*
> + * A negative count (or overflow) is invalid,
> + * it indicates a bug in the rdma controller.
> + */
> + WARN_ON_ONCE(rpool->resources[index].usage < 0);
> + rpool->usage_sum--;

What guarantees that this count wouldn't overflow? More on this
later.

> +/**
> + * rdmacg_uncharge_resource - hierarchically uncharge rdma resource count
> + * @device: pointer to rdmacg device
> + * @index: index of the resource to uncharge in cg in given resource pool
> + * @num: the number of rdma resource to uncharge
> + *
> + */
> +void rdmacg_uncharge(struct rdma_cgroup *cg,
> + struct rdmacg_device *device,
> + int index)
> +{
> + struct rdma_cgroup *p;
> +
> + spin_lock(&rpool_list_lock);
> + for (p = cg; p; p = parent_rdmacg(p))
> + uncharge_cg_resource_locked(p, device, index);
> + spin_unlock(&rpool_list_lock);
> +
> + css_put(&cg->css);

Hmm... what css ref is the above putting?

> +int rdmacg_try_charge(struct rdma_cgroup **rdmacg,
> + struct rdmacg_device *device,
> + int index)
> +{
...
> + spin_lock(&rpool_list_lock);
> + for (p = cg; p; p = parent_rdmacg(p)) {
> +retry:
> + rpool = find_cg_rpool_locked(p, device);

Here, too, you can look up the leaf node once and walk up the
hierarhcy. Maybe create a helper, say rdmacg_get_pool_and_lock(),
which looks up an existing node or creates all pools from leaf to root
and returns with the lock held?

...
> +err:
> + spin_lock(&rpool_list_lock);
> + for (q = cg; q != p; q = parent_rdmacg(q))
> + uncharge_cg_resource_locked(q, device, index);

If some resources are not very plentiful and contended, dropping lock
before error handling like above could lead to spurious failures. It
looks like the above is from trying to create pool at each level while
charging up. Creating all necessary pools at the outset should make
the code simpler and more robust.

> + spin_unlock(&rpool_list_lock);
> + css_put(&cg->css);

Ditto, where is this from?

> +void rdmacg_query_limit(struct rdmacg_device *device, int *limits)
> +{
> + struct rdma_cgroup *cg, *p;
> + struct rdmacg_resource_pool *rpool;
> + struct rdmacg_pool_info *pool_info;
> + int i;
> +
> + pool_info = &device->pool_info;
> +
> + for (i = 0; i < pool_info->table_len; i++)
> + limits[i] = S32_MAX;
> +
> + cg = get_current_rdmacg();
> + /*
> + * Check in hirerchy which pool get the least amount of
> + * resource limits.
> + */
> + spin_lock(&rpool_list_lock);
> + for (p = cg; p; p = parent_rdmacg(p)) {
> + rpool = find_cg_rpool_locked(cg, device);

Ditto with lookup here.

> +static struct rdmacg_device *rdmacg_get_device_locked(const char *name)
> +{
> + struct rdmacg_device *device;

lockdep_assert_held() would be nice.

> + list_for_each_entry(device, &dev_list_head, rdmacg_list)
> + if (!strcmp(name, device->name))
> + return device;
> +
> + return NULL;
> +}
> +
> +static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
> + char *buf, size_t nbytes, loff_t off)
> +{
> + struct rdma_cgroup *cg = css_rdmacg(of_css(of));
> + const char *dev_name;
> + struct rdmacg_resource_pool *rpool;
> + struct rdmacg_device *device;
> + char *options = strstrip(buf);
> + struct rdmacg_pool_info *pool_info;
> + u64 enables = 0;
> + int *new_limits;
> + int i = 0, ret = 0;
> +
> + /* extract the device name first */
> + dev_name = strsep(&options, " ");
> + if (!dev_name) {
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + /* acquire lock to synchronize with hot plug devices */
> + mutex_lock(&dev_mutex);
> +
> + device = rdmacg_get_device_locked(dev_name);
> + if (!device) {
> + ret = -ENODEV;
> + goto parse_err;
> + }
> +
> + pool_info = &device->pool_info;
> +
> + new_limits = kcalloc(pool_info->table_len, sizeof(int), GFP_KERNEL);
> + if (!new_limits) {
> + ret = -ENOMEM;
> + goto parse_err;
> + }

Hmm... except for new_limits allocation and alloc_cg_rpool()
invocation, both of which can be done at the head of the function,
nothing seems to require a mutex here. If we move them to the head of
the function, can we get rid of dev_mutex entirely?

> + ret = rdmacg_parse_limits(options, pool_info, new_limits, &enables);
> + if (ret)
> + goto opt_err;
> +
> +retry:
> + spin_lock(&rpool_list_lock);
> + rpool = find_cg_rpool_locked(cg, device);
> + if (!rpool) {
> + spin_unlock(&rpool_list_lock);
> + ret = alloc_cg_rpool(cg, device);
> + if (ret)
> + goto opt_err;
> + else
> + goto retry;
> + }
> +
> + /* now set the new limits of the rpool */
> + while (enables) {
> + /* if user set the limit, enables bit is set */
> + if (enables & BIT(i)) {
> + enables &= ~BIT(i);
> + set_resource_limit(rpool, i, new_limits[i]);
> + }
> + i++;
> + }

Maybe we can use for_each_set_bit()?

> + if (rpool->usage_sum == 0 &&
> + rpool->num_max_cnt == pool_info->table_len) {
> + /*
> + * No user of the rpool and all entries are set to max, so
> + * safe to delete this rpool.
> + */
> + list_del(&rpool->cg_list);
> + spin_unlock(&rpool_list_lock);
> +
> + free_cg_rpool(rpool);

Can't we just count the number of resources which either have !max
setting or !0 usage?

> +static u32 *get_cg_rpool_values(struct rdma_cgroup *cg,
> + struct rdmacg_device *device,
> + enum rdmacg_file_type sf_type,
> + int count)
> +{
> + struct rdmacg_resource_pool *rpool;
> + u32 *value_tbl;
> + int i, ret;
> +
> + value_tbl = kcalloc(count, sizeof(u32), GFP_KERNEL);
> + if (!value_tbl) {
> + ret = -ENOMEM;
> + goto err;
> + }

Do we need this type of interface? Why not let the caller look up the
target pool and call this function with pool and resource index?

Thanks.

--
tejun

2016-04-04 22:50:59

by Parav Pandit

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

Hi Tejun,

On Mon, Apr 4, 2016 at 12:36 PM, Tejun Heo <[email protected]> wrote:
> Hello, Parav.
>
> Sorry about the delay.
>
Thanks for the review. Comments and answers inline.

>> +struct rdma_cgroup {
>> + struct cgroup_subsys_state css;
>> +
>> + /* protects resource pool list */
>> + spinlock_t rpool_list_lock;
>
> Is this lock still being used?
Nop. I should have cleaned this up, as we have the global lock. It
must got added in patch recreation time.
I will remove it.

>
>> + /*
>> + * head to keep track of all resource pools
>> + * that belongs to this cgroup.
>> + */
>> + struct list_head rpool_head;
>> +};
>> +
>> +struct rdmacg_pool_info {
>> + const char **resource_name_table;
>> + int table_len;
>
> It's a bit bothering that resource type defs are outside the
> controller in a way the controller can't know and assumed to have the
> same type and range. Architecting kernel code so that allow building
> modules separately can modify behaviors usually isn't a priority and

Module cannot really change the behavior just by adding/removing
new/old resource type.
Behavior is defined by the controller as it does today.

> styling things like this makes implementing specific one-off behaviors
> cumbersome.
I agree that other controllers are not doing this way, for some good
reason in those controllers.
But its not right to impose the same restriction here.
Code is really straight forward where pool_info array length is
dynamic, instead of static structure definition.
By addressing some of the comments to change from spin lock to mutex,
will further reduce the allocation complexity.
I believe that should be sufficient.

We have discussed advantages this approach in previous patches along
with consensus from other rdma experts.
In v9 you asked to document them clearly in the Documentation
indicating that its alright to define by the module as its done in v9
and v10 patch.

>
> It now looks okay but let's say in the future someone wants to use a
> different type and/or ranges for one of the resources, given the above
> framework, that person is most likely to blow up pool_info - first
> with types and min, max values and maybe later with callback handlers
> and so on, when the only thing which would have been necessary is an
> one-off handling of that specific setting in the interface functions.
>
We can always go back to change the scope of rdma cgroup at that point
to restrict for what you have described above. At present thats not
the scope and we have reduced the requirements to what is implemented
today.

Additionally if the min,max setting might arrive for a resource in
future, than its likely that it would be applicable to multiple
resources than one resource. And in that case putting them inside the
pool_info would be appropriate as well.
rdma cgroup will have to define the new interface at that point anyway.
We have to let this interface be usable first with current coded
functionality and flexibility that is not harmful.

> Making things modular comes at the cost of complexity and ad-hoc
> flexiblity.

At this point, complexity of this architecture is not really much.
Only overheads are kmalloc and kfree are done on resource pool whose
size is decided dynamically instead of a value defined in header file
in rdma_cgroup.h.
(Instead its in ib_verbs.h)
Its fair enough to have that flexibility.

> There are cases where such overhead is warranted but as I
> mentioned before, I don't think this is one of them. Please look back
> on how this patchset developed. It has been a continuous process of
> cutting down complexities which didn't need to be there which is the
> opposite of what we usually want to do - buliding up sophiscation and
> complexity as necessary from something simple. Please cut down this
> part too.

Tejun,
To repeat the basic motivation of this functionality/architecture is:
kernel upgrades are done over period of years at customers location.
Upgrading kernel for few rdma resources is really silly.
Current implementation anyway doesn't let modules define any crazy count either.
Currently only 64 different types of resources can be defined anyway.
New resource definition anyway goes through reviews by maintainers.
So as we talked in past, I would like to continue with this approach.

2nd advantage is, it allows avoiding lot of rework, in bundling kernel
modules with different kernel versions. So it is certainly valuable
feature with almost nil complexity cost of code or memory that solves
two problems with approach.
If there two kernel versions with different type of resources and
kernel modules with different resources, it gets ugly to maintain that
kind of compatibility using compat.h. This approach avoid all such
cases.

>
>> +};
>> +
>> +struct rdmacg_device {
>> + struct rdmacg_pool_info pool_info;
>> + struct list_head rdmacg_list;
>> + struct list_head rpool_head;
>> + /* protects resource pool list */
>> + spinlock_t rpool_lock;
>
> Does this still need to be a separate lock?
>
Nop. I got to remove this.

>> +#define RDMACG_MAX_STR "max"
>> +
>> +static DEFINE_MUTEX(dev_mutex);
>
> Can you please prefix the above with rdmacg?
o.k.
I will rename to rdmacg_mutex as I intent to remove all the spin locks
and have one mutex lock for all the protections.

>
>> +static LIST_HEAD(dev_list_head);
>
> Ditto, how about rdmacg_dev_list?
>
o.k.

>> +/*
>> + * resource pool object which represents per cgroup, per device
>> + * resources. There are multiple instance of this object per cgroup,
> ^s
will fix this.

>> + * therefore it cannot be embedded within rdma_cgroup structure. It
>> + * is maintained as list.
>> + */
>> +struct rdmacg_resource_pool {
>> + struct list_head cg_list;
>> + struct list_head dev_list;
>
> Isn't it more conventional to use OBJ_list or OBJs for the head and
> _node or _link for the members?
>
_head is more readable than list to me. So I will keep the _head as it
is unless this is blocker for patch acceptance.
I will replace above two _list items with _node.

>> +static int
>> +alloc_cg_rpool(struct rdma_cgroup *cg, struct rdmacg_device *device)
>> +{
>> + struct rdmacg_resource_pool *rpool, *other_rpool;
>> + struct rdmacg_pool_info *pool_info = &device->pool_info;
>> + int ret;
>> +
>> + rpool = kzalloc(sizeof(*rpool), GFP_KERNEL);
>> + if (!rpool) {
>> + ret = -ENOMEM;
>> + goto err;
>> + }
>> + rpool->resources = kcalloc(pool_info->table_len,
>> + sizeof(*rpool->resources),
>> + GFP_KERNEL);
>> + if (!rpool->resources) {
>> + ret = -ENOMEM;
>> + goto alloc_err;
>> + }
>> +
>> + rpool->device = device;
>> + INIT_LIST_HEAD(&rpool->cg_list);
>> + INIT_LIST_HEAD(&rpool->dev_list);
>> + spin_lock_init(&device->rpool_lock);
>> + set_all_resource_max_limit(rpool);
>> +
>> + spin_lock(&rpool_list_lock);
>> +
>> + other_rpool = find_cg_rpool_locked(cg, device);
>> +
>> + /*
>> + * if other task added resource pool for this device for this cgroup
>> + * than free up which was recently created and use the one we found.
>> + */
>
> * It's usually a good idea to have hierarchical objects to be created
> all the way to the root when a leaf node is requested and link
> parent at each level. That way, when a descendant object is looked
> up, the caller can always deref its ->parent pointer till it reaches
> the top.

This is interesting comment. I cannot recall, but I think its v4 or v5
was exactly doing same.
Allocating resource pool first in one loop and charging them in another.
In event of failure, uncharging them in 3rd.
And you gave comment that time is to move away from that approach for
simplicity!
That I had refcnt on each object.

Anyways,
Few things for above comment and some of the below comments.
1. I will replace all the spin lock to single mutex
2. With that it will be, try_charge() will be,
- lock()
- For multiple levels, until null parent,
- allocate() for a given level.
- charge for that level.
- unlock()
3. uncharge() will be,
- lock()
- uncharge(), free() if necessary at that level.
- unlock()

With this spuriousness issue that you described will also be addressed.


>
> * Unless it's a very hot path, using a mutex which protects creation
> of new nodes is usually simpler than doing alloc -> lock ->
> try-insert. The concurrency in the creation path doesn't matter and
> even if it mattered it isn't a good idea to bang on allocation path
> concurrently anyway.
>
>> +static void
>> +uncharge_cg_resource_locked(struct rdma_cgroup *cg,
>> + struct rdmacg_device *device,
>> + int index)
>> +{
>> + struct rdmacg_resource_pool *rpool;
>> + struct rdmacg_pool_info *pool_info = &device->pool_info;
>> +
>> + rpool = find_cg_rpool_locked(cg, device);
>
> So, with each pool linking to its parent, instead of looking up at
> each level, it can just follow ->parent recursively.

No. Lookup is being done for the rpool for that device and not the cgroup.
doing recursively using ->parent would require additional pointer of
rpool type to be maintained which is not necessary because we already
have cgroup level parent.
Adding ->parent would make it little faster compare to traversing 1 to
4 node entry list. However this is not hot path, so keeping existing
code to traverse is more desirable than adding ->parent of rpool type,
which also saves memory for large number of cgroup instances.

>
>> + /*
>> + * A negative count (or overflow) is invalid,
>> + * it indicates a bug in the rdma controller.
>> + */
>> + WARN_ON_ONCE(rpool->resources[index].usage < 0);
>> + rpool->usage_sum--;
>
> What guarantees that this count wouldn't overflow? More on this
> later.

Are you suggesting to add WARN_ON for usage_sum variable for debugging?
I believe 1st warning is sufficient?

>
>> +/**
>> + * rdmacg_uncharge_resource - hierarchically uncharge rdma resource count
>> + * @device: pointer to rdmacg device
>> + * @index: index of the resource to uncharge in cg in given resource pool
>> + * @num: the number of rdma resource to uncharge
>> + *
>> + */
>> +void rdmacg_uncharge(struct rdma_cgroup *cg,
>> + struct rdmacg_device *device,
>> + int index)
>> +{
>> + struct rdma_cgroup *p;
>> +
>> + spin_lock(&rpool_list_lock);
>> + for (p = cg; p; p = parent_rdmacg(p))
>> + uncharge_cg_resource_locked(p, device, index);
>> + spin_unlock(&rpool_list_lock);
>> +
>> + css_put(&cg->css);
>
> Hmm... what css ref is the above putting?
Its putting the reference which was taken previously during charging time using,
get_current_rdmacg() which takes using task_get_css().

>
>> +int rdmacg_try_charge(struct rdma_cgroup **rdmacg,
>> + struct rdmacg_device *device,
>> + int index)
>> +{
> ...
>> + spin_lock(&rpool_list_lock);
>> + for (p = cg; p; p = parent_rdmacg(p)) {
>> +retry:
>> + rpool = find_cg_rpool_locked(p, device);
>
> Here, too, you can look up the leaf node once and walk up the
> hierarhcy. Maybe create a helper, say rdmacg_get_pool_and_lock(),
> which looks up an existing node or creates all pools from leaf to root
> and returns with the lock held?

A explained above, I will reduce rpool_list_lock and dev_mutex to
single rdmacg_mutex.
Lookup will still continue to avoid storing extra parent, because
lookup is for rpool which can be multiple per cgroup.

>
> ...
>> +err:
>> + spin_lock(&rpool_list_lock);
>> + for (q = cg; q != p; q = parent_rdmacg(q))
>> + uncharge_cg_resource_locked(q, device, index);
>
> If some resources are not very plentiful and contended, dropping lock
> before error handling like above could lead to spurious failures. It
> looks like the above is from trying to create pool at each level while
> charging up. Creating all necessary pools at the outset should make
> the code simpler and more robust.
Same comment as above.

>
>> + spin_unlock(&rpool_list_lock);
>> + css_put(&cg->css);
>
> Ditto, where is this from?

>From task_get_css, called by get_current_rdmacg() during charging time.

>
>> +void rdmacg_query_limit(struct rdmacg_device *device, int *limits)
>> +{
>> + struct rdma_cgroup *cg, *p;
>> + struct rdmacg_resource_pool *rpool;
>> + struct rdmacg_pool_info *pool_info;
>> + int i;
>> +
>> + pool_info = &device->pool_info;
>> +
>> + for (i = 0; i < pool_info->table_len; i++)
>> + limits[i] = S32_MAX;
>> +
>> + cg = get_current_rdmacg();
>> + /*
>> + * Check in hirerchy which pool get the least amount of
>> + * resource limits.
>> + */
>> + spin_lock(&rpool_list_lock);
>> + for (p = cg; p; p = parent_rdmacg(p)) {
>> + rpool = find_cg_rpool_locked(cg, device);
>
> Ditto with lookup here.

Same comment as above, need to have lookup.

>
>> +static struct rdmacg_device *rdmacg_get_device_locked(const char *name)
>> +{
>> + struct rdmacg_device *device;
>
> lockdep_assert_held() would be nice.

o.k. I will add this.

>
> +
>> +static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
>> + char *buf, size_t nbytes, loff_t off)
>> +{
>> + struct rdma_cgroup *cg = css_rdmacg(of_css(of));
>> + const char *dev_name;
>> + struct rdmacg_resource_pool *rpool;
>> + struct rdmacg_device *device;
>> + char *options = strstrip(buf);
>> + struct rdmacg_pool_info *pool_info;
>> + u64 enables = 0;
>> + int *new_limits;
>> + int i = 0, ret = 0;
>> +
>> + /* extract the device name first */
>> + dev_name = strsep(&options, " ");
>> + if (!dev_name) {
>> + ret = -EINVAL;
>> + goto err;
>> + }
>> +
>> + /* acquire lock to synchronize with hot plug devices */
>> + mutex_lock(&dev_mutex);
>> +
>> + device = rdmacg_get_device_locked(dev_name);
>> + if (!device) {
>> + ret = -ENODEV;
>> + goto parse_err;
>> + }
>> +
>> + pool_info = &device->pool_info;
>> +
>> + new_limits = kcalloc(pool_info->table_len, sizeof(int), GFP_KERNEL);
>> + if (!new_limits) {
>> + ret = -ENOMEM;
>> + goto parse_err;
>> + }
>
> Hmm... except for new_limits allocation and alloc_cg_rpool()
> invocation, both of which can be done at the head of the function,
> nothing seems to require a mutex here. If we move them to the head of
> the function, can we get rid of dev_mutex entirely?

No. As described in comment where lock is taken,
it ensures that if device is being removed while configuration is
going on, it protects against those race condition.
So will keep it as it is.

>
>> + ret = rdmacg_parse_limits(options, pool_info, new_limits, &enables);
>> + if (ret)
>> + goto opt_err;
>> +
>> +retry:
>> + spin_lock(&rpool_list_lock);
>> + rpool = find_cg_rpool_locked(cg, device);
>> + if (!rpool) {
>> + spin_unlock(&rpool_list_lock);
>> + ret = alloc_cg_rpool(cg, device);
>> + if (ret)
>> + goto opt_err;
>> + else
>> + goto retry;
>> + }
>> +
>> + /* now set the new limits of the rpool */
>> + while (enables) {
>> + /* if user set the limit, enables bit is set */
>> + if (enables & BIT(i)) {
>> + enables &= ~BIT(i);
>> + set_resource_limit(rpool, i, new_limits[i]);
>> + }
>> + i++;
>> + }
>
> Maybe we can use for_each_set_bit()?
ok. I will use that API.
>
>> + if (rpool->usage_sum == 0 &&
>> + rpool->num_max_cnt == pool_info->table_len) {
>> + /*
>> + * No user of the rpool and all entries are set to max, so
>> + * safe to delete this rpool.
>> + */
>> + list_del(&rpool->cg_list);
>> + spin_unlock(&rpool_list_lock);
>> +
>> + free_cg_rpool(rpool);
>
> Can't we just count the number of resources which either have !max
> setting or !0 usage?
>
What you have described here is done above. Instead of running loop to
find that out for every resource,
as you suggested last time, keeping two counters for usage and max to
find out this condition.

>> +static u32 *get_cg_rpool_values(struct rdma_cgroup *cg,
>> + struct rdmacg_device *device,
>> + enum rdmacg_file_type sf_type,
>> + int count)
>> +{
>> + struct rdmacg_resource_pool *rpool;
>> + u32 *value_tbl;
>> + int i, ret;
>> +
>> + value_tbl = kcalloc(count, sizeof(u32), GFP_KERNEL);
>> + if (!value_tbl) {
>> + ret = -ENOMEM;
>> + goto err;
>> + }
>
> Do we need this type of interface?
Yes
> Why not let the caller look up the
> target pool and call this function with pool and resource index?

I can move value_tbl allocation out of this function, but what is the
problem in current code?
I don't see any difference in moving loop count specific code outside
of get_cg_rpool_values() function other than doing rework. What gain
you are looking forward to by doing so?

2016-04-05 01:25:12

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

Hello,

On Mon, Apr 04, 2016 at 03:50:54PM -0700, Parav Pandit wrote:
> 2nd advantage is, it allows avoiding lot of rework, in bundling kernel
> modules with different kernel versions. So it is certainly valuable
> feature with almost nil complexity cost of code or memory that solves
> two problems with approach.
> If there two kernel versions with different type of resources and
> kernel modules with different resources, it gets ugly to maintain that
> kind of compatibility using compat.h. This approach avoid all such
> cases.

Is it actually customary to have rdma core module updated more
frequently separate from the kernel? Out-of-tree modules being
updated separately happens quite a bit but this is an in-kernel
module, which usually is tightly coupled with the rest of the kernel.

> > * It's usually a good idea to have hierarchical objects to be created
> > all the way to the root when a leaf node is requested and link
> > parent at each level. That way, when a descendant object is looked
> > up, the caller can always deref its ->parent pointer till it reaches
> > the top.
>
> This is interesting comment. I cannot recall, but I think its v4 or v5
> was exactly doing same.
> Allocating resource pool first in one loop and charging them in another.
> In event of failure, uncharging them in 3rd.
> And you gave comment that time is to move away from that approach for
> simplicity!
> That I had refcnt on each object.

I don't remember the details well but the code was vastly more complex
back then and the object lifetime management was muddy at best. If I
reviewed in a contradicting way, my apologies, but given the current
code, it'd be better to have objects creation upfront.

> > So, with each pool linking to its parent, instead of looking up at
> > each level, it can just follow ->parent recursively.
>
> No. Lookup is being done for the rpool for that device and not the cgroup.
> doing recursively using ->parent would require additional pointer of
> rpool type to be maintained which is not necessary because we already
> have cgroup level parent.

Yes, I meant adding pool->parent field. Hierarchical operations
become far simpler with the invariant that parent always exists.

> Adding ->parent would make it little faster compare to traversing 1 to
> 4 node entry list. However this is not hot path, so keeping existing
> code to traverse is more desirable than adding ->parent of rpool type,
> which also saves memory for large number of cgroup instances.

It isn't necessarily about speed. It makes clear that the parent
always should exist and makes the code easier to read and update.

> >> + /*
> >> + * A negative count (or overflow) is invalid,
> >> + * it indicates a bug in the rdma controller.
> >> + */
> >> + WARN_ON_ONCE(rpool->resources[index].usage < 0);
> >> + rpool->usage_sum--;
> >
> > What guarantees that this count wouldn't overflow? More on this
> > later.
>
> Are you suggesting to add WARN_ON for usage_sum variable for debugging?
> I believe 1st warning is sufficient?

I mean that it isn't particularly difficult to overflow a s32 when
adding up multiple usages into one.

> >> +static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
> >> + char *buf, size_t nbytes, loff_t off)
> >> +{
> >> + struct rdma_cgroup *cg = css_rdmacg(of_css(of));
> >> + const char *dev_name;
> >> + struct rdmacg_resource_pool *rpool;
> >> + struct rdmacg_device *device;
> >> + char *options = strstrip(buf);
> >> + struct rdmacg_pool_info *pool_info;
> >> + u64 enables = 0;
> >> + int *new_limits;
> >> + int i = 0, ret = 0;
> >> +
> >> + /* extract the device name first */
> >> + dev_name = strsep(&options, " ");
> >> + if (!dev_name) {
> >> + ret = -EINVAL;
> >> + goto err;
> >> + }
> >> +
> >> + /* acquire lock to synchronize with hot plug devices */
> >> + mutex_lock(&dev_mutex);
> >> +
> >> + device = rdmacg_get_device_locked(dev_name);
> >> + if (!device) {
> >> + ret = -ENODEV;
> >> + goto parse_err;
> >> + }
> >> +
> >> + pool_info = &device->pool_info;
> >> +
> >> + new_limits = kcalloc(pool_info->table_len, sizeof(int), GFP_KERNEL);
> >> + if (!new_limits) {
> >> + ret = -ENOMEM;
> >> + goto parse_err;
> >> + }
> >
> > Hmm... except for new_limits allocation and alloc_cg_rpool()
> > invocation, both of which can be done at the head of the function,
> > nothing seems to require a mutex here. If we move them to the head of
> > the function, can we get rid of dev_mutex entirely?
>
> No. As described in comment where lock is taken,
> it ensures that if device is being removed while configuration is
> going on, it protects against those race condition.
> So will keep it as it is.

Yeah, sure, it needs protection. I was wondering why it needed to be
a separate mutex. The only sleeping operations seem to be the ones
which can be done before or with acquiring rpool_list_lock, no?

> >> + if (rpool->usage_sum == 0 &&
> >> + rpool->num_max_cnt == pool_info->table_len) {
> >> + /*
> >> + * No user of the rpool and all entries are set to max, so
> >> + * safe to delete this rpool.
> >> + */
> >> + list_del(&rpool->cg_list);
> >> + spin_unlock(&rpool_list_lock);
> >> +
> >> + free_cg_rpool(rpool);
> >
> > Can't we just count the number of resources which either have !max
> > setting or !0 usage?
> >
> What you have described here is done above. Instead of running loop to
> find that out for every resource,

You're summing up all usages instead of counting the number of !0
usages.

> as you suggested last time, keeping two counters for usage and max to
> find out this condition.

So, whether the counters are separate or not doesn't matter that much
but it's awkward that it's counting the number of !max entries for
limits while summing the actual usages (instead of counting the number
of !0 usages) especially given that it gets summed into an int
counter. Wouldn't counting !max limit && !0 usage be more consistent?

> >> +static u32 *get_cg_rpool_values(struct rdma_cgroup *cg,
> >> + struct rdmacg_device *device,
> >> + enum rdmacg_file_type sf_type,
> >> + int count)
> >> +{
> >> + struct rdmacg_resource_pool *rpool;
> >> + u32 *value_tbl;
> >> + int i, ret;
> >> +
> >> + value_tbl = kcalloc(count, sizeof(u32), GFP_KERNEL);
> >> + if (!value_tbl) {
> >> + ret = -ENOMEM;
> >> + goto err;
> >> + }
> >
> > Do we need this type of interface?
> Yes
> > Why not let the caller look up the
> > target pool and call this function with pool and resource index?
>
> I can move value_tbl allocation out of this function, but what is the
> problem in current code?
> I don't see any difference in moving loop count specific code outside
> of get_cg_rpool_values() function other than doing rework. What gain
> you are looking forward to by doing so?

I meant you don't need the allocation at all.

lock;
for_each_dev() {
pool = lookup_pool();
for_each_field()
seq_printf(sf, "%s ", name, pool_val(pool, index));
}
unlock;

I don't know why you end up missing basic patterns so badly. It's
making the review and iteration process pretty painful. Please don't
be confrontational and try to read the review comments assuming good
will.

Thanks.

--
tejun

2016-04-05 02:30:59

by Parav Pandit

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

Hi Tejun,

On Mon, Apr 4, 2016 at 6:25 PM, Tejun Heo <[email protected]> wrote:
> Hello,
>
> On Mon, Apr 04, 2016 at 03:50:54PM -0700, Parav Pandit wrote:
>> 2nd advantage is, it allows avoiding lot of rework, in bundling kernel
>> modules with different kernel versions. So it is certainly valuable
>> feature with almost nil complexity cost of code or memory that solves
>> two problems with approach.
>> If there two kernel versions with different type of resources and
>> kernel modules with different resources, it gets ugly to maintain that
>> kind of compatibility using compat.h. This approach avoid all such
>> cases.
>
> Is it actually customary to have rdma core module updated more
> frequently separate from the kernel? Out-of-tree modules being
> updated separately happens quite a bit but this is an in-kernel
> module, which usually is tightly coupled with the rest of the kernel.
>
Yes.
rdma core module is compiled as kernel module.
Its often updated for new features, fixes.
So kernel version can be one but RDMA core module(s) get updated more
frequently than kernel.

>
> I don't remember the details well but the code was vastly more complex
> back then and the object lifetime management was muddy at best. If I
> reviewed in a contradicting way, my apologies, but given the current
> code, it'd be better to have objects creation upfront.

Do you mean,
try_charge() should
lock()
run loop to allocate in hierarchy, if not allocated.
run loop again to charge.
unlock()

If so, I prefer to run the loop once.

If you mean, objects creating upfront when cgroup is created, or when
device is created - than we have talked of that approach in past, that
its more complex than what is being done today. I would certainly want
to avoid that in this patch as above approach has two advantages.
(a) its simpler
(b) doesn't require extra parent pointer, which is already available from cgroup

>
>> > So, with each pool linking to its parent, instead of looking up at
>> > each level, it can just follow ->parent recursively.
>>
>> No. Lookup is being done for the rpool for that device and not the cgroup.
>> doing recursively using ->parent would require additional pointer of
>> rpool type to be maintained which is not necessary because we already
>> have cgroup level parent.
>
> Yes, I meant adding pool->parent field. Hierarchical operations
> become far simpler with the invariant that parent always exists.

>
>> Adding ->parent would make it little faster compare to traversing 1 to
>> 4 node entry list. However this is not hot path, so keeping existing
>> code to traverse is more desirable than adding ->parent of rpool type,
>> which also saves memory for large number of cgroup instances.
>
> It isn't necessarily about speed. It makes clear that the parent
> always should exist and makes the code easier to read and update.

It doesn't have to exist. It can get allocated when charging occurs.
Otherwise even if rdma resources are not used, it ends up allocating
rpool in hierarchy. (We talked this before)

>
>> >> + /*
>> >> + * A negative count (or overflow) is invalid,
>> >> + * it indicates a bug in the rdma controller.
>> >> + */
>> >> + WARN_ON_ONCE(rpool->resources[index].usage < 0);
>> >> + rpool->usage_sum--;
>> >
>> > What guarantees that this count wouldn't overflow? More on this
>> > later.
>>
>> Are you suggesting to add WARN_ON for usage_sum variable for debugging?
>> I believe 1st warning is sufficient?
>
> I mean that it isn't particularly difficult to overflow a s32 when
> adding up multiple usages into one.
Possible,but resources are not really in range of 2G range, at least
not any hardware that I am aware of.
But I can make it u64.

>
>> >> +static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
>> >> + char *buf, size_t nbytes, loff_t off)
>> >> +{
>> >> + struct rdma_cgroup *cg = css_rdmacg(of_css(of));
>> >> + const char *dev_name;
>> >> + struct rdmacg_resource_pool *rpool;
>> >> + struct rdmacg_device *device;
>> >> + char *options = strstrip(buf);
>> >> + struct rdmacg_pool_info *pool_info;
>> >> + u64 enables = 0;
>> >> + int *new_limits;
>> >> + int i = 0, ret = 0;
>> >> +
>> >> + /* extract the device name first */
>> >> + dev_name = strsep(&options, " ");
>> >> + if (!dev_name) {
>> >> + ret = -EINVAL;
>> >> + goto err;
>> >> + }
>> >> +
>> >> + /* acquire lock to synchronize with hot plug devices */
>> >> + mutex_lock(&dev_mutex);
>> >> +
>> >> + device = rdmacg_get_device_locked(dev_name);
>> >> + if (!device) {
>> >> + ret = -ENODEV;
>> >> + goto parse_err;
>> >> + }
>> >> +
>> >> + pool_info = &device->pool_info;
>> >> +
>> >> + new_limits = kcalloc(pool_info->table_len, sizeof(int), GFP_KERNEL);
>> >> + if (!new_limits) {
>> >> + ret = -ENOMEM;
>> >> + goto parse_err;
>> >> + }
>> >
>> > Hmm... except for new_limits allocation and alloc_cg_rpool()
>> > invocation, both of which can be done at the head of the function,
>> > nothing seems to require a mutex here. If we move them to the head of
>> > the function, can we get rid of dev_mutex entirely?
>>
>> No. As described in comment where lock is taken,
>> it ensures that if device is being removed while configuration is
>> going on, it protects against those race condition.
>> So will keep it as it is.
>
> Yeah, sure, it needs protection. I was wondering why it needed to be
> a separate mutex. The only sleeping operations seem to be the ones
> which can be done before or with acquiring rpool_list_lock, no?

I will just make one mutex lock now.

>
>> >> + if (rpool->usage_sum == 0 &&
>> >> + rpool->num_max_cnt == pool_info->table_len) {
>> >> + /*
>> >> + * No user of the rpool and all entries are set to max, so
>> >> + * safe to delete this rpool.
>> >> + */
>> >> + list_del(&rpool->cg_list);
>> >> + spin_unlock(&rpool_list_lock);
>> >> +
>> >> + free_cg_rpool(rpool);
>> >
>> > Can't we just count the number of resources which either have !max
>> > setting or !0 usage?
>> >
>> What you have described here is done above. Instead of running loop to
>> find that out for every resource,
>
> You're summing up all usages instead of counting the number of !0
> usages.

I see now. Yeah, I will change that as well to make is consistent like
!max_entries.

>
>> as you suggested last time, keeping two counters for usage and max to
>> find out this condition.
>
> So, whether the counters are separate or not doesn't matter that much
> but it's awkward that it's counting the number of !max entries for
> limits while summing the actual usages (instead of counting the number
> of !0 usages) especially given that it gets summed into an int
> counter. Wouldn't counting !max limit && !0 usage be more consistent?

>
>> >> +static u32 *get_cg_rpool_values(struct rdma_cgroup *cg,
>> >> + struct rdmacg_device *device,
>> >> + enum rdmacg_file_type sf_type,
>> >> + int count)
>> >> +{
>> >> + struct rdmacg_resource_pool *rpool;
>> >> + u32 *value_tbl;
>> >> + int i, ret;
>> >> +
>> >> + value_tbl = kcalloc(count, sizeof(u32), GFP_KERNEL);
>> >> + if (!value_tbl) {
>> >> + ret = -ENOMEM;
>> >> + goto err;
>> >> + }
>> >
>> > Do we need this type of interface?
>> Yes
>> > Why not let the caller look up the
>> > target pool and call this function with pool and resource index?
>>
>> I can move value_tbl allocation out of this function, but what is the
>> problem in current code?
>> I don't see any difference in moving loop count specific code outside
>> of get_cg_rpool_values() function other than doing rework. What gain
>> you are looking forward to by doing so?
>
> I meant you don't need the allocation at all.
>
> lock;
> for_each_dev() {
> pool = lookup_pool();
> for_each_field()
> seq_printf(sf, "%s ", name, pool_val(pool, index));
> }
> unlock;
>
> I don't know why you end up missing basic patterns so badly. It's
> making the review and iteration process pretty painful. Please don't
> be confrontational and try to read the review comments assuming good
> will.
>
My understanding of seq_printf() being blocking call and accessing
pool_info in spin lock context, made me allocate memory to get all
values upfront through allocation.
Now that the lock is going away, I can do what you have described above.



> Thanks.
>
> --
> tejun

2016-04-05 09:06:39

by Christoph Hellwig

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

On Mon, Apr 04, 2016 at 09:25:04PM -0400, Tejun Heo wrote:
> Is it actually customary to have rdma core module updated more
> frequently separate from the kernel? Out-of-tree modules being
> updated separately happens quite a bit but this is an in-kernel
> module, which usually is tightly coupled with the rest of the kernel.

People do it for all the wrong reasons - OFED and organization of morons
wants people to use their modules, which causes lots of harm. Anything
that makes this harder is a good thing.

2016-04-05 12:39:26

by Parav Pandit

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

Hi Christoph,

On Tue, Apr 5, 2016 at 2:06 AM, Christoph Hellwig <[email protected]> wrote:
> On Mon, Apr 04, 2016 at 09:25:04PM -0400, Tejun Heo wrote:
>> Is it actually customary to have rdma core module updated more
>> frequently separate from the kernel? Out-of-tree modules being
>> updated separately happens quite a bit but this is an in-kernel
>> module, which usually is tightly coupled with the rest of the kernel.
>
> People do it for all the wrong reasons - OFED and organization of morons
> wants people to use their modules, which causes lots of harm. Anything
> that makes this harder is a good thing.
>
I am not really trying to address OFED issues here. I am sure you
understand that if ib_core.ko kernel module is in-kernel module than,
for all the fixes/enhancements that goes to it would require people to
upgrade to newer kernel, instead of just modules upgrade. Such heavy
weight upgrade slows down the adoption which i am trying to avoid here
by placing knobs in right kernel module's hand.
Its like making ib_core.ko from module to in kernel component, if I
understand correctly nobody wants to do that.

2016-04-05 12:42:58

by Christoph Hellwig

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

On Tue, Apr 05, 2016 at 05:39:21AM -0700, Parav Pandit wrote:
> I am not really trying to address OFED issues here. I am sure you
> understand that if ib_core.ko kernel module is in-kernel module than,
> for all the fixes/enhancements that goes to it would require people to
> upgrade to newer kernel, instead of just modules upgrade. Such heavy
> weight upgrade slows down the adoption which i am trying to avoid here
> by placing knobs in right kernel module's hand.

What a load of rubbish. The Linux kernel is one program and should be
upgraded together.

> Its like making ib_core.ko from module to in kernel component, if I
> understand correctly nobody wants to do that.

We allow splitting subsystems out where it's easily doable to avoid the
resources consumption if they're all built in. For cgroups it's not
really practical, but that doesn't mean you can upgrade invidual parts
of a complex program without a lot of precaution.

2016-04-05 12:55:32

by Parav Pandit

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

Hi Christoph,

On Tue, Apr 5, 2016 at 5:42 AM, Christoph Hellwig <[email protected]> wrote:
> On Tue, Apr 05, 2016 at 05:39:21AM -0700, Parav Pandit wrote:
>> I am not really trying to address OFED issues here. I am sure you
>> understand that if ib_core.ko kernel module is in-kernel module than,
>> for all the fixes/enhancements that goes to it would require people to
>> upgrade to newer kernel, instead of just modules upgrade. Such heavy
>> weight upgrade slows down the adoption which i am trying to avoid here
>> by placing knobs in right kernel module's hand.
>
> What a load of rubbish. The Linux kernel is one program and should be
> upgraded together.

Just because we add one more rdma resource, we need to ask someone to
upgrade kernel?

Flexibility is coming at very minimal cost, so what exactly is the
issue in having that instead of forcing architecture to give away
that? Whole code is hardly 700 odd lines.

>
>> Its like making ib_core.ko from module to in kernel component, if I
>> understand correctly nobody wants to do that.
>
> We allow splitting subsystems out where it's easily doable to avoid the
> resources consumption if they're all built in. For cgroups it's not
> really practical, but that doesn't mean you can upgrade invidual parts
> of a complex program without a lot of precaution.

2016-04-05 14:01:15

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

Hello, Parav.

On Mon, Apr 04, 2016 at 07:22:38PM -0700, Parav Pandit wrote:
> > Is it actually customary to have rdma core module updated more
> > frequently separate from the kernel? Out-of-tree modules being
> > updated separately happens quite a bit but this is an in-kernel
> > module, which usually is tightly coupled with the rest of the kernel.
> >
> Yes.
> rdma core module is compiled as kernel module.
> Its often updated for new features, fixes.
> So kernel version can be one but RDMA core module(s) get updated more
> frequently than kernel.

As it is a fairly isolated domain, to certain extent, it could be okay
to let it go. At the same time, I know that these enterprise things
tend to go completely wayward and am worried about individual drivers
going crazy with custom attributes in a non-sensical way. The
interface this patch is proposing easily allows that and that at the
cost of internal engineering flexibility. I don't really want to be
caught up in a situation where we're forced to include broken usages
because that's what's already out in the wild. I personally would
much prefer the resources to be defined rigidly. Let's see how the
discussion with Christoph evolves.

> > I don't remember the details well but the code was vastly more complex
> > back then and the object lifetime management was muddy at best. If I
> > reviewed in a contradicting way, my apologies, but given the current
> > code, it'd be better to have objects creation upfront.
>
> Do you mean,
> try_charge() should
> lock()
> run loop to allocate in hierarchy, if not allocated.
> run loop again to charge.
> unlock()
>
> If so, I prefer to run the loop once.

In the original review message, I mentioned creating an interface
which creates the hierarchy of objects as necessary and returns the
target pool with lock held, can you please give it a shot? Something
like the following.

pool *get_pool(...)
{
lock;
if (target pool exists)
return pool w/ lock held;

create the pool hierarchically (might involve unlock);
if (succeeded)
return pool w/ lock held;
return NULL w/o lock;
}

> > It isn't necessarily about speed. It makes clear that the parent
> > always should exist and makes the code easier to read and update.
>
> It doesn't have to exist. It can get allocated when charging occurs.
> Otherwise even if rdma resources are not used, it ends up allocating
> rpool in hierarchy. (We talked this before)

Sure, create pools only for the used combinations but do that
hierarchically so that a child pool always has a parent. I can
promise you that the code will read a lot better with that.

> > I don't know why you end up missing basic patterns so badly. It's
> > making the review and iteration process pretty painful. Please don't
> > be confrontational and try to read the review comments assuming good
> > will.
> >
> My understanding of seq_printf() being blocking call and accessing

seq_printf() can be called from any context; otherwise, it would be a
horrible interface to use, wouldn't it?

> pool_info in spin lock context, made me allocate memory to get all
> values upfront through allocation.
> Now that the lock is going away, I can do what you have described above.

Thanks.

--
tejun

2016-04-05 14:07:13

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

Just one more thing.

On Tue, Apr 05, 2016 at 10:01:07AM -0400, Tejun Heo wrote:
...
> > pool_info in spin lock context, made me allocate memory to get all
> > values upfront through allocation.
> > Now that the lock is going away, I can do what you have described above.

So, this might be okay depending on the use case but it often becomes
painful to require sleeping context for freeing resources. If you're
certain that requiring sleeping context is okay for all paths, using a
single mutex is fine but *usually* it isn't a great idea.

Thanks.

--
tejun

2016-04-05 14:14:54

by Parav Pandit

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

On Tue, Apr 5, 2016 at 7:07 AM, Tejun Heo <[email protected]> wrote:
> Just one more thing.
>
> On Tue, Apr 05, 2016 at 10:01:07AM -0400, Tejun Heo wrote:
> ...
>> > pool_info in spin lock context, made me allocate memory to get all
>> > values upfront through allocation.
>> > Now that the lock is going away, I can do what you have described above.
>
> So, this might be okay depending on the use case but it often becomes
> painful to require sleeping context for freeing resources. If you're
> certain that requiring sleeping context is okay for all paths, using a
> single mutex is fine but *usually* it isn't a great idea.
>
At present charge and uncharge are from sleeping context.

>
> --
> tejun

2016-04-05 14:25:18

by Parav Pandit

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

On Tue, Apr 5, 2016 at 7:01 AM, Tejun Heo <[email protected]> wrote:
> Hello, Parav.
>
> On Mon, Apr 04, 2016 at 07:22:38PM -0700, Parav Pandit wrote:
>> > Is it actually customary to have rdma core module updated more
>> > frequently separate from the kernel? Out-of-tree modules being
>> > updated separately happens quite a bit but this is an in-kernel
>> > module, which usually is tightly coupled with the rest of the kernel.
>> >
>> Yes.
>> rdma core module is compiled as kernel module.
>> Its often updated for new features, fixes.
>> So kernel version can be one but RDMA core module(s) get updated more
>> frequently than kernel.
>
> As it is a fairly isolated domain, to certain extent, it could be okay
> to let it go. At the same time, I know that these enterprise things
> tend to go completely wayward and am worried about individual drivers
> going crazy with custom attributes in a non-sensical way.

If its crazy at driver level, I am sure it will be equally crazy for
any end-user too. Therefore no user would use those crazy resources
either.
Intent is certainly not for the individual drivers as we agreed in
past. IB stack maintainers would be reviewing new enum addition
anyway, whether its verb or hw resource (unlikely).

> The
> interface this patch is proposing easily allows that and that at the
> cost of internal engineering flexibility. I don't really want to be
> caught up in a situation where we're forced to include broken usages
> because that's what's already out in the wild. I personally would
> much prefer the resources to be defined rigidly. Let's see how the
> discussion with Christoph evolves.
>
Sure. I will wait for his comments.

>> > I don't remember the details well but the code was vastly more complex
>> > back then and the object lifetime management was muddy at best. If I
>> > reviewed in a contradicting way, my apologies, but given the current
>> > code, it'd be better to have objects creation upfront.
>>
>> Do you mean,
>> try_charge() should
>> lock()
>> run loop to allocate in hierarchy, if not allocated.
>> run loop again to charge.
>> unlock()
>>
>> If so, I prefer to run the loop once.
>
> In the original review message, I mentioned creating an interface
> which creates the hierarchy of objects as necessary and returns the
> target pool with lock held, can you please give it a shot? Something
> like the following.

o.k. I will attempt. Looks doable.
I am on travel so it will take few more days for me to turn around
with below approach with tested code.

2016-04-05 14:46:14

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

Hello, Parav.

On Tue, Apr 05, 2016 at 07:25:11AM -0700, Parav Pandit wrote:
> > As it is a fairly isolated domain, to certain extent, it could be okay
> > to let it go. At the same time, I know that these enterprise things
> > tend to go completely wayward and am worried about individual drivers
> > going crazy with custom attributes in a non-sensical way.
>
> If its crazy at driver level, I am sure it will be equally crazy for
> any end-user too. Therefore no user would use those crazy resources
> either.

So, the above paragraph simply isn't true. It's not difficult to
twist things bit so that things work in a hackish and often horrible
way and we know this happens quite a bit, especially in enterprise
settings.

> Intent is certainly not for the individual drivers as we agreed in
> past.

You and I agreeing to that doesn't really matter all that much.

> IB stack maintainers would be reviewing new enum addition
> anyway, whether its verb or hw resource (unlikely).

Well, if the additions are unlikely...

> > In the original review message, I mentioned creating an interface
> > which creates the hierarchy of objects as necessary and returns the
> > target pool with lock held, can you please give it a shot? Something
> > like the following.
>
> o.k. I will attempt. Looks doable.
> I am on travel so it will take few more days for me to turn around
> with below approach with tested code.

So, if you go with single mutex, you don't really need get_and_lock
semantics, you can just call the get function with mutex held. Please
do whichever looks best.

Thanks.

--
tejun

2016-04-05 15:44:34

by Leon Romanovsky

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

On Tue, Apr 05, 2016 at 05:55:26AM -0700, Parav Pandit wrote:
> Hi Christoph,
>
> On Tue, Apr 5, 2016 at 5:42 AM, Christoph Hellwig <[email protected]> wrote:
> > On Tue, Apr 05, 2016 at 05:39:21AM -0700, Parav Pandit wrote:
> >> I am not really trying to address OFED issues here. I am sure you
> >> understand that if ib_core.ko kernel module is in-kernel module than,
> >> for all the fixes/enhancements that goes to it would require people to
> >> upgrade to newer kernel, instead of just modules upgrade. Such heavy
> >> weight upgrade slows down the adoption which i am trying to avoid here
> >> by placing knobs in right kernel module's hand.
> >
> > What a load of rubbish. The Linux kernel is one program and should be
> > upgraded together.
>
> Just because we add one more rdma resource, we need to ask someone to
> upgrade kernel?

It doesn't make sense. Kernel and modules are always coming together,
the attempts to mix kernel and modules from different versions can lead
to many interesting debug scenarios.

2016-04-05 17:27:29

by Christoph Hellwig

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

On Tue, Apr 05, 2016 at 05:55:26AM -0700, Parav Pandit wrote:
> Just because we add one more rdma resource, we need to ask someone to
> upgrade kernel?

Yes. Just like when you need any other core kernel resource.

2016-04-19 08:56:18

by Parav Pandit

[permalink] [raw]
Subject: Re: [PATCHv10 1/3] rdmacg: Added rdma cgroup controller

Hi Christoph,

I was on travel. Sorry for the late inline response and question.

Parav



On Tue, Apr 5, 2016 at 10:57 PM, Christoph Hellwig <[email protected]> wrote:
> On Tue, Apr 05, 2016 at 05:55:26AM -0700, Parav Pandit wrote:
>> Just because we add one more rdma resource, we need to ask someone to
>> upgrade kernel?
>
> Yes. Just like when you need any other core kernel resource.

By architecture Linux kernel allows
(a) plugin of any new block level IO scheduler as kernel module.
This is much more fundamental resource or functionality than rdma resource.
(b) plugin of any new file system as kernel module.

Changing both in field and out of box can do be more harmful than
defining new rdma resource.

RDMA Resource definition by IB core module is very similar to above
two functionality, where elevator and VFS provides basic support
framework and so rdma cgroup controller here.

So can you please help me understand - which resource did you compare
against in your above comment for "core kernel resource"?
I compared it with similar functionality, flexibility given by (a)
block IO Scheduler and (b) VFS subsystem to implement them as kernel
module.