mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 04:34:03 +02:00
block: add task-context bio completion infrastructure
Some bio completion handlers need to run from preemptible task context,
but bio_endio() may be called from IRQ context (e.g., buffer_head
writeback). Callers need a way to ensure their callback eventually runs
from a sleepable context. Add infrastructure for that, in two forms:
1. BIO_COMPLETE_IN_TASK, a bio flag the submitter sets when it knows
in advance that its callback needs task context (e.g., dropbehind
writeback). bio_endio() sees the flag and offloads completion to a
worker automatically.
2. bio_complete_in_task(), a helper that completion callbacks can
invoke from within bi_end_io() when the deferral decision is
dynamic (e.g., fserror reporting).
Both share a per-CPU list drained by a work item on a WQ_PERCPU
workqueue. Producers push the bio onto the local CPU's list and schedule
the work item, which then dispatches each bio's bi_end_io() from task
context.
Both methods are gated on bio_in_atomic(), which returns true in any
context where a sleeping bi_end_io() is unsafe, including
non-preemptible task context.
Two CPU hotplug callbacks are used to drain remaining bios from the
departing CPU's batch, while maintaining the per-CPU behavior. The
CPUHP_AP_ONLINE_DYN callback disables the per-CPU work item while the
CPU is still online, preventing it from running on an unbound worker
later. CPUHP_BP_PREPARE_DYN then drains any bios added between disabling
the work item and CPU offline.
Link: https://lore.kernel.org/all/20260409160243.1008358-1-hch@lst.de/
Suggested-by: Matthew Wilcox <willy@infradead.org>
Suggested-by: Christoph Hellwig <hch@infradead.org>
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Link: https://patch.msgid.link/20260730-blk-dontcache-v7-2-3e8e6850068d@columbia.edu
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
parent
8ed0831b52
commit
77e73fa61b
132
block/bio.c
132
block/bio.c
|
|
@ -1791,6 +1791,61 @@ void bio_check_pages_dirty(struct bio *bio)
|
|||
schedule_work(&bio_dirty_work);
|
||||
}
|
||||
|
||||
/*
|
||||
* Infrastructure for deferring bio completions to task-context via a per-CPU
|
||||
* workqueue. Triggered either by the BIO_COMPLETE_IN_TASK bio flag (static
|
||||
* decision at submit time) or by calling bio_complete_in_task() from
|
||||
* bi_end_io() (dynamic decision at completion time).
|
||||
*/
|
||||
|
||||
struct bio_complete_batch {
|
||||
struct bio_list list;
|
||||
struct work_struct work;
|
||||
int cpu;
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct bio_complete_batch, bio_complete_batch);
|
||||
static struct workqueue_struct *bio_complete_wq;
|
||||
|
||||
static void bio_complete_work_fn(struct work_struct *w)
|
||||
{
|
||||
struct bio_complete_batch *batch =
|
||||
container_of(w, struct bio_complete_batch, work);
|
||||
|
||||
while (1) {
|
||||
struct bio_list list;
|
||||
struct bio *bio;
|
||||
|
||||
local_irq_disable();
|
||||
list = batch->list;
|
||||
bio_list_init(&batch->list);
|
||||
local_irq_enable();
|
||||
|
||||
if (bio_list_empty(&list))
|
||||
break;
|
||||
|
||||
while ((bio = bio_list_pop(&list)))
|
||||
bio->bi_end_io(bio);
|
||||
}
|
||||
}
|
||||
|
||||
void __bio_complete_in_task(struct bio *bio)
|
||||
{
|
||||
struct bio_complete_batch *batch;
|
||||
unsigned long flags;
|
||||
bool was_empty;
|
||||
|
||||
local_irq_save(flags);
|
||||
batch = this_cpu_ptr(&bio_complete_batch);
|
||||
was_empty = bio_list_empty(&batch->list);
|
||||
bio_list_add(&batch->list, bio);
|
||||
local_irq_restore(flags);
|
||||
|
||||
if (was_empty)
|
||||
queue_work_on(batch->cpu, bio_complete_wq, &batch->work);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__bio_complete_in_task);
|
||||
|
||||
static inline bool bio_remaining_done(struct bio *bio)
|
||||
{
|
||||
/*
|
||||
|
|
@ -1865,7 +1920,9 @@ void bio_endio(struct bio *bio)
|
|||
}
|
||||
#endif
|
||||
|
||||
if (bio->bi_end_io)
|
||||
if (bio_flagged(bio, BIO_COMPLETE_IN_TASK) && bio_in_atomic())
|
||||
__bio_complete_in_task(bio);
|
||||
else if (bio->bi_end_io)
|
||||
bio->bi_end_io(bio);
|
||||
}
|
||||
EXPORT_SYMBOL(bio_endio);
|
||||
|
|
@ -2051,6 +2108,55 @@ int bioset_init(struct bio_set *bs,
|
|||
}
|
||||
EXPORT_SYMBOL(bioset_init);
|
||||
|
||||
static int bio_complete_batch_cpu_online(unsigned int cpu)
|
||||
{
|
||||
struct bio_complete_batch *batch = &per_cpu(bio_complete_batch, cpu);
|
||||
|
||||
enable_work(&batch->work);
|
||||
if (!bio_list_empty(&batch->list))
|
||||
queue_work_on(cpu, bio_complete_wq, &batch->work);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable this CPU's work item so that it cannot run on an unbound worker
|
||||
* after the CPU is offlined.
|
||||
*/
|
||||
static int bio_complete_batch_cpu_down_prep(unsigned int cpu)
|
||||
{
|
||||
disable_work_sync(&per_cpu(bio_complete_batch, cpu).work);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Drain a dead CPU's deferred bio completions. The CPU is dead and the worker
|
||||
* is canceled so no locking is needed.
|
||||
*/
|
||||
static int bio_complete_batch_cpu_dead(unsigned int cpu)
|
||||
{
|
||||
struct bio_complete_batch *batch =
|
||||
per_cpu_ptr(&bio_complete_batch, cpu);
|
||||
struct bio *bio;
|
||||
|
||||
while ((bio = bio_list_pop(&batch->list)))
|
||||
bio->bi_end_io(bio);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __init bio_complete_batch_init(int cpu)
|
||||
{
|
||||
struct bio_complete_batch *batch =
|
||||
per_cpu_ptr(&bio_complete_batch, cpu);
|
||||
|
||||
bio_list_init(&batch->list);
|
||||
INIT_WORK(&batch->work, bio_complete_work_fn);
|
||||
batch->cpu = cpu;
|
||||
|
||||
if (!cpu_online(cpu))
|
||||
disable_work_sync(&batch->work);
|
||||
}
|
||||
|
||||
static int __init init_bio(void)
|
||||
{
|
||||
int i;
|
||||
|
|
@ -2065,6 +2171,30 @@ static int __init init_bio(void)
|
|||
SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
|
||||
}
|
||||
|
||||
for_each_possible_cpu(i)
|
||||
bio_complete_batch_init(i);
|
||||
|
||||
bio_complete_wq = alloc_workqueue("bio_complete",
|
||||
WQ_MEM_RECLAIM | WQ_PERCPU, 0);
|
||||
if (!bio_complete_wq)
|
||||
panic("bio: can't allocate bio_complete workqueue\n");
|
||||
|
||||
/*
|
||||
* bio task-context completion draining on hot-unplugged CPUs:
|
||||
*
|
||||
* 1. Stop the per-CPU work item while the CPU is still online, so
|
||||
* that it cannot run on an unbound worker later.
|
||||
* 2. Drain leftover bios added between worker disabling and CPU
|
||||
* offlining.
|
||||
*/
|
||||
cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN,
|
||||
"block/bio:complete:online",
|
||||
bio_complete_batch_cpu_online,
|
||||
bio_complete_batch_cpu_down_prep);
|
||||
cpuhp_setup_state_nocalls(CPUHP_BP_PREPARE_DYN,
|
||||
"block/bio:complete:dead",
|
||||
NULL, bio_complete_batch_cpu_dead);
|
||||
|
||||
cpuhp_setup_state_multi(CPUHP_BIO_DEAD, "block/bio:dead", NULL,
|
||||
bio_cpu_dead);
|
||||
|
||||
|
|
|
|||
|
|
@ -383,6 +383,30 @@ static inline bool bio_in_atomic(void)
|
|||
return !preemptible();
|
||||
}
|
||||
|
||||
void __bio_complete_in_task(struct bio *bio);
|
||||
|
||||
/**
|
||||
* bio_complete_in_task - ensure a bio is completed in preemptible task context
|
||||
* @bio: bio to complete
|
||||
*
|
||||
* If called from non-task context, offload the bio completion to a worker
|
||||
* thread and return %true. Else return %false and do nothing.
|
||||
*
|
||||
* Uses BIO_COMPLETE_IN_TASK as a sentinel: if set, the bio was already
|
||||
* deferred and we are running in the worker — return %false so the
|
||||
* callback proceeds instead of re-deferring.
|
||||
*/
|
||||
static inline bool bio_complete_in_task(struct bio *bio)
|
||||
{
|
||||
if (bio_flagged(bio, BIO_COMPLETE_IN_TASK))
|
||||
return false;
|
||||
if (!bio_in_atomic())
|
||||
return false;
|
||||
bio_set_flag(bio, BIO_COMPLETE_IN_TASK);
|
||||
__bio_complete_in_task(bio);
|
||||
return true;
|
||||
}
|
||||
|
||||
extern void bio_endio(struct bio *);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ enum {
|
|||
BIO_REMAPPED,
|
||||
BIO_ZONE_WRITE_PLUGGING, /* bio handled through zone write plugging */
|
||||
BIO_EMULATES_ZONE_APPEND, /* bio emulates a zone append operation */
|
||||
BIO_COMPLETE_IN_TASK, /* complete bi_end_io() in task context */
|
||||
BIO_FLAG_LAST
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user