* [PATCH 0/2] disable I/O priority when prio_aging_expire is zero
@ 2026-08-20 2:12 Ye Bin
2026-08-20 2:12 ` [PATCH 1/2] block/mq-deadline: " Ye Bin
2026-08-20 2:12 ` [PATCH 2/2] block/mq-deadline: add module parameter for prio_aging_expire default Ye Bin
0 siblings, 2 replies; 6+ messages in thread
From: Ye Bin @ 2026-08-20 2:12 UTC (permalink / raw)
To: axboe, linux-block; +Cc: ming.lei
From: Ye Bin <yebin10@huawei.com>
Since the mq-deadline scheduler introduced support for I/O priorities, if
a process does not have an I/O priority configured, it becomes bound to
the process's scheduling priority. This change forces applications to
re-plan their I/O priorities. However, applications do not care about
I/O priorities, yet now they cannot disable priorities.
Ye Bin (2):
block/mq-deadline: disable I/O priority when prio_aging_expire is zero
block/mq-deadline: add module parameter for prio_aging_expire default
block/mq-deadline.c | 84 ++++++++++++++++++++++++++++++++++++++-------
1 file changed, 71 insertions(+), 13 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/2] block/mq-deadline: disable I/O priority when prio_aging_expire is zero
2026-08-20 2:12 [PATCH 0/2] disable I/O priority when prio_aging_expire is zero Ye Bin
@ 2026-08-20 2:12 ` Ye Bin
2026-08-20 15:31 ` Bart Van Assche
2026-08-20 2:12 ` [PATCH 2/2] block/mq-deadline: add module parameter for prio_aging_expire default Ye Bin
1 sibling, 1 reply; 6+ messages in thread
From: Ye Bin @ 2026-08-20 2:12 UTC (permalink / raw)
To: axboe, linux-block; +Cc: ming.lei
From: Ye Bin <yebin10@huawei.com>
Since the mq-deadline scheduler introduced support for I/O priorities,
if a process does not have an I/O priority configured, it becomes bound
to the process's scheduling priority. This change forces applications to
re-plan their I/O priorities. However, applications do not care about I/O
priorities, yet now they cannot disable priorities.
Setting prio_aging_expire to zero does not actually turn off I/O
priority in mq-deadline. Instead the priority aging path in
dd_dispatch_prio_aged_requests() is invoked with "now - 0 == now",
which causes best-effort and idle requests to be dispatched ahead of
pending real-time requests through the aging path -- a classic
priority inversion, not the "priority disabled" behavior users expect
when writing zero.
Treat a zero prio_aging_expire as an explicit request to disable I/O
priority:
* dd_insert_request() and dd_request_merge() file every request in
the DD_BE_PRIO bucket, so the scheduler no longer distinguishes
between RT, BE and IDLE classes.
* dd_dispatch_prio_aged_requests() short-circuits when
prio_aging_expire is zero, closing the aging path that caused the
inversion.
* dd_request_merged() and dd_merged_requests() now look up the
per-priority bucket from rq->elv.priv[0] instead of recomputing it
from the request ioprio. Once priority is disabled the request
ioprio no longer reflects the bucket the request lives in, so the
old computation would touch the wrong rb-tree and FIFO list.
Switching the mode while I/O is in flight could itself invert
priorities, because requests already queued in the RT or IDLE buckets
would keep being dispatched by priority until they drain. Follow the
same sequence used by elevator_switch(): when the sysfs store observes
a transition from a non-zero value to zero it freezes the queue (which
blocks new upper-layer I/O in blk_queue_enter() and waits for every
outstanding request to complete, draining the scheduler), quiesces the
queue so that no dispatch is in progress, flips prio_aging_expire to
zero, then unquiesces and unfreezes. New I/O arriving after the
switch lands in the best-effort bucket. Writing a non-zero value
re-enables priority without draining.
Signed-off-by: Ye Bin <yebin10@huawei.com>
---
block/mq-deadline.c | 77 ++++++++++++++++++++++++++++++++++++++-------
1 file changed, 66 insertions(+), 11 deletions(-)
diff --git a/block/mq-deadline.c b/block/mq-deadline.c
index 824bfc17b2c6..e3314f7aed8b 100644
--- a/block/mq-deadline.c
+++ b/block/mq-deadline.c
@@ -83,6 +83,7 @@ struct deadline_data {
* run time data
*/
+ struct request_queue *q; /* associated request queue */
struct list_head dispatch;
struct dd_per_prio per_prio[DD_PRIO_COUNT];
@@ -184,10 +185,7 @@ static void deadline_remove_request(struct request_queue *q,
static void dd_request_merged(struct request_queue *q, struct request *req,
enum elv_merge type)
{
- struct deadline_data *dd = q->elevator->elevator_data;
- const u8 ioprio_class = dd_rq_ioclass(req);
- const enum dd_prio prio = ioprio_class_to_prio[ioprio_class];
- struct dd_per_prio *per_prio = &dd->per_prio[prio];
+ struct dd_per_prio *per_prio = req->elv.priv[0];
/*
* if the merge was a front merge, we need to reposition request
@@ -205,12 +203,11 @@ static void dd_merged_requests(struct request_queue *q, struct request *req,
struct request *next)
{
struct deadline_data *dd = q->elevator->elevator_data;
- const u8 ioprio_class = dd_rq_ioclass(next);
- const enum dd_prio prio = ioprio_class_to_prio[ioprio_class];
+ struct dd_per_prio *per_prio = next->elv.priv[0];
lockdep_assert_held(&dd->lock);
- dd->per_prio[prio].stats.merged++;
+ per_prio->stats.merged++;
/*
* if next expires before rq, assign its expire time to rq
@@ -227,7 +224,7 @@ static void dd_merged_requests(struct request_queue *q, struct request *req,
/*
* kill knowledge of next, this one is a goner
*/
- deadline_remove_request(q, &dd->per_prio[prio], next);
+ deadline_remove_request(q, per_prio, next);
}
/*
@@ -426,6 +423,15 @@ static struct request *dd_dispatch_prio_aged_requests(struct deadline_data *dd,
lockdep_assert_held(&dd->lock);
+ /*
+ * A prio_aging_expire of zero disables I/O priority: all requests are
+ * filed in the DD_BE_PRIO bucket and must never be dispatched ahead of
+ * higher priority requests through this aging path, which would cause
+ * priority inversion.
+ */
+ if (dd->prio_aging_expire == 0)
+ return NULL;
+
prio_cnt = !!dd_queued(dd, DD_RT_PRIO) + !!dd_queued(dd, DD_BE_PRIO) +
!!dd_queued(dd, DD_IDLE_PRIO);
if (prio_cnt < 2)
@@ -536,6 +542,7 @@ static int dd_init_sched(struct request_queue *q, struct elevator_queue *eq)
return -ENOMEM;
eq->elevator_data = dd;
+ dd->q = q;
INIT_LIST_HEAD(&dd->dispatch);
for (prio = 0; prio <= DD_PRIO_MAX; prio++) {
@@ -573,7 +580,8 @@ static int dd_request_merge(struct request_queue *q, struct request **rq,
{
struct deadline_data *dd = q->elevator->elevator_data;
const u8 ioprio_class = IOPRIO_PRIO_CLASS(bio->bi_ioprio);
- const enum dd_prio prio = ioprio_class_to_prio[ioprio_class];
+ const enum dd_prio prio = dd->prio_aging_expire == 0 ? DD_BE_PRIO :
+ ioprio_class_to_prio[ioprio_class];
struct dd_per_prio *per_prio = &dd->per_prio[prio];
sector_t sector = bio_end_sector(bio);
struct request *__rq;
@@ -633,7 +641,15 @@ static void dd_insert_request(struct blk_mq_hw_ctx *hctx, struct request *rq,
lockdep_assert_held(&dd->lock);
- prio = ioprio_class_to_prio[ioprio_class];
+ /*
+ * When prio_aging_expire is zero, I/O priority is disabled: file every
+ * request in the best-effort bucket so that the dispatch path no longer
+ * distinguishes between RT, BE and IDLE classes.
+ */
+ if (dd->prio_aging_expire == 0)
+ prio = DD_BE_PRIO;
+ else
+ prio = ioprio_class_to_prio[ioprio_class];
per_prio = &dd->per_prio[prio];
if (!rq->elv.priv[0])
per_prio->stats.inserted++;
@@ -773,7 +789,6 @@ static ssize_t __FUNC(struct elevator_queue *e, const char *page, size_t count)
STORE_FUNCTION(__FUNC, __PTR, MIN, MAX, msecs_to_jiffies)
STORE_JIFFIES(deadline_read_expire_store, &dd->fifo_expire[DD_READ], 0, INT_MAX);
STORE_JIFFIES(deadline_write_expire_store, &dd->fifo_expire[DD_WRITE], 0, INT_MAX);
-STORE_JIFFIES(deadline_prio_aging_expire_store, &dd->prio_aging_expire, 0, INT_MAX);
STORE_INT(deadline_writes_starved_store, &dd->writes_starved, INT_MIN, INT_MAX);
STORE_INT(deadline_front_merges_store, &dd->front_merges, 0, 1);
STORE_INT(deadline_fifo_batch_store, &dd->fifo_batch, 0, INT_MAX);
@@ -781,6 +796,46 @@ STORE_INT(deadline_fifo_batch_store, &dd->fifo_batch, 0, INT_MAX);
#undef STORE_INT
#undef STORE_JIFFIES
+/*
+ * Writing zero to prio_aging_expire disables I/O priority: all requests are
+ * treated as best-effort. To avoid priority inversion while the mode is being
+ * switched, first drain all in-flight I/O by following the same sequence used
+ * by elevator_switch(): freeze the queue so that new upper-layer I/O is
+ * blocked and all outstanding requests complete, quiesce the queue so that no
+ * dispatch is in progress, then flip prio_aging_expire to zero. New I/O
+ * queued after the switch lands in the best-effort bucket.
+ * Writing a non-zero value (re-)enables priority without draining.
+ */
+static ssize_t deadline_prio_aging_expire_store(struct elevator_queue *e,
+ const char *page, size_t count)
+{
+ struct deadline_data *dd = e->elevator_data;
+ int val, ret;
+
+ ret = kstrtoint(page, 0, &val);
+ if (ret < 0)
+ return ret;
+ if (val < 0)
+ val = 0;
+ else if (val > INT_MAX)
+ val = INT_MAX;
+ val = msecs_to_jiffies(val);
+
+ if (val == 0 && dd->prio_aging_expire != 0) {
+ unsigned int memflags;
+
+ memflags = blk_mq_freeze_queue(dd->q);
+ blk_mq_quiesce_queue(dd->q);
+ dd->prio_aging_expire = 0;
+ blk_mq_unquiesce_queue(dd->q);
+ blk_mq_unfreeze_queue(dd->q, memflags);
+ } else {
+ dd->prio_aging_expire = val;
+ }
+
+ return count;
+}
+
#define DD_ATTR(name) \
__ATTR(name, 0644, deadline_##name##_show, deadline_##name##_store)
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 2/2] block/mq-deadline: add module parameter for prio_aging_expire default
2026-08-20 2:12 [PATCH 0/2] disable I/O priority when prio_aging_expire is zero Ye Bin
2026-08-20 2:12 ` [PATCH 1/2] block/mq-deadline: " Ye Bin
@ 2026-08-20 2:12 ` Ye Bin
2026-08-20 15:54 ` Bart Van Assche
1 sibling, 1 reply; 6+ messages in thread
From: Ye Bin @ 2026-08-20 2:12 UTC (permalink / raw)
To: axboe, linux-block; +Cc: ming.lei
From: Ye Bin <yebin10@huawei.com>
Allow the default value of prio_aging_expire to be overridden at load
time, in milliseconds to match the sysfs attribute:
- built-in: mq_deadline.prio_aging_expire=0 on the kernel command line
- module: modprobe mq_deadline prio_aging_expire=0
A value of zero disables I/O priority from boot/load: every request is
filed in the best-effort bucket and the priority aging path is
bypassed, so systems that do not want RT/BE/IDLE distinction can opt
out without writing to sysfs after every queue creation.
Previously prio_aging_expire was a compile-time constant (10 * HZ)
with no way to change the default before the first request queue was
initialized. Make the variable a module_param so that the override
works whether mq-deadline is built-in or compiled as a module, and
convert the millisecond value to jiffies in dd_init_sched() when
assigning the per-queue default.
Signed-off-by: Ye Bin <yebin10@huawei.com>
---
block/mq-deadline.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/block/mq-deadline.c b/block/mq-deadline.c
index e3314f7aed8b..95a00a673be5 100644
--- a/block/mq-deadline.c
+++ b/block/mq-deadline.c
@@ -33,7 +33,10 @@ static const int write_expire = 5 * HZ; /* ditto for writes, these limits are SO
* Time after which to dispatch lower priority requests even if higher
* priority requests are pending.
*/
-static const int prio_aging_expire = 10 * HZ;
+static int prio_aging_expire = 10 * MSEC_PER_SEC;
+module_param(prio_aging_expire, int, 0644);
+MODULE_PARM_DESC(prio_aging_expire,
+ "Default prio_aging_expire in milliseconds; 0 disables I/O priority.");
static const int writes_starved = 2; /* max times reads can starve a write */
static const int fifo_batch = 16; /* # of sequential requests treated as one
by the above parameters. For throughput. */
@@ -559,7 +562,7 @@ static int dd_init_sched(struct request_queue *q, struct elevator_queue *eq)
dd->front_merges = 1;
dd->last_dir = DD_WRITE;
dd->fifo_batch = fifo_batch;
- dd->prio_aging_expire = prio_aging_expire;
+ dd->prio_aging_expire = msecs_to_jiffies(prio_aging_expire);
spin_lock_init(&dd->lock);
/* We dispatch from request queue wide instead of hw queue */
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] block/mq-deadline: disable I/O priority when prio_aging_expire is zero
2026-08-20 2:12 ` [PATCH 1/2] block/mq-deadline: " Ye Bin
@ 2026-08-20 15:31 ` Bart Van Assche
2026-08-21 2:06 ` yebin
0 siblings, 1 reply; 6+ messages in thread
From: Bart Van Assche @ 2026-08-20 15:31 UTC (permalink / raw)
To: Ye Bin, axboe, linux-block; +Cc: ming.lei
On 8/19/26 7:12 PM, Ye Bin wrote:
> Since the mq-deadline scheduler introduced support for I/O priorities,
> if a process does not have an I/O priority configured, it becomes bound
> to the process's scheduling priority.
How can this happen? The scheduling priority (sched_setparam()) and I/O
priority (ioprio_set()) are independent as far as I know.
> Setting prio_aging_expire to zero does not actually turn off I/O
> priority in mq-deadline.
prio_aging_expire should not be set to zero. Feel free to submit a patch
that disallows setting prio_aging_expire to zero.
Thanks,
Bart.
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 2/2] block/mq-deadline: add module parameter for prio_aging_expire default
2026-08-20 2:12 ` [PATCH 2/2] block/mq-deadline: add module parameter for prio_aging_expire default Ye Bin
@ 2026-08-20 15:54 ` Bart Van Assche
0 siblings, 0 replies; 6+ messages in thread
From: Bart Van Assche @ 2026-08-20 15:54 UTC (permalink / raw)
To: Ye Bin, axboe, linux-block; +Cc: ming.lei
On 8/19/26 7:12 PM, Ye Bin wrote:
> -static const int prio_aging_expire = 10 * HZ;
> +static int prio_aging_expire = 10 * MSEC_PER_SEC;
> +module_param(prio_aging_expire, int, 0644);
> +MODULE_PARM_DESC(prio_aging_expire,
> + "Default prio_aging_expire in milliseconds; 0 disables I/O priority.");
Disabling I/O priority support by setting prio_aging_expire is ugly
and probably will confuse users. There must be better solutions.
Bart.
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] block/mq-deadline: disable I/O priority when prio_aging_expire is zero
2026-08-20 15:31 ` Bart Van Assche
@ 2026-08-21 2:06 ` yebin
0 siblings, 0 replies; 6+ messages in thread
From: yebin @ 2026-08-21 2:06 UTC (permalink / raw)
To: Bart Van Assche, axboe, linux-block; +Cc: ming.lei
On 2026/8/20 23:31, Bart Van Assche wrote:
> On 8/19/26 7:12 PM, Ye Bin wrote:
>> Since the mq-deadline scheduler introduced support for I/O priorities,
>> if a process does not have an I/O priority configured, it becomes bound
>> to the process's scheduling priority.
>
> How can this happen? The scheduling priority (sched_setparam()) and I/O
> priority (ioprio_set()) are independent as far as I know.
>
Yes, I thought so at first. However, after checking the historical records,
I found that the IO priority was set based on the task scheduling class when
the IO priority was not configured. This was introduced by the f7eda402878b
("block: Return effective IO priority from get_current_ioprio()") and
a78418e6a04c ("block: Always initialize bio IO priority on submit") commits.
>> Setting prio_aging_expire to zero does not actually turn off I/O
>> priority in mq-deadline.
>
> prio_aging_expire should not be set to zero. Feel free to submit a patch
> that disallows setting prio_aging_expire to zero.
>
Yes, setting it to 0 can cause priority inversion issues. However, I understand
that even when a relatively small value is set, priority inversion can still
occur. This is because the system first checks whether the lower-priority IO
has timed out, and if it has, it then dispatches the lower-priority IO. In
cases of high IO pressure, this can actually result in the lower-priority IO
being dispatched first. So, is it sufficient to constrain this value to zero?
Or is this value entirely up to the user to control? From the user's perspective,
a naive view would be that setting this value close to zero means not distinguishing
priorities. Indeed, our product was designed with this idea in mind, setting the
value to 0 to disable priority, but it ended up causing IO priority inversion issues.
> Thanks,
>
> Bart.
>
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-08-21 2:06 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-20 2:12 [PATCH 0/2] disable I/O priority when prio_aging_expire is zero Ye Bin
2026-08-20 2:12 ` [PATCH 1/2] block/mq-deadline: " Ye Bin
2026-08-20 15:31 ` Bart Van Assche
2026-08-21 2:06 ` yebin
2026-08-20 2:12 ` [PATCH 2/2] block/mq-deadline: add module parameter for prio_aging_expire default Ye Bin
2026-08-20 15:54 ` Bart Van Assche
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.