mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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


  reply	other threads:[~2026-10-02 14:17 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-30  7:51 [RFC PATCH v8 0/4] " Tao Cui
2026-09-30  7:51 ` [RFC PATCH v8 1/4] " 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-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 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®