From: Tao Cui <cui.tao@linux.dev>
To: Tejun Heo <tj@kernel.org>
Cc: cui.tao@linux.dev, josef@toxicopanda.com, axboe@kernel.dk,
cgroups@vger.kernel.org, linux-block@vger.kernel.org,
linux-kernel@vger.kernel.org, bpf@vger.kernel.org,
andrii@kernel.org, eddyz87@gmail.com, ast@kernel.org,
daniel@iogearbox.net, linux-kselftest@vger.kernel.org,
Tao Cui <cuitao@kylinos.cn>,
ameryhung@gmail.com, alexei.starovoitov@gmail.com
Subject: Re: [RFC PATCH v8 1/4] blk-iocost: add BPF struct_ops cost model support
Date: Fri, 2 Oct 2026 22:17:36 +0800 [thread overview]
Message-ID: <b83447c2-564a-4d9a-af24-60ad675f59ff@linux.dev> (raw)
In-Reply-To: <6c97ba9ada55be8e7191f78c4db8bcf9@kernel.org>
Hello, Tejun.
在 2026/10/1 08:20, Tejun Heo 写道:
> Hello, Tao.
>
> The following is a Claude-generated review.
>
> On Wed, 30 Sep 2026 15:51:51 +0800, Tao Cui wrote:
>> Add the iocost_model_ops struct_ops. Attachment follows the
>> hid_bpf_ops model: the struct_ops instance is per-device, the target
>> device is set in the dev member from userspace before load, .reg
>> attaches the model to that device and switches it away from the
>> builtin linear model, and .unreg detaches it and restores the builtin
>> model. The struct_ops core owns the program lifetime, so there is no
>> name registry and no bound-state bookkeeping.
> ...
>> The cgroup callbacks are bound to the iocg policy init/free paths,
>> one (cgroup, device) pair per invocation, matching the builtin
>> cursor's lifetime, instead of the blkcg css lifecycle, which also
>> drops the mutex from the cgroup online/offline paths.
>
> The description is written against earlier versions. There is no name
> registry, css lifecycle binding or mutex in the tree, so a reader of the
> commit can't follow it. Can you open with why a pluggable model is wanted
> and then describe what the patch does: attach creates the ioc and
> switches the model under the same freeze as io.cost.model writes,
> model=bpf and model=linear select between the attached and builtin
> models, device removal ejects the model, and so on? Also, the model
> prices every charged IO, not every IO. Root cgroup IOs and IOs while the
> controller is disabled never reach it.
>
The commit message is rewritten to open with why a pluggable model
is wanted and describe the current behavior. It also clarifies that
the model prices every charged IO rather than every IO.
>> +bool blk_get_queue_rcu(struct request_queue *q)
>> +{
>> + return refcount_inc_not_zero(&q->refs);
>> +}
>> +EXPORT_SYMBOL(blk_get_queue_rcu);
> ...
>> +struct block_device *blkdev_get_no_open(dev_t dev, bool autoload);
>> +void blkdev_put_no_open(struct block_device *bdev);
>> +bool blk_get_queue_rcu(struct request_queue *q);
>> +void blk_put_queue(struct request_queue *q);
>
> blk_get_queue_rcu() has no prototype in any header, so every build warns
> on it. Can you declare it in block/blk.h, include "blk.h" from
> blk-iocost-bpf.c and drop these local prototypes? blkdev_get_no_open()
> and blkdev_put_no_open() are already in blk.h and blk_put_queue() in
> blkdev.h. The export isn't needed either as the only user is built-in.
>
Done: declared in block/blk.h without the export, and blk-iocost-bpf.c
includes "blk.h" instead of carrying local prototypes.
>> + case offsetof(struct iocost_model_ops, bdev):
> ...
>> + case offsetof(struct iocost_model_ops, q):
>
> The struct_ops core rejects non-zero non-function members that
> init_member doesn't claim and kvalue starts zeroed, so these two cases
> can go. Same for the name lookup in .init, the core has already found the
> struct by then.
>
The bdev and q cases in .init_member() are gone, and the redundant
name lookup is removed from .init().
>> + rcu_read_lock();
>> + q = rcu_dereference(ops->q);
>
> ops->q isn't __rcu annotated, so sparse will complain here. Either
> annotate it and use RCU_INIT_POINTER() for the stores, or READ_ONCE() it.
> The RCU section protects the queue, not the ops pointer.
>
ops->q and the owning link are written with WRITE_ONCE() and read
with READ_ONCE().
>> +#ifdef CONFIG_BLK_CGROUP_IOCOST_BPF
>> + {
>> + struct iocost_model_ops *ops;
>> +
>> + spin_lock_irq(&ioc->lock);
>> + ops = (struct iocost_model_ops *)rcu_dereference_protected(
>> + ioc->attached, lockdep_is_held(&ioc->lock));
>> + rcu_assign_pointer(ioc->attached, NULL);
>> + rcu_assign_pointer(ioc->model, NULL);
>> + spin_unlock_irq(&ioc->lock);
>> +
>> + /* eject the model completely on device removal */
>> + if (ops) {
>> + WRITE_ONCE(ops->q, NULL);
>> + blkdev_put_no_open(ops->bdev);
>> + }
>> + }
>> +#endif
>
> Once ops->q is NULL, .unreg returns without taking rq_qos_mutex, the link
> can go away and the struct_ops map which holds ops can be freed after a
> grace period. This path isn't in an RCU read section, so reading
> ops->bdev after the store is a use-after-free window. Read bdev first and
> make the NULL store the last access.
>
Done: the ejection reads bdev into a local before clearing ops->q.
> This is also a block scoping a variable. Can you make it an
> ioc_bpf_eject() helper next to attach and detach, with an empty stub for
> !CONFIG_BLK_CGROUP_IOCOST_BPF? That removes the #ifdef and the const
> cast. The remaining #ifdefs in ioc_cost_model_write() would go away too
> if the two pointers in struct ioc were unconditional.
>
Done: ioc_bpf_eject() with a stub drops the #ifdef from
ioc_rqos_exit(), and the attached and model pointers in struct ioc are
unconditional, so the remaining #ifdefs in ioc_cost_model_write() are
gone too.
>> + * Deliver iocg_init()/iocg_free() to the cgroups which already have a
>> + * blkg on the queue, the same q->blkg_list walk the blkcg policy
>> + * teardown uses. The queue is frozen and quiesced and blkcg_mutex
>> + * serializes against blkg creation and destruction. Cgroups without
>> + * a blkg on the device yet are not missed: their blkg is created
>> + * later and ioc_pd_init()/ioc_pd_free() deliver the callbacks then.
>> + */
>
> blkcg_mutex doesn't serialize blkg creation. The IO path creates blkgs
> from bio_associate_blkg() through blkg_lookup_create(), before
> bio_queue_enter(), so the freeze doesn't stop it either, and
> blkg_create() runs pd_init and then list_add() to q->blkg_list under
> queue_lock only. Against the attach:
>
> 1. ioc->attached is published, blkg_create() runs ioc_pd_init() which
> delivers iocg_init(), then list_add(), then the walk delivers
> iocg_init() again.
>
> 2. blkg_create() runs ioc_pd_init() while attached is NULL, the walk runs
> before list_add(), and the blkg never gets iocg_init() but gets
> iocg_free() from ioc_pd_free() or the detach walk.
>
> Detach has the mirror cases. The walk is also the only q->blkg_list
> walker without queue_lock, and a plain list_for_each_entry() against a
> concurrent list_add() isn't safe. The teardown walk this mirrors holds
> blkcg_mutex and queue_lock. Can you publish and clear ioc->attached and
> walk under queue_lock as well? ioc_pd_init() already calls iocg_init()
> under queue_lock, so nothing changes for the BPF side.
>
> While at it, the walk goes newest first, so children usually get
> iocg_init() before their parents. blkcg_activate_policy() walks in
> reverse for that reason.
>
You were right that blkcg_mutex alone did not serialize against blkg
creation from the IO path. The attachment is now published and
cleared under blkcg_mutex and queue_lock, and the blkg walk is
performed under the same locking; the init walk goes parents first
like blkcg_activate_policy().
>> + /* prevent multiple attach of the same struct_ops */
>> + if (ops->q)
>> + return -EINVAL;
>
> On the link path the map stays READY after .unreg, so a map can be
> attached again through a new link. If the device goes away, the ejection
> clears ops->q while the old link is still open, a device with the same
> dev_t comes back and a new link attaches the same map, closing the old
> link then detaches the new attachment. Recording the owning link in the
> ops and having .unreg detach only when it matches would close that.
>
Done: .unreg records the owning link in the ops and only detaches
when the closing link matches.
>> + /*
>> + * check liveness and create the ioc under rq_qos_mutex, like
>> + * blkg_conf_open_bdev() does; enabling stays with io.cost.qos
>> + *
>> + * the queue reference is held across the unlocked window below:
>> + * the bdev reference does not pin the queue, bdev only holds a
>> + * raw bd_queue pointer, and concurrent device removal may eject
>> + * the model and free the ioc while we are off the mutex, so
>> + * without our own reference the second mutex_lock() would touch
>> + * a freed queue
>> + */
>> + mutex_lock(&q->rq_qos_mutex);
>> + if (!disk_live(disk) || !blk_get_queue(q)) {
> ...
>> + ioc = q_to_ioc(q);
>> + if (!ioc) {
>> + ret = blk_iocost_init(disk);
>> + mutex_unlock(&q->rq_qos_mutex);
>
> Why drop rq_qos_mutex here? ioc_cost_model_write() holds it from
> blkg_conf_open_bdev() across blk_iocost_init() and its own freeze and
> quiesce, so attach can hold it from the disk_live() check through the
> unfreeze. Then the ioc can't be freed under us and the queue reference,
> the second q_to_ioc() check and this comment go away. The comment is also
> wrong: a whole-disk no-open bdev reference holds the disk device, and
> disk_release() is what drops the queue reference.
>
Done: the attach holds rq_qos_mutex from the disk_live() check
through the unfreeze, like ioc_cost_model_write() does, so the unlock
window, the queue reference, the second q_to_ioc() and its comment
are gone.
Thanks.
Tao
> Thanks.
>
> --
> tejun
next prev parent reply other threads:[~2026-10-02 14:17 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-30 7:51 [RFC PATCH v8 0/4] blk-iocost: add BPF struct_ops cost model support Tao Cui
2026-09-30 7:51 ` [RFC PATCH v8 1/4] " Tao Cui
2026-09-30 8:11 ` sashiko-bot
2026-09-30 11:50 ` Tao Cui
2026-10-01 0:20 ` Tejun Heo
2026-10-02 14:17 ` Tao Cui [this message]
2026-09-30 7:51 ` [RFC PATCH v8 2/4] selftests/bpf: add iocost cost model test Tao Cui
2026-09-30 8:02 ` sashiko-bot
2026-10-01 0:20 ` Tejun Heo
2026-10-02 14:20 ` Tao Cui
2026-09-30 7:51 ` [RFC PATCH v8 3/4] blk-iocost: add iocost_ioc_tick tracepoint for per-period device summary Tao Cui
2026-10-01 0:20 ` Tejun Heo
2026-10-02 14:22 ` Tao Cui
2026-09-30 7:51 ` [RFC PATCH v8 4/4] docs: cgroup-v2: document the iocost BPF cost model attachment Tao Cui
2026-09-30 8:45 ` bot+bpf-ci
2026-10-01 0:20 ` Tejun Heo
2026-10-02 14:23 ` Tao Cui
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=b83447c2-564a-4d9a-af24-60ad675f59ff@linux.dev \
--to=cui.tao@linux.dev \
--cc=alexei.starovoitov@gmail.com \
--cc=ameryhung@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=axboe@kernel.dk \
--cc=bpf@vger.kernel.org \
--cc=cgroups@vger.kernel.org \
--cc=cuitao@kylinos.cn \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=josef@toxicopanda.com \
--cc=linux-block@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=tj@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.