* [PATCH v7 0/2] nvmet: add cgroup_id to charge namespace I/O to a cgroup
@ 2026-09-29 23:24 Peng Yu
2026-09-29 23:24 ` [PATCH v7 1/2] cgroup: track the effective css in each cgroup Peng Yu
2026-09-29 23:24 ` [PATCH v7 2/2] nvmet: add cgroup_id to charge namespace I/O to a cgroup Peng Yu
0 siblings, 2 replies; 5+ messages in thread
From: Peng Yu @ 2026-09-29 23:24 UTC (permalink / raw)
To: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni
Cc: Tejun Heo, Johannes Weiner, Michal Koutný,
Josef Bacik, Jens Axboe, Maurizio Lombardi, cgroups, linux-block,
linux-nvme, linux-kernel, Peng Yu
Change since v6:
* 1/2: Explain in the commit message why the effective css is tracked,
and add comments on e_css[] and in init_cgroup_root() (Tejun).
* 2/2: Use cgroup_get_e_css() and css_put() in nvmet_blkcg_set_bio()
instead of cgroup_e_css() under rcu_read_lock() (Tejun).
* Drop "cgroup: export cgroup_e_css()", which is no longer needed (Tejun).
Change since v5:
* Add "cgroup: track the effective css in each cgroup", so looking up
the effective css for each I/O is O(1) (Tejun).
Change since v4:
* Hold the cgroup and look up its effective io css for each I/O,
instead of pinning one io css when the namespace is enabled (Tejun).
* Allow a cgroup that doesn't have the io controller enabled (Tejun).
* Add "cgroup: export cgroup_e_css()".
The two patches don't depend on each other: 2/2 only uses symbols that
are already exported and works without 1/2, with the slower lookup.
1/2 can go through the cgroup tree and 2/2 through the nvme tree.
v6: https://lore.kernel.org/all/20260928061417.1574676-1-yupeng0921@gmail.com/
v5: https://lore.kernel.org/all/20260926063328.257023-1-yupeng0921@gmail.com/
v4: https://lore.kernel.org/all/20260924053121.17703-1-yupeng0921@gmail.com/
v3: https://lore.kernel.org/all/20260923152653.40953-1-yupeng0921@gmail.com/
v2: https://lore.kernel.org/all/20260922065115.22508-1-yupeng0921@gmail.com/
v1: https://lore.kernel.org/all/20260908011214.11344-1-yupeng0921@gmail.com/
Scenario:
* Create multiple nvmet subsystems/namespaces.
* The namespaces are backed by different LVM logical volumes.
* Some of the logical volumes share the same physical volumes.
* The subsystems are exported to different users.
* We should provide each user a specific iops/bps quota, thus a noisy
neighbor won't impact the performance of other logical volumes.
Test:
1 Load kernel modules.
sudo modprobe brd rd_nr=2 rd_size=1048576
sudo modprobe nvmet
sudo modprobe nvmet_tcp
sudo modprobe nvme_tcp
2 The two brd devices are used for a bdev backed namespace and a file
backed namespace, remember their major/minor.
lsblk --noheadings --nodeps --output NAME,MAJ:MIN,SIZE /dev/ram0 /dev/ram1
ram0 1:0 1G
ram1 1:1 1G
3 Enable the io controller and create two cgroups, one for the bdev
backed namespace, another for the file backed namespace.
echo "+io" | sudo tee /sys/fs/cgroup/cgroup.subtree_control
sudo mkdir -p /sys/fs/cgroup/nvmet-bdev
sudo mkdir -p /sys/fs/cgroup/nvmet-file
4 Create a file system and a file on ram0.
sudo mkfs.ext4 /dev/ram0
sudo mkdir -p /mnt/nvmet-file
sudo mount -o noatime /dev/ram0 /mnt/nvmet-file
sudo dd if=/dev/zero of=/mnt/nvmet-file/ns.img bs=1M count=512 oflag=direct
5 Create the bdev backed subsystem and namespace.
sudo mkdir -p /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:bdev/namespaces/1
echo 1 | sudo tee /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:bdev/attr_allow_any_host
echo /dev/ram1 | sudo tee /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:bdev/namespaces/1/device_path
stat -c %i /sys/fs/cgroup/nvmet-bdev | sudo tee /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:bdev/namespaces/1/cgroup_id
echo 1 | sudo tee /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:bdev/namespaces/1/enable
6 Create the file backed subsystem and namespace.
sudo mkdir -p /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:file/namespaces/1
echo 1 | sudo tee /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:file/attr_allow_any_host
echo /mnt/nvmet-file/ns.img | sudo tee /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:file/namespaces/1/device_path
stat -c %i /sys/fs/cgroup/nvmet-file | sudo tee /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:file/namespaces/1/cgroup_id
echo 1 | sudo tee /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:file/namespaces/1/enable
7 Export the two subsystems to 127.0.0.1.
sudo mkdir -p /sys/kernel/config/nvmet/ports/1
echo ipv4 | sudo tee /sys/kernel/config/nvmet/ports/1/addr_adrfam
echo tcp | sudo tee /sys/kernel/config/nvmet/ports/1/addr_trtype
echo 127.0.0.1 | sudo tee /sys/kernel/config/nvmet/ports/1/addr_traddr
echo 4420 | sudo tee /sys/kernel/config/nvmet/ports/1/addr_trsvcid
sudo ln -s /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:bdev /sys/kernel/config/nvmet/ports/1/subsystems/nqn.2026-09.io.test01:bdev
sudo ln -s /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:file /sys/kernel/config/nvmet/ports/1/subsystems/nqn.2026-09.io.test01:file
8 Connect the two subsystems from 127.0.0.1.
sudo nvme connect -t tcp -a 127.0.0.1 -s 4420 -n nqn.2026-09.io.test01:bdev
sudo nvme connect -t tcp -a 127.0.0.1 -s 4420 -n nqn.2026-09.io.test01:file
sudo udevadm settle
9 Set riops to 20k and run fio.
echo "1:1 riops=20000 wiops=max rbps=max wbps=max" | sudo tee /sys/fs/cgroup/nvmet-bdev/io.max
echo "1:0 riops=20000 wiops=max rbps=max wbps=max" | sudo tee /sys/fs/cgroup/nvmet-file/io.max
sudo fio --name=t --filename=/dev/nvme0n1 --rw=randread --bs=4k --iodepth=32 --numjobs=1 \
--direct=1 --ioengine=libaio --time_based --runtime=10 --ramp_time=3 \
--group_reporting 2>/dev/null | grep -E '^ +(read|write): IOPS'
read: IOPS=20.0k, BW=78.1MiB/s (81.9MB/s)(781MiB/10001msec)
sudo fio --name=t --filename=/dev/nvme1n1 --rw=randread --bs=4k --iodepth=32 --numjobs=1 \
--direct=1 --ioengine=libaio --time_based --runtime=10 --ramp_time=3 \
--group_reporting 2>/dev/null | grep -E '^ +(read|write): IOPS'
read: IOPS=20.0k, BW=78.2MiB/s (82.0MB/s)(782MiB/10001msec)
10 Set rbps to 100M and run fio.
echo "1:1 riops=max wiops=max rbps=104857600 wbps=max" | sudo tee /sys/fs/cgroup/nvmet-bdev/io.max
echo "1:0 riops=max wiops=max rbps=104857600 wbps=max" | sudo tee /sys/fs/cgroup/nvmet-file/io.max
sudo fio --name=t --filename=/dev/nvme0n1 --rw=read --bs=1M --iodepth=32 --numjobs=1 \
--direct=1 --ioengine=libaio --time_based --runtime=10 --ramp_time=3 \
--group_reporting 2>/dev/null | grep -E '^ +(read|write): IOPS'
read: IOPS=97, BW=100MiB/s (105MB/s)(1041MiB/10399msec)
sudo fio --name=t --filename=/dev/nvme1n1 --rw=read --bs=1M --iodepth=32 --numjobs=1 \
--direct=1 --ioengine=libaio --time_based --runtime=10 --ramp_time=3 \
--group_reporting 2>/dev/null | grep -E '^ +(read|write): IOPS'
read: IOPS=97, BW=100MiB/s (105MB/s)(1031MiB/10299msec)
11 Set wiops to 20k and run fio.
echo "1:1 riops=max wiops=20000 rbps=max wbps=max" | sudo tee /sys/fs/cgroup/nvmet-bdev/io.max
echo "1:0 riops=max wiops=20000 rbps=max wbps=max" | sudo tee /sys/fs/cgroup/nvmet-file/io.max
sudo fio --name=t --filename=/dev/nvme0n1 --rw=randwrite --bs=4k --iodepth=32 --numjobs=1 \
--direct=1 --ioengine=libaio --time_based --runtime=10 --ramp_time=3 \
--group_reporting 2>/dev/null | grep -E '^ +(read|write): IOPS'
write: IOPS=20.0k, BW=78.1MiB/s (81.9MB/s)(781MiB/10001msec); 0 zone resets
sudo fio --name=t --filename=/dev/nvme1n1 --rw=randwrite --bs=4k --iodepth=32 --numjobs=1 \
--direct=1 --ioengine=libaio --time_based --runtime=10 --ramp_time=3 \
--group_reporting 2>/dev/null | grep -E '^ +(read|write): IOPS'
write: IOPS=20.0k, BW=78.1MiB/s (81.9MB/s)(781MiB/10002msec); 0 zone resets
12 Set wbps to 100M and run fio.
echo "1:1 riops=max wiops=max rbps=max wbps=104857600" | sudo tee /sys/fs/cgroup/nvmet-bdev/io.max
echo "1:0 riops=max wiops=max rbps=max wbps=104857600" | sudo tee /sys/fs/cgroup/nvmet-file/io.max
sudo fio --name=t --filename=/dev/nvme0n1 --rw=write --bs=1M --iodepth=32 --numjobs=1 \
--direct=1 --ioengine=libaio --time_based --runtime=10 --ramp_time=3 \
--group_reporting 2>/dev/null | grep -E '^ +(read|write): IOPS'
write: IOPS=97, BW=100MiB/s (105MB/s)(1032MiB/10307msec); 0 zone resets
sudo fio --name=t --filename=/dev/nvme1n1 --rw=write --bs=1M --iodepth=32 --numjobs=1 \
--direct=1 --ioengine=libaio --time_based --runtime=10 --ramp_time=3 \
--group_reporting 2>/dev/null | grep -E '^ +(read|write): IOPS'
write: IOPS=97, BW=100MiB/s (105MB/s)(1033MiB/10296msec); 0 zone resets
13 Cleanup the environment.
sudo nvme disconnect -n nqn.2026-09.io.test01:bdev
sudo nvme disconnect -n nqn.2026-09.io.test01:file
sudo udevadm settle
sudo rm -f /sys/kernel/config/nvmet/ports/1/subsystems/nqn.2026-09.io.test01:bdev
sudo rm -f /sys/kernel/config/nvmet/ports/1/subsystems/nqn.2026-09.io.test01:file
sudo rmdir /sys/kernel/config/nvmet/ports/1
echo 0 | sudo tee /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:bdev/namespaces/1/enable
sudo rmdir /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:bdev/namespaces/1
sudo rmdir /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:bdev
echo 0 | sudo tee /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:file/namespaces/1/enable
sudo rmdir /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:file/namespaces/1
sudo rmdir /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:file
sudo umount /mnt/nvmet-file
sudo rmdir /mnt/nvmet-file
sudo modprobe -r brd
sudo rmdir /sys/fs/cgroup/nvmet-bdev
sudo rmdir /sys/fs/cgroup/nvmet-file
sudo rmmod nvme_tcp
sudo rmmod nvmet_tcp
sudo rmmod nvmet
Peng Yu (2):
cgroup: track the effective css in each cgroup
nvmet: add cgroup_id to charge namespace I/O to a cgroup
drivers/nvme/target/configfs.c | 47 ++++++++++++++++++++++++
drivers/nvme/target/core.c | 54 ++++++++++++++++++++++++++++
drivers/nvme/target/io-cmd-bdev.c | 18 +++++++++-
drivers/nvme/target/io-cmd-file.c | 21 +++++++++--
drivers/nvme/target/nvmet.h | 58 ++++++++++++++++++++++++++++++
drivers/nvme/target/zns.c | 5 +++
include/linux/cgroup-defs.h | 8 +++++
kernel/cgroup/cgroup.c | 60 ++++++++++++++++++++-----------
8 files changed, 247 insertions(+), 24 deletions(-)
base-commit: 2ee54f01f07c0307deaf90ca8691a4643ae0357b
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v7 1/2] cgroup: track the effective css in each cgroup
2026-09-29 23:24 [PATCH v7 0/2] nvmet: add cgroup_id to charge namespace I/O to a cgroup Peng Yu
@ 2026-09-29 23:24 ` Peng Yu
2026-10-01 17:56 ` Tejun Heo
2026-09-29 23:24 ` [PATCH v7 2/2] nvmet: add cgroup_id to charge namespace I/O to a cgroup Peng Yu
1 sibling, 1 reply; 5+ messages in thread
From: Peng Yu @ 2026-09-29 23:24 UTC (permalink / raw)
To: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni
Cc: Tejun Heo, Johannes Weiner, Michal Koutný,
Josef Bacik, Jens Axboe, Maurizio Lombardi, cgroups, linux-block,
linux-nvme, linux-kernel, Peng Yu
cgroup_e_css() and cgroup_get_e_css() find the effective css by walking
up the hierarchy until they reach a cgroup that has the subsystem
enabled. Per-I/O users such as nvmet need this lookup to be O(1).
Track the effective css in each cgroup and make cgroup_e_css() and
cgroup_get_e_css() use it. When a css is brought online or offline,
update the effective css of its cgroup and of all descendants.
Signed-off-by: Peng Yu <yupeng0921@gmail.com>
Assisted-by: Claude:claude-fable-5 [Claude Code]
Assisted-by: Claude:claude-opus-5-5 [Claude Code]
---
include/linux/cgroup-defs.h | 8 +++++
kernel/cgroup/cgroup.c | 60 ++++++++++++++++++++++++-------------
2 files changed, 47 insertions(+), 21 deletions(-)
diff --git a/include/linux/cgroup-defs.h b/include/linux/cgroup-defs.h
index 3754d697854b..cbfe8937647c 100644
--- a/include/linux/cgroup-defs.h
+++ b/include/linux/cgroup-defs.h
@@ -555,6 +555,14 @@ struct cgroup {
/* Private pointers for each registered subsystem */
struct cgroup_subsys_state __rcu *subsys[CGROUP_SUBSYS_COUNT];
+ /*
+ * Effective css for each subsystem: the css of the nearest ancestor,
+ * including this cgroup, that has the subsystem enabled, or the root
+ * css if the subsystem isn't bound to this hierarchy. Updated under
+ * cgroup_mutex and read under RCU.
+ */
+ struct cgroup_subsys_state __rcu *e_css[CGROUP_SUBSYS_COUNT];
+
/*
* Keep track of total number of dying CSSes at and below this cgroup.
* Protected by cgroup_mutex.
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index 2d532bf2c0c7..54b207d246bd 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -548,20 +548,11 @@ static struct cgroup_subsys_state *cgroup_e_css_by_mask(struct cgroup *cgrp,
struct cgroup_subsys_state *cgroup_e_css(struct cgroup *cgrp,
struct cgroup_subsys *ss)
{
- struct cgroup_subsys_state *css;
-
if (!CGROUP_HAS_SUBSYS_CONFIG)
return NULL;
- do {
- css = cgroup_css(cgrp, ss);
-
- if (css)
- return css;
- cgrp = cgroup_parent(cgrp);
- } while (cgrp);
-
- return init_css_set.subsys[ss->id];
+ return rcu_dereference_check(cgrp->e_css[ss->id],
+ lockdep_is_held(&cgroup_mutex));
}
/**
@@ -585,17 +576,10 @@ struct cgroup_subsys_state *cgroup_get_e_css(struct cgroup *cgrp,
rcu_read_lock();
- do {
- css = cgroup_css(cgrp, ss);
-
- if (css && css_tryget_online(css))
- goto out_unlock;
- cgrp = cgroup_parent(cgrp);
- } while (cgrp);
+ css = cgroup_e_css(cgrp, ss);
+ while (!css_tryget_online(css))
+ css = cgroup_e_css(cgroup_parent(css->cgroup), ss);
- css = init_css_set.subsys[ss->id];
- css_get(css);
-out_unlock:
rcu_read_unlock();
return css;
}
@@ -2131,12 +2115,24 @@ void init_cgroup_root(struct cgroup_fs_context *ctx)
{
struct cgroup_root *root = ctx->root;
struct cgroup *cgrp = &root->cgrp;
+ struct cgroup_subsys *ss;
+ int ssid;
INIT_LIST_HEAD_RCU(&root->root_list);
atomic_set(&root->nr_cgrps, 1);
cgrp->root = root;
init_cgroup_housekeeping(cgrp);
+ /*
+ * A root cgroup's effective css is always the root css in
+ * init_css_set.subsys[], whichever hierarchy the subsystem is bound
+ * to, so rebind_subsystems() doesn't need to update e_css[]. For
+ * cgrp_dfl_root this runs before the root csses exist, and
+ * online_css() sets the entries when they come online.
+ */
+ for_each_subsys(ss, ssid)
+ RCU_INIT_POINTER(cgrp->e_css[ssid], init_css_set.subsys[ssid]);
+
/* DYNMODS must be modified through cgroup_favor_dynmods() */
root->flags = ctx->flags & ~CGRP_ROOT_FAVOR_DYNMODS;
if (ctx->release_agent)
@@ -5856,6 +5852,22 @@ static void init_and_link_css(struct cgroup_subsys_state *css,
BUG_ON(cgroup_css(cgrp, ss));
}
+static void cgroup_update_e_css(struct cgroup *cgrp, struct cgroup_subsys *ss)
+{
+ struct cgroup_subsys_state *d_css;
+
+ lockdep_assert_held(&cgroup_mutex);
+
+ css_for_each_descendant_pre(d_css, &cgrp->self) {
+ struct cgroup *dsct = d_css->cgroup;
+ struct cgroup_subsys_state *css = cgroup_css(dsct, ss);
+
+ if (!css)
+ css = cgroup_e_css(cgroup_parent(dsct), ss);
+ rcu_assign_pointer(dsct->e_css[ss->id], css);
+ }
+}
+
/* invoke ->css_online() on a new CSS and mark it online if successful */
static int online_css(struct cgroup_subsys_state *css)
{
@@ -5869,6 +5881,7 @@ static int online_css(struct cgroup_subsys_state *css)
if (!ret) {
css->flags |= CSS_ONLINE;
rcu_assign_pointer(css->cgroup->subsys[ss->id], css);
+ cgroup_update_e_css(css->cgroup, ss);
atomic_inc(&css->online_cnt);
if (css->parent) {
@@ -5895,6 +5908,7 @@ static void offline_css(struct cgroup_subsys_state *css)
css->flags &= ~CSS_ONLINE;
RCU_INIT_POINTER(css->cgroup->subsys[ss->id], NULL);
+ cgroup_update_e_css(css->cgroup, ss);
wake_up_all(&css->cgroup->offline_waitq);
}
@@ -5966,6 +5980,7 @@ static struct cgroup *cgroup_create(struct cgroup *parent, const char *name,
{
struct cgroup_root *root = parent->root;
struct cgroup *cgrp, *tcgrp;
+ struct cgroup_subsys *ss;
struct kernfs_node *kn;
int i, level = parent->level + 1;
int ret;
@@ -6010,6 +6025,9 @@ static struct cgroup *cgroup_create(struct cgroup *parent, const char *name,
for (tcgrp = cgrp; tcgrp; tcgrp = cgroup_parent(tcgrp))
cgrp->ancestors[tcgrp->level] = tcgrp;
+ for_each_subsys(ss, i)
+ RCU_INIT_POINTER(cgrp->e_css[i], cgroup_e_css(parent, ss));
+
/*
* New cgroup inherits effective freeze counter, and
* if the parent has to be frozen, the child has too.
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v7 2/2] nvmet: add cgroup_id to charge namespace I/O to a cgroup
2026-09-29 23:24 [PATCH v7 0/2] nvmet: add cgroup_id to charge namespace I/O to a cgroup Peng Yu
2026-09-29 23:24 ` [PATCH v7 1/2] cgroup: track the effective css in each cgroup Peng Yu
@ 2026-09-29 23:24 ` Peng Yu
2026-10-01 18:10 ` Tejun Heo
1 sibling, 1 reply; 5+ messages in thread
From: Peng Yu @ 2026-09-29 23:24 UTC (permalink / raw)
To: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni
Cc: Tejun Heo, Johannes Weiner, Michal Koutný,
Josef Bacik, Jens Axboe, Maurizio Lombardi, cgroups, linux-block,
linux-nvme, linux-kernel, Peng Yu
Implementation:
* Add a `cgroup_id` attribute under the nvmet namespace folder, e.g.:
/sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:bdev/namespaces/1/cgroup_id
* We can write a cgroup id to it, then that cgroup will control
the IOs used by the namespace.
* Write 0 to clear it.
* The cgroup_id could be modified when the namespace is disabled.
* Support both block device backed namespaces and file backed namespaces.
* Only the direct io scenario is supported, enabling a namespace that
has both cgroup_id and buffered_io set fails with -EINVAL.
Change since v2:
* Fix the in ns->cgroup_path error free issue nvmet_ns_cgroup_path_store.
* Use nvmet_blkcg_begin/end in nvmet_bdev_zmgmt_send_work.
Change since v3:
* Use cgroup_id instead of cgroup_path.
Change since v6:
* Use cgroup_get_e_css() and css_put() in nvmet_blkcg_set_bio() instead
of cgroup_e_css() under rcu_read_lock(), so queue_lock isn't nested
under RCU when a blkg is created.
* Drop the patch that exported cgroup_e_css().
Signed-off-by: Peng Yu <yupeng0921@gmail.com>
Assisted-by: Claude:claude-fable-5 [Claude Code]
Assisted-by: Claude:claude-opus-5-5 [Claude Code]
---
drivers/nvme/target/configfs.c | 47 +++++++++++++++++++++++++
drivers/nvme/target/core.c | 54 ++++++++++++++++++++++++++++
drivers/nvme/target/io-cmd-bdev.c | 18 +++++++++-
drivers/nvme/target/io-cmd-file.c | 21 +++++++++--
drivers/nvme/target/nvmet.h | 58 +++++++++++++++++++++++++++++++
drivers/nvme/target/zns.c | 5 +++
6 files changed, 200 insertions(+), 3 deletions(-)
diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c
index 6286e38436dd..cef832303d72 100644
--- a/drivers/nvme/target/configfs.c
+++ b/drivers/nvme/target/configfs.c
@@ -561,6 +561,50 @@ static ssize_t nvmet_ns_device_path_store(struct config_item *item,
CONFIGFS_ATTR(nvmet_ns_, device_path);
+#ifdef CONFIG_BLK_CGROUP
+static ssize_t nvmet_ns_cgroup_id_show(struct config_item *item, char *page)
+{
+ struct nvmet_ns *ns = to_nvmet_ns(item);
+ struct nvmet_subsys *subsys = ns->subsys;
+ ssize_t ret;
+
+ mutex_lock(&subsys->lock);
+ ret = snprintf(page, PAGE_SIZE, "%llu\n", ns->cgroup_id);
+ mutex_unlock(&subsys->lock);
+ return ret;
+}
+
+static ssize_t nvmet_ns_cgroup_id_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct nvmet_ns *ns = to_nvmet_ns(item);
+ struct nvmet_subsys *subsys = ns->subsys;
+ u64 cgroup_id;
+ int ret;
+
+ ret = kstrtou64(page, 0, &cgroup_id);
+ if (ret)
+ return ret;
+
+ mutex_lock(&subsys->lock);
+
+ if (ns->enabled) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ /* Writing 0 clears the association. */
+ ns->cgroup_id = cgroup_id;
+ ret = count;
+
+out_unlock:
+ mutex_unlock(&subsys->lock);
+ return ret;
+}
+
+CONFIGFS_ATTR(nvmet_ns_, cgroup_id);
+#endif /* CONFIG_BLK_CGROUP */
+
#ifdef CONFIG_PCI_P2PDMA
static ssize_t nvmet_ns_p2pmem_show(struct config_item *item, char *page)
{
@@ -833,6 +877,9 @@ static struct configfs_attribute *nvmet_ns_attrs[] = {
&nvmet_ns_attr_buffered_io,
&nvmet_ns_attr_revalidate_size,
&nvmet_ns_attr_resv_enable,
+#ifdef CONFIG_BLK_CGROUP
+ &nvmet_ns_attr_cgroup_id,
+#endif
#ifdef CONFIG_PCI_P2PDMA
&nvmet_ns_attr_p2pmem,
#endif
diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
index 43871a8f56ca..6d9d55eb11eb 100644
--- a/drivers/nvme/target/core.c
+++ b/drivers/nvme/target/core.c
@@ -4,6 +4,7 @@
* Copyright (c) 2015-2016 HGST, a Western Digital Company.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/cgroup.h>
#include <linux/hex.h>
#include <linux/module.h>
#include <linux/random.h>
@@ -474,8 +475,57 @@ void nvmet_put_namespace(struct nvmet_ns *ns)
percpu_ref_put(&ns->ref);
}
+#ifdef CONFIG_BLK_CGROUP
+static int nvmet_blkcg_ns_enable(struct nvmet_ns *ns)
+{
+ struct cgroup *cgrp;
+
+ if (!ns->cgroup_id)
+ return 0;
+
+ /*
+ * Buffered writes will be handled by a separate thread,
+ * these IOs have no namespace/cgroup information at that time,
+ * so we don't support buffered io.
+ */
+ if (ns->buffered_io) {
+ pr_err("cgroup_id is not supported with buffered_io: %s\n",
+ ns->device_path);
+ return -EINVAL;
+ }
+
+ cgrp = cgroup_get_from_id(ns->cgroup_id);
+ if (IS_ERR(cgrp)) {
+ pr_err("failed to resolve cgroup id %llu: %ld\n",
+ ns->cgroup_id, PTR_ERR(cgrp));
+ return PTR_ERR(cgrp);
+ }
+
+ ns->cgrp = cgrp;
+ return 0;
+}
+
+static void nvmet_blkcg_ns_disable(struct nvmet_ns *ns)
+{
+ if (ns->cgrp) {
+ cgroup_put(ns->cgrp);
+ ns->cgrp = NULL;
+ }
+}
+#else
+static inline int nvmet_blkcg_ns_enable(struct nvmet_ns *ns)
+{
+ return 0;
+}
+
+static inline void nvmet_blkcg_ns_disable(struct nvmet_ns *ns)
+{
+}
+#endif /* CONFIG_BLK_CGROUP */
+
static void nvmet_ns_dev_disable(struct nvmet_ns *ns)
{
+ nvmet_blkcg_ns_disable(ns);
nvmet_bdev_ns_disable(ns);
nvmet_file_ns_disable(ns);
}
@@ -602,6 +652,10 @@ int nvmet_ns_enable(struct nvmet_ns *ns)
if (ret)
goto out_unlock;
+ ret = nvmet_blkcg_ns_enable(ns);
+ if (ret)
+ goto out_dev_disable;
+
ret = nvmet_p2pmem_ns_enable(ns);
if (ret)
goto out_dev_disable;
diff --git a/drivers/nvme/target/io-cmd-bdev.c b/drivers/nvme/target/io-cmd-bdev.c
index f2d9e8901df4..0014e5ef2853 100644
--- a/drivers/nvme/target/io-cmd-bdev.c
+++ b/drivers/nvme/target/io-cmd-bdev.c
@@ -297,6 +297,7 @@ static void nvmet_bdev_execute_rw(struct nvmet_req *req)
bio = bio_alloc(req->ns->bdev, bio_max_segs(sg_cnt), opf,
GFP_KERNEL);
}
+ nvmet_blkcg_set_bio(req->ns, bio);
bio->bi_iter.bi_sector = sector;
bio->bi_private = req;
bio->bi_end_io = nvmet_bio_done;
@@ -322,6 +323,7 @@ static void nvmet_bdev_execute_rw(struct nvmet_req *req)
bio = bio_alloc(req->ns->bdev, bio_max_segs(sg_cnt),
opf, GFP_KERNEL);
+ nvmet_blkcg_set_bio(req->ns, bio);
bio->bi_iter.bi_sector = sector;
bio_chain(bio, prev);
@@ -358,6 +360,7 @@ static void nvmet_bdev_execute_flush(struct nvmet_req *req)
bio_init(bio, req->ns->bdev, req->inline_bvec,
ARRAY_SIZE(req->inline_bvec), REQ_OP_WRITE | REQ_PREFLUSH);
+ nvmet_blkcg_set_bio(req->ns, bio);
bio->bi_private = req;
bio->bi_end_io = nvmet_bio_done;
@@ -366,10 +369,16 @@ static void nvmet_bdev_execute_flush(struct nvmet_req *req)
u16 nvmet_bdev_flush(struct nvmet_req *req)
{
+ bool associated;
+ int ret;
+
if (!bdev_write_cache(req->ns->bdev))
return 0;
- if (blkdev_issue_flush(req->ns->bdev))
+ associated = nvmet_blkcg_begin(req->ns);
+ ret = blkdev_issue_flush(req->ns->bdev);
+ nvmet_blkcg_end(associated);
+ if (ret)
return NVME_SC_INTERNAL | NVME_STATUS_DNR;
return 0;
}
@@ -380,9 +389,11 @@ static void nvmet_bdev_execute_discard(struct nvmet_req *req)
struct nvme_dsm_range range;
struct bio *bio = NULL;
sector_t nr_sects;
+ bool associated;
int i;
u16 status = NVME_SC_SUCCESS;
+ associated = nvmet_blkcg_begin(ns);
for (i = 0; i <= le32_to_cpu(req->cmd->dsm.nr); i++) {
status = nvmet_copy_from_sgl(req, i * sizeof(range), &range,
sizeof(range));
@@ -394,6 +405,7 @@ static void nvmet_bdev_execute_discard(struct nvmet_req *req)
nvmet_lba_to_sect(ns, range.slba), nr_sects,
GFP_KERNEL, &bio);
}
+ nvmet_blkcg_end(associated);
if (bio) {
bio->bi_private = req;
@@ -431,6 +443,7 @@ static void nvmet_bdev_execute_write_zeroes(struct nvmet_req *req)
struct bio *bio = NULL;
sector_t sector;
sector_t nr_sector;
+ bool associated;
int ret;
if (!nvmet_check_transfer_len(req, 0))
@@ -440,8 +453,11 @@ static void nvmet_bdev_execute_write_zeroes(struct nvmet_req *req)
nr_sector = (((sector_t)le16_to_cpu(write_zeroes->length) + 1) <<
(req->ns->blksize_shift - 9));
+ associated = nvmet_blkcg_begin(req->ns);
ret = __blkdev_issue_zeroout(req->ns->bdev, sector, nr_sector,
GFP_KERNEL, &bio, 0);
+ nvmet_blkcg_end(associated);
+
if (bio) {
bio->bi_private = req;
bio->bi_end_io = nvmet_bio_done;
diff --git a/drivers/nvme/target/io-cmd-file.c b/drivers/nvme/target/io-cmd-file.c
index 0b22d183f927..570e65259ad8 100644
--- a/drivers/nvme/target/io-cmd-file.c
+++ b/drivers/nvme/target/io-cmd-file.c
@@ -79,6 +79,8 @@ static ssize_t nvmet_file_submit_bvec(struct nvmet_req *req, loff_t pos,
struct kiocb *iocb = &req->f.iocb;
ssize_t (*call_iter)(struct kiocb *iocb, struct iov_iter *iter);
struct iov_iter iter;
+ bool associated;
+ ssize_t ret;
int rw;
if (req->cmd->rw.opcode == nvme_cmd_write) {
@@ -97,7 +99,10 @@ static ssize_t nvmet_file_submit_bvec(struct nvmet_req *req, loff_t pos,
iocb->ki_filp = req->ns->file;
iocb->ki_flags = ki_flags | iocb->ki_filp->f_iocb_flags;
- return call_iter(iocb, &iter);
+ associated = nvmet_blkcg_begin(req->ns);
+ ret = call_iter(iocb, &iter);
+ nvmet_blkcg_end(associated);
+ return ret;
}
static void nvmet_file_io_done(struct kiocb *iocb, long ret)
@@ -251,7 +256,13 @@ static void nvmet_file_execute_rw(struct nvmet_req *req)
u16 nvmet_file_flush(struct nvmet_req *req)
{
- return errno_to_nvme_status(req, vfs_fsync(req->ns->file, 1));
+ bool associated;
+ int ret;
+
+ associated = nvmet_blkcg_begin(req->ns);
+ ret = vfs_fsync(req->ns->file, 1);
+ nvmet_blkcg_end(associated);
+ return errno_to_nvme_status(req, ret);
}
static void nvmet_file_flush_work(struct work_struct *w)
@@ -274,10 +285,12 @@ static void nvmet_file_execute_discard(struct nvmet_req *req)
int mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
struct nvme_dsm_range range;
loff_t offset, len;
+ bool associated;
u16 status = 0;
int ret;
int i;
+ associated = nvmet_blkcg_begin(req->ns);
for (i = 0; i <= le32_to_cpu(req->cmd->dsm.nr); i++) {
status = nvmet_copy_from_sgl(req, i * sizeof(range), &range,
sizeof(range));
@@ -300,6 +313,7 @@ static void nvmet_file_execute_discard(struct nvmet_req *req)
break;
}
}
+ nvmet_blkcg_end(associated);
nvmet_req_complete(req, status);
}
@@ -336,6 +350,7 @@ static void nvmet_file_write_zeroes_work(struct work_struct *w)
int mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE;
loff_t offset;
loff_t len;
+ bool associated;
int ret;
offset = le64_to_cpu(write_zeroes->slba) << req->ns->blksize_shift;
@@ -347,7 +362,9 @@ static void nvmet_file_write_zeroes_work(struct work_struct *w)
return;
}
+ associated = nvmet_blkcg_begin(req->ns);
ret = vfs_fallocate(req->ns->file, mode, offset, len);
+ nvmet_blkcg_end(associated);
nvmet_req_complete(req, ret < 0 ? errno_to_nvme_status(req, ret) : 0);
}
diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
index dbda55895f4f..d5dfca18f5fa 100644
--- a/drivers/nvme/target/nvmet.h
+++ b/drivers/nvme/target/nvmet.h
@@ -21,6 +21,8 @@
#include <linux/radix-tree.h>
#include <linux/t10-pi.h>
#include <linux/kfifo.h>
+#include <linux/kthread.h>
+#include <linux/cgroup.h>
#define NVMET_DEFAULT_VS NVME_VS(2, 1, 0)
@@ -115,6 +117,16 @@ struct nvmet_ns {
struct nvmet_subsys *subsys;
const char *device_path;
+#ifdef CONFIG_BLK_CGROUP
+ u64 cgroup_id;
+ /*
+ * Resolved from ->cgroup_id when the namespace is enabled and
+ * released when it is disabled, so it has the same lifetime and
+ * visibility rules as ->bdev and ->file.
+ */
+ struct cgroup *cgrp;
+#endif
+
struct config_group device_group;
struct config_group group;
@@ -732,6 +744,52 @@ void nvmet_bdev_execute_zone_mgmt_recv(struct nvmet_req *req);
void nvmet_bdev_execute_zone_mgmt_send(struct nvmet_req *req);
void nvmet_bdev_execute_zone_append(struct nvmet_req *req);
+#ifdef CONFIG_BLK_CGROUP
+static inline void nvmet_blkcg_set_bio(struct nvmet_ns *ns, struct bio *bio)
+{
+ struct cgroup_subsys_state *css;
+
+ if (!ns->cgrp)
+ return;
+
+ css = cgroup_get_e_css(ns->cgrp, &io_cgrp_subsys);
+ bio_associate_blkg_from_css(bio, css);
+ css_put(css);
+}
+
+static inline bool nvmet_blkcg_begin(struct nvmet_ns *ns)
+{
+ struct cgroup_subsys_state *css;
+
+ if (!ns->cgrp || !in_task() || !(current->flags & PF_KTHREAD))
+ return false;
+
+ css = cgroup_get_e_css(ns->cgrp, &io_cgrp_subsys);
+ kthread_associate_blkcg(css);
+ css_put(css);
+ return true;
+}
+
+static inline void nvmet_blkcg_end(bool associated)
+{
+ if (associated)
+ kthread_associate_blkcg(NULL);
+}
+#else
+static inline void nvmet_blkcg_set_bio(struct nvmet_ns *ns, struct bio *bio)
+{
+}
+
+static inline bool nvmet_blkcg_begin(struct nvmet_ns *ns)
+{
+ return false;
+}
+
+static inline void nvmet_blkcg_end(bool associated)
+{
+}
+#endif /* CONFIG_BLK_CGROUP */
+
static inline u32 nvmet_rw_data_len(struct nvmet_req *req)
{
return ((u32)le16_to_cpu(req->cmd->rw.length) + 1) <<
diff --git a/drivers/nvme/target/zns.c b/drivers/nvme/target/zns.c
index 23a17c02abee..a9836d44f5e6 100644
--- a/drivers/nvme/target/zns.c
+++ b/drivers/nvme/target/zns.c
@@ -480,8 +480,11 @@ static void nvmet_bdev_zmgmt_send_work(struct work_struct *w)
struct block_device *bdev = req->ns->bdev;
sector_t zone_sectors = bdev_zone_sectors(bdev);
u16 status = NVME_SC_SUCCESS;
+ bool associated;
int ret;
+ associated = nvmet_blkcg_begin(req->ns);
+
if (op == REQ_OP_LAST) {
req->error_loc = offsetof(struct nvme_zone_mgmt_send_cmd, zsa);
status = NVME_SC_ZONE_INVALID_TRANSITION | NVME_STATUS_DNR;
@@ -511,6 +514,7 @@ static void nvmet_bdev_zmgmt_send_work(struct work_struct *w)
status = blkdev_zone_mgmt_errno_to_nvme_status(ret);
out:
+ nvmet_blkcg_end(associated);
nvmet_req_complete(req, status);
}
@@ -580,6 +584,7 @@ void nvmet_bdev_execute_zone_append(struct nvmet_req *req)
bio = bio_alloc(req->ns->bdev, req->sg_cnt, opf, GFP_KERNEL);
}
+ nvmet_blkcg_set_bio(req->ns, bio);
bio->bi_end_io = nvmet_bdev_zone_append_bio_done;
bio->bi_iter.bi_sector = sect;
bio->bi_private = req;
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v7 1/2] cgroup: track the effective css in each cgroup
2026-09-29 23:24 ` [PATCH v7 1/2] cgroup: track the effective css in each cgroup Peng Yu
@ 2026-10-01 17:56 ` Tejun Heo
0 siblings, 0 replies; 5+ messages in thread
From: Tejun Heo @ 2026-10-01 17:56 UTC (permalink / raw)
To: Peng Yu
Cc: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni,
Johannes Weiner, Michal Koutný,
Josef Bacik, Jens Axboe, Maurizio Lombardi, cgroups, linux-block,
linux-nvme, linux-kernel
On Tue, Sep 29, 2026 at 04:24:10PM -0700, Peng Yu wrote:
> cgroup_e_css() and cgroup_get_e_css() find the effective css by walking
> up the hierarchy until they reach a cgroup that has the subsystem
> enabled. Per-I/O users such as nvmet need this lookup to be O(1).
>
> Track the effective css in each cgroup and make cgroup_e_css() and
> cgroup_get_e_css() use it. When a css is brought online or offline,
> update the effective css of its cgroup and of all descendants.
Applied to cgroup/for-7.4 with the subject capitalized.
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v7 2/2] nvmet: add cgroup_id to charge namespace I/O to a cgroup
2026-09-29 23:24 ` [PATCH v7 2/2] nvmet: add cgroup_id to charge namespace I/O to a cgroup Peng Yu
@ 2026-10-01 18:10 ` Tejun Heo
0 siblings, 0 replies; 5+ messages in thread
From: Tejun Heo @ 2026-10-01 18:10 UTC (permalink / raw)
To: Peng Yu
Cc: Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni,
Johannes Weiner, Michal Koutný,
Josef Bacik, Jens Axboe, Maurizio Lombardi, cgroups, linux-block,
linux-nvme, linux-kernel
Hello, Peng.
On Tue, Sep 29, 2026 at 04:24:11PM -0700, Peng Yu wrote:
> Implementation:
> * Add a `cgroup_id` attribute under the nvmet namespace folder, e.g.:
> /sys/kernel/config/nvmet/subsystems/nqn.2026-09.io.test01:bdev/namespaces/1/cgroup_id
> * We can write a cgroup id to it, then that cgroup will control
> the IOs used by the namespace.
This doesn't follow the usual description format or content. Can you
write it as prose explaining why the change is needed and what it does,
along the lines of the scenario section in the cover letter?
> +CONFIGFS_ATTR(nvmet_ns_, cgroup_id);
Documentation/ABI/stable/configfs-nvmet has an entry for each namespace
attribute. Can you add one for cgroup_id? It should say that it takes a
cgroup2 ID, that 0 clears it, that it can only be changed while the
namespace is disabled, and that the namespace's I/Os are charged to the
cgroup's effective io css.
> +static inline void nvmet_blkcg_set_bio(struct nvmet_ns *ns, struct bio *bio)
> +{
> + struct cgroup_subsys_state *css;
> +
> + if (!ns->cgrp)
> + return;
> +
> + css = cgroup_get_e_css(ns->cgrp, &io_cgrp_subsys);
> + bio_associate_blkg_from_css(bio, css);
> + css_put(css);
> +}
bio_init() and bio_alloc() already associate the bio with the current
kthread's blkcg, so each of these bios gets associated twice. If the rw
and zone append paths wrapped their bio allocations in
nvmet_blkcg_begin()/end() like the discard and flush paths do, this
function wouldn't be needed and chained bios would be covered
automatically. Not a blocker either way.
Otherwise, from the cgroup side, this looks fine to me.
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-01 18:10 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-29 23:24 [PATCH v7 0/2] nvmet: add cgroup_id to charge namespace I/O to a cgroup Peng Yu
2026-09-29 23:24 ` [PATCH v7 1/2] cgroup: track the effective css in each cgroup Peng Yu
2026-10-01 17:56 ` Tejun Heo
2026-09-29 23:24 ` [PATCH v7 2/2] nvmet: add cgroup_id to charge namespace I/O to a cgroup Peng Yu
2026-10-01 18:10 ` Tejun Heo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®