* [PATCH 1/1] loop: convert lo_state to atomic_t type to ensure atomic state checks in queue_rq path @ 2025-12-15 6:54 Yongpeng Yang 2025-12-15 7:12 ` Damien Le Moal 0 siblings, 1 reply; 5+ messages in thread From: Yongpeng Yang @ 2025-12-15 6:54 UTC (permalink / raw) To: Damien Le Moal, Christoph Hellwig, Jens Axboe, Ming Lei Cc: linux-block, Yongpeng Yang, Yongpeng Yang From: Yongpeng Yang <yangyongpeng@xiaomi.com> lo_state is currently defined as an int, which does not guarantee atomicity for state checks. In the queue_rq path, ensuring correct state checks requires holding lo->lo_mutex, which may increase I/O submission latency. This patch converts lo_state to atomic_t type. The main changes are: 1. Updates to lo_state still require holding lo->lo_mutex, since the state must be validated before modification, and the lock ensures that no concurrent operation can change the state. 2. Read-only accesses to lo_state no longer require holding lo->lo_mutex. This allows atomic state checks in the queue_rq fast path while avoiding unnecessary locking overhead. Signed-off-by: Yongpeng Yang <yangyongpeng@xiaomi.com> --- drivers/block/loop.c | 67 ++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/drivers/block/loop.c b/drivers/block/loop.c index 272bc608e528..bc661ecb449a 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -59,7 +59,7 @@ struct loop_device { gfp_t old_gfp_mask; spinlock_t lo_lock; - int lo_state; + atomic_t lo_state; spinlock_t lo_work_lock; struct workqueue_struct *workqueue; struct work_struct rootcg_work; @@ -94,6 +94,16 @@ static DEFINE_IDR(loop_index_idr); static DEFINE_MUTEX(loop_ctl_mutex); static DEFINE_MUTEX(loop_validate_mutex); +static inline int loop_device_get_state(struct loop_device *lo) +{ + return atomic_read(&lo->lo_state); +} + +static inline void loop_device_set_state(struct loop_device *lo, int state) +{ + atomic_set(&lo->lo_state, state); +} + /** * loop_global_lock_killable() - take locks for safe loop_validate_file() test * @@ -200,7 +210,7 @@ static bool lo_can_use_dio(struct loop_device *lo) static inline void loop_update_dio(struct loop_device *lo) { lockdep_assert_held(&lo->lo_mutex); - WARN_ON_ONCE(lo->lo_state == Lo_bound && + WARN_ON_ONCE(loop_device_get_state(lo) == Lo_bound && lo->lo_queue->mq_freeze_depth == 0); if ((lo->lo_flags & LO_FLAGS_DIRECT_IO) && !lo_can_use_dio(lo)) @@ -495,7 +505,7 @@ static int loop_validate_file(struct file *file, struct block_device *bdev) return -EBADF; l = I_BDEV(f->f_mapping->host)->bd_disk->private_data; - if (l->lo_state != Lo_bound) + if (loop_device_get_state(l) != Lo_bound) return -EINVAL; /* Order wrt setting lo->lo_backing_file in loop_configure(). */ rmb(); @@ -563,7 +573,7 @@ static int loop_change_fd(struct loop_device *lo, struct block_device *bdev, if (error) goto out_putf; error = -ENXIO; - if (lo->lo_state != Lo_bound) + if (loop_device_get_state(lo) != Lo_bound) goto out_err; /* the loop device has to be read-only */ @@ -1019,7 +1029,7 @@ static int loop_configure(struct loop_device *lo, blk_mode_t mode, goto out_bdev; error = -EBUSY; - if (lo->lo_state != Lo_unbound) + if (loop_device_get_state(lo) != Lo_unbound) goto out_unlock; error = loop_validate_file(file, bdev); @@ -1082,7 +1092,7 @@ static int loop_configure(struct loop_device *lo, blk_mode_t mode, /* Order wrt reading lo_state in loop_validate_file(). */ wmb(); - lo->lo_state = Lo_bound; + loop_device_set_state(lo, Lo_bound); if (part_shift) lo->lo_flags |= LO_FLAGS_PARTSCAN; partscan = lo->lo_flags & LO_FLAGS_PARTSCAN; @@ -1179,7 +1189,7 @@ static void __loop_clr_fd(struct loop_device *lo) if (!part_shift) set_bit(GD_SUPPRESS_PART_SCAN, &lo->lo_disk->state); mutex_lock(&lo->lo_mutex); - lo->lo_state = Lo_unbound; + loop_device_set_state(lo, Lo_unbound); mutex_unlock(&lo->lo_mutex); /* @@ -1206,7 +1216,7 @@ static int loop_clr_fd(struct loop_device *lo) err = loop_global_lock_killable(lo, true); if (err) return err; - if (lo->lo_state != Lo_bound) { + if (loop_device_get_state(lo) != Lo_bound) { loop_global_unlock(lo, true); return -ENXIO; } @@ -1218,7 +1228,7 @@ static int loop_clr_fd(struct loop_device *lo) lo->lo_flags |= LO_FLAGS_AUTOCLEAR; if (disk_openers(lo->lo_disk) == 1) - lo->lo_state = Lo_rundown; + loop_device_set_state(lo, Lo_rundown); loop_global_unlock(lo, true); return 0; @@ -1235,7 +1245,7 @@ loop_set_status(struct loop_device *lo, const struct loop_info64 *info) err = mutex_lock_killable(&lo->lo_mutex); if (err) return err; - if (lo->lo_state != Lo_bound) { + if (loop_device_get_state(lo) != Lo_bound) { err = -ENXIO; goto out_unlock; } @@ -1289,7 +1299,7 @@ loop_get_status(struct loop_device *lo, struct loop_info64 *info) ret = mutex_lock_killable(&lo->lo_mutex); if (ret) return ret; - if (lo->lo_state != Lo_bound) { + if (loop_device_get_state(lo) != Lo_bound) { mutex_unlock(&lo->lo_mutex); return -ENXIO; } @@ -1408,7 +1418,7 @@ static int loop_set_capacity(struct loop_device *lo) { loff_t size; - if (unlikely(lo->lo_state != Lo_bound)) + if (unlikely(loop_device_get_state(lo) != Lo_bound)) return -ENXIO; size = lo_calculate_size(lo, lo->lo_backing_file); @@ -1422,7 +1432,7 @@ static int loop_set_dio(struct loop_device *lo, unsigned long arg) bool use_dio = !!arg; unsigned int memflags; - if (lo->lo_state != Lo_bound) + if (loop_device_get_state(lo) != Lo_bound) return -ENXIO; if (use_dio == !!(lo->lo_flags & LO_FLAGS_DIRECT_IO)) return 0; @@ -1464,7 +1474,7 @@ static int loop_set_block_size(struct loop_device *lo, blk_mode_t mode, if (err) goto abort_claim; - if (lo->lo_state != Lo_bound) { + if (loop_device_get_state(lo) != Lo_bound) { err = -ENXIO; goto unlock; } @@ -1716,16 +1726,11 @@ static int lo_compat_ioctl(struct block_device *bdev, blk_mode_t mode, static int lo_open(struct gendisk *disk, blk_mode_t mode) { struct loop_device *lo = disk->private_data; - int err; - - err = mutex_lock_killable(&lo->lo_mutex); - if (err) - return err; + int state = loop_device_get_state(lo); - if (lo->lo_state == Lo_deleting || lo->lo_state == Lo_rundown) - err = -ENXIO; - mutex_unlock(&lo->lo_mutex); - return err; + if (state == Lo_deleting || state == Lo_rundown) + return -ENXIO; + return 0; } static void lo_release(struct gendisk *disk) @@ -1742,10 +1747,10 @@ static void lo_release(struct gendisk *disk) */ mutex_lock(&lo->lo_mutex); - if (lo->lo_state == Lo_bound && (lo->lo_flags & LO_FLAGS_AUTOCLEAR)) - lo->lo_state = Lo_rundown; + if (loop_device_get_state(lo) == Lo_bound && (lo->lo_flags & LO_FLAGS_AUTOCLEAR)) + loop_device_set_state(lo, Lo_rundown); - need_clear = (lo->lo_state == Lo_rundown); + need_clear = (loop_device_get_state(lo) == Lo_rundown); mutex_unlock(&lo->lo_mutex); if (need_clear) @@ -1858,7 +1863,7 @@ static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx, blk_mq_start_request(rq); - if (lo->lo_state != Lo_bound) + if (loop_device_get_state(lo) != Lo_bound) return BLK_STS_IOERR; switch (req_op(rq)) { @@ -2016,7 +2021,7 @@ static int loop_add(int i) lo->worker_tree = RB_ROOT; INIT_LIST_HEAD(&lo->idle_worker_list); timer_setup(&lo->timer, loop_free_idle_workers_timer, TIMER_DEFERRABLE); - lo->lo_state = Lo_unbound; + loop_device_set_state(lo, Lo_unbound); err = mutex_lock_killable(&loop_ctl_mutex); if (err) @@ -2168,13 +2173,13 @@ static int loop_control_remove(int idx) ret = mutex_lock_killable(&lo->lo_mutex); if (ret) goto mark_visible; - if (lo->lo_state != Lo_unbound || disk_openers(lo->lo_disk) > 0) { + if (loop_device_get_state(lo) != Lo_unbound || disk_openers(lo->lo_disk) > 0) { mutex_unlock(&lo->lo_mutex); ret = -EBUSY; goto mark_visible; } /* Mark this loop device as no more bound, but not quite unbound yet */ - lo->lo_state = Lo_deleting; + loop_device_set_state(lo, Lo_deleting); mutex_unlock(&lo->lo_mutex); loop_remove(lo); @@ -2198,7 +2203,7 @@ static int loop_control_get_free(int idx) return ret; idr_for_each_entry(&loop_index_idr, lo, id) { /* Hitting a race results in creating a new loop device which is harmless. */ - if (lo->idr_visible && data_race(lo->lo_state) == Lo_unbound) + if (lo->idr_visible && loop_device_get_state(lo) == Lo_unbound) goto found; } mutex_unlock(&loop_ctl_mutex); -- 2.43.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH 1/1] loop: convert lo_state to atomic_t type to ensure atomic state checks in queue_rq path 2025-12-15 6:54 [PATCH 1/1] loop: convert lo_state to atomic_t type to ensure atomic state checks in queue_rq path Yongpeng Yang @ 2025-12-15 7:12 ` Damien Le Moal 2025-12-15 7:34 ` Yongpeng Yang 0 siblings, 1 reply; 5+ messages in thread From: Damien Le Moal @ 2025-12-15 7:12 UTC (permalink / raw) To: Yongpeng Yang, Christoph Hellwig, Jens Axboe, Ming Lei Cc: linux-block, Yongpeng Yang, Yongpeng Yang On 12/15/25 15:54, Yongpeng Yang wrote: > From: Yongpeng Yang <yangyongpeng@xiaomi.com> > > lo_state is currently defined as an int, which does not guarantee > atomicity for state checks. In the queue_rq path, ensuring correct state > checks requires holding lo->lo_mutex, which may increase I/O submission > latency. This patch converts lo_state to atomic_t type. The main changes > are: > 1. Updates to lo_state still require holding lo->lo_mutex, since the > state must be validated before modification, and the lock ensures that > no concurrent operation can change the state. > 2. Read-only accesses to lo_state no longer require holding lo->lo_mutex. > > This allows atomic state checks in the queue_rq fast path while avoiding > unnecessary locking overhead. Code like: if (loop_device_get_state(lo) != Lo_bound) is absolutely *not* atomic, since the state can change in between the atomic read and the comparison instruction. So this is not about atomicity, it is about not reading garbage from the state field if there is a load and a store concurrently executed on different CPUs (that happening depends on the CPU architecture though). As Christoph suggested, using "data_race()" may be enough to silence code checkers. Or use READ_ONCE() WRITE_ONCE() for the state. > > Signed-off-by: Yongpeng Yang <yangyongpeng@xiaomi.com> > --- > drivers/block/loop.c | 67 ++++++++++++++++++++++++-------------------- > 1 file changed, 36 insertions(+), 31 deletions(-) > > diff --git a/drivers/block/loop.c b/drivers/block/loop.c > index 272bc608e528..bc661ecb449a 100644 > --- a/drivers/block/loop.c > +++ b/drivers/block/loop.c > @@ -59,7 +59,7 @@ struct loop_device { > gfp_t old_gfp_mask; > > spinlock_t lo_lock; > - int lo_state; > + atomic_t lo_state; > spinlock_t lo_work_lock; > struct workqueue_struct *workqueue; > struct work_struct rootcg_work; > @@ -94,6 +94,16 @@ static DEFINE_IDR(loop_index_idr); > static DEFINE_MUTEX(loop_ctl_mutex); > static DEFINE_MUTEX(loop_validate_mutex); > > +static inline int loop_device_get_state(struct loop_device *lo) > +{ > + return atomic_read(&lo->lo_state); > +} > + > +static inline void loop_device_set_state(struct loop_device *lo, int state) > +{ > + atomic_set(&lo->lo_state, state); > +} > + > /** > * loop_global_lock_killable() - take locks for safe loop_validate_file() test > * > @@ -200,7 +210,7 @@ static bool lo_can_use_dio(struct loop_device *lo) > static inline void loop_update_dio(struct loop_device *lo) > { > lockdep_assert_held(&lo->lo_mutex); > - WARN_ON_ONCE(lo->lo_state == Lo_bound && > + WARN_ON_ONCE(loop_device_get_state(lo) == Lo_bound && > lo->lo_queue->mq_freeze_depth == 0); > > if ((lo->lo_flags & LO_FLAGS_DIRECT_IO) && !lo_can_use_dio(lo)) > @@ -495,7 +505,7 @@ static int loop_validate_file(struct file *file, struct block_device *bdev) > return -EBADF; > > l = I_BDEV(f->f_mapping->host)->bd_disk->private_data; > - if (l->lo_state != Lo_bound) > + if (loop_device_get_state(l) != Lo_bound) > return -EINVAL; > /* Order wrt setting lo->lo_backing_file in loop_configure(). */ > rmb(); > @@ -563,7 +573,7 @@ static int loop_change_fd(struct loop_device *lo, struct block_device *bdev, > if (error) > goto out_putf; > error = -ENXIO; > - if (lo->lo_state != Lo_bound) > + if (loop_device_get_state(lo) != Lo_bound) > goto out_err; > > /* the loop device has to be read-only */ > @@ -1019,7 +1029,7 @@ static int loop_configure(struct loop_device *lo, blk_mode_t mode, > goto out_bdev; > > error = -EBUSY; > - if (lo->lo_state != Lo_unbound) > + if (loop_device_get_state(lo) != Lo_unbound) > goto out_unlock; > > error = loop_validate_file(file, bdev); > @@ -1082,7 +1092,7 @@ static int loop_configure(struct loop_device *lo, blk_mode_t mode, > /* Order wrt reading lo_state in loop_validate_file(). */ > wmb(); > > - lo->lo_state = Lo_bound; > + loop_device_set_state(lo, Lo_bound); > if (part_shift) > lo->lo_flags |= LO_FLAGS_PARTSCAN; > partscan = lo->lo_flags & LO_FLAGS_PARTSCAN; > @@ -1179,7 +1189,7 @@ static void __loop_clr_fd(struct loop_device *lo) > if (!part_shift) > set_bit(GD_SUPPRESS_PART_SCAN, &lo->lo_disk->state); > mutex_lock(&lo->lo_mutex); > - lo->lo_state = Lo_unbound; > + loop_device_set_state(lo, Lo_unbound); > mutex_unlock(&lo->lo_mutex); > > /* > @@ -1206,7 +1216,7 @@ static int loop_clr_fd(struct loop_device *lo) > err = loop_global_lock_killable(lo, true); > if (err) > return err; > - if (lo->lo_state != Lo_bound) { > + if (loop_device_get_state(lo) != Lo_bound) { > loop_global_unlock(lo, true); > return -ENXIO; > } > @@ -1218,7 +1228,7 @@ static int loop_clr_fd(struct loop_device *lo) > > lo->lo_flags |= LO_FLAGS_AUTOCLEAR; > if (disk_openers(lo->lo_disk) == 1) > - lo->lo_state = Lo_rundown; > + loop_device_set_state(lo, Lo_rundown); > loop_global_unlock(lo, true); > > return 0; > @@ -1235,7 +1245,7 @@ loop_set_status(struct loop_device *lo, const struct loop_info64 *info) > err = mutex_lock_killable(&lo->lo_mutex); > if (err) > return err; > - if (lo->lo_state != Lo_bound) { > + if (loop_device_get_state(lo) != Lo_bound) { > err = -ENXIO; > goto out_unlock; > } > @@ -1289,7 +1299,7 @@ loop_get_status(struct loop_device *lo, struct loop_info64 *info) > ret = mutex_lock_killable(&lo->lo_mutex); > if (ret) > return ret; > - if (lo->lo_state != Lo_bound) { > + if (loop_device_get_state(lo) != Lo_bound) { > mutex_unlock(&lo->lo_mutex); > return -ENXIO; > } > @@ -1408,7 +1418,7 @@ static int loop_set_capacity(struct loop_device *lo) > { > loff_t size; > > - if (unlikely(lo->lo_state != Lo_bound)) > + if (unlikely(loop_device_get_state(lo) != Lo_bound)) > return -ENXIO; > > size = lo_calculate_size(lo, lo->lo_backing_file); > @@ -1422,7 +1432,7 @@ static int loop_set_dio(struct loop_device *lo, unsigned long arg) > bool use_dio = !!arg; > unsigned int memflags; > > - if (lo->lo_state != Lo_bound) > + if (loop_device_get_state(lo) != Lo_bound) > return -ENXIO; > if (use_dio == !!(lo->lo_flags & LO_FLAGS_DIRECT_IO)) > return 0; > @@ -1464,7 +1474,7 @@ static int loop_set_block_size(struct loop_device *lo, blk_mode_t mode, > if (err) > goto abort_claim; > > - if (lo->lo_state != Lo_bound) { > + if (loop_device_get_state(lo) != Lo_bound) { > err = -ENXIO; > goto unlock; > } > @@ -1716,16 +1726,11 @@ static int lo_compat_ioctl(struct block_device *bdev, blk_mode_t mode, > static int lo_open(struct gendisk *disk, blk_mode_t mode) > { > struct loop_device *lo = disk->private_data; > - int err; > - > - err = mutex_lock_killable(&lo->lo_mutex); > - if (err) > - return err; > + int state = loop_device_get_state(lo); > > - if (lo->lo_state == Lo_deleting || lo->lo_state == Lo_rundown) > - err = -ENXIO; > - mutex_unlock(&lo->lo_mutex); > - return err; > + if (state == Lo_deleting || state == Lo_rundown) > + return -ENXIO; > + return 0; > } > > static void lo_release(struct gendisk *disk) > @@ -1742,10 +1747,10 @@ static void lo_release(struct gendisk *disk) > */ > > mutex_lock(&lo->lo_mutex); > - if (lo->lo_state == Lo_bound && (lo->lo_flags & LO_FLAGS_AUTOCLEAR)) > - lo->lo_state = Lo_rundown; > + if (loop_device_get_state(lo) == Lo_bound && (lo->lo_flags & LO_FLAGS_AUTOCLEAR)) > + loop_device_set_state(lo, Lo_rundown); > > - need_clear = (lo->lo_state == Lo_rundown); > + need_clear = (loop_device_get_state(lo) == Lo_rundown); > mutex_unlock(&lo->lo_mutex); > > if (need_clear) > @@ -1858,7 +1863,7 @@ static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx, > > blk_mq_start_request(rq); > > - if (lo->lo_state != Lo_bound) > + if (loop_device_get_state(lo) != Lo_bound) > return BLK_STS_IOERR; > > switch (req_op(rq)) { > @@ -2016,7 +2021,7 @@ static int loop_add(int i) > lo->worker_tree = RB_ROOT; > INIT_LIST_HEAD(&lo->idle_worker_list); > timer_setup(&lo->timer, loop_free_idle_workers_timer, TIMER_DEFERRABLE); > - lo->lo_state = Lo_unbound; > + loop_device_set_state(lo, Lo_unbound); > > err = mutex_lock_killable(&loop_ctl_mutex); > if (err) > @@ -2168,13 +2173,13 @@ static int loop_control_remove(int idx) > ret = mutex_lock_killable(&lo->lo_mutex); > if (ret) > goto mark_visible; > - if (lo->lo_state != Lo_unbound || disk_openers(lo->lo_disk) > 0) { > + if (loop_device_get_state(lo) != Lo_unbound || disk_openers(lo->lo_disk) > 0) { > mutex_unlock(&lo->lo_mutex); > ret = -EBUSY; > goto mark_visible; > } > /* Mark this loop device as no more bound, but not quite unbound yet */ > - lo->lo_state = Lo_deleting; > + loop_device_set_state(lo, Lo_deleting); > mutex_unlock(&lo->lo_mutex); > > loop_remove(lo); > @@ -2198,7 +2203,7 @@ static int loop_control_get_free(int idx) > return ret; > idr_for_each_entry(&loop_index_idr, lo, id) { > /* Hitting a race results in creating a new loop device which is harmless. */ > - if (lo->idr_visible && data_race(lo->lo_state) == Lo_unbound) > + if (lo->idr_visible && loop_device_get_state(lo) == Lo_unbound) > goto found; > } > mutex_unlock(&loop_ctl_mutex); -- Damien Le Moal Western Digital Research ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 1/1] loop: convert lo_state to atomic_t type to ensure atomic state checks in queue_rq path 2025-12-15 7:12 ` Damien Le Moal @ 2025-12-15 7:34 ` Yongpeng Yang 2025-12-15 7:44 ` Damien Le Moal 0 siblings, 1 reply; 5+ messages in thread From: Yongpeng Yang @ 2025-12-15 7:34 UTC (permalink / raw) To: Damien Le Moal, Yongpeng Yang, Christoph Hellwig, Jens Axboe, Ming Lei Cc: linux-block, Yongpeng Yang, Yongpeng Yang On 12/15/25 15:12, Damien Le Moal wrote: > On 12/15/25 15:54, Yongpeng Yang wrote: >> From: Yongpeng Yang <yangyongpeng@xiaomi.com> >> >> lo_state is currently defined as an int, which does not guarantee >> atomicity for state checks. In the queue_rq path, ensuring correct state >> checks requires holding lo->lo_mutex, which may increase I/O submission >> latency. This patch converts lo_state to atomic_t type. The main changes >> are: >> 1. Updates to lo_state still require holding lo->lo_mutex, since the >> state must be validated before modification, and the lock ensures that >> no concurrent operation can change the state. >> 2. Read-only accesses to lo_state no longer require holding lo->lo_mutex. >> >> This allows atomic state checks in the queue_rq fast path while avoiding >> unnecessary locking overhead. > > Code like: > > if (loop_device_get_state(lo) != Lo_bound) > > is absolutely *not* atomic, since the state can change in between the atomic > read and the comparison instruction. So this is not about atomicity, it is about > not reading garbage from the state field if there is a load and a store > concurrently executed on different CPUs (that happening depends on the CPU > architecture though). Yes, I hadn’t considered that before. > > As Christoph suggested, using "data_race()" may be enough to silence code > checkers. Or use READ_ONCE() WRITE_ONCE() for the state. Considering the earlier point that the queue_rq check of lo->lo_state is just an optimization, using READ_ONCE() seems more appropriate. As noted in the comment for data_race(), for accesses without locking, would data_race(READ_ONCE(lo->lo_state)) make more sense here? /* *...If the access must * be atomic *and* KCSAN should ignore the access, use both data_race() * and READ_ONCE(), for example, data_race(READ_ONCE(x)). */ Thanks Yongpeng, > >> >> Signed-off-by: Yongpeng Yang <yangyongpeng@xiaomi.com> >> --- >> drivers/block/loop.c | 67 ++++++++++++++++++++++++-------------------- >> 1 file changed, 36 insertions(+), 31 deletions(-) >> >> diff --git a/drivers/block/loop.c b/drivers/block/loop.c >> index 272bc608e528..bc661ecb449a 100644 >> --- a/drivers/block/loop.c >> +++ b/drivers/block/loop.c >> @@ -59,7 +59,7 @@ struct loop_device { >> gfp_t old_gfp_mask; >> >> spinlock_t lo_lock; >> - int lo_state; >> + atomic_t lo_state; >> spinlock_t lo_work_lock; >> struct workqueue_struct *workqueue; >> struct work_struct rootcg_work; >> @@ -94,6 +94,16 @@ static DEFINE_IDR(loop_index_idr); >> static DEFINE_MUTEX(loop_ctl_mutex); >> static DEFINE_MUTEX(loop_validate_mutex); >> >> +static inline int loop_device_get_state(struct loop_device *lo) >> +{ >> + return atomic_read(&lo->lo_state); >> +} >> + >> +static inline void loop_device_set_state(struct loop_device *lo, int state) >> +{ >> + atomic_set(&lo->lo_state, state); >> +} >> + >> /** >> * loop_global_lock_killable() - take locks for safe loop_validate_file() test >> * >> @@ -200,7 +210,7 @@ static bool lo_can_use_dio(struct loop_device *lo) >> static inline void loop_update_dio(struct loop_device *lo) >> { >> lockdep_assert_held(&lo->lo_mutex); >> - WARN_ON_ONCE(lo->lo_state == Lo_bound && >> + WARN_ON_ONCE(loop_device_get_state(lo) == Lo_bound && >> lo->lo_queue->mq_freeze_depth == 0); >> >> if ((lo->lo_flags & LO_FLAGS_DIRECT_IO) && !lo_can_use_dio(lo)) >> @@ -495,7 +505,7 @@ static int loop_validate_file(struct file *file, struct block_device *bdev) >> return -EBADF; >> >> l = I_BDEV(f->f_mapping->host)->bd_disk->private_data; >> - if (l->lo_state != Lo_bound) >> + if (loop_device_get_state(l) != Lo_bound) >> return -EINVAL; >> /* Order wrt setting lo->lo_backing_file in loop_configure(). */ >> rmb(); >> @@ -563,7 +573,7 @@ static int loop_change_fd(struct loop_device *lo, struct block_device *bdev, >> if (error) >> goto out_putf; >> error = -ENXIO; >> - if (lo->lo_state != Lo_bound) >> + if (loop_device_get_state(lo) != Lo_bound) >> goto out_err; >> >> /* the loop device has to be read-only */ >> @@ -1019,7 +1029,7 @@ static int loop_configure(struct loop_device *lo, blk_mode_t mode, >> goto out_bdev; >> >> error = -EBUSY; >> - if (lo->lo_state != Lo_unbound) >> + if (loop_device_get_state(lo) != Lo_unbound) >> goto out_unlock; >> >> error = loop_validate_file(file, bdev); >> @@ -1082,7 +1092,7 @@ static int loop_configure(struct loop_device *lo, blk_mode_t mode, >> /* Order wrt reading lo_state in loop_validate_file(). */ >> wmb(); >> >> - lo->lo_state = Lo_bound; >> + loop_device_set_state(lo, Lo_bound); >> if (part_shift) >> lo->lo_flags |= LO_FLAGS_PARTSCAN; >> partscan = lo->lo_flags & LO_FLAGS_PARTSCAN; >> @@ -1179,7 +1189,7 @@ static void __loop_clr_fd(struct loop_device *lo) >> if (!part_shift) >> set_bit(GD_SUPPRESS_PART_SCAN, &lo->lo_disk->state); >> mutex_lock(&lo->lo_mutex); >> - lo->lo_state = Lo_unbound; >> + loop_device_set_state(lo, Lo_unbound); >> mutex_unlock(&lo->lo_mutex); >> >> /* >> @@ -1206,7 +1216,7 @@ static int loop_clr_fd(struct loop_device *lo) >> err = loop_global_lock_killable(lo, true); >> if (err) >> return err; >> - if (lo->lo_state != Lo_bound) { >> + if (loop_device_get_state(lo) != Lo_bound) { >> loop_global_unlock(lo, true); >> return -ENXIO; >> } >> @@ -1218,7 +1228,7 @@ static int loop_clr_fd(struct loop_device *lo) >> >> lo->lo_flags |= LO_FLAGS_AUTOCLEAR; >> if (disk_openers(lo->lo_disk) == 1) >> - lo->lo_state = Lo_rundown; >> + loop_device_set_state(lo, Lo_rundown); >> loop_global_unlock(lo, true); >> >> return 0; >> @@ -1235,7 +1245,7 @@ loop_set_status(struct loop_device *lo, const struct loop_info64 *info) >> err = mutex_lock_killable(&lo->lo_mutex); >> if (err) >> return err; >> - if (lo->lo_state != Lo_bound) { >> + if (loop_device_get_state(lo) != Lo_bound) { >> err = -ENXIO; >> goto out_unlock; >> } >> @@ -1289,7 +1299,7 @@ loop_get_status(struct loop_device *lo, struct loop_info64 *info) >> ret = mutex_lock_killable(&lo->lo_mutex); >> if (ret) >> return ret; >> - if (lo->lo_state != Lo_bound) { >> + if (loop_device_get_state(lo) != Lo_bound) { >> mutex_unlock(&lo->lo_mutex); >> return -ENXIO; >> } >> @@ -1408,7 +1418,7 @@ static int loop_set_capacity(struct loop_device *lo) >> { >> loff_t size; >> >> - if (unlikely(lo->lo_state != Lo_bound)) >> + if (unlikely(loop_device_get_state(lo) != Lo_bound)) >> return -ENXIO; >> >> size = lo_calculate_size(lo, lo->lo_backing_file); >> @@ -1422,7 +1432,7 @@ static int loop_set_dio(struct loop_device *lo, unsigned long arg) >> bool use_dio = !!arg; >> unsigned int memflags; >> >> - if (lo->lo_state != Lo_bound) >> + if (loop_device_get_state(lo) != Lo_bound) >> return -ENXIO; >> if (use_dio == !!(lo->lo_flags & LO_FLAGS_DIRECT_IO)) >> return 0; >> @@ -1464,7 +1474,7 @@ static int loop_set_block_size(struct loop_device *lo, blk_mode_t mode, >> if (err) >> goto abort_claim; >> >> - if (lo->lo_state != Lo_bound) { >> + if (loop_device_get_state(lo) != Lo_bound) { >> err = -ENXIO; >> goto unlock; >> } >> @@ -1716,16 +1726,11 @@ static int lo_compat_ioctl(struct block_device *bdev, blk_mode_t mode, >> static int lo_open(struct gendisk *disk, blk_mode_t mode) >> { >> struct loop_device *lo = disk->private_data; >> - int err; >> - >> - err = mutex_lock_killable(&lo->lo_mutex); >> - if (err) >> - return err; >> + int state = loop_device_get_state(lo); >> >> - if (lo->lo_state == Lo_deleting || lo->lo_state == Lo_rundown) >> - err = -ENXIO; >> - mutex_unlock(&lo->lo_mutex); >> - return err; >> + if (state == Lo_deleting || state == Lo_rundown) >> + return -ENXIO; >> + return 0; >> } >> >> static void lo_release(struct gendisk *disk) >> @@ -1742,10 +1747,10 @@ static void lo_release(struct gendisk *disk) >> */ >> >> mutex_lock(&lo->lo_mutex); >> - if (lo->lo_state == Lo_bound && (lo->lo_flags & LO_FLAGS_AUTOCLEAR)) >> - lo->lo_state = Lo_rundown; >> + if (loop_device_get_state(lo) == Lo_bound && (lo->lo_flags & LO_FLAGS_AUTOCLEAR)) >> + loop_device_set_state(lo, Lo_rundown); >> >> - need_clear = (lo->lo_state == Lo_rundown); >> + need_clear = (loop_device_get_state(lo) == Lo_rundown); >> mutex_unlock(&lo->lo_mutex); >> >> if (need_clear) >> @@ -1858,7 +1863,7 @@ static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx, >> >> blk_mq_start_request(rq); >> >> - if (lo->lo_state != Lo_bound) >> + if (loop_device_get_state(lo) != Lo_bound) >> return BLK_STS_IOERR; >> >> switch (req_op(rq)) { >> @@ -2016,7 +2021,7 @@ static int loop_add(int i) >> lo->worker_tree = RB_ROOT; >> INIT_LIST_HEAD(&lo->idle_worker_list); >> timer_setup(&lo->timer, loop_free_idle_workers_timer, TIMER_DEFERRABLE); >> - lo->lo_state = Lo_unbound; >> + loop_device_set_state(lo, Lo_unbound); >> >> err = mutex_lock_killable(&loop_ctl_mutex); >> if (err) >> @@ -2168,13 +2173,13 @@ static int loop_control_remove(int idx) >> ret = mutex_lock_killable(&lo->lo_mutex); >> if (ret) >> goto mark_visible; >> - if (lo->lo_state != Lo_unbound || disk_openers(lo->lo_disk) > 0) { >> + if (loop_device_get_state(lo) != Lo_unbound || disk_openers(lo->lo_disk) > 0) { >> mutex_unlock(&lo->lo_mutex); >> ret = -EBUSY; >> goto mark_visible; >> } >> /* Mark this loop device as no more bound, but not quite unbound yet */ >> - lo->lo_state = Lo_deleting; >> + loop_device_set_state(lo, Lo_deleting); >> mutex_unlock(&lo->lo_mutex); >> >> loop_remove(lo); >> @@ -2198,7 +2203,7 @@ static int loop_control_get_free(int idx) >> return ret; >> idr_for_each_entry(&loop_index_idr, lo, id) { >> /* Hitting a race results in creating a new loop device which is harmless. */ >> - if (lo->idr_visible && data_race(lo->lo_state) == Lo_unbound) >> + if (lo->idr_visible && loop_device_get_state(lo) == Lo_unbound) >> goto found; >> } >> mutex_unlock(&loop_ctl_mutex); > > ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 1/1] loop: convert lo_state to atomic_t type to ensure atomic state checks in queue_rq path 2025-12-15 7:34 ` Yongpeng Yang @ 2025-12-15 7:44 ` Damien Le Moal 2025-12-15 8:19 ` Yongpeng Yang 0 siblings, 1 reply; 5+ messages in thread From: Damien Le Moal @ 2025-12-15 7:44 UTC (permalink / raw) To: Yongpeng Yang, Yongpeng Yang, Christoph Hellwig, Jens Axboe, Ming Lei Cc: linux-block, Yongpeng Yang On 12/15/25 16:34, Yongpeng Yang wrote: > > On 12/15/25 15:12, Damien Le Moal wrote: >> On 12/15/25 15:54, Yongpeng Yang wrote: >>> From: Yongpeng Yang <yangyongpeng@xiaomi.com> >>> >>> lo_state is currently defined as an int, which does not guarantee >>> atomicity for state checks. In the queue_rq path, ensuring correct state >>> checks requires holding lo->lo_mutex, which may increase I/O submission >>> latency. This patch converts lo_state to atomic_t type. The main changes >>> are: >>> 1. Updates to lo_state still require holding lo->lo_mutex, since the >>> state must be validated before modification, and the lock ensures that >>> no concurrent operation can change the state. >>> 2. Read-only accesses to lo_state no longer require holding lo->lo_mutex. >>> >>> This allows atomic state checks in the queue_rq fast path while avoiding >>> unnecessary locking overhead. >> >> Code like: >> >> if (loop_device_get_state(lo) != Lo_bound) >> >> is absolutely *not* atomic, since the state can change in between the atomic >> read and the comparison instruction. So this is not about atomicity, it is about >> not reading garbage from the state field if there is a load and a store >> concurrently executed on different CPUs (that happening depends on the CPU >> architecture though). > > Yes, I hadn’t considered that before. > >> >> As Christoph suggested, using "data_race()" may be enough to silence code >> checkers. Or use READ_ONCE() WRITE_ONCE() for the state. > > Considering the earlier point that the queue_rq check of lo->lo_state is > just an optimization, using READ_ONCE() seems more appropriate. As noted > in the comment for data_race(), for accesses without locking, would > data_race(READ_ONCE(lo->lo_state)) make more sense here? Yes, I think it is OK. -- Damien Le Moal Western Digital Research ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 1/1] loop: convert lo_state to atomic_t type to ensure atomic state checks in queue_rq path 2025-12-15 7:44 ` Damien Le Moal @ 2025-12-15 8:19 ` Yongpeng Yang 0 siblings, 0 replies; 5+ messages in thread From: Yongpeng Yang @ 2025-12-15 8:19 UTC (permalink / raw) To: Damien Le Moal, Yongpeng Yang, Yongpeng Yang, Christoph Hellwig, Jens Axboe, Ming Lei Cc: linux-block, Yongpeng Yang On 12/15/25 15:44, Damien Le Moal wrote: > On 12/15/25 16:34, Yongpeng Yang wrote: >> >> On 12/15/25 15:12, Damien Le Moal wrote: >>> On 12/15/25 15:54, Yongpeng Yang wrote: >>>> From: Yongpeng Yang <yangyongpeng@xiaomi.com> >>>> >>>> lo_state is currently defined as an int, which does not guarantee >>>> atomicity for state checks. In the queue_rq path, ensuring correct state >>>> checks requires holding lo->lo_mutex, which may increase I/O submission >>>> latency. This patch converts lo_state to atomic_t type. The main changes >>>> are: >>>> 1. Updates to lo_state still require holding lo->lo_mutex, since the >>>> state must be validated before modification, and the lock ensures that >>>> no concurrent operation can change the state. >>>> 2. Read-only accesses to lo_state no longer require holding lo->lo_mutex. >>>> >>>> This allows atomic state checks in the queue_rq fast path while avoiding >>>> unnecessary locking overhead. >>> >>> Code like: >>> >>> if (loop_device_get_state(lo) != Lo_bound) >>> >>> is absolutely *not* atomic, since the state can change in between the atomic >>> read and the comparison instruction. So this is not about atomicity, it is about >>> not reading garbage from the state field if there is a load and a store >>> concurrently executed on different CPUs (that happening depends on the CPU >>> architecture though). >> >> Yes, I hadn’t considered that before. >> >>> >>> As Christoph suggested, using "data_race()" may be enough to silence code >>> checkers. Or use READ_ONCE() WRITE_ONCE() for the state. >> >> Considering the earlier point that the queue_rq check of lo->lo_state is >> just an optimization, using READ_ONCE() seems more appropriate. As noted >> in the comment for data_race(), for accesses without locking, would >> data_race(READ_ONCE(lo->lo_state)) make more sense here? > > Yes, I think it is OK. > > I’ll send out the loop and zloop patches shortly. Thanks Yongpeng, ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2025-12-15 8:19 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-12-15 6:54 [PATCH 1/1] loop: convert lo_state to atomic_t type to ensure atomic state checks in queue_rq path Yongpeng Yang 2025-12-15 7:12 ` Damien Le Moal 2025-12-15 7:34 ` Yongpeng Yang 2025-12-15 7:44 ` Damien Le Moal 2025-12-15 8:19 ` Yongpeng Yang
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox